{ "description": "Visione OpenAPI generated from Drogon routes and the Visione contract registry.", "components": { "schemas": { "OkResponse": { "type": "object", "required": ["ok"], "properties": { "ok": { "type": "boolean" } }, "additionalProperties": false }, "ErrorResponse": { "type": "object", "required": ["error"], "properties": { "error": { "type": "string" }, "message": { "type": "string" }, "fields": { "type": "array", "items": { "type": "string" } } }, "additionalProperties": false }, "HealthResponse": { "type": "object", "required": ["status", "service"], "properties": { "status": { "type": "string", "example": "ok" }, "service": { "type": "string", "example": "visione-backend" } }, "additionalProperties": false }, "ReadyResponse": { "type": "object", "required": ["status", "dependencies"], "properties": { "status": { "type": "string", "enum": ["ready", "not_ready"] }, "dependencies": { "type": "object", "required": ["mysql"], "properties": { "mysql": { "type": "boolean" } }, "additionalProperties": false }, "errors": { "type": "object", "properties": { "mysql": { "type": "string" } }, "additionalProperties": false } }, "additionalProperties": false }, "OpenApiDocument": { "type": "object", "description": "Generated OpenAPI 3 document.", "additionalProperties": true }, "RegisterCitizenRequest": { "type": "object", "required": ["first_name", "last_name", "email", "password", "fiscal_code"], "properties": { "first_name": { "type": "string" }, "last_name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "password": { "type": "string", "format": "password", "minLength": 10 }, "fiscal_code": { "type": "string", "minLength": 16, "maxLength": 16 }, "birth_date": { "type": "string", "format": "date" }, "birth_place": { "type": "string" }, "phone": { "type": "string" } }, "additionalProperties": false }, "RegisterCitizenResponse": { "type": "object", "required": ["ok", "user_id"], "properties": { "ok": { "type": "boolean" }, "user_id": { "type": "integer", "format": "int64" } }, "additionalProperties": false }, "EmailRequest": { "type": "object", "required": ["email"], "properties": { "email": { "type": "string", "format": "email" } }, "additionalProperties": false }, "OtpRequestResponse": { "type": "object", "required": ["ok", "delivery", "expires_in_minutes"], "properties": { "ok": { "type": "boolean" }, "delivery": { "type": "string", "example": "mailhog" }, "expires_in_minutes": { "type": "integer" } }, "additionalProperties": false }, "VerifyOtpRequest": { "type": "object", "required": ["email", "code"], "properties": { "email": { "type": "string", "format": "email" }, "code": { "type": "string" } }, "additionalProperties": false }, "PasswordLoginRequest": { "type": "object", "required": ["email", "password"], "properties": { "email": { "type": "string", "format": "email" }, "password": { "type": "string", "format": "password" } }, "additionalProperties": false }, "TokenResponse": { "type": "object", "required": ["ok", "token", "token_type", "expires_in_hours"], "properties": { "ok": { "type": "boolean" }, "token": { "type": "string" }, "token_type": { "type": "string", "example": "Bearer" }, "expires_in_hours": { "type": "integer" } }, "additionalProperties": false }, "ChangePasswordRequest": { "type": "object", "required": ["current_password", "new_password"], "properties": { "current_password": { "type": "string", "format": "password" }, "new_password": { "type": "string", "format": "password", "minLength": 10 } }, "additionalProperties": false }, "CurrentUser": { "type": "object", "required": ["id", "first_name", "last_name", "email", "fiscal_code", "points", "must_change_password", "role", "roles", "permissions"], "properties": { "id": { "type": "integer", "format": "int64" }, "first_name": { "type": "string" }, "last_name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "fiscal_code": { "type": "string" }, "phone": { "type": "string" }, "points": { "type": "integer" }, "must_change_password": { "type": "boolean" }, "role": { "type": "string" }, "roles": { "type": "array", "items": { "type": "string" } }, "permissions": { "type": "array", "items": { "type": "string" } } }, "additionalProperties": false }, "CurrentUserResponse": { "type": "object", "required": ["ok", "user"], "properties": { "ok": { "type": "boolean" }, "user": { "$ref": "#/components/schemas/CurrentUser" } }, "additionalProperties": false }, "ReportStatus": { "type": "string", "enum": ["OPEN", "UNDER_REVIEW", "ASSIGNED", "IN_PROGRESS", "RESOLVED", "REJECTED", "CLOSED"] }, "CreateReportRequest": { "type": "object", "required": ["category_id", "latitude", "longitude", "address", "description"], "properties": { "category_id": { "type": "integer", "format": "int64" }, "latitude": { "type": "number", "format": "double" }, "longitude": { "type": "number", "format": "double" }, "address": { "type": "string" }, "description": { "type": "string" }, "media": { "type": "array", "items": { "type": "string", "format": "uri-reference" } } }, "additionalProperties": false }, "CreateReportResponse": { "type": "object", "required": ["ok", "report_id"], "properties": { "ok": { "type": "boolean" }, "report_id": { "type": "integer", "format": "int64" }, "area_id": { "type": "integer", "format": "int64" } }, "additionalProperties": false }, "Report": { "type": "object", "required": ["id", "category_id", "latitude", "longitude", "address", "description", "status"], "properties": { "id": { "type": "integer", "format": "int64" }, "user_id": { "type": "integer", "format": "int64" }, "category_id": { "type": "integer", "format": "int64" }, "category_name": { "type": "string" }, "area_id": { "type": "integer", "format": "int64" }, "area_name": { "type": "string" }, "assigned_operator_id": { "type": "integer", "format": "int64" }, "latitude": { "type": "number" }, "longitude": { "type": "number" }, "address": { "type": "string" }, "description": { "type": "string" }, "status": { "$ref": "#/components/schemas/ReportStatus" }, "created_at": { "type": "string" }, "updated_at": { "type": "string" } }, "additionalProperties": true }, "ReportListResponse": { "type": "object", "required": ["ok", "reports"], "properties": { "ok": { "type": "boolean" }, "reports": { "type": "array", "items": { "$ref": "#/components/schemas/Report" } } }, "additionalProperties": false }, "StatusHistoryItem": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "report_id": { "type": "integer", "format": "int64" }, "old_status": { "$ref": "#/components/schemas/ReportStatus" }, "new_status": { "$ref": "#/components/schemas/ReportStatus" }, "changed_by_user_id": { "type": "integer", "format": "int64" }, "note": { "type": "string" }, "created_at": { "type": "string" } }, "additionalProperties": true }, "Media": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "report_id": { "type": "integer", "format": "int64" }, "url": { "type": "string" }, "original_filename": { "type": "string" }, "content_type": { "type": "string" }, "media_kind": { "type": "string", "enum": ["IMAGE", "VIDEO"] }, "size_bytes": { "type": "integer", "format": "int64" }, "created_at": { "type": "string" } }, "additionalProperties": true }, "ReportDetailResponse": { "type": "object", "required": ["ok", "report", "history", "media"], "properties": { "ok": { "type": "boolean" }, "report": { "$ref": "#/components/schemas/Report" }, "history": { "type": "array", "items": { "$ref": "#/components/schemas/StatusHistoryItem" } }, "media": { "type": "array", "items": { "$ref": "#/components/schemas/Media" } } }, "additionalProperties": false }, "UpdateStatusRequest": { "type": "object", "required": ["status"], "properties": { "status": { "$ref": "#/components/schemas/ReportStatus" }, "note": { "type": "string" } }, "additionalProperties": false }, "MessageRequest": { "type": "object", "required": ["body"], "properties": { "body": { "type": "string" } }, "additionalProperties": false }, "Message": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "report_id": { "type": "integer", "format": "int64" }, "user_id": { "type": "integer", "format": "int64" }, "body": { "type": "string" }, "first_name": { "type": "string" }, "last_name": { "type": "string" }, "created_at": { "type": "string" } }, "additionalProperties": true }, "MessageListResponse": { "type": "object", "required": ["ok", "messages"], "properties": { "ok": { "type": "boolean" }, "messages": { "type": "array", "items": { "$ref": "#/components/schemas/Message" } } }, "additionalProperties": false }, "AssignTeamRequest": { "type": "object", "required": ["team_id"], "properties": { "team_id": { "type": "integer", "format": "int64" } }, "additionalProperties": false }, "FeedbackRequest": { "type": "object", "required": ["rating"], "properties": { "rating": { "type": "integer", "minimum": 1, "maximum": 5 }, "comment": { "type": "string" } }, "additionalProperties": false }, "MediaUploadRequest": { "type": "object", "required": ["files"], "properties": { "files": { "type": "array", "maxItems": 5, "items": { "type": "string", "format": "binary" } } }, "additionalProperties": false }, "MediaUploadResponse": { "type": "object", "required": ["ok", "media"], "properties": { "ok": { "type": "boolean" }, "media": { "type": "array", "items": { "$ref": "#/components/schemas/Media" } } }, "additionalProperties": false }, "Category": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "description": { "type": "string" }, "active": { "type": "boolean" } }, "additionalProperties": true }, "CategoryListResponse": { "type": "object", "required": ["ok", "categories"], "properties": { "ok": { "type": "boolean" }, "categories": { "type": "array", "items": { "$ref": "#/components/schemas/Category" } } }, "additionalProperties": false }, "CreateCategoryRequest": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string" }, "description": { "type": "string" } }, "additionalProperties": false }, "GeoJsonObject": { "type": "object", "description": "GeoJSON polygon or multipolygon boundary.", "additionalProperties": true }, "Area": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "description": { "type": "string" }, "boundary_geojson": { "$ref": "#/components/schemas/GeoJsonObject" }, "active": { "type": "boolean" } }, "additionalProperties": true }, "AreaListResponse": { "type": "object", "required": ["ok", "areas"], "properties": { "ok": { "type": "boolean" }, "areas": { "type": "array", "items": { "$ref": "#/components/schemas/Area" } } }, "additionalProperties": false }, "CreateAreaRequest": { "type": "object", "required": ["name", "boundary_geojson"], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "boundary_geojson": { "$ref": "#/components/schemas/GeoJsonObject" } }, "additionalProperties": false }, "Team": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "team_category_id": { "type": "integer", "format": "int64" }, "category_name": { "type": "string" }, "area_id": { "type": "integer", "format": "int64" }, "area_name": { "type": "string" }, "name": { "type": "string" }, "description": { "type": "string" }, "active": { "type": "boolean" } }, "additionalProperties": true }, "TeamListResponse": { "type": "object", "required": ["ok", "teams"], "properties": { "ok": { "type": "boolean" }, "teams": { "type": "array", "items": { "$ref": "#/components/schemas/Team" } } }, "additionalProperties": false }, "CreateTeamRequest": { "type": "object", "required": ["team_category_id", "name"], "properties": { "team_category_id": { "type": "integer", "format": "int64" }, "area_id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "description": { "type": "string" } }, "additionalProperties": false }, "OverviewStatsResponse": { "type": "object", "required": ["ok", "reports_by_status", "reports_by_area", "avg_resolution_hours"], "properties": { "ok": { "type": "boolean" }, "reports_by_status": { "type": "array", "items": { "type": "object", "properties": { "status": { "$ref": "#/components/schemas/ReportStatus" }, "total": { "type": "integer" } }, "additionalProperties": true } }, "reports_by_area": { "type": "array", "items": { "type": "object", "properties": { "area": { "type": "string" }, "total": { "type": "integer" } }, "additionalProperties": true } }, "avg_resolution_hours": { "type": "array", "items": { "type": "object", "properties": { "value": { "type": "number" } }, "additionalProperties": true } } }, "additionalProperties": false }, "AdminUser": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "first_name": { "type": "string" }, "last_name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "fiscal_code": { "type": "string" }, "phone": { "type": "string" }, "points": { "type": "integer" }, "banned": { "type": "boolean" }, "must_change_password": { "type": "boolean" }, "roles": { "type": "string", "description": "Comma-separated role names from SQL GROUP_CONCAT." } }, "additionalProperties": true }, "UserListResponse": { "type": "object", "required": ["ok", "users"], "properties": { "ok": { "type": "boolean" }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/AdminUser" } } }, "additionalProperties": false }, "UpdateUserRolesRequest": { "type": "object", "required": ["roles"], "properties": { "roles": { "type": "array", "items": { "type": "string" } } }, "additionalProperties": false }, "UpdateUserBanRequest": { "type": "object", "required": ["banned"], "properties": { "banned": { "type": "boolean" } }, "additionalProperties": false } }, "responses": { "BadRequest": { "description": "Invalid request or business validation failure.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "Unauthorized": { "description": "Missing or invalid bearer token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "Forbidden": { "description": "Authenticated user cannot perform this action.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "NotFound": { "description": "Requested resource was not found.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } }, "operations": { "ApiController.health": { "response": "#/components/schemas/HealthResponse" }, "ApiController.ready": { "response": "#/components/schemas/ReadyResponse", "errors": { "503": { "description": "Dependency readiness failure.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReadyResponse" } } } } } }, "ApiController.openApi": { "response": "#/components/schemas/OpenApiDocument", "errors": { "404": { "$ref": "#/components/responses/NotFound" } } }, "ApiController.docs": { "responseContentType": "text/html", "response": { "type": "string" } }, "AuthController.registerCitizen": { "request": "#/components/schemas/RegisterCitizenRequest", "response": "#/components/schemas/RegisterCitizenResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" } } }, "AuthController.requestOtp": { "request": "#/components/schemas/EmailRequest", "response": "#/components/schemas/OtpRequestResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" } } }, "AuthController.verifyOtp": { "request": "#/components/schemas/VerifyOtpRequest", "response": "#/components/schemas/TokenResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" } } }, "AuthController.loginWithPassword": { "request": "#/components/schemas/PasswordLoginRequest", "response": "#/components/schemas/TokenResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" } } }, "AuthController.changePassword": { "request": "#/components/schemas/ChangePasswordRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AuthController.me": { "response": "#/components/schemas/CurrentUserResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" } } }, "ReportController.create": { "request": "#/components/schemas/CreateReportRequest", "response": "#/components/schemas/CreateReportResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.list": { "response": "#/components/schemas/ReportListResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" } } }, "ReportController.detail": { "response": "#/components/schemas/ReportDetailResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.updateStatus": { "request": "#/components/schemas/UpdateStatusRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.takeReport": { "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.addMessage": { "request": "#/components/schemas/MessageRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.listMessages": { "response": "#/components/schemas/MessageListResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.assignTeam": { "request": "#/components/schemas/AssignTeamRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.addFeedback": { "request": "#/components/schemas/FeedbackRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "ReportController.uploadMedia": { "request": "#/components/schemas/MediaUploadRequest", "response": "#/components/schemas/MediaUploadResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AdminController.listCategories": { "response": "#/components/schemas/CategoryListResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" } } }, "AdminController.createCategory": { "request": "#/components/schemas/CreateCategoryRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AdminController.listAreas": { "response": "#/components/schemas/AreaListResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" } } }, "AdminController.createArea": { "request": "#/components/schemas/CreateAreaRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AdminController.listTeams": { "response": "#/components/schemas/TeamListResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" } } }, "AdminController.createTeam": { "request": "#/components/schemas/CreateTeamRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AdminController.overviewStats": { "response": "#/components/schemas/OverviewStatsResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AdminController.listUsers": { "response": "#/components/schemas/UserListResponse", "errors": { "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AdminController.updateUserRoles": { "request": "#/components/schemas/UpdateUserRolesRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "AdminController.updateUserBan": { "request": "#/components/schemas/UpdateUserBanRequest", "response": "#/components/schemas/OkResponse", "errors": { "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" } } } } }