""" Art-DAG L1 Server Application Factory. Creates and configures the FastAPI application with all routers and middleware. """ from pathlib import Path from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from artdag_common import create_jinja_env from .config import settings def create_app() -> FastAPI: """ Create and configure the L1 FastAPI application. Returns: Configured FastAPI instance """ app = FastAPI( title="Art-DAG L1 Server", description="Content-addressed media processing with distributed execution", version="1.0.0", ) # Database lifecycle events from database import init_db, close_db @app.on_event("startup") async def startup(): await init_db() @app.on_event("shutdown") async def shutdown(): await close_db() # Initialize Jinja2 templates template_dir = Path(__file__).parent / "templates" app.state.templates = create_jinja_env(template_dir) # Custom 404 handler @app.exception_handler(404) async def not_found_handler(request: Request, exc): from artdag_common.middleware import wants_html if wants_html(request): from artdag_common import render return render(app.state.templates, "404.html", request, user=None, status_code=404, ) return JSONResponse({"detail": "Not found"}, status_code=404) # Include routers from .routers import auth, storage, api, recipes, cache, runs, home # Home and auth routers (root level) app.include_router(home.router, tags=["home"]) app.include_router(auth.router, prefix="/auth", tags=["auth"]) # Feature routers app.include_router(storage.router, prefix="/storage", tags=["storage"]) app.include_router(api.router, prefix="/api", tags=["api"]) # Runs router - handles both /runs and /run/{id} patterns app.include_router(runs.router, prefix="/runs", tags=["runs"]) # Also mount at /run for single-run detail URLs from fastapi import APIRouter run_detail_router = APIRouter() @run_detail_router.get("/{run_id}") async def run_detail_redirect(run_id: str, request: Request): from .routers.runs import run_detail return await run_detail(run_id, request) app.include_router(run_detail_router, prefix="/run", tags=["runs"]) # Recipes router - handles both /recipes and /recipe/{id} patterns app.include_router(recipes.router, prefix="/recipes", tags=["recipes"]) recipe_detail_router = APIRouter() @recipe_detail_router.get("/{recipe_id}") async def recipe_detail_redirect(recipe_id: str, request: Request): from .routers.recipes import get_recipe return await get_recipe(recipe_id, request) app.include_router(recipe_detail_router, prefix="/recipe", tags=["recipes"]) # Cache router - handles /cache and /media app.include_router(cache.router, prefix="/cache", tags=["cache"]) # Also mount cache router at /media for convenience app.include_router(cache.router, prefix="/media", tags=["media"]) return app # Create the default app instance app = create_app()