Files
backend/docs/ARCHITECTURE.md
T

108 lines
4.6 KiB
Markdown

# Visione Backend Architecture
## 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.
## 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`
## 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.
## Runtime Model
- Auth uses bearer tokens from `api_tokens`.
- OTP login uses `otp_challenges` and Mailpit in local dev.
- CIE login uses OIDC RP state tables, public federation endpoints under
`/oidc/rp/*`, callback-side token exchange, encrypted/signed `userinfo`,
and frontend polling on `GET /api/v1/auth/cie/session/{state}`.
- DB access uses raw parameterized SQL through `drogon::orm::DbClient`.
- No Drogon generated ORM models. No `Mapper<T>`.
## 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
## 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`.
- Spatial scope is constrained to Bari municipal boundary from
`allowed_boundaries.code = 'BARI'`.
## 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`
- `oidc_authentications`, `oidc_authentication_tokens`,
`oidc_federation_cache`, `user_external_identities`
- `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`
- `allowed_boundaries`
## 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 `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`.
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.
- Allowed-area containment for admin areas is vertex-based, simple not strict GIS-hard.
- No frontend doc set here beyond API/OpenAPI docs.
- OIDC federation trust validation/cache is still pragmatic: metadata comes from
trust-anchor `resolve`, but full trust-chain cryptographic validation is not
implemented yet.