108 lines
4.6 KiB
Markdown
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.
|