# 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 SMTP email delivery through configured mail provider. - CIE login uses external Django RP service: - Visione creates local `state` - Django RP completes CIE/OIDC and sends signed identity payload to Visione - Visione creates or links local user, issues `api_tokens`, frontend polls session status - DB access uses raw parameterized SQL through `drogon::orm::DbClient`. - No Drogon generated ORM models. No `Mapper`. ## 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`, `cie_auth_sessions`, `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. - CIE bridge trusts HMAC-signed callback from Django RP service; keep shared secret rotated and never expose it to frontend.