From 7734a94f8347af3db7c8dd867491cb86f40d1229 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 10 Nov 2026 12:16:23 +0100 Subject: [PATCH] [Server] Move deck file writes into the database interface (#7834) Deleting a deck, inserting an uploaded deck, and updating one in place were SQL statements in the socket handler, including the dynamic is_public clause and the follow-up visibility read. Add DeckFileData and DeckFileWriteResult to Server_DatabaseInterface with create/update/delete operations, implement them in Servatrice_DatabaseInterface with the same statements and bind order, and let cmdDeckUpload and cmdDeckDel ask the injected interface. --- .../server/remote/server_database_interface.h | 30 +++++ .../src/servatrice_database_interface.cpp | 91 ++++++++++++++ .../src/servatrice_database_interface.h | 4 + servatrice/src/serversocketinterface.cpp | 111 +++++------------- 4 files changed, 157 insertions(+), 79 deletions(-) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h index 88d79f882..d9667f1e6 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h @@ -218,6 +218,36 @@ public: virtual void deleteDeckFolder(int /* folderId */) { } + /** @brief The mutable deck-file columns, as the upload command supplies them. */ + struct DeckFileData + { + QString name; + QString content; + bool isPublic = false; + QString bannerCardName; + QString bannerCardProvider; + QString colorIdentity; + QString tagsJson; + }; + struct DeckFileWriteResult + { + DeckStorageResult result = DeckStorageResult::Failed; + int deckId = 0; + bool isPublic = false; + }; + virtual DeckFileWriteResult createDeckFile(int /* folderId */, int /* userId */, const DeckFileData & /* data */) + { + return DeckFileWriteResult{}; + } + virtual DeckFileWriteResult + updateDeckFile(int /* deckId */, int /* userId */, const DeckFileData & /* data */, bool /* updateVisibility */) + { + return DeckFileWriteResult{}; + } + virtual DeckStorageResult deleteDeckFile(int /* deckId */, int /* userId */) + { + return DeckStorageResult::Failed; + } virtual bool removeForgotPassword(const QString & /* user */) { return false; diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index cb0ba24f3..3f9485b0d 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -1093,6 +1093,97 @@ void Servatrice_DatabaseInterface::deleteDeckFolderTree(int basePathId) execSqlQuery(query); } +Servatrice_DatabaseInterface::DeckFileWriteResult +Servatrice_DatabaseInterface::createDeckFile(int folderId, int userId, const DeckFileData &data) +{ + QSqlQuery *query = prepareQuery( + "insert into {prefix}_decklist_files (id_folder, id_user, name, upload_time, content, is_public, " + "banner_card_name, banner_card_provider, color_identity, tags) values(:id_folder, :id_user, :name, NOW(), " + ":content, :is_public, :banner_card_name, :banner_card_provider, :color_identity, :tags)"); + query->bindValue(":id_folder", folderId); + query->bindValue(":id_user", userId); + query->bindValue(":name", data.name); + query->bindValue(":content", data.content); + query->bindValue(":is_public", data.isPublic ? 1 : 0); + query->bindValue(":banner_card_name", data.bannerCardName); + query->bindValue(":banner_card_provider", data.bannerCardProvider); + query->bindValue(":color_identity", data.colorIdentity); + query->bindValue(":tags", data.tagsJson); + + DeckFileWriteResult result; + if (!execSqlQuery(query)) { + return result; + } + result.result = DeckStorageResult::Ok; + result.deckId = query->lastInsertId().toInt(); + result.isPublic = data.isPublic; + return result; +} + +Servatrice_DatabaseInterface::DeckFileWriteResult +Servatrice_DatabaseInterface::updateDeckFile(int deckId, int userId, const DeckFileData &data, bool updateVisibility) +{ + QString updateQuery = "update {prefix}_decklist_files set name=:name, upload_time=NOW(), content=:content, " + "banner_card_name=:banner_card_name, banner_card_provider=:banner_card_provider, " + "color_identity=:color_identity, tags=:tags"; + if (updateVisibility) { + updateQuery += ", is_public=:is_public"; + } + updateQuery += " where id = :id_deck and id_user = :id_user"; + + QSqlQuery *query = prepareQuery(updateQuery); + query->bindValue(":id_deck", deckId); + query->bindValue(":id_user", userId); + query->bindValue(":name", data.name); + query->bindValue(":content", data.content); + query->bindValue(":banner_card_name", data.bannerCardName); + query->bindValue(":banner_card_provider", data.bannerCardProvider); + query->bindValue(":color_identity", data.colorIdentity); + query->bindValue(":tags", data.tagsJson); + if (updateVisibility) { + query->bindValue(":is_public", data.isPublic ? 1 : 0); + } + + DeckFileWriteResult result; + if (!execSqlQuery(query)) { + return result; + } + if (query->numRowsAffected() == 0) { + result.result = DeckStorageResult::NotFound; + return result; + } + + QSqlQuery *visibilityQuery = + prepareQuery("select is_public from {prefix}_decklist_files where id = :id and id_user = :id_user"); + visibilityQuery->bindValue(":id", deckId); + visibilityQuery->bindValue(":id_user", userId); + if (!execSqlQuery(visibilityQuery)) { + return result; + } + + result.result = DeckStorageResult::Ok; + result.deckId = deckId; + result.isPublic = visibilityQuery->next() && visibilityQuery->value(0).toBool(); + return result; +} + +Servatrice_DatabaseInterface::DeckStorageResult Servatrice_DatabaseInterface::deleteDeckFile(int deckId, int userId) +{ + QSqlQuery *query = prepareQuery("select id from {prefix}_decklist_files where id = :id and id_user = :id_user"); + query->bindValue(":id", deckId); + query->bindValue(":id_user", userId); + execSqlQuery(query); + if (!query->next()) { + return DeckStorageResult::NotFound; + } + + query = prepareQuery("delete from {prefix}_decklist_files where id = :id"); + query->bindValue(":id", deckId); + execSqlQuery(query); + + return DeckStorageResult::Ok; +} + void Servatrice_DatabaseInterface::deleteDeckFolder(int folderId) { deleteDeckFolderTree(folderId); diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 1f30c3eb5..e0a32cf00 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -129,6 +129,10 @@ public: DeckStorageResult setDeckFolderVisibility(int folderId, int userId, bool isPublic) override; DeckStorageResult createDeckFolder(int parentId, int userId, const QString &name) override; void deleteDeckFolder(int folderId) override; + DeckFileWriteResult createDeckFile(int folderId, int userId, const DeckFileData &data) override; + DeckFileWriteResult + updateDeckFile(int deckId, int userId, const DeckFileData &data, bool updateVisibility) override; + DeckStorageResult deleteDeckFile(int deckId, int userId) override; QMap getBuddyList(const QString &name) override; QMap getIgnoreList(const QString &name) override; bool isInBuddyList(const QString &whoseList, const QString &who) override; diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 1af48c7e6..b45bf84b7 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -885,21 +885,16 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDel(const Command_D return Response::RespFunctionNotAllowed; } - sqlInterface->checkSql(); - QSqlQuery *query = - sqlInterface->prepareQuery("select id from {prefix}_decklist_files where id = :id and id_user = :id_user"); - query->bindValue(":id", cmd.deck_id()); - query->bindValue(":id_user", userInfo->id()); - sqlInterface->execSqlQuery(query); - if (!query->next()) { - return Response::RespNameNotFound; + databaseInterface->checkSql(); + switch (databaseInterface->deleteDeckFile(cmd.deck_id(), userInfo->id())) { + case Server_DatabaseInterface::DeckStorageResult::Ok: + return Response::RespOk; + case Server_DatabaseInterface::DeckStorageResult::NotFound: + return Response::RespNameNotFound; + case Server_DatabaseInterface::DeckStorageResult::Failed: + return Response::RespContextError; } - - query = sqlInterface->prepareQuery("delete from {prefix}_decklist_files where id = :id"); - query->bindValue(":id", cmd.deck_id()); - sqlInterface->execSqlQuery(query); - - return Response::RespOk; + return Response::RespContextError; } namespace @@ -933,7 +928,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman return Response::RespInvalidData; } - sqlInterface->checkSql(); + databaseInterface->checkSql(); QString deckStr = fileFromStdString(cmd.deck_list()); DeckList deck; @@ -954,87 +949,45 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman const QString tagsJson = serializeDeckTags(deck.getTags()); const QString colorIdentity = sanitizeColorIdentity(nameFromStdString(cmd.color_identity())); + const Server_DatabaseInterface::DeckFileData data{.name = deckName, + .content = deckStr, + .isPublic = cmd.has_is_public() && cmd.is_public(), + .bannerCardName = bannerCardName, + .bannerCardProvider = bannerCardProvider, + .colorIdentity = colorIdentity, + .tagsJson = tagsJson}; + + Server_DatabaseInterface::DeckFileWriteResult writeResult; if (cmd.has_path()) { int folderId = databaseInterface->getDeckPathId(nameFromStdString(cmd.path()), userInfo->id()); if (folderId == -1) { return Response::RespNameNotFound; } - QSqlQuery *query = - sqlInterface->prepareQuery("insert into {prefix}_decklist_files (id_folder, id_user, name, upload_time, " - "content, is_public, banner_card_name, banner_card_provider, color_identity, " - "tags) values(:id_folder, :id_user, :name, NOW(), :content, :is_public, " - ":banner_card_name, :banner_card_provider, :color_identity, :tags)"); - query->bindValue(":id_folder", folderId); - query->bindValue(":id_user", userInfo->id()); - query->bindValue(":name", deckName); - query->bindValue(":content", deckStr); - query->bindValue(":is_public", cmd.has_is_public() && cmd.is_public() ? 1 : 0); - query->bindValue(":banner_card_name", bannerCardName); - query->bindValue(":banner_card_provider", bannerCardProvider); - query->bindValue(":color_identity", colorIdentity); - query->bindValue(":tags", tagsJson); - if (!sqlInterface->execSqlQuery(query)) { + writeResult = databaseInterface->createDeckFile(folderId, userInfo->id(), data); + if (writeResult.result != Server_DatabaseInterface::DeckStorageResult::Ok) { return Response::RespContextError; } - - Response_DeckUpload *re = new Response_DeckUpload; - ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file(); - fileInfo->set_id(query->lastInsertId().toInt()); - fileInfo->set_name(deckName.toStdString()); - fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toSecsSinceEpoch()); - fileInfo->mutable_file()->set_is_public(cmd.has_is_public() && cmd.is_public()); - rc.setResponseExtension(re); } else if (cmd.has_deck_id()) { - QString updateQuery = "update {prefix}_decklist_files set name=:name, upload_time=NOW(), content=:content, " - "banner_card_name=:banner_card_name, banner_card_provider=:banner_card_provider, " - "color_identity=:color_identity, tags=:tags"; - if (cmd.has_is_public()) { - updateQuery += ", is_public=:is_public"; - } - updateQuery += " where id = :id_deck and id_user = :id_user"; - - QSqlQuery *query = sqlInterface->prepareQuery(updateQuery); - query->bindValue(":id_deck", cmd.deck_id()); - query->bindValue(":id_user", userInfo->id()); - query->bindValue(":name", deckName); - query->bindValue(":content", deckStr); - query->bindValue(":banner_card_name", bannerCardName); - query->bindValue(":banner_card_provider", bannerCardProvider); - query->bindValue(":color_identity", colorIdentity); - query->bindValue(":tags", tagsJson); - if (cmd.has_is_public()) { - query->bindValue(":is_public", cmd.is_public() ? 1 : 0); - } - if (!sqlInterface->execSqlQuery(query)) { - return Response::RespContextError; - } - - if (query->numRowsAffected() == 0) { + writeResult = databaseInterface->updateDeckFile(cmd.deck_id(), userInfo->id(), data, cmd.has_is_public()); + if (writeResult.result == Server_DatabaseInterface::DeckStorageResult::NotFound) { return Response::RespNameNotFound; } - - QSqlQuery *visibilityQuery = - sqlInterface->prepareQuery("select is_public from {prefix}_decklist_files where id = :id and " - "id_user = :id_user"); - visibilityQuery->bindValue(":id", cmd.deck_id()); - visibilityQuery->bindValue(":id_user", userInfo->id()); - if (!sqlInterface->execSqlQuery(visibilityQuery)) { + if (writeResult.result != Server_DatabaseInterface::DeckStorageResult::Ok) { return Response::RespContextError; } - const bool isPublic = visibilityQuery->next() && visibilityQuery->value(0).toBool(); - - Response_DeckUpload *re = new Response_DeckUpload; - ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file(); - fileInfo->set_id(cmd.deck_id()); - fileInfo->set_name(deckName.toStdString()); - fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toSecsSinceEpoch()); - fileInfo->mutable_file()->set_is_public(isPublic); - rc.setResponseExtension(re); } else { return Response::RespInvalidData; } + Response_DeckUpload *re = new Response_DeckUpload; + ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file(); + fileInfo->set_id(writeResult.deckId); + fileInfo->set_name(deckName.toStdString()); + fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toSecsSinceEpoch()); + fileInfo->mutable_file()->set_is_public(writeResult.isPublic); + rc.setResponseExtension(re); + return Response::RespOk; }