"""The DTF Portal/API service: assembly only. Configuration and shared helpers are in local.runtime; every route lives in a router under local.api. This module creates the application, applies the cross-cutting middleware, and includes them. """ from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.responses import JSONResponse from starlette.middleware.trustedhost import TrustedHostMiddleware from . import db from .auth import audit, client_ip from .runtime import ALLOWED_HOSTS, ALLOWED_ORIGINS, storage from .api import artwork, customer, health, operator, orders, quotes, uploads @asynccontextmanager async def lifespan(app): with db.connect() as c: c.execute('SELECT 1 FROM dtf_local.operator_sessions LIMIT 1') storage.health() yield app = FastAPI(title='DTF Portal/API', lifespan=lifespan, docs_url=None, redoc_url=None) app.add_middleware(TrustedHostMiddleware, allowed_hosts=ALLOWED_HOSTS) @app.middleware('http') async def safe_headers(request, call_next): if request.method not in ('GET','HEAD','OPTIONS'): origin = request.headers.get('origin') if request.headers.get('sec-fetch-site') == 'cross-site' or (origin and origin not in ALLOWED_ORIGINS): audit('cross_origin_rejected', ip=client_ip(request)) return JSONResponse({'detail':'Cross-origin request rejected'}, status_code=403) response = await call_next(request) if response.status_code in (401,403,429) or response.status_code>=500: audit('http_security_event', method=request.method, status=response.status_code, ip=client_ip(request)) response.headers['Cache-Control'] = 'no-store' response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['Referrer-Policy'] = 'no-referrer' return response # Order is not significant: no two routers declare the same path. for module in (health, uploads, quotes, orders, operator, customer, artwork): app.include_router(module.router)