second commit
This commit is contained in:
69
app/db.py
Normal file
69
app/db.py
Normal file
@ -0,0 +1,69 @@
|
||||
"""
|
||||
Datenbankzugriff (SQLite, WAL-Modus) + einfacher, idempotenter Migration-Runner.
|
||||
|
||||
Bewusst ohne schweres ORM gehalten: alle Queries sind strikt parametrisiert
|
||||
(nie String-Concat mit Nutzereingaben), siehe Security-Konzept 6.6.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger("jumphost.db")
|
||||
|
||||
MIGRATIONS_DIR = Path(__file__).parent / "db" / "migrations"
|
||||
|
||||
_connection: aiosqlite.Connection | None = None
|
||||
|
||||
|
||||
async def init_db() -> None:
|
||||
"""Legt das Datenverzeichnis an, oeffnet die DB und wendet Migrationen an."""
|
||||
settings.data_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
settings.recordings_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
|
||||
global _connection
|
||||
_connection = await aiosqlite.connect(settings.db_path, isolation_level=None)
|
||||
await _connection.execute("PRAGMA journal_mode = WAL;")
|
||||
await _connection.execute("PRAGMA foreign_keys = ON;")
|
||||
await _connection.execute("PRAGMA busy_timeout = 5000;")
|
||||
await _apply_migrations(_connection)
|
||||
|
||||
try:
|
||||
settings.db_path.chmod(0o600)
|
||||
except OSError:
|
||||
logger.warning("Konnte Dateirechte der DB nicht setzen (%s)", settings.db_path)
|
||||
|
||||
|
||||
async def _apply_migrations(conn: aiosqlite.Connection) -> None:
|
||||
await conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS schema_migrations "
|
||||
"(filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')))"
|
||||
)
|
||||
applied = {row[0] async for row in await conn.execute("SELECT filename FROM schema_migrations")}
|
||||
|
||||
for migration_file in sorted(MIGRATIONS_DIR.glob("*.sql")):
|
||||
if migration_file.name in applied:
|
||||
continue
|
||||
logger.info("Wende Migration an: %s", migration_file.name)
|
||||
sql = migration_file.read_text(encoding="utf-8")
|
||||
await conn.executescript(sql)
|
||||
await conn.execute(
|
||||
"INSERT INTO schema_migrations (filename) VALUES (?)", (migration_file.name,)
|
||||
)
|
||||
|
||||
|
||||
async def close_db() -> None:
|
||||
global _connection
|
||||
if _connection is not None:
|
||||
await _connection.close()
|
||||
_connection = None
|
||||
|
||||
|
||||
def get_db() -> aiosqlite.Connection:
|
||||
if _connection is None:
|
||||
raise RuntimeError("Datenbank ist nicht initialisiert (init_db() aufrufen).")
|
||||
return _connection
|
||||
Reference in New Issue
Block a user