浏览代码

basic

log in and sign up pages, db schema
cube 6 天前
父节点
当前提交
d367d459d4

+ 14
- 0
.gitignore 查看文件

@@ -0,0 +1,14 @@
1
+.venv/
2
+
3
+*.pyc
4
+__pycache__/
5
+
6
+instance/
7
+
8
+.pytest_cache/
9
+.coverage
10
+htmlcov/
11
+
12
+dist/
13
+build/
14
+*.egg-info/

+ 39
- 0
myriad/__init__.py 查看文件

@@ -0,0 +1,39 @@
1
+import os
2
+
3
+from flask import Flask
4
+
5
+
6
+def create_app(test_config=None):
7
+    # create and configure the app
8
+    app = Flask(__name__, instance_relative_config=True)
9
+    app.config.from_mapping(
10
+        SECRET_KEY='dev',
11
+        DATABASE=os.path.join(app.instance_path, 'database.sqlite'),
12
+    )
13
+
14
+    if test_config is None:
15
+        # load the instance config, if it exists, when not testing
16
+        app.config.from_pyfile('config.py', silent=True)
17
+    else:
18
+        # load the test config if passed in
19
+        app.config.from_mapping(test_config)
20
+
21
+    # ensure the instance folder exists
22
+    os.makedirs(app.instance_path, exist_ok=True)
23
+
24
+    # a simple page that says hello
25
+    @app.route('/hello')
26
+    def hello():
27
+        return 'Hello, World!'
28
+    
29
+    from . import db
30
+    db.init_app(app)
31
+
32
+    from . import auth
33
+    app.register_blueprint(auth.bp)
34
+
35
+    from . import home
36
+    app.register_blueprint(home.bp)
37
+    app.add_url_rule('/', endpoint='index')
38
+
39
+    return app

+ 92
- 0
myriad/auth.py 查看文件

@@ -0,0 +1,92 @@
1
+import functools
2
+
3
+from flask import (
4
+    Blueprint, flash, g, redirect, render_template, request, session, url_for
5
+)
6
+from werkzeug.security import check_password_hash, generate_password_hash
7
+
8
+from myriad.db import get_db
9
+
10
+bp = Blueprint('auth', __name__, url_prefix='/auth')
11
+
12
+@bp.route('/register', methods=('GET', 'POST'))
13
+def register():
14
+    if request.method == 'POST':
15
+        username = request.form['username']
16
+        password = request.form['password']
17
+        db = get_db()
18
+        error = None
19
+
20
+        if not username:
21
+            error = 'Username is required.'
22
+        elif not password:
23
+            error = 'Password is required.'
24
+
25
+        if error is None:
26
+            try:
27
+                db.execute(
28
+                    "INSERT INTO user (username, password) VALUES (?, ?)",
29
+                    (username, generate_password_hash(password)),
30
+                )
31
+                db.commit()
32
+            except db.IntegrityError:
33
+                error = f"User {username} is already registered."
34
+            else:
35
+                return redirect(url_for("auth.login"))
36
+
37
+        flash(error)
38
+
39
+    return render_template('auth/register.html')
40
+
41
+@bp.route('/login', methods=('GET', 'POST'))
42
+def login():
43
+    if request.method == 'POST':
44
+        username = request.form['username']
45
+        password = request.form['password']
46
+        db = get_db()
47
+        error = None
48
+        user = db.execute(
49
+            'SELECT * FROM user WHERE username = ?', (username,)
50
+        ).fetchone()
51
+
52
+        if user is None:
53
+            error = 'Incorrect username.'
54
+        elif not check_password_hash(user['password'], password):
55
+            error = 'Incorrect password.'
56
+
57
+        if error is None:
58
+            session.clear()
59
+            session['user_id'] = user['id']
60
+            return redirect(url_for('index'))
61
+
62
+        flash(error)
63
+
64
+    return render_template('auth/login.html')
65
+
66
+@bp.before_app_request
67
+def load_logged_in_user():
68
+    user_id = session.get('user_id')
69
+
70
+    if user_id is None:
71
+        g.user = None
72
+    else:
73
+        g.user = get_db().execute(
74
+            'SELECT * FROM user WHERE id = ?', (user_id,)
75
+        ).fetchone()
76
+
77
+
78
+@bp.route('/logout')
79
+def logout():
80
+    session.clear()
81
+    return redirect(url_for('index'))
82
+
83
+
84
+def login_required(view):
85
+    @functools.wraps(view)
86
+    def wrapped_view(**kwargs):
87
+        if g.user is None:
88
+            return redirect(url_for('auth.login'))
89
+
90
+        return view(**kwargs)
91
+
92
+    return wrapped_view

+ 47
- 0
myriad/db.py 查看文件

@@ -0,0 +1,47 @@
1
+import sqlite3
2
+from datetime import datetime
3
+
4
+import click
5
+from flask import current_app, g
6
+
7
+
8
+def get_db():
9
+    if 'db' not in g:
10
+        g.db = sqlite3.connect(
11
+            current_app.config['DATABASE'],
12
+            detect_types=sqlite3.PARSE_DECLTYPES
13
+        )
14
+        g.db.row_factory = sqlite3.Row
15
+
16
+    return g.db
17
+
18
+
19
+def close_db(e=None):
20
+    db = g.pop('db', None)
21
+
22
+    if db is not None:
23
+        db.close()
24
+
25
+
26
+def init_db():
27
+    db = get_db()
28
+
29
+    with current_app.open_resource('schema.sql') as f:
30
+        db.executescript(f.read().decode('utf8'))
31
+        
32
+
33
+def init_app(app):
34
+    app.teardown_appcontext(close_db)
35
+    app.cli.add_command(init_db_command)
36
+
37
+
38
+@click.command('init-db')
39
+def init_db_command():
40
+    """Clear the existing data and create new tables."""
41
+    init_db()
42
+    click.echo('Initialized the database.')
43
+
44
+
45
+sqlite3.register_converter(
46
+    "timestamp", lambda v: datetime.fromisoformat(v.decode())
47
+)

+ 19
- 0
myriad/home.py 查看文件

@@ -0,0 +1,19 @@
1
+from flask import (
2
+    Blueprint, flash, g, redirect, render_template, request, url_for
3
+)
4
+from werkzeug.exceptions import abort
5
+
6
+from myriad.auth import login_required
7
+from myriad.db import get_db
8
+
9
+bp = Blueprint('home', __name__)
10
+
11
+@bp.route('/')
12
+def index():
13
+    # db = get_db()
14
+    # posts = db.execute(
15
+    #     'SELECT p.id, title, body, created, author_id, username'
16
+    #     ' FROM post p JOIN user u ON p.author_id = u.id'
17
+    #     ' ORDER BY created DESC'
18
+    # ).fetchall()
19
+    return render_template('index.html')

+ 61
- 0
myriad/schema.sql 查看文件

@@ -0,0 +1,61 @@
1
+DROP TABLE IF EXISTS user;
2
+DROP TABLE IF EXISTS member;
3
+DROP TABLE IF EXISTS icons;
4
+DROP TABLE IF EXISTS groups;
5
+DROP TABLE IF EXISTS group_members;
6
+DROP TABLE IF EXISTS user_front;
7
+DROP TABLE IF EXISTS pages;
8
+
9
+CREATE TABLE user (
10
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
11
+  username TEXT UNIQUE NOT NULL,
12
+  password TEXT NOT NULL
13
+);
14
+
15
+CREATE TABLE member (
16
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+  user_id INTEGER NOT NULL,
18
+  created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
19
+  member_name TEXT NOT NULL,
20
+  subtitle TEXT,
21
+  bio TEXT,
22
+  main_icon INTEGER,
23
+  homepage BOOLEAN NOT NULL DEFAULT 0,
24
+  FOREIGN KEY (user_id) REFERENCES user (id),
25
+  FOREIGN KEY (main_icon) REFERENCES icons (id)
26
+);
27
+
28
+CREATE TABLE icons (
29
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
30
+  member_id INTEGER NOT NULL,
31
+  icon_location TEXT NOT NULL,
32
+  FOREIGN KEY (member_id) REFERENCES member (id)
33
+);
34
+
35
+CREATE TABLE groups (
36
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
37
+  group_name TEXT NOT NULL
38
+);
39
+
40
+CREATE TABLE group_members (
41
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+  group_id INTEGER NOT NULL,
43
+  member_id INTEGER NOT NULL,
44
+  FOREIGN KEY (group_id) REFERENCES groups (id),
45
+  FOREIGN KEY (member_id) REFERENCES member (id)
46
+);
47
+
48
+CREATE TABLE user_front (
49
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
50
+  member_id INTEGER NOT NULL,
51
+  user_id INTEGER NOT NULL,
52
+  FOREIGN KEY (member_id) REFERENCES member (id),
53
+  FOREIGN KEY (user_id) REFERENCES user (id)
54
+);
55
+
56
+CREATE TABLE pages (
57
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
58
+  member_id INTEGER NOT NULL,
59
+  page_location TEXT NOT NULL,
60
+  FOREIGN KEY (member_id) REFERENCES member (id)
61
+);

+ 196
- 0
myriad/static/style.css 查看文件

@@ -0,0 +1,196 @@
1
+@font-face {
2
+    font-family: 'daydream';
3
+    src: url('/fonts/daydream.otf');
4
+}
5
+
6
+body{
7
+    background: #00b7ff;
8
+    background: linear-gradient(90deg, rgba(0, 183, 255, 1) 0%, rgba(87, 199, 133, 1) 50%, rgba(237, 221, 83, 1) 100%);
9
+    font-family:monospace;
10
+    font-size:12px; 
11
+    scrollbar-color:#008bcc #b3e7ff;
12
+}
13
+hr{
14
+    width:50%;
15
+    margin-top:20px;
16
+    margin-bottom:20px;
17
+}
18
+
19
+a{
20
+    text-decoration:none;
21
+}
22
+a:hover{
23
+    font-weight:bold;
24
+}
25
+
26
+form{
27
+    margin:20px;
28
+}
29
+label,input{
30
+    margin:10px;
31
+}
32
+
33
+
34
+
35
+#main{
36
+    background-color: rgb(255, 255, 255, 0);
37
+    border-radius: 5px;
38
+    width: 60%;
39
+    margin:auto;
40
+    padding:20px;
41
+    display:flex;
42
+    margin-top:20px;
43
+}
44
+#nav{
45
+    margin-right:20px;
46
+    flex:20%;
47
+    height:fit-content;
48
+    position:sticky;
49
+    position: -webkit-sticky;
50
+}
51
+.navitem{
52
+    display:block;
53
+}
54
+
55
+.container{
56
+    background-color:#e6f7ff;
57
+    border-color:#99dfff;
58
+    border-style:solid;
59
+    border-width:2px;
60
+    flex: 80%;
61
+    padding: 10px;
62
+    border-radius:5px;
63
+}
64
+
65
+.profile{
66
+    margin:15px;
67
+    border-style:solid;
68
+    border-width:1.2px;
69
+    border-color:#99dfff;
70
+    border-radius:5px;
71
+    box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2); 
72
+}
73
+.clear { clear: both; }
74
+.dsgame{
75
+    position:relative;
76
+    float:right;
77
+    margin-right:10px;
78
+    margin-bottom:10px;
79
+}
80
+
81
+.heading{
82
+    background-color:#b3e7ff;
83
+    border-left:solid;
84
+    border-color:#008bcc;
85
+    padding: 4px 0px 8px 10px;
86
+    margin-bottom: 5px;
87
+    margin:15px;
88
+    border-width:5px;
89
+}
90
+
91
+
92
+
93
+.icon{
94
+    display:inline;
95
+    float:left;
96
+    width:120px;
97
+    height:auto;
98
+    border-radius:10px;
99
+    margin:12px;
100
+    border-style:solid;
101
+    border-width:2px;
102
+    border-color:#008bcc;
103
+    box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2); 
104
+}
105
+
106
+.bio{
107
+    display:inline-block;
108
+    vertical-align:middle;
109
+    padding:10px;
110
+    width:60%;
111
+    color:black;
112
+}
113
+
114
+.minis{
115
+    padding:20px;
116
+}
117
+
118
+.divider{
119
+    width:50%;
120
+    height:auto;
121
+    margin:auto;
122
+    display:block;
123
+}
124
+
125
+.title{
126
+    font-size:20px;
127
+    margin-top:20px;
128
+    margin-bottom:20px;
129
+    display:block;
130
+}
131
+.heading.big{
132
+    font-size:20px;
133
+    font-weight:bold;
134
+}
135
+.heading.links{
136
+    position:relative;
137
+    float:left;
138
+    padding-right:20px;
139
+}
140
+
141
+.maintext{
142
+    display:block;
143
+}
144
+
145
+#blog{
146
+    max-height:300px;
147
+    overflow-y:scroll;
148
+}
149
+.post .title{
150
+    font-size:20px;
151
+    margin:10px;
152
+}
153
+.post .timestamp{
154
+    font-style:italic;
155
+    font-size:10px;
156
+    color:rgb(80, 80, 80);
157
+    margin:10px;
158
+}
159
+.post .content{
160
+    margin:10px;
161
+}
162
+.post{
163
+    border-width:1px;
164
+    border-radius:5px;
165
+    border-style:solid;
166
+    margin:20px;
167
+}
168
+
169
+.imgbullet{
170
+    margin-right:10px;
171
+}
172
+.imgright{
173
+    display: block;
174
+    margin-left: auto;
175
+}
176
+
177
+#frontbanner{
178
+    font-size:20px;
179
+}
180
+#frontbanner sub{
181
+    font-size:10px;
182
+}
183
+
184
+
185
+
186
+
187
+
188
+@media (max-width: 1000px) {
189
+  #main{
190
+    width:90%;
191
+  }
192
+  #nav{
193
+    display:none;
194
+  }
195
+  
196
+}

+ 15
- 0
myriad/templates/auth/login.html 查看文件

@@ -0,0 +1,15 @@
1
+{% extends 'base.html' %}
2
+
3
+{% block header %}
4
+  <div class="title">{% block title %}Log In{% endblock %}</div>
5
+{% endblock %}
6
+
7
+{% block content %}
8
+  <form method="post">
9
+    <label for="username">Username</label>
10
+    <input name="username" id="username" required><br>
11
+    <label for="password">Password</label>
12
+    <input type="password" name="password" id="password" required><br>
13
+    <input type="submit" value="Log In">
14
+  </form>
15
+{% endblock %}

+ 15
- 0
myriad/templates/auth/register.html 查看文件

@@ -0,0 +1,15 @@
1
+{% extends 'base.html' %}
2
+
3
+{% block header %}
4
+  <div class="title">{% block title %}Register{% endblock %}</div>
5
+{% endblock %}
6
+
7
+{% block content %}
8
+  <form method="post">
9
+    <label for="username">Username</label>
10
+    <input name="username" id="username" required><br>
11
+    <label for="password">Password</label>
12
+    <input type="password" name="password" id="password" required><br>
13
+    <input type="submit" value="Register">
14
+  </form>
15
+{% endblock %}

+ 29
- 0
myriad/templates/base.html 查看文件

@@ -0,0 +1,29 @@
1
+<!doctype html>
2
+
3
+<title>{% block title %}{% endblock %} - Myriad</title>
4
+<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
5
+
6
+
7
+<div id="main">
8
+  
9
+  <div class="container" id="nav">
10
+    <div class="heading">myriad</div>
11
+    <div class="navitem">> <a href="{{ url_for('index') }}">Home</a></div>
12
+    {% if g.user %}
13
+      <div class="navitem">> <a href="{{ url_for('auth.logout') }}">Log out</a></div>
14
+    {% else %}
15
+      <div class="navitem">> <a href="{{ url_for('auth.register') }}">Register</a></div>
16
+      <div class="navitem">> <a href="{{ url_for('auth.login') }}">Log in</a></div>
17
+    {% endif %}
18
+    </div>
19
+
20
+  <div class="container">
21
+
22
+    {% block header %}{% endblock %}
23
+    {% for message in get_flashed_messages() %}
24
+      <div class="flash">{{ message }}</div>
25
+    {% endfor %}
26
+    {% block content %}{% endblock %}
27
+  </div>
28
+
29
+</div>

+ 11
- 0
myriad/templates/index.html 查看文件

@@ -0,0 +1,11 @@
1
+{% extends 'base.html' %}
2
+
3
+{% block header %}
4
+  <div class="title">{% block title %}Welcome{% endblock %}</div>
5
+{% endblock %}
6
+
7
+{% block content %}
8
+  <div class="maintext">
9
+    homepage :) 
10
+  </div>
11
+{% endblock %}