"""Verwaltungsportal - FastAPI Anwendung. Auth: Magic-Link (analog Monitor). Passwort-Login wurde mit Migration 2026-05-09 entfernt. Erlaubte Email-Adresse(n) sind in config.ALLOWED_EMAIL. """ import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from config import STATIC_DIR, PORT from routers import auth, organizations, licenses, users, dashboard, sources, token_usage, audit, translation, x_scraper, pricing, statistik logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s [%(name)s] %(message)s", ) logger = logging.getLogger("verwaltung") @asynccontextmanager async def lifespan(app: FastAPI): # Schutz gegen falsche Deploy-Reihenfolge: Die Credits-Spalten in licenses # legt die Monitor-Migration an. Fehlen sie, scheitern Lizenz-Anlage und # Token-Tab mit "no such column". Hier nur ein deutlicher Log-Hinweis, # kein Hard-Fail. try: from database import get_db db = await get_db() try: cursor = await db.execute("PRAGMA table_info(licenses)") cols = [row[1] for row in await cursor.fetchall()] if "credits_period" not in cols: logger.error( "licenses-Tabelle ohne Credits-Spalten (credits_period fehlt)! " "Zuerst den Monitor deployen (dessen Migration legt sie an) " "oder migrations/2026-07-25_credits_period.py gegen diese DB fahren." ) finally: await db.close() except Exception as exc: logger.warning("Startup-Schema-Check uebersprungen: %s", exc) logger.info("Verwaltungsportal gestartet auf Port %s", PORT) yield logger.info("Verwaltungsportal beendet") app = FastAPI( title="AegisSight Verwaltungsportal", version="2.0.0", lifespan=lifespan, ) # --- Routen --- app.include_router(auth.router) app.include_router(organizations.router) app.include_router(licenses.router) app.include_router(users.router) app.include_router(dashboard.router) app.include_router(sources.router) app.include_router(token_usage.router) app.include_router(audit.router) app.include_router(translation.router) app.include_router(x_scraper.router) app.include_router(pricing.router) app.include_router(statistik.router) # --- Statische Dateien --- app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") @app.get("/") async def index(): return FileResponse(f"{STATIC_DIR}/index.html") @app.get("/dashboard") async def dashboard_page(): return FileResponse(f"{STATIC_DIR}/dashboard.html") if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=PORT, reload=True)