feat(api): add POST/GET /v1/files with auth, error envelope, and request-id middleware

Why:
- Wires the ADR-0008 error envelope, per-request correlation id, and the
  /v1/files routes into the app.

Changes:
- Extend AppResources/lifespan with the ingestion CapacityLimiter and
  ObjectStorage adapter.

Impact:
- /v1 now exposes routes for the first time.
This commit is contained in:
2026-08-19 15:00:39 +03:30
parent c9cf7b368b
commit 3bced65926
10 changed files with 355 additions and 1 deletions

View File

@@ -0,0 +1,38 @@
"""Auth dependencies (ADR-0008): resolve `AuthContext` from a bearer token,
then gate routes on scope.
"""
from collections.abc import Awaitable, Callable
from typing import Annotated
from fastapi import Depends
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from src.application.auth.context import AuthContext
from src.application.auth.errors import InvalidApiKeyError, MissingScopeError
from src.application.auth.service import resolve_auth_context
from src.bootstrap.dependencies import get_sessionmaker
_bearer_scheme = HTTPBearer(auto_error=False)
async def get_auth_context(
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(_bearer_scheme)],
sessionmaker: Annotated[async_sessionmaker[AsyncSession], Depends(get_sessionmaker)],
) -> AuthContext:
if credentials is None:
raise InvalidApiKeyError("missing Authorization header")
return await resolve_auth_context(sessionmaker, credentials.credentials)
AuthContextDep = Annotated[AuthContext, Depends(get_auth_context)]
def require_scope(scope: str) -> Callable[[AuthContext], Awaitable[AuthContext]]:
async def _dependency(auth: AuthContextDep) -> AuthContext:
if not auth.has_scope(scope):
raise MissingScopeError(f"missing required scope '{scope}'")
return auth
return _dependency