feat: update area management to implement hard delete and adjust related references
This commit is contained in:
+2
-1
@@ -46,7 +46,7 @@ Authorization: Bearer <token>
|
||||
|
||||
## Public Reference / Public Access
|
||||
- `GET /areas`
|
||||
active municipal areas
|
||||
municipal areas
|
||||
- `GET /interventions/{token}`
|
||||
public external-team summary link
|
||||
- `GET /open-data/reports`
|
||||
@@ -115,6 +115,7 @@ Authorization: Bearer <token>
|
||||
- `POST /admin/areas`
|
||||
- `PATCH /admin/areas/{id}`
|
||||
- `DELETE /admin/areas/{id}`
|
||||
deletes area and clears linked `reports.area_id` and `teams.area_id`
|
||||
area polygon must stay inside allowed Bari municipal boundary
|
||||
- `POST /admin/teams`
|
||||
- `PATCH /admin/teams/{id}`
|
||||
|
||||
@@ -28,7 +28,7 @@ Schema truth is `migrations/*.sql`. This file is compact human map of current DB
|
||||
- `report_categories`
|
||||
citizen-facing report categories, `active`
|
||||
- `areas`
|
||||
named municipal areas, GeoJSON polygon in `boundary_geojson`, `active`
|
||||
named municipal areas, GeoJSON polygon in `boundary_geojson`
|
||||
- `team_categories`
|
||||
category of external intervention team
|
||||
- `teams`
|
||||
@@ -92,7 +92,9 @@ Schema truth is `migrations/*.sql`. This file is compact human map of current DB
|
||||
- `reward_events(report_id, reason)` unique
|
||||
- `api_tokens.token_hash` unique
|
||||
- `intervention_access_links.token_hash` unique
|
||||
- category/team/area delete behavior is soft delete via `active = FALSE`
|
||||
- category/team delete behavior is soft delete via `active = FALSE`
|
||||
- area delete behavior is hard delete; linked `reports.area_id` and `teams.area_id`
|
||||
become `NULL`
|
||||
|
||||
## Current Query Model
|
||||
- No generated ORM models
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ public revocable link generated by operator/admin.
|
||||
1. Register/login.
|
||||
2. `POST /reports` with category, coordinates, address, description, optional media array.
|
||||
3. Backend first rejects coordinates outside Bari municipal boundary.
|
||||
4. Backend infers `area_id` by GeoJSON polygon match when possible.
|
||||
4. Backend infers `area_id` by GeoJSON polygon match when possible from existing areas.
|
||||
5. Report starts `OPEN`, status history row inserted.
|
||||
6. Citizen can:
|
||||
- list own reports
|
||||
|
||||
@@ -82,14 +82,14 @@ moderate abuse, and inspect operational stats.
|
||||
- soft-delete categories
|
||||
- create/update areas
|
||||
area polygons must stay inside Bari municipal boundary
|
||||
- soft-delete areas
|
||||
- delete areas
|
||||
- create/update teams
|
||||
- soft-delete teams
|
||||
- list users, update roles, ban/unban
|
||||
- global stats including area/category/team resolution aggregates
|
||||
|
||||
### Public Data
|
||||
- active area list
|
||||
- area list
|
||||
- anonymized resolved/closed open-data export
|
||||
- public intervention token endpoint
|
||||
- open-data output filtered to Bari municipal boundary
|
||||
|
||||
@@ -85,9 +85,6 @@
|
||||
"Area": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"boundary_geojson": {
|
||||
"$ref": "#/components/schemas/GeoJsonObject"
|
||||
},
|
||||
@@ -1713,7 +1710,7 @@
|
||||
},
|
||||
"/api/v1/admin/areas/{id}": {
|
||||
"delete": {
|
||||
"description": "Soft-deletes an existing municipal area by setting active to false.",
|
||||
"description": "Deletes an existing municipal area and clears linked report/team area references.",
|
||||
"operationId": "AdminController.deleteArea",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2268,7 +2265,7 @@
|
||||
},
|
||||
"/api/v1/areas": {
|
||||
"get": {
|
||||
"description": "Lists active municipal areas.",
|
||||
"description": "Lists municipal areas.",
|
||||
"operationId": "PublicController.listAreas",
|
||||
"parameters": [ ],
|
||||
"responses": {
|
||||
|
||||
@@ -82,7 +82,6 @@ CREATE TABLE IF NOT EXISTS areas (
|
||||
name VARCHAR(120) NOT NULL,
|
||||
description TEXT NULL,
|
||||
boundary_geojson JSON NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
UNIQUE KEY areas_name_unique (name)
|
||||
);
|
||||
|
||||
@@ -102,7 +101,7 @@ CREATE TABLE IF NOT EXISTS teams (
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
UNIQUE KEY teams_name_unique (name),
|
||||
CONSTRAINT teams_category_fk FOREIGN KEY (team_category_id) REFERENCES team_categories(id),
|
||||
CONSTRAINT teams_area_fk FOREIGN KEY (area_id) REFERENCES areas(id)
|
||||
CONSTRAINT teams_area_fk FOREIGN KEY (area_id) REFERENCES areas(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS reports (
|
||||
@@ -119,7 +118,7 @@ CREATE TABLE IF NOT EXISTS reports (
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT reports_user_fk FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT reports_category_fk FOREIGN KEY (category_id) REFERENCES report_categories(id),
|
||||
CONSTRAINT reports_area_fk FOREIGN KEY (area_id) REFERENCES areas(id),
|
||||
CONSTRAINT reports_area_fk FOREIGN KEY (area_id) REFERENCES areas(id) ON DELETE SET NULL,
|
||||
KEY reports_status_idx (status),
|
||||
KEY reports_category_idx (category_id),
|
||||
KEY reports_area_idx (area_id)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE teams DROP FOREIGN KEY teams_area_fk;
|
||||
ALTER TABLE reports DROP FOREIGN KEY reports_area_fk;
|
||||
|
||||
ALTER TABLE areas DROP COLUMN active;
|
||||
|
||||
ALTER TABLE teams
|
||||
ADD CONSTRAINT teams_area_fk
|
||||
FOREIGN KEY (area_id) REFERENCES areas(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE reports
|
||||
ADD CONSTRAINT reports_area_fk
|
||||
FOREIGN KEY (area_id) REFERENCES areas(id) ON DELETE SET NULL;
|
||||
@@ -444,7 +444,9 @@ Json::Value AdminService::deleteArea(const auth::UserContext &user,
|
||||
{
|
||||
return failure("area_not_found");
|
||||
}
|
||||
db->execSqlSync("UPDATE areas SET active = FALSE WHERE id = ?", areaId);
|
||||
db->execSqlSync("UPDATE teams SET area_id = NULL WHERE area_id = ?", areaId);
|
||||
db->execSqlSync("UPDATE reports SET area_id = NULL WHERE area_id = ?", areaId);
|
||||
db->execSqlSync("DELETE FROM areas WHERE id = ?", areaId);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -474,12 +476,6 @@ Json::Value AdminService::updateArea(const auth::UserContext &user,
|
||||
body["description"].asString(),
|
||||
areaId);
|
||||
}
|
||||
if (isActiveField(body))
|
||||
{
|
||||
db->execSqlSync("UPDATE areas SET active = ? WHERE id = ?",
|
||||
body["active"].asBool(),
|
||||
areaId);
|
||||
}
|
||||
if (body["boundary_geojson"].isObject())
|
||||
{
|
||||
if (!isAreaInsideAllowedBoundary(body["boundary_geojson"]))
|
||||
|
||||
@@ -107,7 +107,7 @@ std::optional<long long> findArea(double latitude, double longitude)
|
||||
{
|
||||
const auto result = drogon::app().getDbClient()->execSqlSync(
|
||||
"SELECT id, JSON_UNQUOTE(JSON_EXTRACT(boundary_geojson, '$')) AS geojson "
|
||||
"FROM areas WHERE active = TRUE");
|
||||
"FROM areas");
|
||||
|
||||
Json::CharReaderBuilder builder;
|
||||
for (const auto &row : result)
|
||||
@@ -647,8 +647,7 @@ Json::Value ReportService::listAreas()
|
||||
{
|
||||
auto response = success();
|
||||
response["areas"] = repositories::resultToJson(
|
||||
drogon::app().getDbClient()->execSqlSync(
|
||||
"SELECT * FROM areas WHERE active = TRUE ORDER BY name"));
|
||||
drogon::app().getDbClient()->execSqlSync("SELECT * FROM areas ORDER BY name"));
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user