This commit is contained in:
team3
2026-09-16 17:00:35 +02:00
parent bfaf0313c1
commit cb78225e86
29 changed files with 1992 additions and 1227 deletions

39
backend/app/backup.py Normal file
View File

@@ -0,0 +1,39 @@
"""Nächtliche Kopie der Datenbank.
Sammlungen, Wertungen und Wünsche liegen nur in dieser einen Datei. Die
Kopie entsteht über SQLites eigene Backup-Schnittstelle — die ist auch
dann konsistent, wenn gerade jemand schreibt. Es bleiben die letzten
vierzehn Stände, jeder Tag einer.
"""
import logging
import sqlite3
from datetime import date
from .config import DB_PATH
log = logging.getLogger("app.backup")
BEHALTEN = 14
def sichern():
ordner = DB_PATH.parent / "backups"
ordner.mkdir(parents=True, exist_ok=True)
ziel = ordner / f"{DB_PATH.stem}-{date.today():%Y-%m-%d}{DB_PATH.suffix}"
quelle = sqlite3.connect(DB_PATH)
kopie = sqlite3.connect(ziel)
try:
quelle.backup(kopie)
finally:
kopie.close()
quelle.close()
# Die Namen sortieren nach Datum, das Alte steht vorn
alte = sorted(ordner.glob(f"{DB_PATH.stem}-*{DB_PATH.suffix}"))[:-BEHALTEN]
for alt in alte:
alt.unlink()
log.info("Datenbank gesichert: %s", ziel.name)
return ziel

View File

@@ -10,6 +10,7 @@ from fastapi.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles
from . import (
backup,
db,
events,
features,
@@ -69,6 +70,11 @@ app.add_middleware(
)
async def sicherung():
# Die Kopie blockiert kurz — nicht im Ereignisschleifen-Thread
await asyncio.to_thread(backup.sichern)
async def guarded_sync():
if sync_lock.locked():
log.info("Sync läuft bereits")
@@ -85,6 +91,9 @@ async def startup():
watched.init()
wishlist.init()
features.init()
# Erst die Sicherung, dann der Sync — so liegt vor jedem Katalog-Update
# ein sauberer Stand
scheduler.add_job(sicherung, "cron", hour=4, minute=0, id="daily-backup")
scheduler.add_job(guarded_sync, "cron", hour=4, minute=30, id="daily-sync")
scheduler.start()
if not db.get_meta("last_sync"):