diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c414af7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.venv/ + +*.pyc +__pycache__/ + +instance/ + +.pytest_cache/ +.coverage +htmlcov/ + +dist/ +build/ +*.egg-info/ \ No newline at end of file diff --git a/myriad/__init__.py b/myriad/__init__.py new file mode 100644 index 0000000..6a261b6 --- /dev/null +++ b/myriad/__init__.py @@ -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 \ No newline at end of file diff --git a/myriad/auth.py b/myriad/auth.py new file mode 100644 index 0000000..50afc49 --- /dev/null +++ b/myriad/auth.py @@ -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 \ No newline at end of file diff --git a/myriad/db.py b/myriad/db.py new file mode 100644 index 0000000..24f3ce9 --- /dev/null +++ b/myriad/db.py @@ -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()) +) diff --git a/myriad/home.py b/myriad/home.py new file mode 100644 index 0000000..28d5beb --- /dev/null +++ b/myriad/home.py @@ -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') \ No newline at end of file diff --git a/myriad/schema.sql b/myriad/schema.sql new file mode 100644 index 0000000..9ac8d85 --- /dev/null +++ b/myriad/schema.sql @@ -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) +); \ No newline at end of file diff --git a/myriad/static/style.css b/myriad/static/style.css new file mode 100644 index 0000000..48618de --- /dev/null +++ b/myriad/static/style.css @@ -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; + } + +} diff --git a/myriad/templates/auth/login.html b/myriad/templates/auth/login.html new file mode 100644 index 0000000..fcd2cc7 --- /dev/null +++ b/myriad/templates/auth/login.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +
{% block title %}Log In{% endblock %}
+{% endblock %} + +{% block content %} +
+ +
+ +
+ +
+{% endblock %} \ No newline at end of file diff --git a/myriad/templates/auth/register.html b/myriad/templates/auth/register.html new file mode 100644 index 0000000..9ee1ea2 --- /dev/null +++ b/myriad/templates/auth/register.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +
{% block title %}Register{% endblock %}
+{% endblock %} + +{% block content %} +
+ +
+ +
+ +
+{% endblock %} \ No newline at end of file diff --git a/myriad/templates/base.html b/myriad/templates/base.html new file mode 100644 index 0000000..1effd19 --- /dev/null +++ b/myriad/templates/base.html @@ -0,0 +1,29 @@ + + +{% block title %}{% endblock %} - Myriad + + + +
+ + + +
+ + {% block header %}{% endblock %} + {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% endblock %} +
+ +
\ No newline at end of file diff --git a/myriad/templates/index.html b/myriad/templates/index.html new file mode 100644 index 0000000..dfaede2 --- /dev/null +++ b/myriad/templates/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block header %} +
{% block title %}Welcome{% endblock %}
+{% endblock %} + +{% block content %} +
+ homepage :) +
+{% endblock %} \ No newline at end of file