[Server] Serve report mutations through the database interface (#7846)

cmdReportAssign, cmdReportResolve, cmdReportReopen and cmdReportAddComment still
ran their lookups and updates inline through Servatrice_DatabaseInterface. Add
assignReport, resolveReport, reopenReport, getUserNameById,
getReportNotificationTarget, countReportCommentsInLastHour,
getReportCommentTarget and addReportComment to Server_DatabaseInterface with
fail-closed defaults, and model the outcomes as ReportAssignResult and
ReportUpdateResult so "no such report", "wrong state" and "query failed" stay
distinguishable. Authorization checks, audit records, notification recipients
and every response code stay in the handler.
This commit is contained in:
BruebachL 2026-11-10 12:16:28 +01:00 • committed by GitHub
parent a54e13756c
commit 948035af06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 339 additions and 148 deletions

View file

@ -4041,6 +4041,203 @@ bool Servatrice_DatabaseInterface::getReportUserInfo(const QString &userName, Re
return true;
}
Server_DatabaseInterface::ReportAssignResult Servatrice_DatabaseInterface::assignReport(int reportId, int moderatorId)
{
if (!checkSql()) {
return ReportAssignResult::Failed;
}
QSqlQuery *lookupQuery = prepareQuery("SELECT status FROM {prefix}_reports WHERE id = :id");
lookupQuery->bindValue(":id", reportId);
if (!execSqlQuery(lookupQuery)) {
return ReportAssignResult::Failed;
}
if (!lookupQuery->next()) {
return ReportAssignResult::NotFound;
}
if (lookupQuery->value(0).toString() != "open") {
return ReportAssignResult::NotOpen;
}
QSqlQuery *query = prepareQuery("UPDATE {prefix}_reports "
"SET status = 'assigned', assigned_to = :mod_id "
"WHERE id = :id AND status = 'open'");
query->bindValue(":mod_id", moderatorId);
query->bindValue(":id", reportId);
if (!execSqlQuery(query)) {
return ReportAssignResult::Failed;
}
if (query->numRowsAffected() == 0) {
// The report was taken by another moderator between the lookup and this update.
return ReportAssignResult::NotOpen;
}
return ReportAssignResult::Ok;
}
Server_DatabaseInterface::ReportUpdateResult
Servatrice_DatabaseInterface::resolveReport(int reportId, const QString &status, const QString &note, int moderatorId)
{
if (!checkSql()) {
return ReportUpdateResult::Failed;
}
QSqlQuery *query = prepareQuery("UPDATE {prefix}_reports "
"SET status = :status, resolution_note = :note, "
"resolution_time = NOW(), resolved_by = :mod_id "
"WHERE id = :id AND status IN ('open', 'assigned')");
query->bindValue(":status", status);
query->bindValue(":note", note);
query->bindValue(":mod_id", moderatorId);
query->bindValue(":id", reportId);
if (!execSqlQuery(query)) {
return ReportUpdateResult::Failed;
}
if (query->numRowsAffected() == 0) {
return ReportUpdateResult::InvalidState;
}
return ReportUpdateResult::Ok;
}
Server_DatabaseInterface::ReportUpdateResult Servatrice_DatabaseInterface::reopenReport(int reportId)
{
if (!checkSql()) {
return ReportUpdateResult::Failed;
}
QSqlQuery *query = prepareQuery("UPDATE {prefix}_reports "
"SET status = 'open', resolution_note = NULL, "
"resolution_time = NULL, resolved_by = NULL, assigned_to = NULL, "
"notified = 0 "
"WHERE id = :id AND status IN ('resolved', 'dismissed')");
query->bindValue(":id", reportId);
if (!execSqlQuery(query)) {
return ReportUpdateResult::Failed;
}
if (query->numRowsAffected() == 0) {
return ReportUpdateResult::InvalidState;
}
return ReportUpdateResult::Ok;
}
bool Servatrice_DatabaseInterface::getUserNameById(int userId, QString &userName)
{
if (!checkSql()) {
return false;
}
QSqlQuery *query = prepareQuery("SELECT name FROM {prefix}_users WHERE id = :id");
query->bindValue(":id", userId);
if (!execSqlQuery(query) || !query->next()) {
return false;
}
userName = query->value(0).toString();
return true;
}
bool Servatrice_DatabaseInterface::getReportNotificationTarget(int reportId, int &reporterId, QString &reportedUserName)
{
if (!checkSql()) {
return false;
}
QSqlQuery *query = prepareQuery("SELECT reporter_id, reported_user_name FROM {prefix}_reports WHERE id = :id");
query->bindValue(":id", reportId);
if (!execSqlQuery(query) || !query->next()) {
return false;
}
reporterId = query->value(0).toInt();
reportedUserName = query->value(1).toString();
return true;
}
int Servatrice_DatabaseInterface::countReportCommentsInLastHour(int authorId)
{
if (!checkSql()) {
return 0;
}
QSqlQuery *query = prepareQuery("SELECT COUNT(*) FROM {prefix}_report_comments WHERE author_id = :id "
"AND created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR)");
query->bindValue(":id", authorId);
if (!execSqlQuery(query) || !query->next()) {
return 0;
}
return query->value(0).toInt();
}
bool Servatrice_DatabaseInterface::getReportCommentTarget(int reportId, ReportCommentTargetRecord &target)
{
if (!checkSql()) {
return false;
}
QSqlQuery *query =
prepareQuery("SELECT reporter_id, reporter_name, assigned_to, status FROM {prefix}_reports WHERE id = :id");
query->bindValue(":id", reportId);
if (!execSqlQuery(query)) {
return false;
}
if (!query->next()) {
return true;
}
target.found = true;
target.reporterId = query->value(0).toInt();
target.reporterName = query->value(1).toString();
target.assignedToId = query->value(2).toInt();
target.status = query->value(3).toString();
return true;
}
bool Servatrice_DatabaseInterface::addReportComment(int reportId,
const QString &authorName,
int authorId,
const QString &commentText,
bool isModerator,
qlonglong &commentId)
{
if (!checkSql()) {
return false;
}
QSqlQuery *query =
prepareQuery("INSERT INTO {prefix}_report_comments (report_id, author_name, author_id, "
"comment_text, created_at, is_moderator) "
"VALUES (:report_id, :author_name, :author_id, :comment_text, NOW(), :is_moderator)");
query->bindValue(":report_id", reportId);
query->bindValue(":author_name", authorName);
query->bindValue(":author_id", authorId);
query->bindValue(":comment_text", commentText);
query->bindValue(":is_moderator", isModerator);
if (!execSqlQuery(query)) {
return false;
}
commentId = query->lastInsertId().toLongLong();
return true;
}
int Servatrice_DatabaseInterface::countReportsSubmittedInLastDay(int reporterId)
{
if (!checkSql()) {