feat: add canOperateReports and canFlagReportContent functions for report management
This commit is contained in:
@@ -113,6 +113,11 @@ bool canManageReports(const UserContext &user)
|
|||||||
return isAdmin(user) || hasPermission(user, "reports.manage");
|
return isAdmin(user) || hasPermission(user, "reports.manage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canOperateReports(const UserContext &user)
|
||||||
|
{
|
||||||
|
return !isAdmin(user) && canManageReports(user);
|
||||||
|
}
|
||||||
|
|
||||||
bool isAdmin(const UserContext &user)
|
bool isAdmin(const UserContext &user)
|
||||||
{
|
{
|
||||||
return hasRole(user, "admin") || hasPermission(user, "admin.manage");
|
return hasRole(user, "admin") || hasPermission(user, "admin.manage");
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ std::optional<UserContext> authenticate(const drogon::HttpRequestPtr &request);
|
|||||||
bool hasRole(const UserContext &user, const std::string &role);
|
bool hasRole(const UserContext &user, const std::string &role);
|
||||||
bool hasPermission(const UserContext &user, const std::string &permission);
|
bool hasPermission(const UserContext &user, const std::string &permission);
|
||||||
bool canManageReports(const UserContext &user);
|
bool canManageReports(const UserContext &user);
|
||||||
|
bool canOperateReports(const UserContext &user);
|
||||||
bool isAdmin(const UserContext &user);
|
bool isAdmin(const UserContext &user);
|
||||||
bool mustChangePassword(const UserContext &user);
|
bool mustChangePassword(const UserContext &user);
|
||||||
} // namespace visione::auth
|
} // namespace visione::auth
|
||||||
|
|||||||
+4
-3
@@ -32,13 +32,14 @@ Authorization: Bearer <token>
|
|||||||
- `GET /reports/{id}/intervention-summary`: operator-facing report summary with
|
- `GET /reports/{id}/intervention-summary`: operator-facing report summary with
|
||||||
citizen contact data, media and latest messages.
|
citizen contact data, media and latest messages.
|
||||||
- `PATCH /reports/{id}/status`: update status.
|
- `PATCH /reports/{id}/status`: update status.
|
||||||
- `POST /reports/{id}/take`: operator/admin takes an open report.
|
- `POST /reports/{id}/take`: assigned-queue operator takes an open report.
|
||||||
- `POST /reports/{id}/messages`: write a report message.
|
- `POST /reports/{id}/messages`: write a report message.
|
||||||
- `GET /reports/{id}/messages`: list report messages.
|
- `GET /reports/{id}/messages`: list report messages.
|
||||||
- `POST /reports/{id}/messages/{message_id}/flags`: flag a message for moderation.
|
- `POST /reports/{id}/messages/{message_id}/flags`: assigned operator flags a
|
||||||
|
report message for moderation.
|
||||||
- `POST /reports/{id}/assignments`: assign a team.
|
- `POST /reports/{id}/assignments`: assign a team.
|
||||||
- `POST /reports/{id}/feedback`: add citizen feedback.
|
- `POST /reports/{id}/feedback`: add citizen feedback.
|
||||||
- `POST /reports/{id}/flags`: flag a report for moderation.
|
- `POST /reports/{id}/flags`: assigned operator flags a report for moderation.
|
||||||
- `POST /reports/{id}/media`: upload up to five multipart media files. Supported
|
- `POST /reports/{id}/media`: upload up to five multipart media files. Supported
|
||||||
types are JPEG, PNG, WebP, MP4, WebM and QuickTime.
|
types are JPEG, PNG, WebP, MP4, WebM and QuickTime.
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ assign and resolve interventions while preserving history and basic analytics.
|
|||||||
- `citizen`: registers, requests OTP login, creates reports, follows own reports,
|
- `citizen`: registers, requests OTP login, creates reports, follows own reports,
|
||||||
sends messages and leaves feedback after resolution.
|
sends messages and leaves feedback after resolution.
|
||||||
- `operator`: views reports, takes open reports, assigns teams, updates managed
|
- `operator`: views reports, takes open reports, assigns teams, updates managed
|
||||||
reports and messages citizens.
|
reports, messages citizens and flags abusive/fake citizen content on managed
|
||||||
|
reports.
|
||||||
- `admin`: manages users, roles, reference data, teams, areas and all reports.
|
- `admin`: manages users, roles, reference data, teams, areas and all reports.
|
||||||
|
|
||||||
The system admin seed must change password before using protected operational
|
The system admin seed must change password before using protected operational
|
||||||
@@ -25,7 +26,7 @@ or administrative APIs.
|
|||||||
|
|
||||||
Valid milestone 2 transitions:
|
Valid milestone 2 transitions:
|
||||||
|
|
||||||
- `OPEN -> UNDER_REVIEW` when an operator/admin takes the report.
|
- `OPEN -> UNDER_REVIEW` when an operator takes the report.
|
||||||
- `UNDER_REVIEW -> ASSIGNED` when a team is assigned.
|
- `UNDER_REVIEW -> ASSIGNED` when a team is assigned.
|
||||||
- `ASSIGNED -> IN_PROGRESS`.
|
- `ASSIGNED -> IN_PROGRESS`.
|
||||||
- `IN_PROGRESS -> RESOLVED`.
|
- `IN_PROGRESS -> RESOLVED`.
|
||||||
|
|||||||
@@ -3056,7 +3056,7 @@
|
|||||||
},
|
},
|
||||||
"/api/v1/reports/{id}/flags": {
|
"/api/v1/reports/{id}/flags": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Creates a moderation flag for a report.",
|
"description": "Creates a moderation flag for a report by assigned operator only.",
|
||||||
"operationId": "ReportController.flagReport",
|
"operationId": "ReportController.flagReport",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -3313,7 +3313,7 @@
|
|||||||
},
|
},
|
||||||
"/api/v1/reports/{id}/messages/{message_id}/flags": {
|
"/api/v1/reports/{id}/messages/{message_id}/flags": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Creates a moderation flag for a report message.",
|
"description": "Creates a moderation flag for a report message by assigned operator only.",
|
||||||
"operationId": "ReportController.flagMessage",
|
"operationId": "ReportController.flagMessage",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -3436,7 +3436,7 @@
|
|||||||
},
|
},
|
||||||
"/api/v1/reports/{id}/take": {
|
"/api/v1/reports/{id}/take": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Assigns an open report to the current operator.",
|
"description": "Assigns an open report to current non-admin operator.",
|
||||||
"operationId": "ReportController.takeReport",
|
"operationId": "ReportController.takeReport",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <drogon/orm/Field.h>
|
||||||
#include <drogon/orm/Result.h>
|
#include <drogon/orm/Result.h>
|
||||||
|
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
|||||||
+21
-12
@@ -109,6 +109,20 @@ bool canModifyReport(const auth::UserContext &user, long long reportId)
|
|||||||
return !result.empty();
|
return !result.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canFlagReportContent(const auth::UserContext &user, long long reportId)
|
||||||
|
{
|
||||||
|
if (!auth::canOperateReports(user))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = drogon::app().getDbClient()->execSqlSync(
|
||||||
|
"SELECT id FROM reports WHERE id = ? AND assigned_operator_id = ?",
|
||||||
|
reportId,
|
||||||
|
user.id);
|
||||||
|
return !result.empty();
|
||||||
|
}
|
||||||
|
|
||||||
bool ownsReport(const auth::UserContext &user, long long reportId)
|
bool ownsReport(const auth::UserContext &user, long long reportId)
|
||||||
{
|
{
|
||||||
const auto result = drogon::app().getDbClient()->execSqlSync(
|
const auto result = drogon::app().getDbClient()->execSqlSync(
|
||||||
@@ -616,7 +630,7 @@ Json::Value ReportService::updateStatus(const auth::UserContext &user,
|
|||||||
Json::Value ReportService::takeReport(const auth::UserContext &user,
|
Json::Value ReportService::takeReport(const auth::UserContext &user,
|
||||||
long long reportId)
|
long long reportId)
|
||||||
{
|
{
|
||||||
if (!auth::canManageReports(user))
|
if (!auth::canOperateReports(user))
|
||||||
{
|
{
|
||||||
return failure("forbidden");
|
return failure("forbidden");
|
||||||
}
|
}
|
||||||
@@ -737,6 +751,10 @@ Json::Value ReportService::assignTeam(const auth::UserContext &user,
|
|||||||
return failure("team_not_found");
|
return failure("team_not_found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db->execSqlSync(
|
||||||
|
"UPDATE assignments SET ended_at = NOW() "
|
||||||
|
"WHERE report_id = ? AND ended_at IS NULL",
|
||||||
|
reportId);
|
||||||
db->execSqlSync(
|
db->execSqlSync(
|
||||||
"INSERT INTO assignments (report_id, team_id, assigned_by_user_id) "
|
"INSERT INTO assignments (report_id, team_id, assigned_by_user_id) "
|
||||||
"VALUES (?, ?, ?)",
|
"VALUES (?, ?, ?)",
|
||||||
@@ -814,16 +832,7 @@ Json::Value ReportService::flagReport(const auth::UserContext &user,
|
|||||||
return failure("invalid_flag");
|
return failure("invalid_flag");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto owner = reportOwner(reportId);
|
if (!canFlagReportContent(user, 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");
|
return failure("forbidden");
|
||||||
}
|
}
|
||||||
@@ -853,7 +862,7 @@ Json::Value ReportService::flagMessage(const auth::UserContext &user,
|
|||||||
{
|
{
|
||||||
return failure("invalid_flag");
|
return failure("invalid_flag");
|
||||||
}
|
}
|
||||||
if (!canInteractWithReport(user, reportId))
|
if (!canFlagReportContent(user, reportId))
|
||||||
{
|
{
|
||||||
return failure("forbidden");
|
return failure("forbidden");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user