initial commit

This commit is contained in:
2026-02-12 18:45:10 +01:00
commit be7bbba456
42 changed files with 3767 additions and 0 deletions

98
backend/app/main.py Normal file
View File

@@ -0,0 +1,98 @@
"""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")