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;
|
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. */
|
/** @brief One resolved or dismissed report whose reporter has not been told yet. */
|
||||||
struct PendingReportNotification
|
struct PendingReportNotification
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4041,6 +4041,203 @@ bool Servatrice_DatabaseInterface::getReportUserInfo(const QString &userName, Re
|
||||||
return true;
|
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)
|
int Servatrice_DatabaseInterface::countReportsSubmittedInLastDay(int reporterId)
|
||||||
{
|
{
|
||||||
if (!checkSql()) {
|
if (!checkSql()) {
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,20 @@ public:
|
||||||
bool getReportComments(int reportId, QList<ServerInfo_ReportComment> &comments) override;
|
bool getReportComments(int reportId, QList<ServerInfo_ReportComment> &comments) override;
|
||||||
bool getReportStats(ReportStatsRecord &stats) override;
|
bool getReportStats(ReportStatsRecord &stats) override;
|
||||||
bool getReportUserInfo(const QString &userName, ReportUserInfoRecord &info) 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,
|
bool addReport(int reporterId,
|
||||||
const QString &reporterName,
|
const QString &reporterName,
|
||||||
int reportedUserId,
|
int reportedUserId,
|
||||||
|
|
|
||||||
|
|
@ -1849,42 +1849,22 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdReportAssign(const Command_ReportAssign &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdReportAssign(const Command_ReportAssign &cmd,
|
||||||
ResponseContainer & /*rc*/)
|
ResponseContainer & /*rc*/)
|
||||||
{
|
{
|
||||||
if (!sqlInterface->checkSql()) {
|
if (!databaseInterface->checkSql()) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reportId = cmd.report_id();
|
const int reportId = cmd.report_id();
|
||||||
int modId = userInfo->id();
|
const int modId = userInfo->id();
|
||||||
|
|
||||||
QSqlQuery *lookupQuery = sqlInterface->prepareQuery("SELECT status FROM {prefix}_reports WHERE id = :id");
|
switch (databaseInterface->assignReport(reportId, modId)) {
|
||||||
lookupQuery->bindValue(":id", reportId);
|
case Server_DatabaseInterface::ReportAssignResult::Ok:
|
||||||
|
break;
|
||||||
if (!sqlInterface->execSqlQuery(lookupQuery)) {
|
case Server_DatabaseInterface::ReportAssignResult::NotFound:
|
||||||
return Response::RespInternalError;
|
return Response::RespNameNotFound;
|
||||||
}
|
case Server_DatabaseInterface::ReportAssignResult::NotOpen:
|
||||||
|
return Response::RespInvalidData;
|
||||||
if (!lookupQuery->next()) {
|
case Server_DatabaseInterface::ReportAssignResult::Failed:
|
||||||
return Response::RespNameNotFound;
|
return Response::RespInternalError;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
|
|
@ -1893,32 +1873,23 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAssign(const Comm
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Command_ReportResolve &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Command_ReportResolve &cmd,
|
||||||
ResponseContainer & /*rc*/)
|
ResponseContainer & /*rc*/)
|
||||||
{
|
{
|
||||||
if (!sqlInterface->checkSql()) {
|
if (!databaseInterface->checkSql()) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reportId = cmd.report_id();
|
const int reportId = cmd.report_id();
|
||||||
QString note = textFromStdString(cmd.resolution_note());
|
const QString note = textFromStdString(cmd.resolution_note());
|
||||||
bool dismissed = cmd.dismissed();
|
const bool dismissed = cmd.dismissed();
|
||||||
|
|
||||||
QString newStatus = dismissed ? "dismissed" : "resolved";
|
const QString newStatus = dismissed ? "dismissed" : "resolved";
|
||||||
|
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports "
|
switch (databaseInterface->resolveReport(reportId, newStatus, note, userInfo->id())) {
|
||||||
"SET status = :status, resolution_note = :note, "
|
case Server_DatabaseInterface::ReportUpdateResult::Ok:
|
||||||
"resolution_time = NOW(), resolved_by = :mod_id "
|
break;
|
||||||
"WHERE id = :id AND status IN ('open', 'assigned')");
|
case Server_DatabaseInterface::ReportUpdateResult::InvalidState:
|
||||||
|
return Response::RespInvalidData;
|
||||||
query->bindValue(":status", newStatus);
|
case Server_DatabaseInterface::ReportUpdateResult::Failed:
|
||||||
query->bindValue(":note", note);
|
return Response::RespInternalError;
|
||||||
query->bindValue(":mod_id", userInfo->id());
|
|
||||||
query->bindValue(":id", reportId);
|
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
|
||||||
return Response::RespInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query->numRowsAffected() == 0) {
|
|
||||||
return Response::RespInvalidData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseInterface->addAuditRecord(QString::number(reportId), this->getAddress(),
|
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
|
// 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
|
// queries below fail, the report is simply left with notified = 0 and the notification is
|
||||||
// delivered later via sendPendingReportNotifications.
|
// delivered later via sendPendingReportNotifications.
|
||||||
QSqlQuery *lookupQuery =
|
int reporterId = -1;
|
||||||
sqlInterface->prepareQuery("SELECT reporter_id, reported_user_name FROM {prefix}_reports WHERE id = :id");
|
|
||||||
lookupQuery->bindValue(":id", reportId);
|
|
||||||
|
|
||||||
QString reporterName;
|
|
||||||
QString reportedUser;
|
QString reportedUser;
|
||||||
if (sqlInterface->execSqlQuery(lookupQuery) && lookupQuery->next()) {
|
QString reporterName;
|
||||||
int reporterId = lookupQuery->value(0).toInt();
|
if (databaseInterface->getReportNotificationTarget(reportId, reporterId, reportedUser)) {
|
||||||
reportedUser = lookupQuery->value(1).toString();
|
databaseInterface->getUserNameById(reporterId, reporterName);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString ownName = QString::fromStdString(userInfo->name());
|
const QString ownName = QString::fromStdString(userInfo->name());
|
||||||
|
|
@ -1951,10 +1912,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportResolve(const Com
|
||||||
if (reporterName == ownName) {
|
if (reporterName == ownName) {
|
||||||
// Resolving your own report: no self-notification needed, but mark it as notified
|
// Resolving your own report: no self-notification needed, but mark it as notified
|
||||||
// so it is not delivered on the next login via sendPendingReportNotifications.
|
// so it is not delivered on the next login via sendPendingReportNotifications.
|
||||||
QSqlQuery *notifiedQuery =
|
databaseInterface->markReportNotified(reportId);
|
||||||
sqlInterface->prepareQuery("UPDATE {prefix}_reports SET notified = 1 WHERE id = :id");
|
|
||||||
notifiedQuery->bindValue(":id", reportId);
|
|
||||||
sqlInterface->execSqlQuery(notifiedQuery);
|
|
||||||
} else {
|
} else {
|
||||||
QReadLocker clientsLocker(&servatrice->clientsLock);
|
QReadLocker clientsLocker(&servatrice->clientsLock);
|
||||||
AbstractServerSocketInterface *reporter =
|
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
|
// Only mark the report as notified if the reporter is still connected; otherwise the
|
||||||
// notification is delivered on their next login via sendPendingReportNotifications.
|
// notification is delivered on their next login via sendPendingReportNotifications.
|
||||||
QSqlQuery *notifiedQuery =
|
databaseInterface->markReportNotified(reportId);
|
||||||
sqlInterface->prepareQuery("UPDATE {prefix}_reports SET notified = 1 WHERE id = :id");
|
|
||||||
notifiedQuery->bindValue(":id", reportId);
|
|
||||||
sqlInterface->execSqlQuery(notifiedQuery);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2472,27 +2427,20 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdReportReopen(const Command_ReportReopen &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdReportReopen(const Command_ReportReopen &cmd,
|
||||||
ResponseContainer & /*rc*/)
|
ResponseContainer & /*rc*/)
|
||||||
{
|
{
|
||||||
if (!sqlInterface->checkSql()) {
|
if (!databaseInterface->checkSql()) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reportId = cmd.report_id();
|
const int reportId = cmd.report_id();
|
||||||
QString note = textFromStdString(cmd.reopen_note());
|
const QString note = textFromStdString(cmd.reopen_note());
|
||||||
|
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports "
|
switch (databaseInterface->reopenReport(reportId)) {
|
||||||
"SET status = 'open', resolution_note = NULL, "
|
case Server_DatabaseInterface::ReportUpdateResult::Ok:
|
||||||
"resolution_time = NULL, resolved_by = NULL, assigned_to = NULL, "
|
break;
|
||||||
"notified = 0 "
|
case Server_DatabaseInterface::ReportUpdateResult::InvalidState:
|
||||||
"WHERE id = :id AND status IN ('resolved', 'dismissed')");
|
return Response::RespInvalidData;
|
||||||
|
case Server_DatabaseInterface::ReportUpdateResult::Failed:
|
||||||
query->bindValue(":id", reportId);
|
return Response::RespInternalError;
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
|
||||||
return Response::RespInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query->numRowsAffected() == 0) {
|
|
||||||
return Response::RespInvalidData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString auditDetails = QString("Report #%1 reopened").arg(reportId);
|
QString auditDetails = QString("Report #%1 reopened").arg(reportId);
|
||||||
|
|
@ -2555,89 +2503,58 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAddComment(const
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sqlInterface->checkSql()) {
|
if (!databaseInterface->checkSql()) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reportId = cmd.report_id();
|
const int reportId = cmd.report_id();
|
||||||
QString commentText = textFromStdString(cmd.comment()).trimmed();
|
const QString commentText = textFromStdString(cmd.comment()).trimmed();
|
||||||
|
|
||||||
if (commentText.isEmpty()) {
|
if (commentText.isEmpty()) {
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int maxCommentsPerHour = settingsCache->value("reporting/max_comments_per_hour", 30).toInt();
|
const int maxCommentsPerHour = settingsCache->value("reporting/max_comments_per_hour", 30).toInt();
|
||||||
if (maxCommentsPerHour > 0) {
|
if (maxCommentsPerHour > 0 &&
|
||||||
QSqlQuery *countQuery =
|
databaseInterface->countReportCommentsInLastHour(userInfo->id()) >= maxCommentsPerHour) {
|
||||||
sqlInterface->prepareQuery("SELECT COUNT(*) FROM {prefix}_report_comments WHERE author_id = :id "
|
return Response::RespTooManyRequests;
|
||||||
"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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlQuery *checkQuery = sqlInterface->prepareQuery(
|
Server_DatabaseInterface::ReportCommentTargetRecord target;
|
||||||
"SELECT reporter_id, reporter_name, assigned_to, status FROM {prefix}_reports WHERE id = :id");
|
if (!databaseInterface->getReportCommentTarget(reportId, target)) {
|
||||||
checkQuery->bindValue(":id", reportId);
|
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(checkQuery)) {
|
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkQuery->next()) {
|
if (!target.found) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reporterId = checkQuery->value(0).toInt();
|
const bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
||||||
QString reporterName = checkQuery->value(1).toString();
|
|
||||||
int assignedToId = checkQuery->value(2).toInt();
|
|
||||||
|
|
||||||
bool isMod = userInfo->user_level() & ServerInfo_User::IsModerator;
|
|
||||||
|
|
||||||
// Only the reporter (by id) or a moderator may comment on a report.
|
// 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;
|
return Response::RespAccessDenied;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString reportStatus = checkQuery->value(3).toString();
|
if (target.status == "resolved" || target.status == "dismissed") {
|
||||||
if (reportStatus == "resolved" || reportStatus == "dismissed") {
|
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlQuery *insertQuery = sqlInterface->prepareQuery(
|
qlonglong commentId = 0;
|
||||||
"INSERT INTO {prefix}_report_comments (report_id, author_name, author_id, comment_text, created_at, "
|
if (!databaseInterface->addReportComment(reportId, QString::fromStdString(userInfo->name()), userInfo->id(),
|
||||||
"is_moderator) "
|
commentText, isMod, commentId)) {
|
||||||
"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)) {
|
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const qlonglong commentId = insertQuery->lastInsertId().toLongLong();
|
|
||||||
|
|
||||||
QStringList recipients;
|
QStringList recipients;
|
||||||
if (isMod) {
|
if (isMod) {
|
||||||
// A moderator comment reaches both the reporter and (if assigned) the assigned moderator.
|
// A moderator comment reaches both the reporter and (if assigned) the assigned moderator.
|
||||||
recipients.append(reporterName);
|
recipients.append(target.reporterName);
|
||||||
if (assignedToId > 0) {
|
}
|
||||||
QSqlQuery *modNameQuery = sqlInterface->prepareQuery("SELECT name FROM {prefix}_users WHERE id = :id");
|
if (target.assignedToId > 0) {
|
||||||
modNameQuery->bindValue(":id", assignedToId);
|
QString modName;
|
||||||
if (sqlInterface->execSqlQuery(modNameQuery) && modNameQuery->next()) {
|
if (databaseInterface->getUserNameById(target.assignedToId, modName)) {
|
||||||
recipients.append(modNameQuery->value(0).toString());
|
recipients.append(modName);
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2670,10 +2587,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportAddComment(const
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allNotified) {
|
if (allNotified) {
|
||||||
QSqlQuery *notifiedQuery =
|
databaseInterface->markReportCommentNotified(commentId);
|
||||||
sqlInterface->prepareQuery("UPDATE {prefix}_report_comments SET notified = 1 WHERE id = :id");
|
|
||||||
notifiedQuery->bindValue(":id", commentId);
|
|
||||||
sqlInterface->execSqlQuery(notifiedQuery);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue