mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 03:53:26 -08:00
[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:
parent
a54e13756c
commit
948035af06
4 changed files with 339 additions and 148 deletions
|
|
@ -484,6 +484,72 @@ public:
|
|||
{
|
||||
return false;
|
||||
}
|
||||
/** @brief Outcome of a report state change. */
|
||||
enum class ReportUpdateResult
|
||||
{
|
||||
Ok,
|
||||
InvalidState,
|
||||
Failed
|
||||
};
|
||||
/** @brief Outcome of claiming an open report for a moderator. */
|
||||
enum class ReportAssignResult
|
||||
{
|
||||
Ok,
|
||||
NotFound,
|
||||
NotOpen,
|
||||
Failed
|
||||
};
|
||||
virtual ReportAssignResult assignReport(int /* reportId */, int /* moderatorId */)
|
||||
{
|
||||
return ReportAssignResult::Failed;
|
||||
}
|
||||
virtual ReportUpdateResult
|
||||
resolveReport(int /* reportId */, const QString & /* status */, const QString & /* note */, int /* moderatorId */)
|
||||
{
|
||||
return ReportUpdateResult::Failed;
|
||||
}
|
||||
virtual ReportUpdateResult reopenReport(int /* reportId */)
|
||||
{
|
||||
return ReportUpdateResult::Failed;
|
||||
}
|
||||
/** @brief The user name behind a database user id; false when the lookup fails or finds no row. */
|
||||
virtual bool getUserNameById(int /* userId */, QString & /* userName */)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/** @brief The reporter and reported user of a report, for the resolve notification. */
|
||||
virtual bool
|
||||
getReportNotificationTarget(int /* reportId */, int & /* reporterId */, QString & /* reportedUserName */)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/** @brief Comments the author wrote in the last hour, for the hourly cap. */
|
||||
virtual int countReportCommentsInLastHour(int /* authorId */)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
/** @brief One report's reporter, assignee and status, for the comment checks. */
|
||||
struct ReportCommentTargetRecord
|
||||
{
|
||||
bool found = false;
|
||||
int reporterId = 0;
|
||||
QString reporterName;
|
||||
int assignedToId = 0;
|
||||
QString status;
|
||||
};
|
||||
virtual bool getReportCommentTarget(int /* reportId */, ReportCommentTargetRecord & /* target */)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool addReportComment(int /* reportId */,
|
||||
const QString & /* authorName */,
|
||||
int /* authorId */,
|
||||
const QString & /* commentText */,
|
||||
bool /* isModerator */,
|
||||
qlonglong & /* commentId */)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/** @brief One resolved or dismissed report whose reporter has not been told yet. */
|
||||
struct PendingReportNotification
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 ¬e, 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()) {
|
||||
|
|
|
|||
|
|
@ -303,6 +303,20 @@ public:
|
|||
bool getReportComments(int reportId, QList<ServerInfo_ReportComment> &comments) override;
|
||||
bool getReportStats(ReportStatsRecord &stats) override;
|
||||
bool getReportUserInfo(const QString &userName, ReportUserInfoRecord &info) override;
|
||||
Server_DatabaseInterface::ReportAssignResult assignReport(int reportId, int moderatorId) override;
|
||||
Server_DatabaseInterface::ReportUpdateResult
|
||||
resolveReport(int reportId, const QString &status, const QString ¬e, int moderatorId) override;
|
||||
Server_DatabaseInterface::ReportUpdateResult reopenReport(int reportId) override;
|
||||
bool getUserNameById(int userId, QString &userName) override;
|
||||
bool getReportNotificationTarget(int reportId, int &reporterId, QString &reportedUserName) override;
|
||||
int countReportCommentsInLastHour(int authorId) override;
|
||||
bool getReportCommentTarget(int reportId, ReportCommentTargetRecord &target) override;
|
||||
bool addReportComment(int reportId,
|
||||
const QString &authorName,
|
||||
int authorId,
|
||||
const QString &commentText,
|
||||
bool isModerator,
|
||||
qlonglong &commentId) override;
|
||||
bool addReport(int reporterId,
|
||||
const QString &reporterName,
|
||||
int reportedUserId,
|
||||
|
|
|
|||
|
|
@ -1849,42 +1849,22 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman
|
|||
Response::ResponseCode AbstractServerSocketInterface::cmdReportAssign(const Command_ReportAssign &cmd,
|
||||
ResponseContainer & /*rc*/)
|
||||
{
|
||||
if (!sqlInterface->checkSql()) {
|
||||
if (!databaseInterface->checkSql()) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
int reportId = cmd.report_id();
|
||||
int modId = userInfo->id();
|
||||
const int reportId = cmd.report_id();
|
||||
const int modId = userInfo->id();
|
||||
|
||||
QSqlQuery *lookupQuery = sqlInterface->prepareQuery("SELECT status FROM {prefix}_reports WHERE id = :id");
|
||||
lookupQuery->bindValue(":id", reportId);
|
||||
|
||||
if (!sqlInterface->execSqlQuery(lookupQuery)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
if (!lookupQuery->next()) {
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
|
||||
if (lookupQuery->value(0).toString() != "open") {
|
||||
return Response::RespInvalidData;
|
||||
}
|
||||
|
||||
QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports "
|
||||
"SET status = 'assigned', assigned_to = :mod_id "
|
||||
"WHERE id = :id AND status = 'open'");
|
||||
|
||||
query->bindValue(":mod_id", modId);
|
||||
query->bindValue(":id", reportId);
|
||||
|
||||
if (!sqlInterface->execSqlQuery(query)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
if (query->numRowsAffected() == 0) {
|
||||
// The report was taken by another moderator between the lookup and this update.
|
||||
return Response::RespInvalidData;
|
||||
switch (databaseInterface->assignReport(reportId, modId)) {
|
||||
case Server_DatabaseInterface::ReportAssignResult::Ok:
|
||||
break;
|
||||
case Server_DatabaseInterface::ReportAssignResult::NotFound:
|
||||
return Response::RespNameNotFound;
|
||||
case Server_DatabaseInterface::ReportAssignResult::NotOpen:
|
||||
return Response::RespInvalidData;
|
||||
case Server_DatabaseInterface::ReportAssignResult::Failed:
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
return Response::RespOk;
|
||||
|
|
@ -1893,32 +1873,23 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAssign(const Comm
|
|||
Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Command_ReportResolve &cmd,
|
||||
ResponseContainer & /*rc*/)
|
||||
{
|
||||
if (!sqlInterface->checkSql()) {
|
||||
if (!databaseInterface->checkSql()) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
int reportId = cmd.report_id();
|
||||
QString note = textFromStdString(cmd.resolution_note());
|
||||
bool dismissed = cmd.dismissed();
|
||||
const int reportId = cmd.report_id();
|
||||
const QString note = textFromStdString(cmd.resolution_note());
|
||||
const bool dismissed = cmd.dismissed();
|
||||
|
||||
QString newStatus = dismissed ? "dismissed" : "resolved";
|
||||
const QString newStatus = dismissed ? "dismissed" : "resolved";
|
||||
|
||||
QSqlQuery *query = sqlInterface->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", newStatus);
|
||||
query->bindValue(":note", note);
|
||||
query->bindValue(":mod_id", userInfo->id());
|
||||
query->bindValue(":id", reportId);
|
||||
|
||||
if (!sqlInterface->execSqlQuery(query)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
if (query->numRowsAffected() == 0) {
|
||||
return Response::RespInvalidData;
|
||||
switch (databaseInterface->resolveReport(reportId, newStatus, note, userInfo->id())) {
|
||||
case Server_DatabaseInterface::ReportUpdateResult::Ok:
|
||||
break;
|
||||
case Server_DatabaseInterface::ReportUpdateResult::InvalidState:
|
||||
return Response::RespInvalidData;
|
||||
case Server_DatabaseInterface::ReportUpdateResult::Failed:
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
databaseInterface->addAuditRecord(QString::number(reportId), this->getAddress(),
|
||||
|
|
@ -1929,21 +1900,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Com
|
|||
// Notifying the reporter is best-effort: the resolve already succeeded. If any of the lookup
|
||||
// queries below fail, the report is simply left with notified = 0 and the notification is
|
||||
// delivered later via sendPendingReportNotifications.
|
||||
QSqlQuery *lookupQuery =
|
||||
sqlInterface->prepareQuery("SELECT reporter_id, reported_user_name FROM {prefix}_reports WHERE id = :id");
|
||||
lookupQuery->bindValue(":id", reportId);
|
||||
|
||||
QString reporterName;
|
||||
int reporterId = -1;
|
||||
QString reportedUser;
|
||||
if (sqlInterface->execSqlQuery(lookupQuery) && lookupQuery->next()) {
|
||||
int reporterId = lookupQuery->value(0).toInt();
|
||||
reportedUser = lookupQuery->value(1).toString();
|
||||
|
||||
QSqlQuery *nameQuery = sqlInterface->prepareQuery("SELECT name FROM {prefix}_users WHERE id = :id");
|
||||
nameQuery->bindValue(":id", reporterId);
|
||||
if (sqlInterface->execSqlQuery(nameQuery) && nameQuery->next()) {
|
||||
reporterName = nameQuery->value(0).toString();
|
||||
}
|
||||
QString reporterName;
|
||||
if (databaseInterface->getReportNotificationTarget(reportId, reporterId, reportedUser)) {
|
||||
databaseInterface->getUserNameById(reporterId, reporterName);
|
||||
}
|
||||
|
||||
const QString ownName = QString::fromStdString(userInfo->name());
|
||||
|
|
@ -1951,10 +1912,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Com
|
|||
if (reporterName == ownName) {
|
||||
// Resolving your own report: no self-notification needed, but mark it as notified
|
||||
// so it is not delivered on the next login via sendPendingReportNotifications.
|
||||
QSqlQuery *notifiedQuery =
|
||||
sqlInterface->prepareQuery("UPDATE {prefix}_reports SET notified = 1 WHERE id = :id");
|
||||
notifiedQuery->bindValue(":id", reportId);
|
||||
sqlInterface->execSqlQuery(notifiedQuery);
|
||||
databaseInterface->markReportNotified(reportId);
|
||||
} else {
|
||||
QReadLocker clientsLocker(&servatrice->clientsLock);
|
||||
AbstractServerSocketInterface *reporter =
|
||||
|
|
@ -1978,10 +1936,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Com
|
|||
|
||||
// Only mark the report as notified if the reporter is still connected; otherwise the
|
||||
// notification is delivered on their next login via sendPendingReportNotifications.
|
||||
QSqlQuery *notifiedQuery =
|
||||
sqlInterface->prepareQuery("UPDATE {prefix}_reports SET notified = 1 WHERE id = :id");
|
||||
notifiedQuery->bindValue(":id", reportId);
|
||||
sqlInterface->execSqlQuery(notifiedQuery);
|
||||
databaseInterface->markReportNotified(reportId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2472,27 +2427,20 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm
|
|||
Response::ResponseCode AbstractServerSocketInterface::cmdReportReopen(const Command_ReportReopen &cmd,
|
||||
ResponseContainer & /*rc*/)
|
||||
{
|
||||
if (!sqlInterface->checkSql()) {
|
||||
if (!databaseInterface->checkSql()) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
int reportId = cmd.report_id();
|
||||
QString note = textFromStdString(cmd.reopen_note());
|
||||
const int reportId = cmd.report_id();
|
||||
const QString note = textFromStdString(cmd.reopen_note());
|
||||
|
||||
QSqlQuery *query = sqlInterface->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 (!sqlInterface->execSqlQuery(query)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
if (query->numRowsAffected() == 0) {
|
||||
return Response::RespInvalidData;
|
||||
switch (databaseInterface->reopenReport(reportId)) {
|
||||
case Server_DatabaseInterface::ReportUpdateResult::Ok:
|
||||
break;
|
||||
case Server_DatabaseInterface::ReportUpdateResult::InvalidState:
|
||||
return Response::RespInvalidData;
|
||||
case Server_DatabaseInterface::ReportUpdateResult::Failed:
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
QString auditDetails = QString("Report #%1 reopened").arg(reportId);
|
||||
|
|
@ -2555,89 +2503,58 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAddComment(const
|
|||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
if (!sqlInterface->checkSql()) {
|
||||
if (!databaseInterface->checkSql()) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
int reportId = cmd.report_id();
|
||||
QString commentText = textFromStdString(cmd.comment()).trimmed();
|
||||
const int reportId = cmd.report_id();
|
||||
const QString commentText = textFromStdString(cmd.comment()).trimmed();
|
||||
|
||||
if (commentText.isEmpty()) {
|
||||
return Response::RespInvalidData;
|
||||
}
|
||||
|
||||
const int maxCommentsPerHour = settingsCache->value("reporting/max_comments_per_hour", 30).toInt();
|
||||
if (maxCommentsPerHour > 0) {
|
||||
QSqlQuery *countQuery =
|
||||
sqlInterface->prepareQuery("SELECT COUNT(*) FROM {prefix}_report_comments WHERE author_id = :id "
|
||||
"AND created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR)");
|
||||
countQuery->bindValue(":id", userInfo->id());
|
||||
if (sqlInterface->execSqlQuery(countQuery) && countQuery->next() &&
|
||||
countQuery->value(0).toInt() >= maxCommentsPerHour) {
|
||||
return Response::RespTooManyRequests;
|
||||
}
|
||||
if (maxCommentsPerHour > 0 &&
|
||||
databaseInterface->countReportCommentsInLastHour(userInfo->id()) >= maxCommentsPerHour) {
|
||||
return Response::RespTooManyRequests;
|
||||
}
|
||||
|
||||
QSqlQuery *checkQuery = sqlInterface->prepareQuery(
|
||||
"SELECT reporter_id, reporter_name, assigned_to, status FROM {prefix}_reports WHERE id = :id");
|
||||
checkQuery->bindValue(":id", reportId);
|
||||
|
||||
if (!sqlInterface->execSqlQuery(checkQuery)) {
|
||||
Server_DatabaseInterface::ReportCommentTargetRecord target;
|
||||
if (!databaseInterface->getReportCommentTarget(reportId, target)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
if (!checkQuery->next()) {
|
||||
if (!target.found) {
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
|
||||
int reporterId = checkQuery->value(0).toInt();
|
||||
QString reporterName = checkQuery->value(1).toString();
|
||||
int assignedToId = checkQuery->value(2).toInt();
|
||||
|
||||
bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
||||
const bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
||||
|
||||
// Only the reporter (by id) or a moderator may comment on a report.
|
||||
if (reporterId != userInfo->id() && !isMod) {
|
||||
if (target.reporterId != userInfo->id() && !isMod) {
|
||||
return Response::RespAccessDenied;
|
||||
}
|
||||
|
||||
QString reportStatus = checkQuery->value(3).toString();
|
||||
if (reportStatus == "resolved" || reportStatus == "dismissed") {
|
||||
if (target.status == "resolved" || target.status == "dismissed") {
|
||||
return Response::RespInvalidData;
|
||||
}
|
||||
|
||||
QSqlQuery *insertQuery = sqlInterface->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)");
|
||||
insertQuery->bindValue(":report_id", reportId);
|
||||
insertQuery->bindValue(":author_name", QString::fromStdString(userInfo->name()));
|
||||
insertQuery->bindValue(":author_id", userInfo->id());
|
||||
insertQuery->bindValue(":comment_text", commentText);
|
||||
insertQuery->bindValue(":is_moderator", isMod);
|
||||
|
||||
if (!sqlInterface->execSqlQuery(insertQuery)) {
|
||||
qlonglong commentId = 0;
|
||||
if (!databaseInterface->addReportComment(reportId, QString::fromStdString(userInfo->name()), userInfo->id(),
|
||||
commentText, isMod, commentId)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
const qlonglong commentId = insertQuery->lastInsertId().toLongLong();
|
||||
|
||||
QStringList recipients;
|
||||
if (isMod) {
|
||||
// A moderator comment reaches both the reporter and (if assigned) the assigned moderator.
|
||||
recipients.append(reporterName);
|
||||
if (assignedToId > 0) {
|
||||
QSqlQuery *modNameQuery = sqlInterface->prepareQuery("SELECT name FROM {prefix}_users WHERE id = :id");
|
||||
modNameQuery->bindValue(":id", assignedToId);
|
||||
if (sqlInterface->execSqlQuery(modNameQuery) && modNameQuery->next()) {
|
||||
recipients.append(modNameQuery->value(0).toString());
|
||||
}
|
||||
}
|
||||
} else if (assignedToId > 0) {
|
||||
QSqlQuery *modNameQuery = sqlInterface->prepareQuery("SELECT name FROM {prefix}_users WHERE id = :id");
|
||||
modNameQuery->bindValue(":id", assignedToId);
|
||||
if (sqlInterface->execSqlQuery(modNameQuery) && modNameQuery->next()) {
|
||||
recipients.append(modNameQuery->value(0).toString());
|
||||
recipients.append(target.reporterName);
|
||||
}
|
||||
if (target.assignedToId > 0) {
|
||||
QString modName;
|
||||
if (databaseInterface->getUserNameById(target.assignedToId, modName)) {
|
||||
recipients.append(modName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2670,10 +2587,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAddComment(const
|
|||
}
|
||||
|
||||
if (allNotified) {
|
||||
QSqlQuery *notifiedQuery =
|
||||
sqlInterface->prepareQuery("UPDATE {prefix}_report_comments SET notified = 1 WHERE id = :id");
|
||||
notifiedQuery->bindValue(":id", commentId);
|
||||
sqlInterface->execSqlQuery(notifiedQuery);
|
||||
databaseInterface->markReportCommentNotified(commentId);
|
||||
}
|
||||
|
||||
return Response::RespOk;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue