Files
timemaster/backend/app/main.py
T
patrickandClaude Sonnet 5 c733ddfe40
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
fix(redis): gepoolten async-Redis-Client statt Connect/Close pro Request
TOTP-Login und Kiosk-Nonce-Check öffneten/schlossen bisher pro Request eine
neue aioredis-Verbindung. Neuer get_async_redis()-Pool in core/redis.py wird
von beiden Stellen genutzt, sauberer Shutdown im FastAPI-Lifespan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 22:29:08 +02:00

126 lines
5.2 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.
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from app.core.config import settings
from app.core.database import engine, Base
from app.core.limiter import limiter
from app.routers import auth, users, companies
from app.routers import time_entries, absences, reports, ldap, smtp, caldav
from app.routers import import_kimai
from app.routers import kiosk
from app.routers import busylight
from app.routers import audit
from app.routers import special_assignments
from app.routers import hours_payouts
from app.routers import public_stamp
from app.routers import ical
from app.routers import reseller, tenants
from app.routers import tls_admin
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Tabellen anlegen nur in Development/Test.
# In Production verwaltet Alembic das Schema create_all würde mit Migrationen kollidieren.
import logging
_log = logging.getLogger(__name__)
if not settings.is_production:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
_log.info("Development-Modus: create_all ausgeführt.")
else:
_log.info("Production-Modus: create_all übersprungen — Alembic verwaltet das Schema.")
# M-4: Warnung wenn Production ohne ALLOWED_HOSTS läuft
if settings.is_production and not settings.allowed_hosts:
_log.warning(
"SICHERHEITSWARNUNG: ALLOWED_HOSTS ist nicht gesetzt. "
"In Production sollte ALLOWED_HOSTS in .env konfiguriert sein "
"um Host-Header-Injection zu verhindern."
)
# Erinnerungs-Scheduler (agent-11 PR3) nicht im Test-Kontext starten
import sys
if settings.scheduler_enabled and "pytest" not in sys.modules:
from app.services.scheduler_service import start as start_scheduler
start_scheduler()
yield
# Shutdown
from app.services.scheduler_service import shutdown as shutdown_scheduler
shutdown_scheduler()
from app.core.redis import close_async_redis
await close_async_redis()
await engine.dispose()
app = FastAPI(
title=settings.app_name,
version="0.1.0",
docs_url="/docs" if not settings.is_production else None,
redoc_url="/redoc" if not settings.is_production else None,
lifespan=lifespan,
)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# ── Middleware ────────────────────────────────────────────────────────────────
app.add_middleware(
CORSMiddleware,
allow_origins=[settings.frontend_url],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=[
"Content-Type",
"Authorization",
"X-Kiosk-Key-Id",
"X-Kiosk-Timestamp",
"X-Kiosk-Nonce",
"X-Kiosk-Signature",
],
)
# TrustedHostMiddleware: aktiv sobald ALLOWED_HOSTS gesetzt (Development: leer = deaktiviert)
# Production: ALLOWED_HOSTS=timemaster.example.com in .env setzen
if settings.allowed_hosts:
app.add_middleware(TrustedHostMiddleware, allowed_hosts=settings.allowed_hosts)
# ── Routers ───────────────────────────────────────────────────────────────────
API_PREFIX = "/api/v1"
app.include_router(auth.router, prefix=API_PREFIX)
app.include_router(users.router, prefix=API_PREFIX)
app.include_router(companies.router, prefix=API_PREFIX)
app.include_router(time_entries.router, prefix=API_PREFIX)
app.include_router(public_stamp.router, prefix=API_PREFIX)
app.include_router(ical.router, prefix=API_PREFIX)
app.include_router(absences.router, prefix=API_PREFIX)
app.include_router(reports.router, prefix=API_PREFIX)
app.include_router(ldap.router, prefix=API_PREFIX)
app.include_router(smtp.router, prefix=API_PREFIX)
app.include_router(caldav.router, prefix=API_PREFIX)
app.include_router(import_kimai.router, prefix=API_PREFIX)
app.include_router(kiosk.router, prefix=API_PREFIX)
app.include_router(busylight.router, prefix=API_PREFIX)
app.include_router(audit.router, prefix=API_PREFIX)
app.include_router(special_assignments.router, prefix=API_PREFIX)
app.include_router(hours_payouts.router, prefix=API_PREFIX)
app.include_router(reseller.router, prefix=API_PREFIX)
app.include_router(tenants.router, prefix=API_PREFIX)
app.include_router(tls_admin.router, prefix=API_PREFIX)
# ── Health ────────────────────────────────────────────────────────────────────
@app.get("/health", tags=["System"])
async def health():
return {"status": "ok", "app": settings.app_name, "env": settings.app_env}