feat: enhance Admin and report services with CRUD operations and intervention links
This commit is contained in:
+203
-36
@@ -82,24 +82,24 @@ const JsonRowSchema &adminUserSchema()
|
||||
{"must_change_password", JsonFieldType::Bool}};
|
||||
return schema;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Json::Value AdminService::listCategories()
|
||||
bool isActiveField(const Json::Value &body)
|
||||
{
|
||||
auto response = success();
|
||||
response["categories"] = repositories::resultToJson(
|
||||
drogon::app().getDbClient()->execSqlSync(
|
||||
"SELECT * FROM report_categories ORDER BY name"));
|
||||
return response;
|
||||
return body.isMember("active") && body["active"].isBool();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Json::Value AdminService::createCategory(const auth::UserContext &user,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!auth::isAdmin(user) || !validation::hasString(body, "name"))
|
||||
if (!auth::isAdmin(user))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
if (!validation::hasString(body, "name"))
|
||||
{
|
||||
return failure("invalid_category");
|
||||
}
|
||||
|
||||
drogon::app().getDbClient()->execSqlSync(
|
||||
"INSERT INTO report_categories (name, description) VALUES (?, ?)",
|
||||
@@ -108,19 +108,14 @@ Json::Value AdminService::createCategory(const auth::UserContext &user,
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AdminService::listAreas()
|
||||
{
|
||||
auto response = success();
|
||||
response["areas"] = repositories::resultToJson(
|
||||
drogon::app().getDbClient()->execSqlSync("SELECT * FROM areas ORDER BY name"));
|
||||
return response;
|
||||
}
|
||||
|
||||
Json::Value AdminService::createArea(const auth::UserContext &user,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!auth::isAdmin(user) || !validation::hasString(body, "name") ||
|
||||
!body["boundary_geojson"].isObject())
|
||||
if (!auth::isAdmin(user))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
if (!validation::hasString(body, "name") || !body["boundary_geojson"].isObject())
|
||||
{
|
||||
return failure("invalid_area");
|
||||
}
|
||||
@@ -135,29 +130,38 @@ Json::Value AdminService::createArea(const auth::UserContext &user,
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AdminService::listTeams()
|
||||
{
|
||||
auto response = success();
|
||||
response["teams"] = repositories::resultToJson(
|
||||
drogon::app().getDbClient()->execSqlSync(
|
||||
"SELECT t.*, tc.name AS category_name, a.name AS area_name "
|
||||
"FROM teams t JOIN team_categories tc ON tc.id = t.team_category_id "
|
||||
"LEFT JOIN areas a ON a.id = t.area_id ORDER BY t.name"));
|
||||
return response;
|
||||
}
|
||||
|
||||
Json::Value AdminService::createTeam(const auth::UserContext &user,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!auth::isAdmin(user) || !validation::hasString(body, "name") ||
|
||||
if (!auth::isAdmin(user))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
if (!validation::hasString(body, "name") ||
|
||||
!validation::hasNumber(body, "team_category_id"))
|
||||
{
|
||||
return failure("invalid_team");
|
||||
}
|
||||
|
||||
auto db = drogon::app().getDbClient();
|
||||
const auto teamCategoryRows = db->execSqlSync(
|
||||
"SELECT id FROM team_categories WHERE id = ?",
|
||||
body["team_category_id"].asInt64());
|
||||
if (teamCategoryRows.empty())
|
||||
{
|
||||
return failure("invalid_reference");
|
||||
}
|
||||
|
||||
if (body["area_id"].isNumeric())
|
||||
{
|
||||
drogon::app().getDbClient()->execSqlSync(
|
||||
const auto areaRows = db->execSqlSync(
|
||||
"SELECT id FROM areas WHERE id = ?",
|
||||
body["area_id"].asInt64());
|
||||
if (areaRows.empty())
|
||||
{
|
||||
return failure("invalid_reference");
|
||||
}
|
||||
db->execSqlSync(
|
||||
"INSERT INTO teams (team_category_id, area_id, name, description) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
body["team_category_id"].asInt64(),
|
||||
@@ -167,7 +171,7 @@ Json::Value AdminService::createTeam(const auth::UserContext &user,
|
||||
}
|
||||
else
|
||||
{
|
||||
drogon::app().getDbClient()->execSqlSync(
|
||||
db->execSqlSync(
|
||||
"INSERT INTO teams (team_category_id, name, description) VALUES (?, ?, ?)",
|
||||
body["team_category_id"].asInt64(),
|
||||
body["name"].asString(),
|
||||
@@ -198,10 +202,21 @@ Json::Value AdminService::overviewStats(const auth::UserContext &user)
|
||||
"SELECT c.name AS category, COUNT(*) AS total "
|
||||
"FROM reports r JOIN report_categories c ON c.id = r.category_id "
|
||||
"GROUP BY c.name ORDER BY total DESC"));
|
||||
response["reports_by_team"] = repositories::resultToJson(db->execSqlSync(
|
||||
"SELECT t.name AS team, COUNT(*) AS total "
|
||||
response["assignments_by_team"] = repositories::resultToJson(db->execSqlSync(
|
||||
"SELECT t.name AS team, COUNT(DISTINCT ass.id) AS total "
|
||||
"FROM assignments ass JOIN teams t ON t.id = ass.team_id "
|
||||
"GROUP BY t.name ORDER BY total DESC"));
|
||||
response["resolved_reports_by_team"] = repositories::resultToJson(db->execSqlSync(
|
||||
"SELECT t.name AS team, COUNT(DISTINCT ass.report_id) AS total "
|
||||
"FROM assignments ass "
|
||||
"JOIN teams t ON t.id = ass.team_id "
|
||||
"JOIN reports r ON r.id = ass.report_id "
|
||||
"WHERE r.status IN ('RESOLVED', 'CLOSED') "
|
||||
"AND NOT EXISTS ("
|
||||
" SELECT 1 FROM assignments ass2 "
|
||||
" WHERE ass2.report_id = ass.report_id AND ass2.assigned_at > ass.assigned_at"
|
||||
") "
|
||||
"GROUP BY t.name ORDER BY total DESC"));
|
||||
response["monthly_reports"] = repositories::resultToJson(db->execSqlSync(
|
||||
"SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, COUNT(*) AS total "
|
||||
"FROM reports GROUP BY DATE_FORMAT(created_at, '%Y-%m') "
|
||||
@@ -229,7 +244,7 @@ Json::Value AdminService::listModeration(
|
||||
const std::optional<std::string> &page,
|
||||
const std::optional<std::string> &perPage)
|
||||
{
|
||||
if (!auth::canManageReports(user))
|
||||
if (!auth::isAdmin(user))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
@@ -262,7 +277,7 @@ Json::Value AdminService::updateModeration(const auth::UserContext &user,
|
||||
long long flagId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!auth::canManageReports(user))
|
||||
if (!auth::isAdmin(user))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
@@ -324,6 +339,158 @@ Json::Value AdminService::updateModeration(const auth::UserContext &user,
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AdminService::updateCategory(const auth::UserContext &user,
|
||||
long long categoryId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
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");
|
||||
}
|
||||
if (validation::hasString(body, "name"))
|
||||
{
|
||||
db->execSqlSync("UPDATE report_categories SET name = ? WHERE id = ?",
|
||||
body["name"].asString(),
|
||||
categoryId);
|
||||
}
|
||||
if (body.isMember("description") && body["description"].isString())
|
||||
{
|
||||
db->execSqlSync("UPDATE report_categories SET description = ? WHERE id = ?",
|
||||
body["description"].asString(),
|
||||
categoryId);
|
||||
}
|
||||
if (isActiveField(body))
|
||||
{
|
||||
db->execSqlSync("UPDATE report_categories SET active = ? WHERE id = ?",
|
||||
body["active"].asBool(),
|
||||
categoryId);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AdminService::updateArea(const auth::UserContext &user,
|
||||
long long areaId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
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");
|
||||
}
|
||||
if (validation::hasString(body, "name"))
|
||||
{
|
||||
db->execSqlSync("UPDATE areas SET name = ? WHERE id = ?",
|
||||
body["name"].asString(),
|
||||
areaId);
|
||||
}
|
||||
if (body.isMember("description") && body["description"].isString())
|
||||
{
|
||||
db->execSqlSync("UPDATE areas SET description = ? WHERE id = ?",
|
||||
body["description"].asString(),
|
||||
areaId);
|
||||
}
|
||||
if (isActiveField(body))
|
||||
{
|
||||
db->execSqlSync("UPDATE areas SET active = ? WHERE id = ?",
|
||||
body["active"].asBool(),
|
||||
areaId);
|
||||
}
|
||||
if (body["boundary_geojson"].isObject())
|
||||
{
|
||||
Json::StreamWriterBuilder writer;
|
||||
db->execSqlSync(
|
||||
"UPDATE areas SET boundary_geojson = CAST(? AS JSON) WHERE id = ?",
|
||||
Json::writeString(writer, body["boundary_geojson"]),
|
||||
areaId);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AdminService::updateTeam(const auth::UserContext &user,
|
||||
long long teamId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
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");
|
||||
}
|
||||
if (validation::hasString(body, "name"))
|
||||
{
|
||||
db->execSqlSync("UPDATE teams SET name = ? WHERE id = ?",
|
||||
body["name"].asString(),
|
||||
teamId);
|
||||
}
|
||||
if (body.isMember("description") && body["description"].isString())
|
||||
{
|
||||
db->execSqlSync("UPDATE teams SET description = ? WHERE id = ?",
|
||||
body["description"].asString(),
|
||||
teamId);
|
||||
}
|
||||
if (validation::hasNumber(body, "team_category_id"))
|
||||
{
|
||||
const auto categoryRows = db->execSqlSync(
|
||||
"SELECT id FROM team_categories WHERE id = ?",
|
||||
body["team_category_id"].asInt64());
|
||||
if (categoryRows.empty())
|
||||
{
|
||||
return failure("invalid_reference");
|
||||
}
|
||||
db->execSqlSync("UPDATE teams SET team_category_id = ? WHERE id = ?",
|
||||
body["team_category_id"].asInt64(),
|
||||
teamId);
|
||||
}
|
||||
if (body.isMember("area_id"))
|
||||
{
|
||||
if (body["area_id"].isNull())
|
||||
{
|
||||
db->execSqlSync("UPDATE teams SET area_id = NULL WHERE id = ?", teamId);
|
||||
}
|
||||
else if (body["area_id"].isNumeric())
|
||||
{
|
||||
const auto areaRows =
|
||||
db->execSqlSync("SELECT id FROM areas WHERE id = ?",
|
||||
body["area_id"].asInt64());
|
||||
if (areaRows.empty())
|
||||
{
|
||||
return failure("invalid_reference");
|
||||
}
|
||||
db->execSqlSync("UPDATE teams SET area_id = ? WHERE id = ?",
|
||||
body["area_id"].asInt64(),
|
||||
teamId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return failure("invalid_reference");
|
||||
}
|
||||
}
|
||||
if (isActiveField(body))
|
||||
{
|
||||
db->execSqlSync("UPDATE teams SET active = ? WHERE id = ?",
|
||||
body["active"].asBool(),
|
||||
teamId);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
Json::Value AdminService::listUsers(const auth::UserContext &user)
|
||||
{
|
||||
if (!auth::isAdmin(user))
|
||||
|
||||
Reference in New Issue
Block a user