basic
log in and sign up pages, db schema
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.venv/
|
||||||
|
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
instance/
|
||||||
|
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
39
myriad/__init__.py
Normal file
39
myriad/__init__.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(test_config=None):
|
||||||
|
# create and configure the app
|
||||||
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
app.config.from_mapping(
|
||||||
|
SECRET_KEY='dev',
|
||||||
|
DATABASE=os.path.join(app.instance_path, 'database.sqlite'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if test_config is None:
|
||||||
|
# load the instance config, if it exists, when not testing
|
||||||
|
app.config.from_pyfile('config.py', silent=True)
|
||||||
|
else:
|
||||||
|
# load the test config if passed in
|
||||||
|
app.config.from_mapping(test_config)
|
||||||
|
|
||||||
|
# ensure the instance folder exists
|
||||||
|
os.makedirs(app.instance_path, exist_ok=True)
|
||||||
|
|
||||||
|
# a simple page that says hello
|
||||||
|
@app.route('/hello')
|
||||||
|
def hello():
|
||||||
|
return 'Hello, World!'
|
||||||
|
|
||||||
|
from . import db
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
from . import auth
|
||||||
|
app.register_blueprint(auth.bp)
|
||||||
|
|
||||||
|
from . import home
|
||||||
|
app.register_blueprint(home.bp)
|
||||||
|
app.add_url_rule('/', endpoint='index')
|
||||||
|
|
||||||
|
return app
|
||||||
92
myriad/auth.py
Normal file
92
myriad/auth.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
|
from flask import (
|
||||||
|
Blueprint, flash, g, redirect, render_template, request, session, url_for
|
||||||
|
)
|
||||||
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
|
from myriad.db import get_db
|
||||||
|
|
||||||
|
bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||||
|
|
||||||
|
@bp.route('/register', methods=('GET', 'POST'))
|
||||||
|
def register():
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form['username']
|
||||||
|
password = request.form['password']
|
||||||
|
db = get_db()
|
||||||
|
error = None
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
error = 'Username is required.'
|
||||||
|
elif not password:
|
||||||
|
error = 'Password is required.'
|
||||||
|
|
||||||
|
if error is None:
|
||||||
|
try:
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO user (username, password) VALUES (?, ?)",
|
||||||
|
(username, generate_password_hash(password)),
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
except db.IntegrityError:
|
||||||
|
error = f"User {username} is already registered."
|
||||||
|
else:
|
||||||
|
return redirect(url_for("auth.login"))
|
||||||
|
|
||||||
|
flash(error)
|
||||||
|
|
||||||
|
return render_template('auth/register.html')
|
||||||
|
|
||||||
|
@bp.route('/login', methods=('GET', 'POST'))
|
||||||
|
def login():
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form['username']
|
||||||
|
password = request.form['password']
|
||||||
|
db = get_db()
|
||||||
|
error = None
|
||||||
|
user = db.execute(
|
||||||
|
'SELECT * FROM user WHERE username = ?', (username,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
error = 'Incorrect username.'
|
||||||
|
elif not check_password_hash(user['password'], password):
|
||||||
|
error = 'Incorrect password.'
|
||||||
|
|
||||||
|
if error is None:
|
||||||
|
session.clear()
|
||||||
|
session['user_id'] = user['id']
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
flash(error)
|
||||||
|
|
||||||
|
return render_template('auth/login.html')
|
||||||
|
|
||||||
|
@bp.before_app_request
|
||||||
|
def load_logged_in_user():
|
||||||
|
user_id = session.get('user_id')
|
||||||
|
|
||||||
|
if user_id is None:
|
||||||
|
g.user = None
|
||||||
|
else:
|
||||||
|
g.user = get_db().execute(
|
||||||
|
'SELECT * FROM user WHERE id = ?', (user_id,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/logout')
|
||||||
|
def logout():
|
||||||
|
session.clear()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
|
def login_required(view):
|
||||||
|
@functools.wraps(view)
|
||||||
|
def wrapped_view(**kwargs):
|
||||||
|
if g.user is None:
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
return view(**kwargs)
|
||||||
|
|
||||||
|
return wrapped_view
|
||||||
47
myriad/db.py
Normal file
47
myriad/db.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import sqlite3
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import click
|
||||||
|
from flask import current_app, g
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
if 'db' not in g:
|
||||||
|
g.db = sqlite3.connect(
|
||||||
|
current_app.config['DATABASE'],
|
||||||
|
detect_types=sqlite3.PARSE_DECLTYPES
|
||||||
|
)
|
||||||
|
g.db.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
return g.db
|
||||||
|
|
||||||
|
|
||||||
|
def close_db(e=None):
|
||||||
|
db = g.pop('db', None)
|
||||||
|
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
db = get_db()
|
||||||
|
|
||||||
|
with current_app.open_resource('schema.sql') as f:
|
||||||
|
db.executescript(f.read().decode('utf8'))
|
||||||
|
|
||||||
|
|
||||||
|
def init_app(app):
|
||||||
|
app.teardown_appcontext(close_db)
|
||||||
|
app.cli.add_command(init_db_command)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command('init-db')
|
||||||
|
def init_db_command():
|
||||||
|
"""Clear the existing data and create new tables."""
|
||||||
|
init_db()
|
||||||
|
click.echo('Initialized the database.')
|
||||||
|
|
||||||
|
|
||||||
|
sqlite3.register_converter(
|
||||||
|
"timestamp", lambda v: datetime.fromisoformat(v.decode())
|
||||||
|
)
|
||||||
19
myriad/home.py
Normal file
19
myriad/home.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from flask import (
|
||||||
|
Blueprint, flash, g, redirect, render_template, request, url_for
|
||||||
|
)
|
||||||
|
from werkzeug.exceptions import abort
|
||||||
|
|
||||||
|
from myriad.auth import login_required
|
||||||
|
from myriad.db import get_db
|
||||||
|
|
||||||
|
bp = Blueprint('home', __name__)
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
def index():
|
||||||
|
# db = get_db()
|
||||||
|
# posts = db.execute(
|
||||||
|
# 'SELECT p.id, title, body, created, author_id, username'
|
||||||
|
# ' FROM post p JOIN user u ON p.author_id = u.id'
|
||||||
|
# ' ORDER BY created DESC'
|
||||||
|
# ).fetchall()
|
||||||
|
return render_template('index.html')
|
||||||
61
myriad/schema.sql
Normal file
61
myriad/schema.sql
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
DROP TABLE IF EXISTS user;
|
||||||
|
DROP TABLE IF EXISTS member;
|
||||||
|
DROP TABLE IF EXISTS icons;
|
||||||
|
DROP TABLE IF EXISTS groups;
|
||||||
|
DROP TABLE IF EXISTS group_members;
|
||||||
|
DROP TABLE IF EXISTS user_front;
|
||||||
|
DROP TABLE IF EXISTS pages;
|
||||||
|
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE member (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
member_name TEXT NOT NULL,
|
||||||
|
subtitle TEXT,
|
||||||
|
bio TEXT,
|
||||||
|
main_icon INTEGER,
|
||||||
|
homepage BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES user (id),
|
||||||
|
FOREIGN KEY (main_icon) REFERENCES icons (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE icons (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
member_id INTEGER NOT NULL,
|
||||||
|
icon_location TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (member_id) REFERENCES member (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE groups (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
group_name TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE group_members (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
member_id INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (group_id) REFERENCES groups (id),
|
||||||
|
FOREIGN KEY (member_id) REFERENCES member (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_front (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
member_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (member_id) REFERENCES member (id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES user (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE pages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
member_id INTEGER NOT NULL,
|
||||||
|
page_location TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (member_id) REFERENCES member (id)
|
||||||
|
);
|
||||||
196
myriad/static/style.css
Normal file
196
myriad/static/style.css
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'daydream';
|
||||||
|
src: url('/fonts/daydream.otf');
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
background: #00b7ff;
|
||||||
|
background: linear-gradient(90deg, rgba(0, 183, 255, 1) 0%, rgba(87, 199, 133, 1) 50%, rgba(237, 221, 83, 1) 100%);
|
||||||
|
font-family:monospace;
|
||||||
|
font-size:12px;
|
||||||
|
scrollbar-color:#008bcc #b3e7ff;
|
||||||
|
}
|
||||||
|
hr{
|
||||||
|
width:50%;
|
||||||
|
margin-top:20px;
|
||||||
|
margin-bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a{
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
a:hover{
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
form{
|
||||||
|
margin:20px;
|
||||||
|
}
|
||||||
|
label,input{
|
||||||
|
margin:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#main{
|
||||||
|
background-color: rgb(255, 255, 255, 0);
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 60%;
|
||||||
|
margin:auto;
|
||||||
|
padding:20px;
|
||||||
|
display:flex;
|
||||||
|
margin-top:20px;
|
||||||
|
}
|
||||||
|
#nav{
|
||||||
|
margin-right:20px;
|
||||||
|
flex:20%;
|
||||||
|
height:fit-content;
|
||||||
|
position:sticky;
|
||||||
|
position: -webkit-sticky;
|
||||||
|
}
|
||||||
|
.navitem{
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
background-color:#e6f7ff;
|
||||||
|
border-color:#99dfff;
|
||||||
|
border-style:solid;
|
||||||
|
border-width:2px;
|
||||||
|
flex: 80%;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile{
|
||||||
|
margin:15px;
|
||||||
|
border-style:solid;
|
||||||
|
border-width:1.2px;
|
||||||
|
border-color:#99dfff;
|
||||||
|
border-radius:5px;
|
||||||
|
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.clear { clear: both; }
|
||||||
|
.dsgame{
|
||||||
|
position:relative;
|
||||||
|
float:right;
|
||||||
|
margin-right:10px;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading{
|
||||||
|
background-color:#b3e7ff;
|
||||||
|
border-left:solid;
|
||||||
|
border-color:#008bcc;
|
||||||
|
padding: 4px 0px 8px 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin:15px;
|
||||||
|
border-width:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.icon{
|
||||||
|
display:inline;
|
||||||
|
float:left;
|
||||||
|
width:120px;
|
||||||
|
height:auto;
|
||||||
|
border-radius:10px;
|
||||||
|
margin:12px;
|
||||||
|
border-style:solid;
|
||||||
|
border-width:2px;
|
||||||
|
border-color:#008bcc;
|
||||||
|
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio{
|
||||||
|
display:inline-block;
|
||||||
|
vertical-align:middle;
|
||||||
|
padding:10px;
|
||||||
|
width:60%;
|
||||||
|
color:black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minis{
|
||||||
|
padding:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider{
|
||||||
|
width:50%;
|
||||||
|
height:auto;
|
||||||
|
margin:auto;
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size:20px;
|
||||||
|
margin-top:20px;
|
||||||
|
margin-bottom:20px;
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
.heading.big{
|
||||||
|
font-size:20px;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
.heading.links{
|
||||||
|
position:relative;
|
||||||
|
float:left;
|
||||||
|
padding-right:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintext{
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog{
|
||||||
|
max-height:300px;
|
||||||
|
overflow-y:scroll;
|
||||||
|
}
|
||||||
|
.post .title{
|
||||||
|
font-size:20px;
|
||||||
|
margin:10px;
|
||||||
|
}
|
||||||
|
.post .timestamp{
|
||||||
|
font-style:italic;
|
||||||
|
font-size:10px;
|
||||||
|
color:rgb(80, 80, 80);
|
||||||
|
margin:10px;
|
||||||
|
}
|
||||||
|
.post .content{
|
||||||
|
margin:10px;
|
||||||
|
}
|
||||||
|
.post{
|
||||||
|
border-width:1px;
|
||||||
|
border-radius:5px;
|
||||||
|
border-style:solid;
|
||||||
|
margin:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imgbullet{
|
||||||
|
margin-right:10px;
|
||||||
|
}
|
||||||
|
.imgright{
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#frontbanner{
|
||||||
|
font-size:20px;
|
||||||
|
}
|
||||||
|
#frontbanner sub{
|
||||||
|
font-size:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
#main{
|
||||||
|
width:90%;
|
||||||
|
}
|
||||||
|
#nav{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
myriad/templates/auth/login.html
Normal file
15
myriad/templates/auth/login.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<div class="title">{% block title %}Log In{% endblock %}</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input name="username" id="username" required><br>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required><br>
|
||||||
|
<input type="submit" value="Log In">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
15
myriad/templates/auth/register.html
Normal file
15
myriad/templates/auth/register.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<div class="title">{% block title %}Register{% endblock %}</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input name="username" id="username" required><br>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required><br>
|
||||||
|
<input type="submit" value="Register">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
29
myriad/templates/base.html
Normal file
29
myriad/templates/base.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<title>{% block title %}{% endblock %} - Myriad</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
|
||||||
|
|
||||||
|
<div id="main">
|
||||||
|
|
||||||
|
<div class="container" id="nav">
|
||||||
|
<div class="heading">myriad</div>
|
||||||
|
<div class="navitem">> <a href="{{ url_for('index') }}">Home</a></div>
|
||||||
|
{% if g.user %}
|
||||||
|
<div class="navitem">> <a href="{{ url_for('auth.logout') }}">Log out</a></div>
|
||||||
|
{% else %}
|
||||||
|
<div class="navitem">> <a href="{{ url_for('auth.register') }}">Register</a></div>
|
||||||
|
<div class="navitem">> <a href="{{ url_for('auth.login') }}">Log in</a></div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="flash">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
11
myriad/templates/index.html
Normal file
11
myriad/templates/index.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<div class="title">{% block title %}Welcome{% endblock %}</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="maintext">
|
||||||
|
homepage :)
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user