feat: update API documentation and project specifications
This commit is contained in:
+134
-55
@@ -1,59 +1,110 @@
|
|||||||
# Visione API Spec
|
# Visione API Spec
|
||||||
|
|
||||||
Base path: `/api/v1`.
|
Base path: `/api/v1`
|
||||||
|
|
||||||
## Technical
|
## Auth Header
|
||||||
- `GET /health`: process health.
|
|
||||||
- `GET /ready`: MySQL readiness.
|
|
||||||
- `GET /openapi.json`: generated OpenAPI 3.0 JSON.
|
|
||||||
- `GET /docs`: Swagger UI page backed by the generated spec.
|
|
||||||
|
|
||||||
## Auth
|
|
||||||
- `POST /auth/register`: create a citizen account.
|
|
||||||
- `POST /auth/otp/request`: send an OTP email through Mailpit.
|
|
||||||
- `POST /auth/otp/verify`: verify OTP and create an API token.
|
|
||||||
- `POST /auth/login/password`: password login fallback.
|
|
||||||
- `POST /auth/change-password`: change the authenticated user's password.
|
|
||||||
- `GET /me`: return the authenticated user.
|
|
||||||
- `PATCH /me`: edit user informations.
|
|
||||||
|
|
||||||
Protected endpoints use:
|
|
||||||
|
|
||||||
```http
|
```http
|
||||||
Authorization: Bearer <token>
|
Authorization: Bearer <token>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reports
|
## Roles
|
||||||
- `POST /reports`: create a report.
|
- public: no auth
|
||||||
- `GET /reports`: list reports with optional `status`, `category_id`, `area_id`.
|
- authenticated: any valid user
|
||||||
- `GET /report-categories`: list active report categories for authenticated users.
|
- operator: `reports.manage` or admin
|
||||||
- `GET /operator/teams`: list active intervention teams for operators/admins.
|
- admin: role/permission admin
|
||||||
- `GET /operator/reports/assigned`: list reports assigned to the current operator.
|
|
||||||
- `GET /operator/reports/open`: list open unassigned reports.
|
|
||||||
- `GET /operator/reports/others`: list reports assigned to other operators.
|
|
||||||
- `GET /reports/{id}`: report detail.
|
|
||||||
- `GET /reports/{id}/intervention-summary`: operator-facing report summary with
|
|
||||||
citizen contact data, media and latest messages.
|
|
||||||
- `GET /reports/{id}/team-suggestions`: suggest compatible teams by area/category.
|
|
||||||
- `POST /reports/{id}/intervention-link`: create or rotate public team link.
|
|
||||||
- `DELETE /reports/{id}/intervention-link`: revoke public team link.
|
|
||||||
- `PATCH /reports/{id}/status`: update status.
|
|
||||||
- `POST /reports/{id}/take`: assigned-queue operator takes an open report.
|
|
||||||
- `POST /reports/{id}/messages`: write a report message.
|
|
||||||
- `GET /reports/{id}/messages`: list report messages.
|
|
||||||
- `POST /reports/{id}/messages/{message_id}/flags`: assigned operator flags a
|
|
||||||
report message for moderation.
|
|
||||||
- `POST /reports/{id}/assignments`: assign a team.
|
|
||||||
- `POST /reports/{id}/feedback`: add citizen feedback.
|
|
||||||
- `POST /reports/{id}/flags`: assigned operator flags a report for moderation.
|
|
||||||
- `POST /reports/{id}/media`: upload up to five multipart media files. Supported
|
|
||||||
types are JPEG, PNG, WebP, MP4, WebM and QuickTime.
|
|
||||||
|
|
||||||
## User
|
## HTTP Semantics
|
||||||
- `GET /notifications`: list authenticated user notifications.
|
- `200`: success
|
||||||
- `PATCH /notifications/{id}/read`: mark one notification as read.
|
- `401`: missing or invalid auth
|
||||||
- `PATCH /notifications/read-all`: mark all authenticated user notifications as read.
|
- `403`: authenticated but forbidden
|
||||||
- `GET /me/rewards`: return current points and reward event history.
|
- `404`: missing resource or invalid public intervention token
|
||||||
|
- `409`: workflow or ownership conflict
|
||||||
|
- `422`: semantic payload/reference error
|
||||||
|
|
||||||
|
## Technical
|
||||||
|
- `GET /health`
|
||||||
|
- `GET /ready`
|
||||||
|
- `GET /openapi.json`
|
||||||
|
- `GET /docs`
|
||||||
|
|
||||||
|
## Auth + Profile
|
||||||
|
- `POST /auth/register`
|
||||||
|
citizen registration
|
||||||
|
- `POST /auth/otp/request`
|
||||||
|
sends OTP through Mailpit
|
||||||
|
- `POST /auth/otp/verify`
|
||||||
|
consumes OTP, returns bearer token
|
||||||
|
- `POST /auth/login/password`
|
||||||
|
password fallback login
|
||||||
|
- `POST /auth/change-password`
|
||||||
|
authenticated password change, also clears `must_change_password`
|
||||||
|
- `GET /me`
|
||||||
|
current user profile, roles, permissions, points
|
||||||
|
- `PATCH /me`
|
||||||
|
editable fields today: `first_name`, `last_name`, `phone`
|
||||||
|
|
||||||
|
## Public Reference / Public Access
|
||||||
|
- `GET /areas`
|
||||||
|
active municipal areas
|
||||||
|
- `GET /interventions/{token}`
|
||||||
|
public external-team summary link
|
||||||
|
- `GET /open-data/reports`
|
||||||
|
anonymized reports in `RESOLVED` or `CLOSED`
|
||||||
|
|
||||||
|
## User Reference / Personal
|
||||||
|
- `GET /report-categories`
|
||||||
|
active report categories, authenticated users only
|
||||||
|
- `GET /notifications`
|
||||||
|
- `PATCH /notifications/{id}/read`
|
||||||
|
- `PATCH /notifications/read-all`
|
||||||
|
- `GET /me/rewards`
|
||||||
|
|
||||||
|
## Report Endpoints
|
||||||
|
- `POST /reports`
|
||||||
|
create citizen report, optional inline `media`, area inferred by GeoJSON
|
||||||
|
- `GET /reports`
|
||||||
|
citizen sees own reports; operator/admin see all
|
||||||
|
- `GET /reports/{id}`
|
||||||
|
detail + status history + media
|
||||||
|
- `GET /reports/{id}/messages`
|
||||||
|
- `POST /reports/{id}/messages`
|
||||||
|
- `POST /reports/{id}/media`
|
||||||
|
max 5 total media, image/video only
|
||||||
|
- `POST /reports/{id}/feedback`
|
||||||
|
only owner, only when status is `RESOLVED`
|
||||||
|
|
||||||
|
## Operator Workflow
|
||||||
|
- `GET /operator/teams`
|
||||||
|
active external intervention teams
|
||||||
|
- `GET /operator/reports/open`
|
||||||
|
- `GET /operator/reports/assigned`
|
||||||
|
- `GET /operator/reports/others`
|
||||||
|
- `POST /reports/{id}/take`
|
||||||
|
`OPEN -> UNDER_REVIEW`
|
||||||
|
- `PATCH /reports/{id}/status`
|
||||||
|
only owner operator or admin, valid transitions only
|
||||||
|
- `POST /reports/{id}/assignments`
|
||||||
|
assign external team
|
||||||
|
if report is `OPEN`, assignment can directly claim report and move to `ASSIGNED`
|
||||||
|
- `GET /reports/{id}/intervention-summary`
|
||||||
|
operator/admin full summary with citizen contact data
|
||||||
|
- `GET /reports/{id}/team-suggestions`
|
||||||
|
suggestions by category/team-category mapping and area
|
||||||
|
- `POST /reports/{id}/intervention-link`
|
||||||
|
create/rotate public intervention token for current assignment
|
||||||
|
- `DELETE /reports/{id}/intervention-link`
|
||||||
|
revoke active intervention token
|
||||||
|
|
||||||
|
## Moderation
|
||||||
|
- `POST /reports/{id}/flags`
|
||||||
|
operator flags owned report
|
||||||
|
- `POST /reports/{id}/messages/{message_id}/flags`
|
||||||
|
operator flags owned report message
|
||||||
|
- `GET /admin/moderation`
|
||||||
|
admin moderation queue
|
||||||
|
- `PATCH /admin/moderation/{id}`
|
||||||
|
admin resolves/dismisses; may also reject report
|
||||||
|
|
||||||
## Admin Reference Data
|
## Admin Reference Data
|
||||||
- `POST /admin/categories`
|
- `POST /admin/categories`
|
||||||
@@ -62,15 +113,43 @@ Authorization: Bearer <token>
|
|||||||
- `PATCH /admin/areas/{id}`
|
- `PATCH /admin/areas/{id}`
|
||||||
- `POST /admin/teams`
|
- `POST /admin/teams`
|
||||||
- `PATCH /admin/teams/{id}`
|
- `PATCH /admin/teams/{id}`
|
||||||
- `GET /admin/stats/overview`
|
|
||||||
- `GET /admin/moderation`: list moderation flags with optional `status`.
|
|
||||||
- `PATCH /admin/moderation/{id}`: resolve or dismiss a moderation flag, optionally
|
|
||||||
rejecting the related report.
|
|
||||||
- `GET /admin/users`
|
- `GET /admin/users`
|
||||||
- `PATCH /admin/users/{id}/roles`
|
- `PATCH /admin/users/{id}/roles`
|
||||||
- `PATCH /admin/users/{id}/ban`
|
- `PATCH /admin/users/{id}/ban`
|
||||||
|
- `GET /admin/stats/overview`
|
||||||
|
|
||||||
## Public Data
|
## Workflow Summary
|
||||||
- `GET /areas`: list active municipal areas.
|
### Citizen
|
||||||
- `GET /interventions/{token}`: public intervention summary for external teams.
|
1. register/login
|
||||||
- `GET /open-data/reports`: anonymized resolved/closed report export.
|
2. create `OPEN` report
|
||||||
|
3. watch status/history/media
|
||||||
|
4. chat with operator
|
||||||
|
5. receive notifications
|
||||||
|
6. leave feedback on `RESOLVED`
|
||||||
|
|
||||||
|
### Operator
|
||||||
|
1. inspect open queue
|
||||||
|
2. `take` or assign team directly
|
||||||
|
3. own report through `assigned_operator_id`
|
||||||
|
4. update status / chat / moderate
|
||||||
|
5. generate public team link
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
1. manage reference data and users
|
||||||
|
2. resolve moderation
|
||||||
|
3. read global stats
|
||||||
|
|
||||||
|
## Current Status Machine
|
||||||
|
- `OPEN -> UNDER_REVIEW | REJECTED`
|
||||||
|
- `UNDER_REVIEW -> ASSIGNED | REJECTED`
|
||||||
|
- `ASSIGNED -> IN_PROGRESS | REJECTED`
|
||||||
|
- `IN_PROGRESS -> RESOLVED | REJECTED`
|
||||||
|
- `RESOLVED -> CLOSED`
|
||||||
|
- `REJECTED -> CLOSED`
|
||||||
|
|
||||||
|
## Important Current Truths
|
||||||
|
- External teams are not platform users.
|
||||||
|
- Platform owner is always `reports.assigned_operator_id`.
|
||||||
|
- `assignments` is historical external team linkage only.
|
||||||
|
- Feedback does not auto-close report.
|
||||||
|
- Public intervention summary exposes citizen phone by design, but not email or full surname.
|
||||||
|
|||||||
+87
-38
@@ -1,48 +1,97 @@
|
|||||||
# Visione Backend Architecture
|
# Visione Backend Architecture
|
||||||
|
|
||||||
## Layout
|
## Purpose
|
||||||
- `controllers/`: Drogon `HttpController` classes.
|
Visione backend is C++20 + Drogon API for municipal urban issue reporting.
|
||||||
- `services/`: application/business logic.
|
Citizens create reports. Operators own report workflow on platform. External
|
||||||
- `repositories/`: database helpers and migrations.
|
intervention teams do not log into platform; operators can assign them and
|
||||||
- `common/`: reusable HTTP, auth, crypto, email and validation utilities.
|
generate public read-only intervention links.
|
||||||
- `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.
|
|
||||||
|
|
||||||
## Drogon Usage
|
## Source Of Truth
|
||||||
Controllers use Drogon route macros and return JSON with
|
- Route truth: `controllers/*.h`
|
||||||
`HttpResponse::newHttpJsonResponse`. Database access uses
|
- Business truth: `services/*.cc`
|
||||||
`drogon::orm::DbClient` with raw parameterized SQL placeholders.
|
- 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
|
## Code Layout
|
||||||
in repository/database helpers because the schema is still evolving.
|
- `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
|
## Runtime Model
|
||||||
OTP emails are sent to Mailpit over SMTP. Mailpit keeps messages local and
|
- Auth uses bearer tokens from `api_tokens`.
|
||||||
inspectable through its web UI.
|
- 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
|
## Role Model
|
||||||
User passwords use PBKDF2-HMAC-SHA256 with per-password random salts and 210000
|
- `citizen`: own reports only, own messages, feedback, notifications.
|
||||||
iterations. This is intentionally slower than a plain SHA-256 digest and is
|
- `operator`: can read all reports, manage workflow, assign teams, create flags.
|
||||||
available through OpenSSL, which is already a Drogon dependency in this project.
|
- `admin`: full admin + report management + moderation approval.
|
||||||
Legacy `sha256$` password hashes are rejected instead of silently accepted.
|
|
||||||
|
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
|
## OpenAPI Generation
|
||||||
`scripts/generate_openapi.py` scans Drogon `ADD_METHOD_TO` route macros in
|
`scripts/generate_openapi.py` scans `ADD_METHOD_TO(...)` in controller headers.
|
||||||
`controllers/*.h` and extracts only paths, HTTP methods, operation ids, tags and
|
Generated route skeletons merge with `docs/openapi/visione.openapi.yaml`.
|
||||||
path parameters. It merges those route skeletons with
|
Build fails if manual spec contains operation without matching route, unless
|
||||||
`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
|
|
||||||
`x-manual-only: true`.
|
`x-manual-only: true`.
|
||||||
|
|
||||||
## Visione Lab
|
Output path:
|
||||||
`scripts/lab/` is a Streamlit dev frontend for visual endpoint testing. It is
|
- `build/openapi/visione.openapi.json`
|
||||||
intentionally separate from the real product frontend and should stay small:
|
|
||||||
forms, uploads and response rendering only. Run it with uv from `scripts/lab/`.
|
## 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.
|
||||||
|
|||||||
+97
-25
@@ -1,31 +1,103 @@
|
|||||||
# Visione Database Schema
|
# Visione Database Schema
|
||||||
|
|
||||||
The schema is stored in ordered SQL migrations under `migrations/`.
|
Schema truth is `migrations/*.sql`. This file is compact human map of current DB.
|
||||||
|
|
||||||
Persistence currently uses `drogon::orm::DbClient` with raw parameterized SQL.
|
## Migration Set
|
||||||
The project does not use Drogon generated models or `Mapper<T>` while the schema
|
- `001_initial_schema.sql`
|
||||||
is still evolving.
|
base auth, users, reports, areas, teams, messages, feedback, rewards
|
||||||
|
- `004_report_workflow.sql`
|
||||||
|
`reports.assigned_operator_id`, workflow indexes, richer media columns
|
||||||
|
- `006_completion_features.sql`
|
||||||
|
notifications, moderation flags, reward uniqueness
|
||||||
|
- `007_reference_data_and_intervention_links.sql`
|
||||||
|
category-to-team-category mapping, public intervention links
|
||||||
|
|
||||||
Important tables:
|
## Core Auth Tables
|
||||||
|
- `users`
|
||||||
|
identity, profile, password hash, points, `banned`, `must_change_password`
|
||||||
|
- `roles`
|
||||||
|
- `permissions`
|
||||||
|
- `role_permissions`
|
||||||
|
- `user_roles`
|
||||||
|
- `otp_challenges`
|
||||||
|
OTP flow, hashed codes, expiry, consumption
|
||||||
|
- `api_tokens`
|
||||||
|
bearer token hashes, expiry, revocation
|
||||||
|
|
||||||
- `users`: identity, personal data, password hash, points and moderation flags.
|
## Reference Data
|
||||||
- `roles`, `permissions`, `role_permissions`, `user_roles`: authorization.
|
- `report_categories`
|
||||||
- `otp_challenges`: local OTP login flow.
|
citizen-facing report categories, `active`
|
||||||
- `api_tokens`: bearer tokens for the local API.
|
- `areas`
|
||||||
- `report_categories`: report classification.
|
named municipal areas, GeoJSON polygon in `boundary_geojson`, `active`
|
||||||
- `areas`: named city areas with GeoJSON polygon boundaries.
|
- `team_categories`
|
||||||
- `team_categories`, `teams`: municipal intervention teams.
|
category of external intervention team
|
||||||
- `reports`: citizen reports with coordinates, address, status, area and assigned operator.
|
- `teams`
|
||||||
- `report_status_history`: immutable status transition log.
|
external teams, optional `area_id`, `team_category_id`, `active`
|
||||||
- `assignments`: report-to-team assignment history.
|
- `report_category_team_categories`
|
||||||
- `report_photos`: uploaded report media URL, original filename, content type,
|
explicit compatibility mapping:
|
||||||
media kind and size. The table name is historical; rows can be images or videos.
|
report category -> team category
|
||||||
- `messages`: citizen/operator report conversation.
|
|
||||||
- `feedback`: citizen rating and comment after resolution.
|
|
||||||
- `reward_events`: indirect gamification points ledger.
|
|
||||||
- `notifications`: local in-app notifications for status changes, messages and
|
|
||||||
reward events.
|
|
||||||
- `moderation_flags`: report/message review queue for abusive or invalid content.
|
|
||||||
|
|
||||||
GeoJSON is intentionally used instead of MySQL Spatial in milestone 1 to keep
|
## Report Domain
|
||||||
the local setup simple and portable.
|
- `reports`
|
||||||
|
owner citizen, category, inferred area, coordinates, address, description,
|
||||||
|
status, `assigned_operator_id`
|
||||||
|
- `report_status_history`
|
||||||
|
immutable workflow log
|
||||||
|
- `assignments`
|
||||||
|
historical report -> external team assignments, active row has `ended_at IS NULL`
|
||||||
|
- `report_photos`
|
||||||
|
historical name; stores both images and videos
|
||||||
|
current columns include `original_filename`, `content_type`, `media_kind`,
|
||||||
|
`size_bytes`
|
||||||
|
- `messages`
|
||||||
|
citizen/operator conversation per report
|
||||||
|
- `feedback`
|
||||||
|
one row per report, unique on `report_id`
|
||||||
|
|
||||||
|
## Engagement / Moderation
|
||||||
|
- `reward_events`
|
||||||
|
points ledger, unique `(report_id, reason)`
|
||||||
|
- `notifications`
|
||||||
|
local in-app notifications
|
||||||
|
- `moderation_flags`
|
||||||
|
operator-created flags, admin-resolved moderation queue
|
||||||
|
- `intervention_access_links`
|
||||||
|
public revocable hashed links for external teams, tied to `report_id` and
|
||||||
|
`assignment_id`
|
||||||
|
|
||||||
|
## Ownership Model
|
||||||
|
- Platform owner of report: `reports.assigned_operator_id`
|
||||||
|
- External team linkage: `assignments`
|
||||||
|
- Public field-team access: `intervention_access_links`
|
||||||
|
|
||||||
|
## Status Model
|
||||||
|
`reports.status` enum:
|
||||||
|
- `OPEN`
|
||||||
|
- `UNDER_REVIEW`
|
||||||
|
- `ASSIGNED`
|
||||||
|
- `IN_PROGRESS`
|
||||||
|
- `RESOLVED`
|
||||||
|
- `REJECTED`
|
||||||
|
- `CLOSED`
|
||||||
|
|
||||||
|
## Important Constraints / Semantics
|
||||||
|
- `users.email` unique
|
||||||
|
- `users.fiscal_code` unique
|
||||||
|
- `teams.name` unique
|
||||||
|
- `areas.name` unique
|
||||||
|
- `report_categories.name` unique
|
||||||
|
- `feedback.report_id` unique
|
||||||
|
- `reward_events(report_id, reason)` unique
|
||||||
|
- `api_tokens.token_hash` unique
|
||||||
|
- `intervention_access_links.token_hash` unique
|
||||||
|
|
||||||
|
## Current Query Model
|
||||||
|
- No generated ORM models
|
||||||
|
- No `Mapper<T>`
|
||||||
|
- Raw parameterized SQL through `drogon::orm::DbClient`
|
||||||
|
- JSON serialization helpers in `repositories/Sql.*`
|
||||||
|
|
||||||
|
## Current Gaps
|
||||||
|
- No dedicated table for external team personnel
|
||||||
|
- No route-optimization/routing tables
|
||||||
|
- No audit table for intervention link access beyond `last_accessed_at`
|
||||||
|
|||||||
+115
-17
@@ -1,22 +1,120 @@
|
|||||||
# Visione Backend LLM Context
|
# Visione Backend LLM Context
|
||||||
|
|
||||||
Visione is a C++20/Drogon backend for a municipal urban issue reporting system.
|
## What This Repo Is
|
||||||
Citizens create reports with category, position, address, description and media
|
Visione is C++20 + Drogon backend for urban issue reporting.
|
||||||
attachments.
|
|
||||||
Operators manage report queues, assign teams, update status, chat with citizens
|
|
||||||
and use intervention summaries.
|
|
||||||
Admins manage roles, users, areas, teams, reference data, moderation and stats.
|
|
||||||
|
|
||||||
The project runs locally on the host machine. Docker is used only for external
|
Core actors:
|
||||||
services such as MySQL and Mailpit. Real production deployment and Dockerizing
|
- `citizen`: creates and follows reports
|
||||||
the backend are later delivery tasks.
|
- `operator`: manages report lifecycle on platform
|
||||||
|
- `admin`: manages users/reference data and approves moderation
|
||||||
|
|
||||||
Important conventions:
|
External intervention teams are off-platform. They receive summary through
|
||||||
|
public revocable link generated by operator/admin.
|
||||||
|
|
||||||
- Code and database identifiers are English.
|
## Environment
|
||||||
- The webapp is Italian-facing, so labels or seeded category names may be Italian.
|
- Local backend runs outside Docker.
|
||||||
- Use Drogon references from `/home/gabdevele/Projects/snitchitaly/backend/drogon`
|
- Docker is only for local dependencies like MySQL and Mailpit.
|
||||||
and `https://drogonframework.github.io/drogon-docs/`.
|
- Primary commands:
|
||||||
- `AGENTS.md` is the durable agent instruction entrypoint.
|
- configure: `cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug`
|
||||||
- `docs/MILESTONE_01.md`, `docs/MILESTONE_02.md`, `docs/MILESTONE_03.md` and
|
- build: `cmake --build build`
|
||||||
`docs/MILESTONE_04.md` describe what has been implemented and what comes next.
|
- test: `ctest --test-dir build --output-on-failure`
|
||||||
|
- openapi: `cmake --build build --target openapi`
|
||||||
|
|
||||||
|
## High-Signal Conventions
|
||||||
|
- Code, DB identifiers, API fields, comments: English.
|
||||||
|
- User-facing labels may be Italian.
|
||||||
|
- Keep controller thin, service owns business logic.
|
||||||
|
- Use parameterized SQL only.
|
||||||
|
- Prefer current code over old assumptions. Route truth is in controller headers.
|
||||||
|
|
||||||
|
## Current Auth Model
|
||||||
|
- Registration creates `citizen`.
|
||||||
|
- Login paths:
|
||||||
|
- OTP email via Mailpit
|
||||||
|
- password fallback
|
||||||
|
- Auth token: bearer token from `api_tokens`
|
||||||
|
- Banned users cannot authenticate.
|
||||||
|
- `must_change_password` blocks most protected operator/admin flows until changed.
|
||||||
|
|
||||||
|
## Current Report Workflow
|
||||||
|
### Citizen flow
|
||||||
|
1. Register/login.
|
||||||
|
2. `POST /reports` with category, coordinates, address, description, optional media array.
|
||||||
|
3. Backend infers `area_id` by GeoJSON polygon match when possible.
|
||||||
|
4. Report starts `OPEN`, status history row inserted.
|
||||||
|
5. Citizen can:
|
||||||
|
- list own reports
|
||||||
|
- read detail/history/media
|
||||||
|
- chat on own report
|
||||||
|
- upload more media up to 5 total
|
||||||
|
- read notifications
|
||||||
|
- read rewards
|
||||||
|
- leave feedback only when report is `RESOLVED`
|
||||||
|
|
||||||
|
### Operator flow
|
||||||
|
1. Read queues:
|
||||||
|
- `/operator/reports/open`
|
||||||
|
- `/operator/reports/assigned`
|
||||||
|
- `/operator/reports/others`
|
||||||
|
2. Optional `POST /reports/{id}/take`:
|
||||||
|
`OPEN -> UNDER_REVIEW`, owner becomes operator.
|
||||||
|
3. `POST /reports/{id}/assignments`:
|
||||||
|
- if report `OPEN`, operator can assign directly without `take`
|
||||||
|
- owner becomes operator if report had no owner
|
||||||
|
- status becomes `ASSIGNED`
|
||||||
|
- current active assignment closes, new one inserted
|
||||||
|
4. Continue status flow:
|
||||||
|
- `ASSIGNED -> IN_PROGRESS -> RESOLVED -> CLOSED`
|
||||||
|
- rejection path to `REJECTED`, then `CLOSED`
|
||||||
|
5. Operator can chat with citizen on owned report.
|
||||||
|
6. Operator can create moderation flags for report/message on owned report.
|
||||||
|
7. Operator can request team suggestions and generate/revoke public intervention link.
|
||||||
|
|
||||||
|
### Admin flow
|
||||||
|
- Create/update categories, areas, teams
|
||||||
|
- List users, edit roles, ban/unban
|
||||||
|
- Read stats
|
||||||
|
- Review moderation queue and resolve/dismiss flags
|
||||||
|
- Optionally reject report during moderation resolution
|
||||||
|
|
||||||
|
## Team Model
|
||||||
|
- `teams` are external intervention squads, not platform users.
|
||||||
|
- `assignments` is history of which external team was assigned.
|
||||||
|
- `report_category_team_categories` maps report categories to compatible team categories.
|
||||||
|
- `GET /reports/{id}/team-suggestions` returns recommended active teams by:
|
||||||
|
- mapped team category
|
||||||
|
- same area first
|
||||||
|
- area-less team as fallback
|
||||||
|
- `POST /reports/{id}/intervention-link` creates revocable public link.
|
||||||
|
- `GET /interventions/{token}` returns public summary for field team:
|
||||||
|
report data, location, media, assigned team, masked citizen identity, phone.
|
||||||
|
|
||||||
|
## Current Notifications / Rewards / Moderation
|
||||||
|
- Notifications: local in-app only.
|
||||||
|
- Reward points: awarded once per `(report_id, reason)`.
|
||||||
|
- Moderation:
|
||||||
|
- operator creates flag
|
||||||
|
- admin resolves/dismisses
|
||||||
|
- admin may reject report during moderation decision
|
||||||
|
|
||||||
|
## Important Behavioral Truths
|
||||||
|
- Owner on platform is always `reports.assigned_operator_id`, not team.
|
||||||
|
- Feedback does not auto-close report.
|
||||||
|
- Public areas list is anonymous.
|
||||||
|
- Report categories require auth.
|
||||||
|
- Team list requires operator/admin auth.
|
||||||
|
- Moderation approval is admin-only.
|
||||||
|
- Public intervention token invalid/revoked/scoped-out returns `404`.
|
||||||
|
|
||||||
|
## Files To Read First
|
||||||
|
- `docs/API_SPEC.md`
|
||||||
|
- `docs/ARCHITECTURE.md`
|
||||||
|
- `docs/openapi/visione.openapi.yaml`
|
||||||
|
- `controllers/*.h`
|
||||||
|
- `services/*.cc`
|
||||||
|
- `migrations/*.sql`
|
||||||
|
|
||||||
|
## External References
|
||||||
|
- Local Drogon source: `/home/gabdevele/Projects/snitchitaly/backend/drogon`
|
||||||
|
- Drogon docs: `https://drogonframework.github.io/drogon-docs/`
|
||||||
|
- Durable agent instructions: `AGENTS.md`
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# Milestone 01: Local Backend Foundation
|
|
||||||
|
|
||||||
## Implemented in this milestone
|
|
||||||
- Project identity and documentation for Visione.
|
|
||||||
- Root `AGENTS.md` for future coding agents.
|
|
||||||
- Local Docker services for MySQL and Mailpit.
|
|
||||||
- Initial MySQL schema and seed data.
|
|
||||||
- Drogon backend structure for auth, reports, admin data and stats.
|
|
||||||
- OTP email delivery through Mailpit.
|
|
||||||
- Focused regression tests for technical endpoints and core validation helpers.
|
|
||||||
- Local admin seed: `[email protected]` / `ChangeMe123!`, with forced password change flag.
|
|
||||||
- Multipart report media upload endpoint storing files under `uploads/reports/`.
|
|
||||||
- PBKDF2-HMAC-SHA256 password hashes for credentials. Legacy SHA-256 hashes are intentionally rejected.
|
|
||||||
|
|
||||||
## To do next
|
|
||||||
- Consider Argon2id/bcrypt and password reset flows before production hardening.
|
|
||||||
- Add real email provider configuration for non-local environments.
|
|
||||||
- Add CIE/SPID integration design and provider adapters.
|
|
||||||
- Expand frontend-driven API coverage as Vue screens are implemented.
|
|
||||||
- Add stronger frontend-driven endpoint coverage as Vue screens are implemented.
|
|
||||||
- Add notification channels, route optimization and open-data exports.
|
|
||||||
- Dockerize the backend only for final delivery/deployment.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Milestone 02: Workflow Core and Hardening
|
|
||||||
|
|
||||||
## Implemented in this milestone
|
|
||||||
- Documentation/config fixes from Milestone 01 review.
|
|
||||||
- Authorization context with roles, permissions and forced password-change state.
|
|
||||||
- Authenticated password change endpoint.
|
|
||||||
- Admin user listing, role assignment and ban/unban endpoints.
|
|
||||||
- Report ownership workflow with assigned operator and take endpoint.
|
|
||||||
- Valid report status transitions.
|
|
||||||
- Citizen report filters scoped to own reports.
|
|
||||||
- Report media upload validation and metadata storage.
|
|
||||||
|
|
||||||
## To do next
|
|
||||||
- Add broader API tests once the frontend contracts stabilize.
|
|
||||||
- Add notification delivery for status changes.
|
|
||||||
- Add moderation flow for abusive reports/messages.
|
|
||||||
- Add open-data exports and statistics refinements.
|
|
||||||
- Design CIE/SPID integration.
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Milestone 03: Media, OpenAPI and API Lab
|
|
||||||
|
|
||||||
## Implemented in this milestone
|
|
||||||
- Removed legacy `sha256$` password verification and updated local admin seeds to
|
|
||||||
PBKDF2-HMAC-SHA256.
|
|
||||||
- Documented why PBKDF2-HMAC-SHA256 is used now: salted, intentionally slower
|
|
||||||
than plain SHA-256 and available through the existing OpenSSL dependency.
|
|
||||||
- Added report media uploads at `POST /api/v1/reports/{id}/media`.
|
|
||||||
- Supported JPEG, PNG, WebP, MP4, WebM and QuickTime uploads.
|
|
||||||
- Report attachments now use the `/media` upload path.
|
|
||||||
- Added generated OpenAPI JSON from Drogon route macros plus the manual
|
|
||||||
`docs/openapi/visione.openapi.yaml` contract.
|
|
||||||
- Exposed `/api/v1/openapi.json` and `/api/v1/docs`.
|
|
||||||
- Added a small Streamlit API lab for visual endpoint testing.
|
|
||||||
|
|
||||||
## To do next
|
|
||||||
- Expand the manual OpenAPI contract as endpoints evolve.
|
|
||||||
- Add minimal media upload integration tests after endpoint contracts settle.
|
|
||||||
- Keep API and docs on `media` terminology only.
|
|
||||||
- Keep the Streamlit lab small and replace it with the real frontend later.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Milestone 04: Backend Completion
|
|
||||||
|
|
||||||
## Implemented in this milestone
|
|
||||||
- Added operator queues for assigned reports, open unassigned reports and reports
|
|
||||||
assigned to other operators.
|
|
||||||
- Added an operator intervention summary with report data, citizen contact data,
|
|
||||||
media and latest messages.
|
|
||||||
- Added local in-app notifications for status changes, team assignment, report
|
|
||||||
messages and reward events.
|
|
||||||
- Added citizen reward ledger APIs and automatic points for resolved reports.
|
|
||||||
- Added report/message moderation flags with admin/operator review and optional
|
|
||||||
report rejection.
|
|
||||||
- Expanded admin statistics with category, team, monthly and segmented
|
|
||||||
resolution-time aggregates.
|
|
||||||
- Added anonymized public open-data report export.
|
|
||||||
- Updated the manual OpenAPI contract for the new backend routes.
|
|
||||||
|
|
||||||
## Intentionally not implemented
|
|
||||||
- Real CIE/SPID integration.
|
|
||||||
- Real SMS, Telegram, WhatsApp or production SMTP notification delivery.
|
|
||||||
- Route optimization for intervention teams.
|
|
||||||
- Frontend-specific pages outside the Streamlit API lab.
|
|
||||||
+114
-40
@@ -1,51 +1,125 @@
|
|||||||
# Visione Project Spec
|
# Visione Project Spec
|
||||||
|
|
||||||
|
This file describes current implemented product shape, not aspirational backlog.
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Visione lets citizens report urban problems and lets municipal staff manage,
|
Visione lets citizens report urban issues and lets municipal staff manage report
|
||||||
assign and resolve interventions while preserving history and basic analytics.
|
workflow, communicate with citizens, assign real external intervention teams,
|
||||||
|
moderate abuse, and inspect operational stats.
|
||||||
|
|
||||||
## Roles
|
## Roles
|
||||||
- `citizen`: registers, requests OTP login, creates reports, follows own reports,
|
- `citizen`
|
||||||
sends messages and leaves feedback after resolution.
|
register, login, create reports, read own reports, chat, upload media,
|
||||||
- `operator`: views reports, takes open reports, assigns teams, updates managed
|
read notifications, read rewards, leave feedback on resolved reports
|
||||||
reports, messages citizens and flags abusive/fake citizen content on managed
|
- `operator`
|
||||||
reports.
|
read all reports, take open reports, own workflow, assign external teams,
|
||||||
- `admin`: manages users, roles, reference data, teams, areas and all reports.
|
change statuses, chat with citizens, create moderation flags, generate public
|
||||||
|
intervention links
|
||||||
|
- `admin`
|
||||||
|
all operator powers plus user/role management, reference data management,
|
||||||
|
moderation approval, bans, stats
|
||||||
|
|
||||||
The system admin seed must change password before using protected operational
|
## Product Model
|
||||||
or administrative APIs.
|
- Citizens use platform directly.
|
||||||
|
- Operators use platform directly.
|
||||||
|
- External intervention teams do not authenticate to platform.
|
||||||
|
- Teams receive public read-only intervention summary through revocable link.
|
||||||
|
|
||||||
## Report Statuses
|
## Implemented Features
|
||||||
- `OPEN`: created and waiting for review.
|
### Auth
|
||||||
- `UNDER_REVIEW`: an operator is checking the report.
|
- citizen registration
|
||||||
- `ASSIGNED`: a team has been assigned.
|
- OTP login through Mailpit
|
||||||
- `IN_PROGRESS`: intervention is active.
|
- password login fallback
|
||||||
- `RESOLVED`: intervention was completed.
|
- bearer token auth
|
||||||
- `REJECTED`: report was invalid or abusive.
|
- forced first password change for seed admin
|
||||||
- `CLOSED`: administrative closure after feedback or final review.
|
- profile read + limited self-edit (`first_name`, `last_name`, `phone`)
|
||||||
|
|
||||||
Valid milestone 2 transitions:
|
### Reports
|
||||||
|
- create report with category, coordinates, address, description
|
||||||
|
- optional inline media on creation
|
||||||
|
- later media upload up to total 5 files
|
||||||
|
- area inferred by GeoJSON polygon match
|
||||||
|
- citizen report list/detail/history/media
|
||||||
|
- operator queues:
|
||||||
|
- open unassigned
|
||||||
|
- assigned to current operator
|
||||||
|
- assigned to other operators
|
||||||
|
|
||||||
- `OPEN -> UNDER_REVIEW` when an operator takes the report.
|
### Workflow
|
||||||
- `UNDER_REVIEW -> ASSIGNED` when a team is assigned.
|
- statuses:
|
||||||
- `ASSIGNED -> IN_PROGRESS`.
|
`OPEN`, `UNDER_REVIEW`, `ASSIGNED`, `IN_PROGRESS`, `RESOLVED`, `REJECTED`, `CLOSED`
|
||||||
- `IN_PROGRESS -> RESOLVED`.
|
- valid transitions enforced in backend
|
||||||
- `RESOLVED -> CLOSED`.
|
- `take` claims open report into `UNDER_REVIEW`
|
||||||
- `OPEN/UNDER_REVIEW/ASSIGNED/IN_PROGRESS -> REJECTED` only for invalid reports.
|
- team assignment can directly claim open report and move to `ASSIGNED`
|
||||||
- `REJECTED -> CLOSED`.
|
- owner on platform remains operator, not team
|
||||||
|
|
||||||
## MVP Scope
|
### Communication
|
||||||
- Local auth with email OTP delivered through Mailpit.
|
- citizen/operator report chat
|
||||||
- MySQL persistence with migrations and seed data.
|
- local in-app notifications for status, assignment, messages, rewards
|
||||||
- Report creation/list/detail/status/message/assignment/feedback/moderation flows.
|
|
||||||
- Area polygons stored as GeoJSON and matched by backend logic.
|
|
||||||
- Operator queues for assigned, open and other-operator reports.
|
|
||||||
- Local in-app notifications and reward points ledger.
|
|
||||||
- Public anonymized open-data report export.
|
|
||||||
- Basic and segmented stats for administration.
|
|
||||||
|
|
||||||
## Out of Scope for the Local Backend MVP
|
### External Team Handling
|
||||||
- Real CIE integration.
|
- external teams are reference data, not platform accounts
|
||||||
- Real SMTP provider, SMS, Telegram or WhatsApp notifications.
|
- active teams filtered for operator list
|
||||||
- Route optimization.
|
- suggestion endpoint recommends compatible teams by category mapping and area
|
||||||
- Full Docker packaging of the backend.
|
- operator/admin can create or revoke public intervention summary link
|
||||||
|
- public summary exposes operational info needed on field:
|
||||||
|
location, description, media, assigned team, masked citizen identity, phone
|
||||||
|
|
||||||
|
### Moderation
|
||||||
|
- operator can flag report or message on owned report
|
||||||
|
- admin resolves or dismisses flag
|
||||||
|
- admin can reject report during moderation decision
|
||||||
|
|
||||||
|
### Rewards / Feedback
|
||||||
|
- reward events ledger
|
||||||
|
- points awarded on resolved/closed report, deduplicated by `(report_id, reason)`
|
||||||
|
- citizen feedback allowed only on `RESOLVED`
|
||||||
|
- feedback does not auto-close report
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
- create/update categories
|
||||||
|
- create/update areas
|
||||||
|
- create/update teams
|
||||||
|
- list users, update roles, ban/unban
|
||||||
|
- global stats including area/category/team resolution aggregates
|
||||||
|
|
||||||
|
### Public Data
|
||||||
|
- active area list
|
||||||
|
- anonymized resolved/closed open-data export
|
||||||
|
- public intervention token endpoint
|
||||||
|
|
||||||
|
## Current Workflow Summary
|
||||||
|
### Citizen
|
||||||
|
1. register/login
|
||||||
|
2. create report
|
||||||
|
3. report starts `OPEN`
|
||||||
|
4. follow history/status
|
||||||
|
5. chat with operator if needed
|
||||||
|
6. receive notifications
|
||||||
|
7. leave feedback when resolved
|
||||||
|
|
||||||
|
### Operator
|
||||||
|
1. inspect open queue
|
||||||
|
2. `take` or assign external team directly
|
||||||
|
3. become report owner on platform
|
||||||
|
4. update status / chat / flag abuse
|
||||||
|
5. generate public intervention link for team
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
1. manage users and reference data
|
||||||
|
2. approve moderation decisions
|
||||||
|
3. inspect stats
|
||||||
|
|
||||||
|
## Out Of Scope Today
|
||||||
|
- real CIE/SPID integration
|
||||||
|
- production email/SMS/Telegram/WhatsApp notifications
|
||||||
|
- routing optimization for teams
|
||||||
|
- team platform accounts
|
||||||
|
- citizen-facing frontend in this repo
|
||||||
|
- hard-delete lifecycle for reference data
|
||||||
|
|
||||||
|
## Product Truth Rules
|
||||||
|
- Code truth beats stale docs.
|
||||||
|
- Route truth: controller headers.
|
||||||
|
- Business truth: services.
|
||||||
|
- DB truth: migrations.
|
||||||
|
|||||||
Reference in New Issue
Block a user