Files
BanGUI/Docs/API_VERSIONING.md
Lukas 65fe747cba feat(backend): add deprecation middleware and API versioning support
- Add deprecation middleware for warning headers on sunset endpoints
- Add jails_v2 router for API v2 migration path
- Update CI workflow with new test coverage
- Update API versioning documentation
- Remove completed tasks from Tasks.md
2026-05-04 00:03:52 +02:00

166 lines
5.7 KiB
Markdown

# API Versioning Strategy
**Status:** Active — Current version: **v1**
All BanGUI API endpoints are versioned using URI path versioning (e.g., `/api/v1/`).
This document explains when and how to version endpoints, how deprecation works, and what guarantees consumers can rely on.
---
## 1. Version Lifecycle
| Stage | Meaning |
|-------|---------|
| **Current** | Active, receiving new features and bug fixes. |
| **Deprecated** | Still functional but marked for removal. Clients receive `Deprecation: true` and `Sunset: <date>` response headers. |
| **Removed** | Endpoint no longer exists. Clients must migrate to a newer version. |
---
## 2. URL Structure
```
/api/v{major}/<resource>/<path>
```
- **v1** — current version (released 2026-05-02)
- **v2** — reserved; skeleton router deployed at `/api/v2/jails` but **not yet active** for production traffic
- **PATCH** versions (v1.1, v1.2) are **not** used; only **major** version bumps indicate breaking changes
- The OpenAPI schema is always available at `/api/openapi.json` regardless of version
---
## 3. What Triggers a Version Bump
A new major version is required when a **breaking change** must be introduced, including:
- Removing or renaming a field in a response model
- Changing the type of a request or response field
- Removing an endpoint entirely
- Changing authentication/authorization semantics
- Modifying the semantics of an existing operation
**Non-breaking changes** (backward-compatible):
- Adding new optional request fields
- Adding new response fields
- Adding new endpoints
- Fixing bugs that caused incorrect behavior
These do **not** require a version bump.
---
## 4. Deprecation Policy
When an endpoint is deprecated:
1. The endpoint **remains functional** for a minimum of **6 months** from the `Sunset` date
2. Response headers are added to every 2xx response:
```
Deprecation: true
Sunset: <RFC-5322 date>
Link: <https://bangui.example.com/api/v2/...>; rel="successor-version"
```
3. The endpoint is registered in the deprecation middleware (``app/middleware/deprecation.py``)
4. The OpenAPI schema marks the endpoint with `deprecated: true`
5. Documentation is updated to show the endpoint as deprecated
### Implementing Deprecation Headers
The ``DeprecationHeaderMiddleware`` (``app/middleware/deprecation.py``) automatically injects
the correct headers for any registered deprecated endpoint. To schedule an endpoint for removal:
```python
from datetime import datetime, timezone, timedelta
from app.middleware.deprecation import register_deprecated_endpoint
# Example: deprecate /api/v1/jails on 2026-11-03 (6 months from v2 release)
register_deprecated_endpoint(
path_prefix="/api/v1/jails",
sunset_date=datetime(2026, 11, 3, tzinfo=timezone.utc),
successor_url="/api/v2/jails",
)
```
The middleware runs on every response; if the request path matches a registered deprecated prefix,
the appropriate headers are appended before the response is returned.
---
## 5. Backend Development: Adding Versioned Endpoints
### New endpoints
All new endpoints are added to the **current** version (`/api/v1/`). Prefix your router:
```python
router = APIRouter(prefix="/api/v1/my-resource", tags=["My Resource"])
```
### Breaking changes requiring v2
1. Create a new router file (e.g., `routers/my_resource_v2.py`) with the v2 prefix:
```python
router = APIRouter(prefix="/api/v2/my-resource", tags=["My Resource (v2)"])
```
2. Copy or adapt the v1 handler logic as needed. Extract shared business logic into
a **service layer function** so both routers call the same underlying code.
3. Register the new router in `app/main.py`:
```python
app.include_router(my_resource_v2.router)
```
4. Register the v1 endpoint for deprecation headers (see §4 above)
5. Update this document to reflect the new version lifecycle
### Keeping routers DRY
Routers should only contain HTTP concerns (parameters, responses, status codes). Business logic
belongs in the service layer. Both v1 and v2 handlers can call the same service function.
---
## 6. Frontend Development
The frontend always uses the current version's base URL:
```typescript
const BASE_URL: string = import.meta.env.VITE_API_URL ?? "/api/v1";
```
All endpoint paths in `frontend/src/api/endpoints.ts` are defined as relative paths (e.g., `/bans`, `/jails`) and are appended to `BASE_URL` at runtime.
When v2 is released, update ``VITE_API_URL`` in the environment configuration to point to `/api/v2`.
---
## 7. OpenAPI / Documentation
- Swagger UI: `/api/docs`
- ReDoc: `/api/redoc`
- OpenAPI schema: `/api/openapi.json`
- Docs are **not** versioned; they always reflect the **current** (latest) API version
---
## 8. CI Breaking-Change Checks
A GitHub Actions job runs on every pull request to detect breaking OpenAPI changes:
- ``openapi-breaking-changes`` job (PR only): generates the current OpenAPI spec and
compares it against the baseline committed on the last push to `main`. If any breaking
changes are found, the job fails and the PR cannot be merged.
- ``openapi-baseline-commit`` job (main push only): generates and commits the current
OpenAPI spec as the new baseline for future PR comparisons.
To trigger the baseline update, push to main after merging a version bump or any change
that legitimately alters the OpenAPI surface.
---
## 9. Version History
| Version | Status | Released | Sunset Date | Notes |
|---------|--------|---------|-------------|-------|
| v1 | **Current** | 2026-05-02 | — | Initial versioning; all endpoints moved from `/api/` to `/api/v1/` |
| v2 | **Reserved — skeleton active, endpoints not yet available** | — | — | Router skeleton at `app/routers/jails_v2.py`; real endpoints will be added before activation |