Init project

This commit is contained in:
Verox001 2024-01-12 10:57:58 +01:00
parent 45748f5d2f
commit d13dff5146
18 changed files with 428 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -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

11
.idea/HabitTracker.iml generated Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

17
.idea/dataSources.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="db" uuid="293bf84a-a146-451e-bdde-9f7806476a67">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:C:\Users\alter\Documents\Schule\Informatik\HabitTracker\db\db.sqlite</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>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</url>
</library>
</libraries>
</data-source>
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (HabitTracker) (2)" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="Python 3.10 (HabitTracker) (2)" project-jdk-type="Python SDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/HabitTracker.iml" filepath="$PROJECT_DIR$/.idea/HabitTracker.iml" />
</modules>
</component>
</project>

7
.idea/sqldialects.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/db/migrations/1705052036_create_user_table.sql" dialect="SQLite" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

79
app.py Normal file
View File

@ -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)

28
db/SQLiteClient.py Normal file
View File

@ -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

30
db/create_migration.py Normal file
View File

@ -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.")

BIN
db/db.sqlite Normal file

Binary file not shown.

63
db/migrate.py Normal file
View File

@ -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.")

View File

@ -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
);

18
models/User.py Normal file
View File

@ -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

28
templates/auth/login.html Normal file
View File

@ -0,0 +1,28 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="card">
<h5 class="card-header">Login</h5>
<div class="card-body column">
<form method="POST" action="/login">
<div class="mb-3 row">
<label for="email" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="email" placeholder="email@example.com">
</div>
</div>
<div class="mb-3 row">
<label for="password" class="col-sm-2 col-form-label">Passwort</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="password">
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-3">Einloggen</button>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="column">
<h3>Registrieren</h3>
<form method="POST" action="/signup">
<div class="mb-3">
<label for="email" class="form-label">Email-Adresse</label>
<input type="email" class="form-control {% if errors.get('email') %} is-invalid {% endif %}" id="email" name="email" placeholder="name@example.com" value="{{ email }}">
<div class="invalid-feedback">
{{ errors.get('email', '') }}
</div>
</div>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control {% if errors.get('name') %} is-invalid {% endif %}" id="name" name="name" placeholder="Max" value="{{ name }}">
<div class="invalid-feedback">
{{ errors.get('name', '') }}
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Passwort</label>
<input type="password" class="form-control {% if errors.get('password') %} is-invalid {% endif %}" id="password" name="password" value="{{ password }}">
<div class="invalid-feedback">
{{ errors.get('password', '') }}
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-3">Registrieren</button>
</div>
</form>
</div>
{% endblock %}

17
templates/index.html Normal file
View File

@ -0,0 +1,17 @@
{% extends 'layouts/main.html' %}
{% block content %}
<h1>Hello World!</h1>
<h3>{{ utc_dt }}</h3>
<button id="test">
Drück nicht!
</button>
<script>
document.getElementById('test').addEventListener('click', function() {
alert('Du hast mich gedrückt!');
});
</script>
{% endblock %}

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" >
<title>{{ title }} - HabitTracker</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">HabitTracker</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link {% if title == 'Home' %} active {% endif %}" aria-current="page" href="{{ url_for('index') }}">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
</li>
</ul>
<ul class="navbar-nav mb-2 mb-lg-0">
<li class="nav-item me-2">
<a class="btn btn-primary" aria-current="page" href="{{ url_for('login') }}">Login</a>
</li>
<li class="nav-item">
<a class="btn btn-outline-secondary" aria-current="page" href="{{ url_for('signup') }}">Signup</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-3">
{% block content %} {% endblock %}
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
</div>
</body>
</html>