diff --git a/cockatrice/src/client/tabs/tab_admin.cpp b/cockatrice/src/client/tabs/tab_admin.cpp index a3ea1d174..eb0910f96 100644 --- a/cockatrice/src/client/tabs/tab_admin.cpp +++ b/cockatrice/src/client/tabs/tab_admin.cpp @@ -1,19 +1,20 @@ #include "tab_admin.h" +#include "../../server/pending_command.h" #include "../game_logic/abstract_client.h" #include "pb/admin_commands.pb.h" +#include "pb/event_replay_added.pb.h" +#include "pb/moderator_commands.pb.h" #include "trice_limits.h" #include #include #include -#include #include #include #include #include #include -#include ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent) { @@ -63,10 +64,23 @@ TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool reloadConfigButton = new QPushButton; connect(reloadConfigButton, &QPushButton::clicked, this, &TabAdmin::actReloadConfig); + grantReplayAccessButton = new QPushButton; + grantReplayAccessButton->setEnabled(false); + connect(grantReplayAccessButton, &QPushButton::clicked, this, &TabAdmin::actGrantReplayAccess); + replayIdToGrant = new QLineEdit; + replayIdToGrant->setMaximumWidth(500); + replayIdToGrant->setValidator(new QIntValidator(0, INT_MAX, this)); + connect(replayIdToGrant, &QLineEdit::textChanged, this, + [=, this]() { grantReplayAccessButton->setEnabled(!replayIdToGrant->text().isEmpty()); }); + auto *grandReplayAccessLayout = new QGridLayout(this); + grandReplayAccessLayout->addWidget(replayIdToGrant, 0, 0); + grandReplayAccessLayout->addWidget(grantReplayAccessButton, 0, 1); + QVBoxLayout *vbox = new QVBoxLayout; vbox->addWidget(updateServerMessageButton); vbox->addWidget(shutdownServerButton); vbox->addWidget(reloadConfigButton); + vbox->addLayout(grandReplayAccessLayout); vbox->addStretch(); adminGroupBox = new QGroupBox; @@ -100,6 +114,9 @@ void TabAdmin::retranslateUi() reloadConfigButton->setText(tr("&Reload configuration")); adminGroupBox->setTitle(tr("Server administration functions")); + replayIdToGrant->setPlaceholderText(tr("Replay ID")); + grantReplayAccessButton->setText(tr("Grant Replay Access")); + unlockButton->setText(tr("&Unlock functions")); lockButton->setText(tr("&Lock functions")); } @@ -127,6 +144,41 @@ void TabAdmin::actReloadConfig() client->sendCommand(client->prepareAdminCommand(cmd)); } +void TabAdmin::actGrantReplayAccess() +{ + if (!replayIdToGrant) { + return; + } + + Command_GrantReplayAccess cmd; + cmd.set_replay_id(replayIdToGrant->text().toUInt()); + cmd.set_moderator_name(client->getUserName().toStdString()); + + auto *pend = client->prepareModeratorCommand(cmd); + connect(pend, + QOverload::of(&PendingCommand::finished), + this, &TabAdmin::grantReplayAccessProcessResponse); + client->sendCommand(pend); +} + +void TabAdmin::grantReplayAccessProcessResponse(const Response &response, const CommandContainer &, const QVariant &) +{ + auto *event = new Event_ReplayAdded(); + + switch (response.response_code()) { + case Response::RespOk: + client->replayAddedEventReceived(*event); + QMessageBox::information(this, tr("Success"), tr("Replay access granted")); + break; + case Response::RespContextError: + QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Replay ID invalid")); + break; + default: + QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access: Internal error")); + break; + } +} + void TabAdmin::actUnlock() { if (fullAdmin) diff --git a/cockatrice/src/client/tabs/tab_admin.h b/cockatrice/src/client/tabs/tab_admin.h index 650e881a9..3df93f68e 100644 --- a/cockatrice/src/client/tabs/tab_admin.h +++ b/cockatrice/src/client/tabs/tab_admin.h @@ -1,6 +1,8 @@ #ifndef TAB_ADMIN_H #define TAB_ADMIN_H +#include "pb/commands.pb.h" +#include "pb/response.pb.h" #include "tab.h" #include @@ -32,15 +34,18 @@ private: bool locked; AbstractClient *client; bool fullAdmin; - QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton; + QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton, *grantReplayAccessButton; QGroupBox *adminGroupBox; QPushButton *unlockButton, *lockButton; + QLineEdit *replayIdToGrant; signals: void adminLockChanged(bool lock); private slots: void actUpdateServerMessage(); void actShutdownServer(); void actReloadConfig(); + void actGrantReplayAccess(); + void grantReplayAccessProcessResponse(const Response &resp, const CommandContainer &, const QVariant &); void actUnlock(); void actLock(); diff --git a/cockatrice/src/client/tabs/tab_replays.cpp b/cockatrice/src/client/tabs/tab_replays.cpp index b53e68989..5756595c8 100644 --- a/cockatrice/src/client/tabs/tab_replays.cpp +++ b/cockatrice/src/client/tabs/tab_replays.cpp @@ -401,5 +401,11 @@ void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandCont void TabReplays::replayAddedEventReceived(const Event_ReplayAdded &event) { - serverDirView->addMatchInfo(event.match_info()); + if (event.has_match_info()) { + // 99.9% of events will have match info (Normal Workflow) + serverDirView->addMatchInfo(event.match_info()); + } else { + // When a Moderator force adds a replay, we need to refresh their view + serverDirView->refreshTree(); + } } diff --git a/cockatrice/src/server/remote/remote_replay_list_tree_widget.h b/cockatrice/src/server/remote/remote_replay_list_tree_widget.h index 7822fceda..f64d87857 100644 --- a/cockatrice/src/server/remote/remote_replay_list_tree_widget.h +++ b/cockatrice/src/server/remote/remote_replay_list_tree_widget.h @@ -115,7 +115,10 @@ public: ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const; QList getSelectedReplays() const; QSet getSelectedReplayMatches() const; - void refreshTree(); + void refreshTree() + { + treeModel->refreshTree(); + } void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo) { treeModel->addMatchInfo(matchInfo); diff --git a/common/pb/moderator_commands.proto b/common/pb/moderator_commands.proto index d300241bb..f8ff69c03 100644 --- a/common/pb/moderator_commands.proto +++ b/common/pb/moderator_commands.proto @@ -7,6 +7,7 @@ message ModeratorCommand { WARN_HISTORY = 1003; WARN_LIST = 1004; VIEWLOG_HISTORY = 1005; + GRANT_REPLAY_ACCESS = 1006; } extensions 100 to max; } @@ -71,3 +72,11 @@ message Command_ViewLogHistory { required uint32 date_range = 7; // the length of time (in minutes) to look back for optional uint32 maximum_results = 8; // the maximum number of query results } + +message Command_GrantReplayAccess { + extend ModeratorCommand { + optional Command_GrantReplayAccess ext = 1006; + } + optional uint32 replay_id = 1; + optional string moderator_name = 2; +} \ No newline at end of file diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 4e1d45696..9c0986532 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -224,6 +224,8 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo return cmdGetWarnList(cmd.GetExtension(Command_GetWarnList::ext), rc); case ModeratorCommand::VIEWLOG_HISTORY: return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc); + case ModeratorCommand::GRANT_REPLAY_ACCESS: + return cmdGrantReplayAccess(cmd.GetExtension(Command_GrantReplayAccess::ext), rc); default: return Response::RespFunctionNotAllowed; } @@ -1656,6 +1658,52 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAdjustMod(const Command return Response::RespOk; } +Response::ResponseCode AbstractServerSocketInterface::cmdGrantReplayAccess(const Command_GrantReplayAccess &cmd, + ResponseContainer & /*rc*/) +{ + // Determine if the replay actually exists already + auto *replayExistsQuery = + sqlInterface->prepareQuery("select count(*) from {prefix}_replays_access where id_game = :idgame"); + replayExistsQuery->bindValue(":idgame", cmd.replay_id()); + if (!sqlInterface->execSqlQuery(replayExistsQuery)) { + return Response::RespInternalError; + } + if (!replayExistsQuery->next()) { + return Response::RespInternalError; + } + + const auto &replayExists = replayExistsQuery->value(0).toInt() > 0; + if (!replayExists) { + return Response::RespContextError; + } + + // Determine the Moderator's User ID (As it's not apart of client, only username is) + auto *getModeratorUserIdQuery = sqlInterface->prepareQuery("select id from {prefix}_users WHERE name = :name"); + getModeratorUserIdQuery->bindValue(":name", QString::fromStdString(cmd.moderator_name())); + if (!sqlInterface->execSqlQuery(getModeratorUserIdQuery)) { + return Response::RespInternalError; + } + if (!getModeratorUserIdQuery->next()) { + return Response::RespInternalError; + } + + const auto &moderator_id = getModeratorUserIdQuery->value(0).toString(); + + // Grant the Moderator access to the replay + auto *grantReplayAccessQuery = + sqlInterface->prepareQuery("insert into {prefix}_replays_access (id_game, id_player, replay_name, do_not_hide) " + "values(:idgame, :idplayer, :replayname, 0)"); + grantReplayAccessQuery->bindValue(":idgame", cmd.replay_id()); + grantReplayAccessQuery->bindValue(":idplayer", moderator_id); + grantReplayAccessQuery->bindValue(":replayname", "Moderator Access Replay Grant"); + + if (!sqlInterface->execSqlQuery(grantReplayAccessQuery)) { + return Response::RespInternalError; + } + + return Response::RespOk; +} + TcpServerSocketInterface::TcpServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QObject *parent) diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index b6aa7c1e8..2f3aec9b9 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -126,6 +126,7 @@ private: Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc); Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc); Response::ResponseCode cmdAccountPassword(const Command_AccountPassword &cmd, ResponseContainer &rc); + Response::ResponseCode cmdGrantReplayAccess(const Command_GrantReplayAccess &cmd, ResponseContainer &rc); bool addAdminFlagToUser(const QString &user, int flag); bool removeAdminFlagFromUser(const QString &user, int flag);