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>
63 lines
1.6 KiB
Python
63 lines
1.6 KiB
Python
"""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()
|