99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
"""FluentGerman.ai — FastAPI application entry point."""
|
|
|
|
import logging
|
|
import time
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from sqlalchemy import select
|
|
|
|
from app.auth import hash_password
|
|
from app.config import get_settings
|
|
from app.database import Base, engine, async_session
|
|
from app.models import User
|
|
from app.routers import auth, chat, instructions, users, voice
|
|
|
|
# ── Logging ──────────────────────────────────────────────────────────
|
|
import os
|
|
os.makedirs("logs", exist_ok=True)
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
handlers=[
|
|
logging.StreamHandler(),
|
|
logging.FileHandler("logs/app.log", mode="a"),
|
|
],
|
|
)
|
|
logger = logging.getLogger("fluentgerman")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Create tables and bootstrap admin user on startup."""
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
# Bootstrap admin if not exists
|
|
settings = get_settings()
|
|
async with async_session() as db:
|
|
result = await db.execute(select(User).where(User.is_admin == True)) # noqa: E712
|
|
if not result.scalar_one_or_none():
|
|
admin = User(
|
|
username=settings.admin_username,
|
|
email=settings.admin_email,
|
|
hashed_password=hash_password(settings.admin_password),
|
|
is_admin=True,
|
|
)
|
|
db.add(admin)
|
|
await db.commit()
|
|
logger.info("Admin user created: %s", settings.admin_username)
|
|
|
|
logger.info("FluentGerman.ai started — LLM: %s/%s, Voice: %s",
|
|
settings.llm_provider, settings.llm_model, settings.voice_mode)
|
|
yield
|
|
logger.info("FluentGerman.ai shutting down")
|
|
|
|
|
|
app = FastAPI(
|
|
title="FluentGerman.ai",
|
|
description="Personalized LLM-powered German language learning platform",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
|
|
# ── Request logging middleware ────────────────────────────────────────
|
|
@app.middleware("http")
|
|
async def log_requests(request: Request, call_next):
|
|
start = time.time()
|
|
response = await call_next(request)
|
|
duration = round((time.time() - start) * 1000)
|
|
# Skip logging static file requests to keep logs clean
|
|
path = request.url.path
|
|
if path.startswith("/api/"):
|
|
logger.info("%s %s → %s (%dms)", request.method, path, response.status_code, duration)
|
|
return response
|
|
|
|
|
|
# CORS — restrict in production
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# API routers
|
|
app.include_router(auth.router)
|
|
app.include_router(users.router)
|
|
app.include_router(instructions.router)
|
|
app.include_router(chat.router)
|
|
app.include_router(voice.router)
|
|
|
|
# Serve frontend static files
|
|
app.mount("/", StaticFiles(directory="../frontend", html=True), name="frontend")
|