feat: Phase 0 – Gerüst (FastAPI+SQLite, Multi-User-Login, React-App-Shell, Deploy)
Grundgerüst für Certain Finance: - Backend: FastAPI + SQLAlchemy + SQLite, Alembic-Migrationen (Batch-Mode), argon2-Passwort-Hashing, server-seitige Sitzungen im Cookie, Erst-Admin, Benutzerverwaltung mit Temporär-Passwort, Einstellungen je Benutzer. - Ein Prozess, ein Port: uvicorn liefert API (/api) und gebautes SPA aus, inkl. SPA-Deeplink-Fallback. - Frontend: React 18 + Vite + JSX, Design-Tokens (Light/Dark), i18n DE/EN, Inline-SVG-Icons (keine Emojis), Login, Passwort-ändern, Dashboard-Platzhalter, Benutzer- und Einstellungsseiten. - Deploy: nativer LXC-Installer + Update-Skript (systemd), env-Vorlage, Doku. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""Datenbank-Anbindung (SQLite).
|
||||
|
||||
SQLite braucht ein paar PRAGMAs, damit es sich unter FastAPIs Thread-Pool
|
||||
vernünftig verhält: WAL für gleichzeitige Leser, ``foreign_keys=ON`` (sonst
|
||||
greifen die ON-DELETE-Regeln nicht), und ein Busy-Timeout, damit kurze
|
||||
Schreibsperren nicht sofort krachen.
|
||||
"""
|
||||
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
||||
|
||||
from .config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
_url = settings.effective_database_url
|
||||
|
||||
# Für die SQLite-Datei das Datenverzeichnis sicherstellen.
|
||||
if _url.startswith("sqlite:///"):
|
||||
_path = _url.replace("sqlite:///", "", 1)
|
||||
_dir = os.path.dirname(_path)
|
||||
if _dir:
|
||||
os.makedirs(_dir, exist_ok=True)
|
||||
|
||||
connect_args = {"check_same_thread": False} if _url.startswith("sqlite") else {}
|
||||
|
||||
engine = create_engine(
|
||||
_url,
|
||||
connect_args=connect_args,
|
||||
pool_pre_ping=True,
|
||||
future=True,
|
||||
)
|
||||
|
||||
|
||||
@event.listens_for(engine, "connect")
|
||||
def _sqlite_pragmas(dbapi_connection, _record):
|
||||
"""Bei jeder neuen SQLite-Verbindung sinnvolle Voreinstellungen setzen."""
|
||||
if not _url.startswith("sqlite"):
|
||||
return
|
||||
cur = dbapi_connection.cursor()
|
||||
cur.execute("PRAGMA journal_mode=WAL")
|
||||
cur.execute("PRAGMA foreign_keys=ON")
|
||||
cur.execute("PRAGMA busy_timeout=5000")
|
||||
cur.close()
|
||||
|
||||
|
||||
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user