feat: update API documentation and project specifications

This commit is contained in:
gabdevele
2026-06-15 12:01:34 +02:00 Unverified
parent 170fadbfe0
commit 088a2ed866
9 changed files with 547 additions and 257 deletions
+87 -38
View File
@@ -1,48 +1,97 @@
# Visione Backend Architecture
## Layout
- `controllers/`: Drogon `HttpController` classes.
- `services/`: application/business logic.
- `repositories/`: database helpers and migrations.
- `common/`: reusable HTTP, auth, crypto, email and validation utilities.
- `migrations/`: versioned SQL files.
- `scripts/generate_openapi.py`: minimal OpenAPI route extractor.
- `docs/openapi/`: manual OpenAPI contract documents.
- `scripts/lab/`: local Streamlit frontend for visual endpoint testing.
- `docs/`: product, API, schema and milestone documentation.
## Purpose
Visione backend is C++20 + Drogon API for municipal urban issue reporting.
Citizens create reports. Operators own report workflow on platform. External
intervention teams do not log into platform; operators can assign them and
generate public read-only intervention links.
## Drogon Usage
Controllers use Drogon route macros and return JSON with
`HttpResponse::newHttpJsonResponse`. Database access uses
`drogon::orm::DbClient` with raw parameterized SQL placeholders.
## Source Of Truth
- Route truth: `controllers/*.h`
- Business truth: `services/*.cc`
- Schema truth: `migrations/*.sql`
- Contract truth: `docs/openapi/visione.openapi.yaml` merged with route scan
- High-signal human summary: `docs/API_SPEC.md` and `docs/LLM_CONTEXT.md`
The codebase does not use Drogon generated models or `Mapper<T>`. Raw SQL stays
in repository/database helpers because the schema is still evolving.
## Code Layout
- `controllers/`: HTTP input/output only, auth gate, status mapping.
- `services/`: business rules, workflow, permission checks, orchestration.
- `repositories/`: SQL serialization helpers and migration runner.
- `common/`: auth, crypto, validation, HTTP helpers, email, CORS.
- `migrations/`: ordered SQL schema evolution.
- `docs/openapi/`: manual OpenAPI layer.
- `scripts/generate_openapi.py`: extracts Drogon routes and merges manual spec.
## Local Email
OTP emails are sent to Mailpit over SMTP. Mailpit keeps messages local and
inspectable through its web UI.
## Runtime Model
- Auth uses bearer tokens from `api_tokens`.
- OTP login uses `otp_challenges` and Mailpit in local dev.
- Password hash uses PBKDF2-HMAC-SHA256. Legacy `sha256$` hashes rejected.
- DB access uses raw parameterized SQL through `drogon::orm::DbClient`.
- No Drogon generated ORM models. No `Mapper<T>`.
## Password Hashing
User passwords use PBKDF2-HMAC-SHA256 with per-password random salts and 210000
iterations. This is intentionally slower than a plain SHA-256 digest and is
available through OpenSSL, which is already a Drogon dependency in this project.
Legacy `sha256$` password hashes are rejected instead of silently accepted.
## Role Model
- `citizen`: own reports only, own messages, feedback, notifications.
- `operator`: can read all reports, manage workflow, assign teams, create flags.
- `admin`: full admin + report management + moderation approval.
Effective permission helpers:
- `auth::canManageReports(user)`: admin or `reports.manage`
- `auth::canOperateReports(user)`: non-admin report manager
- `auth::mustChangePassword(user)`: blocks protected operational/admin flows
## Workflow Ownership Rules
- `reports.assigned_operator_id` is single platform owner of report.
- `assignments` is external team assignment history only.
- Team assignment can claim `OPEN` report directly:
operator becomes `assigned_operator_id`, report moves to `ASSIGNED`.
- `take` exists for `OPEN -> UNDER_REVIEW` without team assignment.
- External team link is public, revocable, tied to current assignment, hashed in
`intervention_access_links`.
## Status Machine
- Allowed statuses: `OPEN`, `UNDER_REVIEW`, `ASSIGNED`, `IN_PROGRESS`,
`RESOLVED`, `REJECTED`, `CLOSED`
- Allowed transitions:
- `OPEN -> UNDER_REVIEW | REJECTED`
- `UNDER_REVIEW -> ASSIGNED | REJECTED`
- `ASSIGNED -> IN_PROGRESS | REJECTED`
- `IN_PROGRESS -> RESOLVED | REJECTED`
- `RESOLVED -> CLOSED`
- `REJECTED -> CLOSED`
## Main Persistence Objects
- `users`, `roles`, `permissions`, `user_roles`, `role_permissions`
- `api_tokens`, `otp_challenges`
- `report_categories`, `areas`, `team_categories`, `teams`
- `reports`, `report_status_history`, `assignments`, `report_photos`
- `messages`, `feedback`, `reward_events`, `notifications`
- `moderation_flags`
- `report_category_team_categories`
- `intervention_access_links`
## HTTP/Error Conventions
- `401`: missing/invalid auth
- `403`: authenticated but forbidden
- `404`: missing resource or invalid public intervention token
- `409`: workflow/ownership conflict
- `422`: semantic payload/reference error
Controllers currently map service error strings heuristically. When adding new
service errors, update controller mappers too.
## OpenAPI Generation
`scripts/generate_openapi.py` scans Drogon `ADD_METHOD_TO` route macros in
`controllers/*.h` and extracts only paths, HTTP methods, operation ids, tags and
path parameters. It merges those route skeletons with
`docs/openapi/visione.openapi.yaml`, which owns summaries, request bodies,
responses, schemas and examples. The manual file uses JSON-compatible YAML so
the CMake `openapi` target can run without extra Python packages and writes
`build/openapi/visione.openapi.json`.
If the manual YAML contains an operation that no longer exists in the Drogon
route list, generation fails unless that operation is explicitly marked
`scripts/generate_openapi.py` scans `ADD_METHOD_TO(...)` in controller headers.
Generated route skeletons merge with `docs/openapi/visione.openapi.yaml`.
Build fails if manual spec contains operation without matching route, unless
`x-manual-only: true`.
## Visione Lab
`scripts/lab/` is a Streamlit dev frontend for visual endpoint testing. It is
intentionally separate from the real product frontend and should stay small:
forms, uploads and response rendering only. Run it with uv from `scripts/lab/`.
Output path:
- `build/openapi/visione.openapi.json`
## Current Gaps / Sharp Edges
- No dedicated repository layer split yet; SQL still lives inside services.
- `AuthService::editUser()` updates fields with dynamic SQL column name from
hardcoded allowlist, not user input.
- Public intervention summary currently excludes email and full surname, but
still exposes citizen phone by design.
- No frontend doc set here beyond API/OpenAPI docs.