feat: enhance error handling and permissions in Admin and Report services
This commit is contained in:
@@ -6,6 +6,12 @@
|
||||
- User-facing product labels may be Italian when they represent UI/business vocabulary.
|
||||
- Docker is for local external services only: MySQL, Mailpit and future Redis-like dependencies.
|
||||
|
||||
ENVIRONMENT:
|
||||
You are running in a docker container, so it's normal if something doesn't work as expected.
|
||||
You are allowed to install new tools, libraries or dependencies if you think they are necessary to complete your task.
|
||||
Since you are running in a docker container, you could have some build issues sometimes, do not waste tokens over-trying to fix them, just report the issue and move on to the next task if you believe the issue is not related to your task.
|
||||
Prefer using commands to read files, search for patterns ecc... since I have installed a tool that helps reduce token usage for those operations.
|
||||
|
||||
## Authoritative References
|
||||
- Local Drogon source: `/home/gabdevele/Projects/snitchitaly/backend/drogon`
|
||||
- Official Drogon docs: `https://drogonframework.github.io/drogon-docs/`
|
||||
|
||||
@@ -56,8 +56,19 @@ void sendAdmin(Json::Value body,
|
||||
}
|
||||
|
||||
const auto error = body.get("error", "").asString();
|
||||
const auto status = error == "forbidden" ? drogon::k403Forbidden
|
||||
: drogon::k400BadRequest;
|
||||
drogon::HttpStatusCode status = drogon::k400BadRequest;
|
||||
if (error == "forbidden" || error.rfind("cannot_", 0) == 0)
|
||||
{
|
||||
status = drogon::k403Forbidden;
|
||||
}
|
||||
else if (error.ends_with("_not_found"))
|
||||
{
|
||||
status = drogon::k404NotFound;
|
||||
}
|
||||
else if (error == "invalid_status_transition")
|
||||
{
|
||||
status = drogon::k409Conflict;
|
||||
}
|
||||
callback(visione::http::json(std::move(body), status));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -58,8 +58,20 @@ void sendService(Json::Value body,
|
||||
return;
|
||||
}
|
||||
const auto error = body.get("error", "").asString();
|
||||
const auto status = error == "forbidden" ? drogon::k403Forbidden
|
||||
: drogon::k400BadRequest;
|
||||
drogon::HttpStatusCode status = drogon::k400BadRequest;
|
||||
if (error == "forbidden" || error.rfind("cannot_", 0) == 0)
|
||||
{
|
||||
status = drogon::k403Forbidden;
|
||||
}
|
||||
else if (error.ends_with("_not_found"))
|
||||
{
|
||||
status = drogon::k404NotFound;
|
||||
}
|
||||
else if (error == "invalid_status_transition" ||
|
||||
error == "media_limit_reached")
|
||||
{
|
||||
status = drogon::k409Conflict;
|
||||
}
|
||||
callback(visione::http::json(std::move(body), status));
|
||||
}
|
||||
|
||||
|
||||
@@ -364,7 +364,8 @@ Json::Value AdminService::updateUserRoles(const auth::UserContext &user,
|
||||
return failure("user_not_found");
|
||||
}
|
||||
|
||||
db->execSqlSync("DELETE FROM user_roles WHERE user_id = ?", targetUserId);
|
||||
std::vector<long long> roleIds;
|
||||
roleIds.reserve(body["roles"].size());
|
||||
for (const auto &role : body["roles"])
|
||||
{
|
||||
if (!role.isString())
|
||||
@@ -378,10 +379,15 @@ Json::Value AdminService::updateUserRoles(const auth::UserContext &user,
|
||||
{
|
||||
return failure("role_not_found");
|
||||
}
|
||||
db->execSqlSync(
|
||||
"INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)",
|
||||
roleIds.push_back(roleRows[0]["id"].as<long long>());
|
||||
}
|
||||
|
||||
db->execSqlSync("DELETE FROM user_roles WHERE user_id = ?", targetUserId);
|
||||
for (const auto roleId : roleIds)
|
||||
{
|
||||
db->execSqlSync("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)",
|
||||
targetUserId,
|
||||
roleRows[0]["id"].as<long long>());
|
||||
roleId);
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -123,6 +123,11 @@ bool canInteractWithReport(const auth::UserContext &user, long long reportId)
|
||||
return ownsReport(user, reportId) || canModifyReport(user, reportId);
|
||||
}
|
||||
|
||||
bool canCreateCitizenReport(const auth::UserContext &user)
|
||||
{
|
||||
return !auth::canManageReports(user);
|
||||
}
|
||||
|
||||
int boundedPage(const std::optional<std::string> &value)
|
||||
{
|
||||
if (!value)
|
||||
@@ -257,6 +262,11 @@ void notifyReportOwner(long long reportId,
|
||||
Json::Value ReportService::createReport(const auth::UserContext &user,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!canCreateCitizenReport(user))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
|
||||
const auto missing =
|
||||
validation::missingStrings(body, {"address", "description"});
|
||||
if (!missing.empty() || !validation::hasNumber(body, "category_id") ||
|
||||
@@ -318,8 +328,13 @@ Json::Value ReportService::createReport(const auth::UserContext &user,
|
||||
|
||||
if (body["media"].isArray())
|
||||
{
|
||||
size_t mediaCount = 0;
|
||||
for (const auto &item : body["media"])
|
||||
{
|
||||
if (mediaCount >= 5)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (item.isString() && !item.asString().empty())
|
||||
{
|
||||
db->execSqlSync(
|
||||
@@ -327,6 +342,7 @@ Json::Value ReportService::createReport(const auth::UserContext &user,
|
||||
"VALUES (?, ?, 'IMAGE')",
|
||||
reportId,
|
||||
item.asString());
|
||||
++mediaCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,6 +729,13 @@ Json::Value ReportService::assignTeam(const auth::UserContext &user,
|
||||
{
|
||||
return failure("invalid_status_transition");
|
||||
}
|
||||
const auto team = db->execSqlSync(
|
||||
"SELECT id FROM teams WHERE id = ? AND active = TRUE",
|
||||
body["team_id"].asInt64());
|
||||
if (team.empty())
|
||||
{
|
||||
return failure("team_not_found");
|
||||
}
|
||||
|
||||
db->execSqlSync(
|
||||
"INSERT INTO assignments (report_id, team_id, assigned_by_user_id) "
|
||||
@@ -786,11 +809,25 @@ Json::Value ReportService::flagReport(const auth::UserContext &user,
|
||||
long long reportId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!canAccessReport(user, reportId) ||
|
||||
!validation::hasString(body, "reason"))
|
||||
if (!validation::hasString(body, "reason"))
|
||||
{
|
||||
return failure("invalid_flag");
|
||||
}
|
||||
|
||||
const auto owner = reportOwner(reportId);
|
||||
if (!owner)
|
||||
{
|
||||
return failure("report_not_found");
|
||||
}
|
||||
if (*owner == user.id)
|
||||
{
|
||||
return failure("cannot_flag_own_report");
|
||||
}
|
||||
if (!canAccessReport(user, reportId))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
|
||||
auto db = drogon::app().getDbClient();
|
||||
db->execSqlSync(
|
||||
"INSERT INTO moderation_flags "
|
||||
@@ -812,20 +849,27 @@ Json::Value ReportService::flagMessage(const auth::UserContext &user,
|
||||
long long messageId,
|
||||
const Json::Value &body)
|
||||
{
|
||||
if (!canInteractWithReport(user, reportId) ||
|
||||
!validation::hasString(body, "reason"))
|
||||
if (!validation::hasString(body, "reason"))
|
||||
{
|
||||
return failure("invalid_flag");
|
||||
}
|
||||
if (!canInteractWithReport(user, reportId))
|
||||
{
|
||||
return failure("forbidden");
|
||||
}
|
||||
auto db = drogon::app().getDbClient();
|
||||
const auto message = db->execSqlSync(
|
||||
"SELECT id FROM messages WHERE id = ? AND report_id = ?",
|
||||
"SELECT id, user_id FROM messages WHERE id = ? AND report_id = ?",
|
||||
messageId,
|
||||
reportId);
|
||||
if (message.empty())
|
||||
{
|
||||
return failure("message_not_found");
|
||||
}
|
||||
if (message[0]["user_id"].as<long long>() == user.id)
|
||||
{
|
||||
return failure("cannot_flag_own_message");
|
||||
}
|
||||
db->execSqlSync(
|
||||
"INSERT INTO moderation_flags "
|
||||
"(content_type, report_id, message_id, reason, details, created_by_user_id) "
|
||||
|
||||
Reference in New Issue
Block a user