From 152590fc271f5960dd09f434c0081af4642ba005 Mon Sep 17 00:00:00 2001 From: snow virus Date: Mon, 23 Mar 2026 12:04:31 +0300 Subject: [PATCH] backend API documentation for all current endpoints and adds small supporting fixes --- README.md | 7 + backend/app/api/alert_router.py | 2 +- backend/app/api/deps.py | 2 +- backend/app/schemas/alert.py | 2 +- docs/backend_api_reference.md | 457 ++++++++++++++++++++++++++++++++ 5 files changed, 467 insertions(+), 3 deletions(-) create mode 100644 docs/backend_api_reference.md diff --git a/README.md b/README.md index b0217c3..47eef4e 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,22 @@ uvicorn backend.app.main:app --reload The service listens on `http://127.0.0.1:8000` by default. OpenAPI documentation is available at `http://127.0.0.1:8000/docs`. +Detailed endpoint documentation with request/response examples, authentication +flow, and testing steps is available at +`docs/backend_api_reference.md`. + ### Core Endpoints | Entity | Base Path | Notes | | --- | --- | --- | +| Authentication | `/api/v1/auth` | User registration + token issuance | +| Health | `/health`, `/api/v1/analytics/health` | Public readiness checks | | Projects | `/api/v1/projects` | CRUD with pagination & search | | ICT Resources | `/api/v1/resources` | Validates project/location references and enforces ticket rules | | Locations | `/api/v1/locations` | CRUD with geo metadata | | Maintenance Tickets | `/api/v1/maintenance-tickets` | Requires resolution metadata when closing a ticket | | Sensor Sites | `/api/v1/sensor-sites` | Links IoT deployments to resources, projects, and locations | +| Alerts | `/api/v1/alerts` | Sensor-threshold alert creation and lookup | Each list endpoint accepts `limit`, `offset`, and `search` query parameters and returns pagination metadata to keep API consumers informed. diff --git a/backend/app/api/alert_router.py b/backend/app/api/alert_router.py index 2579008..14c6f72 100644 --- a/backend/app/api/alert_router.py +++ b/backend/app/api/alert_router.py @@ -26,7 +26,7 @@ def get_alert_service(session: AsyncSession = Depends(get_session)) -> AlertServ return AlertService(alert_repository) -@router.post("", response_model=AlertRead) +@router.post("", response_model=AlertRead | None) async def create_alert( sensor_id: int, metric: str, diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py index 3c91cb1..02f8639 100644 --- a/backend/app/api/deps.py +++ b/backend/app/api/deps.py @@ -75,7 +75,7 @@ async def get_current_user( headers={"WWW-Authenticate": "Bearer"}, ) try: - payload = jwt.decode(token, SECRET_key, algorithms=[ALGORITHM]) + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception diff --git a/backend/app/schemas/alert.py b/backend/app/schemas/alert.py index f047f04..534feaf 100644 --- a/backend/app/schemas/alert.py +++ b/backend/app/schemas/alert.py @@ -12,4 +12,4 @@ class AlertRead(BaseSchema): metric: str value: float threshold: float - created_at: datetime + timestamp: datetime diff --git a/docs/backend_api_reference.md b/docs/backend_api_reference.md new file mode 100644 index 0000000..29431f8 --- /dev/null +++ b/docs/backend_api_reference.md @@ -0,0 +1,457 @@ +# Backend API Reference + +This guide documents the backend endpoints in `backend/app` and provides practical +request/response examples for local integration testing. + +## Base URL and Interactive Docs + +- Local base URL: `http://127.0.0.1:8000` +- OpenAPI JSON: `GET /openapi.json` +- Swagger UI: `GET /docs` +- ReDoc: `GET /redoc` + +## Authentication + +### Auth Endpoints (No Bearer Token Required) + +| Method | Path | Purpose | +| --- | --- | --- | +| `POST` | `/api/v1/auth/users` | Register a user | +| `POST` | `/api/v1/auth/token` | Obtain a bearer access token | + +### Token Flow + +1. Create user credentials. +2. Exchange credentials for a bearer token. +3. Send `Authorization: Bearer ` on protected routes. + +```bash +# 1) Register user +curl -X POST "http://127.0.0.1:8000/api/v1/auth/users" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=apiuser&password=StrongPass123!" +``` + +Example response: + +```json +{ + "username": "apiuser" +} +``` + +```bash +# 2) Login for token +curl -X POST "http://127.0.0.1:8000/api/v1/auth/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=apiuser&password=StrongPass123!" +``` + +Example response: + +```json +{ + "access_token": "", + "token_type": "bearer" +} +``` + +```bash +# 3) Reuse token +export TOKEN="" +``` + +### Protected Route Groups + +These routers require bearer authentication: + +- `/api/v1/projects` +- `/api/v1/resources` +- `/api/v1/locations` +- `/api/v1/maintenance-tickets` +- `/api/v1/sensor-sites` +- `/api/v1/alerts` + +Public endpoints: + +- `/health` +- `/api/v1/analytics/health` +- `/api/v1/auth/*` + +## Common Conventions + +### Pagination and Search + +List endpoints for projects/resources/locations/tickets/sensor-sites accept: + +- `limit` (integer, min `1`) +- `offset` (integer, min `0`) +- `search` (string) + +Paginated response shape: + +```json +{ + "data": [], + "pagination": { + "total": 0, + "limit": 20, + "offset": 0 + } +} +``` + +Search fields by entity: + +| Entity | Search fields | +| --- | --- | +| Projects | `name`, `sponsor` | +| Resources | `name`, `category`, `serial_number` | +| Locations | `campus`, `building`, `room` | +| Maintenance Tickets | `reported_by`, `issue_summary` | +| Sensor Sites | `data_collection_endpoint` | + +### Error Format + +Service-level errors return JSON in this format: + +```json +{ + "detail": "Human-readable message", + "code": "RESOURCE_NOT_FOUND" +} +``` + +Common error codes: + +- `RESOURCE_NOT_FOUND` (HTTP `404`) +- `VALIDATION_ERROR` (HTTP `400`) +- `SERVICE_ERROR` (HTTP `500`) + +Auth failures may return HTTP `401` with: + +```json +{ + "detail": "Could not validate credentials" +} +``` + +## Endpoint Catalog + +### Health and Analytics + +| Method | Path | Auth | Description | +| --- | --- | --- | --- | +| `GET` | `/health` | No | App health status | +| `GET` | `/api/v1/analytics/health` | No | Analytics service health status | + +```bash +curl "http://127.0.0.1:8000/health" +``` + +```json +{ + "status": "ok" +} +``` + +### Projects + +Enum values: + +- `status`: `planned`, `in_progress`, `on_hold`, `completed`, `cancelled` + +| Method | Path | Auth | Description | +| --- | --- | --- | --- | +| `GET` | `/api/v1/projects` | Bearer | List projects | +| `GET` | `/api/v1/projects/{project_id}` | Bearer | Get project by ID | +| `POST` | `/api/v1/projects` | Bearer | Create project | +| `PUT` | `/api/v1/projects/{project_id}` | Bearer | Full replace project | +| `PATCH` | `/api/v1/projects/{project_id}` | Bearer | Partial update project | +| `DELETE` | `/api/v1/projects/{project_id}` | Bearer | Delete project | + +Create request example: + +```bash +curl -X POST "http://127.0.0.1:8000/api/v1/projects" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Campus Network Upgrade", + "description": "Upgrade backbone links for the main campus.", + "status": "planned", + "sponsor": "ICT Directorate", + "start_date": "2026-03-23", + "end_date": "2026-12-31", + "primary_contact_email": "ict-directorate@example.edu" + }' +``` + +Create response example (`201`): + +```json +{ + "name": "Campus Network Upgrade", + "description": "Upgrade backbone links for the main campus.", + "status": "planned", + "sponsor": "ICT Directorate", + "start_date": "2026-03-23", + "end_date": "2026-12-31", + "primary_contact_email": "ict-directorate@example.edu", + "id": 1 +} +``` + +List example: + +```bash +curl "http://127.0.0.1:8000/api/v1/projects?limit=5&offset=0&search=Campus" \ + -H "Authorization: Bearer $TOKEN" +``` + +### Resources + +Enum values: + +- `lifecycle_state`: `draft`, `active`, `maintenance`, `retired` + +| Method | Path | Auth | Description | +| --- | --- | --- | --- | +| `GET` | `/api/v1/resources` | Bearer | List resources | +| `GET` | `/api/v1/resources/{resource_id}` | Bearer | Get resource by ID | +| `POST` | `/api/v1/resources` | Bearer | Create resource | +| `PUT` | `/api/v1/resources/{resource_id}` | Bearer | Full replace resource | +| `PATCH` | `/api/v1/resources/{resource_id}` | Bearer | Partial update resource | +| `DELETE` | `/api/v1/resources/{resource_id}` | Bearer | Delete resource | + +Create request example: + +```bash +curl -X POST "http://127.0.0.1:8000/api/v1/resources" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Edge Sensor", + "category": "sensor", + "lifecycle_state": "active", + "serial_number": "SN-001", + "procurement_date": "2026-03-23", + "description": "Outdoor air-quality node", + "project_id": 1, + "location_id": 1 + }' +``` + +Create response example (`201`): + +```json +{ + "name": "Edge Sensor", + "category": "sensor", + "lifecycle_state": "active", + "serial_number": "SN-001", + "procurement_date": "2026-03-23", + "description": "Outdoor air-quality node", + "project_id": 1, + "location_id": 1, + "id": 1 +} +``` + +Validation failure example (`400`): + +```json +{ + "detail": "Project 9999 does not exist.", + "code": "VALIDATION_ERROR" +} +``` + +### Locations + +`geom` is optional and uses: + +```json +{ + "lat": 0.3476, + "lon": 32.5825 +} +``` + +| Method | Path | Auth | Description | +| --- | --- | --- | --- | +| `GET` | `/api/v1/locations` | Bearer | List locations | +| `GET` | `/api/v1/locations/{location_id}` | Bearer | Get location by ID | +| `POST` | `/api/v1/locations` | Bearer | Create location | +| `PUT` | `/api/v1/locations/{location_id}` | Bearer | Full replace location | +| `PATCH` | `/api/v1/locations/{location_id}` | Bearer | Partial update location | +| `DELETE` | `/api/v1/locations/{location_id}` | Bearer | Delete location | + +Create request example: + +```bash +curl -X POST "http://127.0.0.1:8000/api/v1/locations" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "campus": "Main Campus", + "building": "Engineering Block", + "room": "Lab 2", + "geom": {"lat": 0.3476, "lon": 32.5825} + }' +``` + +Create response example (`201`): + +```json +{ + "campus": "Main Campus", + "building": "Engineering Block", + "room": "Lab 2", + "geom": {"lat": 0.3476, "lon": 32.5825}, + "id": 1 +} +``` + +### Maintenance Tickets + +Enum values: + +- `severity`: `low`, `medium`, `high`, `critical` +- `status`: `open`, `in_progress`, `resolved`, `closed` + +Rule: when `status` is `resolved` or `closed`, both `closed_at` and `notes` are +required. + +| Method | Path | Auth | Description | +| --- | --- | --- | --- | +| `GET` | `/api/v1/maintenance-tickets` | Bearer | List tickets | +| `GET` | `/api/v1/maintenance-tickets/{ticket_id}` | Bearer | Get ticket by ID | +| `POST` | `/api/v1/maintenance-tickets` | Bearer | Create ticket | +| `PUT` | `/api/v1/maintenance-tickets/{ticket_id}` | Bearer | Full replace ticket | +| `PATCH` | `/api/v1/maintenance-tickets/{ticket_id}` | Bearer | Partial update ticket | +| `DELETE` | `/api/v1/maintenance-tickets/{ticket_id}` | Bearer | Delete ticket | + +Create request example: + +```bash +curl -X POST "http://127.0.0.1:8000/api/v1/maintenance-tickets" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "resource_id": 1, + "reported_by": "helpdesk@example.edu", + "issue_summary": "Device intermittently disconnects.", + "severity": "high", + "status": "open", + "opened_at": "2026-03-23T09:00:00Z", + "closed_at": null, + "notes": null + }' +``` + +Create response example (`201`): + +```json +{ + "resource_id": 1, + "reported_by": "helpdesk@example.edu", + "issue_summary": "Device intermittently disconnects.", + "severity": "high", + "status": "open", + "opened_at": "2026-03-23T09:00:00+00:00", + "closed_at": null, + "notes": null, + "id": 1 +} +``` + +### Sensor Sites + +| Method | Path | Auth | Description | +| --- | --- | --- | --- | +| `GET` | `/api/v1/sensor-sites` | Bearer | List sensor sites | +| `GET` | `/api/v1/sensor-sites/{site_id}` | Bearer | Get sensor site by ID | +| `POST` | `/api/v1/sensor-sites` | Bearer | Create sensor site | +| `PUT` | `/api/v1/sensor-sites/{site_id}` | Bearer | Full replace sensor site | +| `PATCH` | `/api/v1/sensor-sites/{site_id}` | Bearer | Partial update sensor site | +| `DELETE` | `/api/v1/sensor-sites/{site_id}` | Bearer | Delete sensor site | + +Create request example: + +```bash +curl -X POST "http://127.0.0.1:8000/api/v1/sensor-sites" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "resource_id": 1, + "project_id": 1, + "location_id": 1, + "data_collection_endpoint": "https://collector.example.edu/intake", + "notes": "Roof-mounted sensor cluster" + }' +``` + +Create response example (`201`): + +```json +{ + "resource_id": 1, + "project_id": 1, + "location_id": 1, + "data_collection_endpoint": "https://collector.example.edu/intake", + "notes": "Roof-mounted sensor cluster", + "id": 1 +} +``` + +### Alerts + +| Method | Path | Auth | Description | +| --- | --- | --- | --- | +| `POST` | `/api/v1/alerts` | Bearer | Create an alert when value breaches threshold | +| `GET` | `/api/v1/alerts/{sensor_id}` | Bearer | List alerts for a sensor site | + +`POST /api/v1/alerts` uses query parameters (`sensor_id`, `metric`, `value`, +`threshold`). + +```bash +curl -X POST "http://127.0.0.1:8000/api/v1/alerts?sensor_id=1&metric=temperature&value=48.5&threshold=40.0" \ + -H "Authorization: Bearer $TOKEN" +``` + +Example response when threshold is exceeded: + +If `value <= threshold`, the endpoint returns `null`. + +```json +{ + "id": 7, + "sensor_id": 1, + "metric": "temperature", + "value": 48.5, + "threshold": 40.0, + "timestamp": "2026-03-23T09:10:00" +} +``` + +Get alerts by sensor: + +```bash +curl "http://127.0.0.1:8000/api/v1/alerts/1" \ + -H "Authorization: Bearer $TOKEN" +``` + +## Quick End-to-End Test Sequence + +Use this order for local smoke testing: + +1. `POST /api/v1/auth/users` +2. `POST /api/v1/auth/token` +3. `POST /api/v1/projects` +4. `POST /api/v1/locations` +5. `POST /api/v1/resources` +6. `POST /api/v1/sensor-sites` +7. `POST /api/v1/maintenance-tickets` +8. `GET` list endpoints with `limit`, `offset`, and `search` +9. `PATCH` and `DELETE` on created IDs