feat: add soft-delete functionality for categories, areas, and teams in Admin service
This commit is contained in:
@@ -138,6 +138,20 @@ void AdminController::updateCategory(
|
|||||||
std::move(callback));
|
std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdminController::deleteCategory(
|
||||||
|
const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
|
long long categoryId)
|
||||||
|
{
|
||||||
|
auto user = requireUser(req, callback);
|
||||||
|
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visione::services::AdminService service;
|
||||||
|
sendAdmin(service.deleteCategory(*user, categoryId), std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
void AdminController::updateArea(
|
void AdminController::updateArea(
|
||||||
const drogon::HttpRequestPtr &req,
|
const drogon::HttpRequestPtr &req,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
@@ -153,6 +167,20 @@ void AdminController::updateArea(
|
|||||||
std::move(callback));
|
std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdminController::deleteArea(
|
||||||
|
const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
|
long long areaId)
|
||||||
|
{
|
||||||
|
auto user = requireUser(req, callback);
|
||||||
|
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visione::services::AdminService service;
|
||||||
|
sendAdmin(service.deleteArea(*user, areaId), std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
void AdminController::updateTeam(
|
void AdminController::updateTeam(
|
||||||
const drogon::HttpRequestPtr &req,
|
const drogon::HttpRequestPtr &req,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
@@ -168,6 +196,20 @@ void AdminController::updateTeam(
|
|||||||
std::move(callback));
|
std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdminController::deleteTeam(
|
||||||
|
const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
|
long long teamId)
|
||||||
|
{
|
||||||
|
auto user = requireUser(req, callback);
|
||||||
|
if (!user || blockPasswordChangeRequired(*user, callback))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visione::services::AdminService service;
|
||||||
|
sendAdmin(service.deleteTeam(*user, teamId), std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
void AdminController::overviewStats(
|
void AdminController::overviewStats(
|
||||||
const drogon::HttpRequestPtr &req,
|
const drogon::HttpRequestPtr &req,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||||
|
|||||||
@@ -14,12 +14,21 @@ class AdminController : public drogon::HttpController<AdminController>
|
|||||||
ADD_METHOD_TO(AdminController::updateCategory,
|
ADD_METHOD_TO(AdminController::updateCategory,
|
||||||
"/api/v1/admin/categories/{1}",
|
"/api/v1/admin/categories/{1}",
|
||||||
drogon::Patch);
|
drogon::Patch);
|
||||||
|
ADD_METHOD_TO(AdminController::deleteCategory,
|
||||||
|
"/api/v1/admin/categories/{1}",
|
||||||
|
drogon::Delete);
|
||||||
ADD_METHOD_TO(AdminController::updateArea,
|
ADD_METHOD_TO(AdminController::updateArea,
|
||||||
"/api/v1/admin/areas/{1}",
|
"/api/v1/admin/areas/{1}",
|
||||||
drogon::Patch);
|
drogon::Patch);
|
||||||
|
ADD_METHOD_TO(AdminController::deleteArea,
|
||||||
|
"/api/v1/admin/areas/{1}",
|
||||||
|
drogon::Delete);
|
||||||
ADD_METHOD_TO(AdminController::updateTeam,
|
ADD_METHOD_TO(AdminController::updateTeam,
|
||||||
"/api/v1/admin/teams/{1}",
|
"/api/v1/admin/teams/{1}",
|
||||||
drogon::Patch);
|
drogon::Patch);
|
||||||
|
ADD_METHOD_TO(AdminController::deleteTeam,
|
||||||
|
"/api/v1/admin/teams/{1}",
|
||||||
|
drogon::Delete);
|
||||||
ADD_METHOD_TO(AdminController::overviewStats,
|
ADD_METHOD_TO(AdminController::overviewStats,
|
||||||
"/api/v1/admin/stats/overview",
|
"/api/v1/admin/stats/overview",
|
||||||
drogon::Get);
|
drogon::Get);
|
||||||
@@ -51,14 +60,26 @@ class AdminController : public drogon::HttpController<AdminController>
|
|||||||
const drogon::HttpRequestPtr &req,
|
const drogon::HttpRequestPtr &req,
|
||||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
long long categoryId);
|
long long categoryId);
|
||||||
|
void deleteCategory(
|
||||||
|
const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
long long categoryId);
|
||||||
void updateArea(
|
void updateArea(
|
||||||
const drogon::HttpRequestPtr &req,
|
const drogon::HttpRequestPtr &req,
|
||||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
long long areaId);
|
long long areaId);
|
||||||
|
void deleteArea(
|
||||||
|
const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
long long areaId);
|
||||||
void updateTeam(
|
void updateTeam(
|
||||||
const drogon::HttpRequestPtr &req,
|
const drogon::HttpRequestPtr &req,
|
||||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
long long teamId);
|
long long teamId);
|
||||||
|
void deleteTeam(
|
||||||
|
const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
long long teamId);
|
||||||
void overviewStats(
|
void overviewStats(
|
||||||
const drogon::HttpRequestPtr &req,
|
const drogon::HttpRequestPtr &req,
|
||||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|||||||
@@ -111,11 +111,14 @@ Authorization: Bearer <token>
|
|||||||
## Admin Reference Data
|
## Admin Reference Data
|
||||||
- `POST /admin/categories`
|
- `POST /admin/categories`
|
||||||
- `PATCH /admin/categories/{id}`
|
- `PATCH /admin/categories/{id}`
|
||||||
|
- `DELETE /admin/categories/{id}`
|
||||||
- `POST /admin/areas`
|
- `POST /admin/areas`
|
||||||
- `PATCH /admin/areas/{id}`
|
- `PATCH /admin/areas/{id}`
|
||||||
|
- `DELETE /admin/areas/{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}`
|
||||||
|
- `DELETE /admin/teams/{id}`
|
||||||
- `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`
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ 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`
|
||||||
|
|
||||||
## Current Query Model
|
## Current Query Model
|
||||||
- No generated ORM models
|
- No generated ORM models
|
||||||
|
|||||||
@@ -79,9 +79,12 @@ moderate abuse, and inspect operational stats.
|
|||||||
|
|
||||||
### Admin
|
### Admin
|
||||||
- create/update categories
|
- create/update 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
|
||||||
- create/update teams
|
- create/update 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
|
||||||
|
|
||||||
|
|||||||
@@ -1712,6 +1712,51 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/admin/areas/{id}": {
|
"/api/v1/admin/areas/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Soft-deletes an existing municipal area by setting active to false.",
|
||||||
|
"operationId": "AdminController.deleteArea",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/OkResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Deleted area result."
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#/components/responses/Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/components/responses/Forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/components/responses/NotFound"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": [ ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Delete area",
|
||||||
|
"tags": [
|
||||||
|
"Admin"
|
||||||
|
]
|
||||||
|
},
|
||||||
"patch": {
|
"patch": {
|
||||||
"description": "Updates an existing municipal area.",
|
"description": "Updates an existing municipal area.",
|
||||||
"operationId": "AdminController.updateArea",
|
"operationId": "AdminController.updateArea",
|
||||||
@@ -1816,6 +1861,51 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/admin/categories/{id}": {
|
"/api/v1/admin/categories/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Soft-deletes an existing report category by setting active to false.",
|
||||||
|
"operationId": "AdminController.deleteCategory",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/OkResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Deleted category result."
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#/components/responses/Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/components/responses/Forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/components/responses/NotFound"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": [ ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Delete category",
|
||||||
|
"tags": [
|
||||||
|
"Admin"
|
||||||
|
]
|
||||||
|
},
|
||||||
"patch": {
|
"patch": {
|
||||||
"description": "Updates an existing report category.",
|
"description": "Updates an existing report category.",
|
||||||
"operationId": "AdminController.updateCategory",
|
"operationId": "AdminController.updateCategory",
|
||||||
@@ -2075,6 +2165,51 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/admin/teams/{id}": {
|
"/api/v1/admin/teams/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Soft-deletes an existing intervention team by setting active to false.",
|
||||||
|
"operationId": "AdminController.deleteTeam",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/OkResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Deleted team result."
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#/components/responses/Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/components/responses/Forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/components/responses/NotFound"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": [ ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Delete team",
|
||||||
|
"tags": [
|
||||||
|
"Admin"
|
||||||
|
]
|
||||||
|
},
|
||||||
"patch": {
|
"patch": {
|
||||||
"description": "Updates an existing intervention team.",
|
"description": "Updates an existing intervention team.",
|
||||||
"operationId": "AdminController.updateTeam",
|
"operationId": "AdminController.updateTeam",
|
||||||
|
|||||||
@@ -374,6 +374,26 @@ Json::Value AdminService::updateModeration(const auth::UserContext &user,
|
|||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Json::Value AdminService::deleteCategory(const auth::UserContext &user,
|
||||||
|
long long categoryId)
|
||||||
|
{
|
||||||
|
if (!auth::isAdmin(user))
|
||||||
|
{
|
||||||
|
return failure("forbidden");
|
||||||
|
}
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
const auto rows = db->execSqlSync(
|
||||||
|
"SELECT id FROM report_categories WHERE id = ?",
|
||||||
|
categoryId);
|
||||||
|
if (rows.empty())
|
||||||
|
{
|
||||||
|
return failure("category_not_found");
|
||||||
|
}
|
||||||
|
db->execSqlSync("UPDATE report_categories SET active = FALSE WHERE id = ?",
|
||||||
|
categoryId);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
Json::Value AdminService::updateCategory(const auth::UserContext &user,
|
Json::Value AdminService::updateCategory(const auth::UserContext &user,
|
||||||
long long categoryId,
|
long long categoryId,
|
||||||
const Json::Value &body)
|
const Json::Value &body)
|
||||||
@@ -411,6 +431,23 @@ Json::Value AdminService::updateCategory(const auth::UserContext &user,
|
|||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Json::Value AdminService::deleteArea(const auth::UserContext &user,
|
||||||
|
long long areaId)
|
||||||
|
{
|
||||||
|
if (!auth::isAdmin(user))
|
||||||
|
{
|
||||||
|
return failure("forbidden");
|
||||||
|
}
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
const auto rows = db->execSqlSync("SELECT id FROM areas WHERE id = ?", areaId);
|
||||||
|
if (rows.empty())
|
||||||
|
{
|
||||||
|
return failure("area_not_found");
|
||||||
|
}
|
||||||
|
db->execSqlSync("UPDATE areas SET active = FALSE WHERE id = ?", areaId);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
Json::Value AdminService::updateArea(const auth::UserContext &user,
|
Json::Value AdminService::updateArea(const auth::UserContext &user,
|
||||||
long long areaId,
|
long long areaId,
|
||||||
const Json::Value &body)
|
const Json::Value &body)
|
||||||
@@ -458,6 +495,23 @@ Json::Value AdminService::updateArea(const auth::UserContext &user,
|
|||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Json::Value AdminService::deleteTeam(const auth::UserContext &user,
|
||||||
|
long long teamId)
|
||||||
|
{
|
||||||
|
if (!auth::isAdmin(user))
|
||||||
|
{
|
||||||
|
return failure("forbidden");
|
||||||
|
}
|
||||||
|
auto db = drogon::app().getDbClient();
|
||||||
|
const auto rows = db->execSqlSync("SELECT id FROM teams WHERE id = ?", teamId);
|
||||||
|
if (rows.empty())
|
||||||
|
{
|
||||||
|
return failure("team_not_found");
|
||||||
|
}
|
||||||
|
db->execSqlSync("UPDATE teams SET active = FALSE WHERE id = ?", teamId);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
Json::Value AdminService::updateTeam(const auth::UserContext &user,
|
Json::Value AdminService::updateTeam(const auth::UserContext &user,
|
||||||
long long teamId,
|
long long teamId,
|
||||||
const Json::Value &body)
|
const Json::Value &body)
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ class AdminService
|
|||||||
Json::Value updateModeration(const auth::UserContext &user,
|
Json::Value updateModeration(const auth::UserContext &user,
|
||||||
long long flagId,
|
long long flagId,
|
||||||
const Json::Value &body);
|
const Json::Value &body);
|
||||||
|
Json::Value deleteCategory(const auth::UserContext &user, long long categoryId);
|
||||||
|
Json::Value deleteArea(const auth::UserContext &user, long long areaId);
|
||||||
|
Json::Value deleteTeam(const auth::UserContext &user, long long teamId);
|
||||||
Json::Value updateCategory(const auth::UserContext &user,
|
Json::Value updateCategory(const auth::UserContext &user,
|
||||||
long long categoryId,
|
long long categoryId,
|
||||||
const Json::Value &body);
|
const Json::Value &body);
|
||||||
|
|||||||
Reference in New Issue
Block a user