Files
moneyfy/backend/app/main.py
T
moneyfyandClaude Opus 5 54c59c9f71 feat(notifications): täglicher Lauf über SMTP und Apprise
- Kanäle SMTP (HTML-Mail mit Logos als CID-Anhang) und Apprise, beide
  blockierenden Bibliotheken laufen in einem Thread
- Vier Anlässe: Fälligkeiten im Vorlauf, Kündigungsfristen in drei Stufen,
  überschrittene Budgets je Monat, Vertragsverlängerungen im Folgemonat
- APScheduler im Anwendungsprozess, täglich 07:00 Europe/Berlin, räumt zugleich
  abgelaufene Sitzungen auf
- Duplikatsschutz über den Zieltag statt den Versandtag; fehlgeschlagener
  Versand wird beim nächsten Lauf erneut versucht
- Endpunkte für Regeln, Protokoll, Testversand und sofortigen Lauf
- Einstellungsseite mit Einrichtungsstand, Regelpflege und Protokoll
- 25 neue Backend-Tests (260 gesamt), 9 neue Frontend-Tests (74 gesamt)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
2026-09-09 17:04:30 +02:00

80 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Einstiegspunkt der moneyfy-Anwendung."""
import logging
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.router import api_router
from app.core.config import settings
from app.core.errors import register_exception_handlers
from app.db.session import SessionLocal, engine
from app.scheduler import shutdown_scheduler, start_scheduler
from app.services.auth import ensure_admin_user, purge_expired_refresh_tokens
logging.basicConfig(
level=logging.DEBUG if settings.debug else logging.INFO,
format="%(asctime)s %(levelname)-8s %(name)s: %(message)s",
)
logger = logging.getLogger("moneyfy")
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
"""Start- und Stopplogik der Anwendung."""
settings.logo_storage_dir.mkdir(parents=True, exist_ok=True)
await _bootstrap()
start_scheduler()
logger.info("moneyfy %s gestartet (%s)", settings.app_version, settings.environment)
yield
shutdown_scheduler()
await engine.dispose()
logger.info("moneyfy wird beendet")
async def _bootstrap() -> None:
"""Erststart: Administrator anlegen und abgelaufene Sitzungen aufräumen.
Fehler werden protokolliert, beenden die Anwendung aber nicht der
Health-Check meldet eine nicht erreichbare Datenbank ohnehin.
"""
try:
async with SessionLocal() as session:
await ensure_admin_user(session)
removed = await purge_expired_refresh_tokens(session)
await session.commit()
if removed:
logger.info("%d abgelaufene Sitzungen entfernt.", removed)
except Exception:
logger.exception("Start-Initialisierung fehlgeschlagen.")
def create_app() -> FastAPI:
app = FastAPI(
title="moneyfy",
version=settings.app_version,
description="Planung monatlicher Kosten und Einkünfte.",
openapi_url="/api/openapi.json",
docs_url="/api/docs",
redoc_url=None,
lifespan=lifespan,
)
if settings.cors_origin_list:
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origin_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
register_exception_handlers(app)
app.include_router(api_router)
return app
app = create_app()