import logging import os from contextlib import asynccontextmanager from pathlib import Path from dotenv import load_dotenv load_dotenv() from fastapi import FastAPI, Request, Response from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles from starlette.middleware.sessions import SessionMiddleware from app.database import init_db from app.dependencies import NeedsLogin, NeedsOnboarding, NeedsAdmin from app.routers import auth, onboarding, dashboard, admin, profile, games, courses, users logging.basicConfig(level=logging.INFO) AVATARS_DIR = Path(os.getenv("DATABASE_PATH", "/data/skinspins.db")).parent / "avatars" AVATARS_DIR.mkdir(parents=True, exist_ok=True) @asynccontextmanager async def lifespan(app: FastAPI): await init_db() yield app = FastAPI(lifespan=lifespan) app.add_middleware(SessionMiddleware, secret_key=os.getenv("SECRET_KEY", "dev-secret")) app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/avatars", StaticFiles(directory=str(AVATARS_DIR)), name="avatars") app.include_router(auth.router) app.include_router(onboarding.router) app.include_router(dashboard.router) app.include_router(admin.router) app.include_router(profile.router) app.include_router(games.router) app.include_router(courses.router) app.include_router(users.router) @app.exception_handler(NeedsLogin) async def needs_login_handler(request: Request, exc: NeedsLogin): response = RedirectResponse("/login", status_code=302) response.delete_cookie("session") return response @app.exception_handler(NeedsOnboarding) async def needs_onboarding_handler(request: Request, exc: NeedsOnboarding): return RedirectResponse("/onboarding", status_code=302) @app.exception_handler(NeedsAdmin) async def needs_admin_handler(request: Request, exc: NeedsAdmin): return RedirectResponse("/", status_code=302) @app.middleware("http") async def security_headers(request: Request, call_next): response: Response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "DENY" return response