initial commit

This commit is contained in:
Lera Elvoé 2025-06-29 18:17:04 +03:00
commit 6c731b04d3
Signed by: yagich
SSH Key Fingerprint: SHA256:6xjGb6uA7lAVcULa7byPEN//rQ0wPoG+UzYVMfZnbvc
11 changed files with 323 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.venv
**/*.pyc
data/db/*
data/static/avatars/*
!data/static/avatars/default.webp

23
app/__init__.py Normal file
View File

@ -0,0 +1,23 @@
from flask import Flask
import os
def create_app():
app = Flask(__name__)
app.config["DB_PATH"] = "data/db/db.sqlite"
os.makedirs(os.path.dirname(app.config["DB_PATH"]), exist_ok = True)
with app.app_context():
from .schema import create as create_tables
from .migrations import run_migrations
create_tables()
run_migrations()
if os.getenv("PYROM_PROD") is None:
app.static_folder = os.path.join(os.path.dirname(__file__), "../data/static")
app.debug = True
from app.routes.app import bp as app_bp
app.register_blueprint(app_bp)
return app

12
app/auth.py Normal file
View File

@ -0,0 +1,12 @@
from argon2 import PasswordHasher
ph = PasswordHasher()
def hash_password(password):
return ph.hash(password)
def verify(expected, given):
try:
return ph.verify(expected, given)
except:
return False

218
app/db.py Normal file
View File

@ -0,0 +1,218 @@
import sqlite3
from contextlib import contextmanager
from flask import current_app
class DB:
def __init__(self):
self._transaction_depth = 0
self._connection = None
@contextmanager
def _get_connection(self):
if self._connection and self._transaction_depth > 0:
yield self._connection
return
conn = sqlite3.connect(current_app.config["DB_PATH"])
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA FOREIGN_KEYS = 1")
try:
yield conn
finally:
if self._transaction_depth == 0:
conn.close()
@contextmanager
def transaction(self):
"""Transaction context."""
self.begin()
try:
yield
self.commit()
except Exception:
self.rollback()
raise
def begin(self):
"""Begins a new transaction."""
if self._transaction_depth == 0:
if not self._connection:
self._connection = sqlite3.connect(current_app.config["DB_PATH"])
self._connection.row_factory = sqlite3.Row
self._connection.execute("PRAGMA FOREIGN_KEYS = 1")
self._connection.execute("BEGIN")
self._transaction_depth += 1
def commit(self):
"""Commits the current transaction."""
if self._transaction_depth > 0:
self._transaction_depth -= 1
if self._transaction_depth == 0:
self._connection.commit()
def rollback(self):
"""Rolls back the current transaction."""
if self._transaction_depth > 0:
self._transaction_depth = 0
self._connection.rollback()
def query(self, sql, *args):
"""Executes a query and returns a list of dictionaries."""
with self._get_connection() as conn:
rows = conn.execute(sql, args).fetchall()
return [dict(row) for row in rows]
def insert(self, table, columns, *values):
if isinstance(columns, (list, tuple)):
columns = ", ".join(columns)
placeholders = ", ".join(["?"] * len(values))
sql = f"""
INSERT INTO {table} ({columns})
VALUES ({placeholders})
RETURNING *
"""
with self._get_connection() as conn:
result = conn.execute(sql, values).fetchone()
conn.commit()
return dict(result) if result else None
def execute(self, sql, *args):
"""Executes a query without returning."""
with self._get_connection() as conn:
conn.execute(sql, args)
conn.commit()
def fetch_one(self, sql, *args):
"""Grabs the first row of a query."""
with self._get_connection() as conn:
row = conn.execute(sql, args).fetchone()
return dict(row) if row else None
class QueryBuilder:
def __init__(self, table):
self.table = table
self._where = {}
self._select = "*"
self._params = []
def select(self, columns = "*"):
self._select = columns
return self
def where(self, condition):
self._where.update(condition)
return self
def build_select(self):
sql = f"SELECT {self._select} FROM {self.table}"
if self._where:
conditions = " AND ".join(f"{k} = ?" for k in self._where.keys())
sql += f" WHERE {conditions}"
return sql, list(self._where.values())
def build_update(self, data):
columns = ", ".join(f"{k} = ?" for k in data.keys())
sql = f"UPDATE {self.table} SET {columns}"
if self._where:
conditions = " AND ".join(f"{k} = ?" for k in self._where.keys())
sql += f" WHERE {conditions}"
params = list(data.values()) + list(self._where.values())
return sql, params
def build_delete(self):
sql = f"DELETE FROM {self.table}"
if self._where:
conditions = " AND ".join(f"{k} = ?" for k in self._where.keys())
sql += f" WHERE {conditions}"
return sql, list(self._where.values())
def first(self):
sql, params = self.build_select()
print(sql, params)
return db.fetch_one(f"{sql} LIMIT 1", *params)
def all(self):
sql, params = self.build_select()
return db.query(sql, *params)
class Model:
def __init__(self, table):
self.table = table
self._data = {}
def __getitem__(self, key):
return self._data[key]
def __getattr__(self, key):
try:
return self._data[key]
except KeyError:
raise AttributeError(f"No column '{key}'")
@classmethod
def find(cls, condition):
row = db.QueryBuilder(cls.table)\
.where(condition)\
.first()
if not row:
return None
instance = cls(cls.table)
instance._data = dict(row)
return instance
@classmethod
def create(cls, values):
if not values:
return None
columns = list(values.keys())
row = db.insert(cls.table, columns, *values.values())
if row:
instance = cls(cls.table)
instance._data = row
return instance
return None
def update(self, data):
qb = db.QueryBuilder(self.table)\
.where({"id": self._data["id"]})
sql, params = qb.build_update(data)
db.execute(sql, *params)
self._data.update(data)
def delete(self):
qb = db.QueryBuilder(self.table)\
.where({"id": self._data["id"]})
sql, params = qb.build_delete()
db.execute(sql, *params)
self._data = {}
db = DB()

30
app/migrations.py Normal file
View File

@ -0,0 +1,30 @@
from .db import db
# format: {integer: str|list<str>}
MIGRATIONS = {
}
def run_migrations():
print("Running migrations...")
ran = 0
db.execute("""
CREATE TABLE IF NOT EXISTS _migrations(
id INTEGER PRIMARY KEY
)
""")
completed = [row["id"] for row in db.query("SELECT id FROM _migrations")]
for migration_id in sorted(MIGRATIONS.keys()):
if migration_id not in completed:
print(f"Running migration #{migration_id}")
ran += 1
statements = MIGRATIONS[migration_id]
# support both strings and lists
if isinstance(statements, str):
statements = [statements]
for sql in statements:
db.execute(sql)
db.execute("INSERT INTO _migrations (id) VALUES (?)", migration_id)
print(f"Ran {ran} migrations.")

1
app/models.py Normal file
View File

@ -0,0 +1 @@
from .db import Model

7
app/routes/app.py Normal file
View File

@ -0,0 +1,7 @@
from flask import Blueprint
bp = Blueprint("app", __name__, url_prefix = "/")
@bp.route("/")
def hello_world():
return f"<img src='static/avatars/default.webp'></img>"

10
app/run.py Normal file
View File

@ -0,0 +1,10 @@
from app import create_app
import os
app = create_app()
if __name__ == "__main__":
app.run(
host = "127.0.0.1",
port = 8080
)

13
app/schema.py Normal file
View File

@ -0,0 +1,13 @@
from .db import db
# list of statements
SCHEMA = [
]
def create():
print("Creating schema...")
with db.transaction():
for stmt in SCHEMA:
db.execute(stmt)
print("Schema completed.")

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
flask
argon2-cffi
wand