diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/HabitTracker.iml b/.idea/HabitTracker.iml new file mode 100644 index 0000000..2cdb1e3 --- /dev/null +++ b/.idea/HabitTracker.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..c73c374 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\alter\Documents\Schule\Informatik\HabitTracker\db\db.sqlite + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.43.0/org/xerial/sqlite-jdbc/3.43.0.0/sqlite-jdbc-3.43.0.0.jar + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..dae6042 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..fef1093 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..6af65ab --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..a4921ba --- /dev/null +++ b/app.py @@ -0,0 +1,79 @@ +import datetime + +from flask import Flask, render_template, redirect, url_for, request +from flask_login import login_required, LoginManager + +from models.User import User + +# Create a new Flask instance +app = Flask(__name__) + + +# Create a new route +@app.route('/') +def index(): + # return 'Hello World' + return render_template('index.html', title='Home', utc_dt=datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")) + +@app.route('/test') +@login_required +def secret(): + return 'Pssst!' + +@app.route('/login') +def login(): + return render_template('auth/login.html') + + +@app.route('/signup') +def signup(): + return render_template('auth/signup.html', errors={}) + +@app.route('/signup', methods=['POST']) +def signup_post(): + email = request.form.get('email') + name = request.form.get('name') + password = request.form.get('password') + + # Check for errors + errors = {} + if not email: + errors['email'] = 'Email is required.' + if not name: + errors['name'] = 'Name is required.' + if not password: + errors['password'] = 'Password is required.' + + return render_template( + 'auth/signup.html', + email=email, + name=name, + password=password, + errors=errors + ) + + # Save user to database. Maybe log the user in directly. + + # Redirect to login page + return redirect(url_for('login')) + + +@app.route('/logout') +@login_required +def logout(): + # Log out functionality + return redirect(url_for('index')) + + +# Run the application +if __name__ == '__main__': + login_manager = LoginManager() + login_manager.login_view = 'login' + login_manager.init_app(app) + app.secret_key = 'PSSSSSHHHT!' + + @login_manager.user_loader + def load_user(user_id): + return User.get(user_id) + + app.run(port=5000, debug=True) diff --git a/db/SQLiteClient.py b/db/SQLiteClient.py new file mode 100644 index 0000000..4fa6c8c --- /dev/null +++ b/db/SQLiteClient.py @@ -0,0 +1,28 @@ +import hashlib +import sqlite3 + + +def con3(): + conn = sqlite3.connect('db/db.sqlite') + return conn + + +def create_user(name, email, password): + password = hashlib.md5(password.encode()).hexdigest() + query = f"INSERT INTO users (name, email, password) VALUES ({name}, {email}, {password})" + conn = con3() + cursor = conn.cursor() + cursor.execute(query) + conn.commit() + conn.close() + return cursor.lastrowid + + +def get_user(id): + query = f"SELECT * FROM users WHERE id = {id}" + conn = con3() + cursor = conn.cursor() + cursor.execute(query) + user = cursor.fetchone() + conn.close() + return user diff --git a/db/create_migration.py b/db/create_migration.py new file mode 100644 index 0000000..8b00067 --- /dev/null +++ b/db/create_migration.py @@ -0,0 +1,30 @@ +import datetime +import os + + +def is_snake_case(s): + if not s: + return False + + return s.islower() and all(char.isalpha() or char == '_' for char in s) + +if __name__ == "__main__": + migration_name = "" + while not is_snake_case(migration_name): + if migration_name: + print(f"{migration_name} ist nicht im Camcel-Case") + migration_name = input('Gebe einen Namen für die Migration im snake_case an: ') + + timestamp = datetime.datetime.now().timestamp() + migrations_path = os.path.join(os.path.dirname(__file__), 'migrations') + print(migrations_path) + + try: + if not os.path.isdir(migrations_path): + raise FileNotFoundError + + file = open(os.path.join(os.path.dirname(__file__), 'migrations', str(int(timestamp)) + '_' + migration_name + '.sql'), 'w') + file.close() + + except FileNotFoundError: + print("migrations/ Directive could not be found within the db directory, contained in the project main directory.") \ No newline at end of file diff --git a/db/db.sqlite b/db/db.sqlite new file mode 100644 index 0000000..5602513 Binary files /dev/null and b/db/db.sqlite differ diff --git a/db/migrate.py b/db/migrate.py new file mode 100644 index 0000000..16dbe07 --- /dev/null +++ b/db/migrate.py @@ -0,0 +1,63 @@ +import datetime +import os +import sqlite3 +from pathlib import Path +from sqlite3 import Connection + +CREATE_MIGRATIONS_TABLE = """ +CREATE TABLE IF NOT EXISTS migrations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + file TEXT, + created_at TEXT +); +""" + +def init_db(): + conn = sqlite3.connect("db.sqlite") + + # Create migration table, if not exists yet + conn.execute(CREATE_MIGRATIONS_TABLE) + conn.commit() + +def create_migration(conn: Connection, migration: str): + conn.execute(f""" + INSERT INTO migrations (file, created_at) VALUES ("{migration}", "{datetime.datetime.now()}"); + """) + +""" +Returns True, if the migration has already been migrates once. +""" +def check_migration(conn: Connection, migration: str): + res = conn.cursor().execute(f""" + SELECT EXISTS(SELECT file FROM migrations WHERE file = "{migration}"); + """) + return res.fetchone()[0] == 1 + +if __name__ == "__main__": + # Init DB with migrations table + init_db() + + migrations_path = os.path.join(os.path.dirname(__file__), 'migrations') + + try: + if not os.path.isdir(migrations_path): + raise FileNotFoundError + + conn = sqlite3.connect("db.sqlite") + for file in os.listdir(migrations_path): + if not file.endswith('.sql'): + continue + with open(os.path.join(os.path.dirname(__file__), 'migrations', file)) as migration: + migration_name = Path(file).stem + + if check_migration(conn, migration_name): + continue + + print(f"Migrating {migration_name}...") + create_migration(conn, migration_name) + conn.execute(migration.read()) + print(f"Migrated {migration_name}\n") + + conn.commit() + except FileNotFoundError: + print("migrations/ Directive could not be found within the db directory, contained in the project main directory.") \ No newline at end of file diff --git a/db/migrations/1705052036_create_user_table.sql b/db/migrations/1705052036_create_user_table.sql new file mode 100644 index 0000000..da295c4 --- /dev/null +++ b/db/migrations/1705052036_create_user_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS users +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT NOT NULL, + password TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); diff --git a/models/User.py b/models/User.py new file mode 100644 index 0000000..466b112 --- /dev/null +++ b/models/User.py @@ -0,0 +1,18 @@ +from db.SQLiteClient import create_user, get_user + + +class User: + def __init__(self, id, name, email): + self.id = id + self.name = name + self.email = email + + @staticmethod + def create(name, email, password): + id = create_user(name, email, password) + return User(id, name, email) + + @staticmethod + def get(id): + user = get_user(id) + return User(user[0], user[1], user[2]) if user else None \ No newline at end of file diff --git a/templates/auth/login.html b/templates/auth/login.html new file mode 100644 index 0000000..c4af8fa --- /dev/null +++ b/templates/auth/login.html @@ -0,0 +1,28 @@ +{% extends 'layouts/main.html' %} + +{% block content %} +
+
Login
+
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/auth/signup.html b/templates/auth/signup.html new file mode 100644 index 0000000..bbbf537 --- /dev/null +++ b/templates/auth/signup.html @@ -0,0 +1,33 @@ +{% extends 'layouts/main.html' %} + +{% block content %} +
+

Registrieren

+
+
+ + +
+ {{ errors.get('email', '') }} +
+
+
+ + +
+ {{ errors.get('name', '') }} +
+
+
+ + +
+ {{ errors.get('password', '') }} +
+
+
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..b0170d6 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,17 @@ +{% extends 'layouts/main.html' %} + +{% block content %} +

Hello World!

+

{{ utc_dt }}

+ + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/layouts/main.html b/templates/layouts/main.html new file mode 100644 index 0000000..6667d19 --- /dev/null +++ b/templates/layouts/main.html @@ -0,0 +1,57 @@ + + + + + + {{ title }} - HabitTracker + + + + + + +
+ {% block content %} {% endblock %} + + + + +
+ + \ No newline at end of file