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