#include "services/AdminService.h" #include "common/Validation.h" #include "repositories/Sql.h" #include namespace visione::services { namespace { Json::Value failure(const std::string &code) { Json::Value body; body["ok"] = false; body["error"] = code; return body; } Json::Value success() { Json::Value body; body["ok"] = true; return body; } } // namespace Json::Value AdminService::listCategories() { auto response = success(); response["categories"] = repositories::resultToJson( drogon::app().getDbClient()->execSqlSync( "SELECT * FROM report_categories ORDER BY name")); return response; } Json::Value AdminService::createCategory(const auth::UserContext &user, const Json::Value &body) { if (!auth::isAdmin(user) || !validation::hasString(body, "name")) { return failure("forbidden"); } drogon::app().getDbClient()->execSqlSync( "INSERT INTO report_categories (name, description) VALUES (?, ?)", body["name"].asString(), body.get("description", "").asString()); 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()) { return failure("invalid_area"); } Json::StreamWriterBuilder writer; drogon::app().getDbClient()->execSqlSync( "INSERT INTO areas (name, description, boundary_geojson) " "VALUES (?, ?, CAST(? AS JSON))", body["name"].asString(), body.get("description", "").asString(), Json::writeString(writer, body["boundary_geojson"])); 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") || !validation::hasNumber(body, "team_category_id")) { return failure("invalid_team"); } if (body["area_id"].isNumeric()) { drogon::app().getDbClient()->execSqlSync( "INSERT INTO teams (team_category_id, area_id, name, description) " "VALUES (?, ?, ?, ?)", body["team_category_id"].asInt64(), body["area_id"].asInt64(), body["name"].asString(), body.get("description", "").asString()); } else { drogon::app().getDbClient()->execSqlSync( "INSERT INTO teams (team_category_id, name, description) VALUES (?, ?, ?)", body["team_category_id"].asInt64(), body["name"].asString(), body.get("description", "").asString()); } return success(); } Json::Value AdminService::overviewStats(const auth::UserContext &user) { if (!auth::canManageReports(user)) { return failure("forbidden"); } auto db = drogon::app().getDbClient(); auto response = success(); response["reports_by_status"] = repositories::resultToJson(db->execSqlSync( "SELECT status, COUNT(*) AS total FROM reports GROUP BY status")); response["reports_by_area"] = repositories::resultToJson(db->execSqlSync( "SELECT COALESCE(a.name, 'Unassigned') AS area, COUNT(*) AS total " "FROM reports r LEFT JOIN areas a ON a.id = r.area_id " "GROUP BY COALESCE(a.name, 'Unassigned')")); response["avg_resolution_hours"] = repositories::resultToJson(db->execSqlSync( "SELECT AVG(TIMESTAMPDIFF(HOUR, created_at, updated_at)) AS value " "FROM reports WHERE status IN ('RESOLVED', 'CLOSED')")); return response; } } // namespace visione::services