feat: implement API versioning /api/v1/

- All backend routers moved to /api/v1/ prefix
- Frontend BASE_URL updated to /api/v1
- Setup redirect middleware updated to redirect to /api/v1/setup
- Health router path fixed: prefix=/api/v1/health, @router.get('')
- conftest.py: set server_status=online for test fixture
- Created Docs/API_VERSIONING.md with deprecation policy
- Updated Docs/Backend-Development.md with versioning section
- Updated Instructions.md curl examples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-02 21:29:30 +02:00
parent 0d5882b32f
commit cc6dbcf3f0
51 changed files with 1886 additions and 671 deletions

View File

@@ -749,20 +749,20 @@ async def _request_validation_error_handler(
# the guard without being explicitly allowed.
_EXACT_ALLOWED: frozenset[str] = frozenset(
{
"/api/setup", # GET/POST /api/setup
"/api/health", # Health check endpoint
"/api/docs", # Swagger UI
"/api/redoc", # ReDoc
"/api/openapi.json", # OpenAPI schema
"/api/v1/setup", # GET/POST /api/v1/setup
"/api/v1/health", # Health check endpoint
"/api/docs", # Swagger UI
"/api/redoc", # ReDoc
"/api/openapi.json", # OpenAPI schema
},
)
# Prefix paths that are always reachable. These MUST end with "/" to prevent
# matching paths like "/api/setup-debug" while still matching nested routes
# like "/api/setup/timezone".
# matching paths like "/api/v1/setup-debug" while still matching nested routes
# like "/api/v1/setup/timezone".
_PREFIX_ALLOWED: frozenset[str] = frozenset(
{
"/api/setup/", # Nested setup routes (e.g., /api/setup/timezone)
"/api/v1/setup/", # Nested setup routes (e.g., /api/v1/setup/timezone)
},
)
@@ -857,13 +857,18 @@ class SetupRedirectMiddleware(BaseHTTPMiddleware):
if path == prefix.rstrip("/") or path.startswith(prefix):
return await call_next(request)
# Health endpoint is always reachable (needed for Docker/health checks
# and load balancer probes before setup is complete).
if path == "/api/v1/health":
return await call_next(request)
# If setup is not complete, block all other API requests.
# The setup completion state is resolved at startup and stored in
# ``app.state.setup_complete_cached`` so this middleware does not
# perform any database queries during normal request handling.
if path.startswith("/api") and not is_setup_complete_cached(request.app):
if path.startswith("/api/v1") and not is_setup_complete_cached(request.app):
return RedirectResponse(
url="/api/setup",
url="/api/v1/setup",
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
@@ -998,7 +1003,7 @@ def create_app(settings: Settings | None = None) -> FastAPI:
app.add_exception_handler(Exception, _unhandled_exception_handler)
# --- Routers ---
app.include_router(metrics.router)
app.include_router(metrics.router, prefix="/api/v1")
app.include_router(health.router)
app.include_router(setup.router)
app.include_router(auth.router)