[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.
This commit is contained in:
BruebachL 2026-11-10 12:16:23 +01:00 • committed by GitHub
parent 23781af1f0
commit 7734a94f83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 157 additions and 79 deletions

View file

@ -218,6 +218,36 @@ public:
virtual void deleteDeckFolder(int /* folderId */) 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 */) virtual bool removeForgotPassword(const QString & /* user */)
{ {
return false; return false;

View file

@ -1093,6 +1093,97 @@ void Servatrice_DatabaseInterface::deleteDeckFolderTree(int basePathId)
execSqlQuery(query); 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) void Servatrice_DatabaseInterface::deleteDeckFolder(int folderId)
{ {
deleteDeckFolderTree(folderId); deleteDeckFolderTree(folderId);

View file

@ -129,6 +129,10 @@ public:
DeckStorageResult setDeckFolderVisibility(int folderId, int userId, bool isPublic) override; DeckStorageResult setDeckFolderVisibility(int folderId, int userId, bool isPublic) override;
DeckStorageResult createDeckFolder(int parentId, int userId, const QString &name) override; DeckStorageResult createDeckFolder(int parentId, int userId, const QString &name) override;
void deleteDeckFolder(int folderId) 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<QString, ServerInfo_User> getBuddyList(const QString &name) override; QMap<QString, ServerInfo_User> getBuddyList(const QString &name) override;
QMap<QString, ServerInfo_User> getIgnoreList(const QString &name) override; QMap<QString, ServerInfo_User> getIgnoreList(const QString &name) override;
bool isInBuddyList(const QString &whoseList, const QString &who) override; bool isInBuddyList(const QString &whoseList, const QString &who) override;

View file

@ -885,21 +885,16 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckDel(const Command_D
return Response::RespFunctionNotAllowed; return Response::RespFunctionNotAllowed;
} }
sqlInterface->checkSql(); databaseInterface->checkSql();
QSqlQuery *query = switch (databaseInterface->deleteDeckFile(cmd.deck_id(), userInfo->id())) {
sqlInterface->prepareQuery("select id from {prefix}_decklist_files where id = :id and id_user = :id_user"); case Server_DatabaseInterface::DeckStorageResult::Ok:
query->bindValue(":id", cmd.deck_id()); return Response::RespOk;
query->bindValue(":id_user", userInfo->id()); case Server_DatabaseInterface::DeckStorageResult::NotFound:
sqlInterface->execSqlQuery(query); return Response::RespNameNotFound;
if (!query->next()) { case Server_DatabaseInterface::DeckStorageResult::Failed:
return Response::RespNameNotFound; return Response::RespContextError;
} }
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;
} }
namespace namespace
@ -933,7 +928,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman
return Response::RespInvalidData; return Response::RespInvalidData;
} }
sqlInterface->checkSql(); databaseInterface->checkSql();
QString deckStr = fileFromStdString(cmd.deck_list()); QString deckStr = fileFromStdString(cmd.deck_list());
DeckList deck; DeckList deck;
@ -954,87 +949,45 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckUpload(const Comman
const QString tagsJson = serializeDeckTags(deck.getTags()); const QString tagsJson = serializeDeckTags(deck.getTags());
const QString colorIdentity = sanitizeColorIdentity(nameFromStdString(cmd.color_identity())); 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()) { if (cmd.has_path()) {
int folderId = databaseInterface->getDeckPathId(nameFromStdString(cmd.path()), userInfo->id()); int folderId = databaseInterface->getDeckPathId(nameFromStdString(cmd.path()), userInfo->id());
if (folderId == -1) { if (folderId == -1) {
return Response::RespNameNotFound; return Response::RespNameNotFound;
} }
QSqlQuery *query = writeResult = databaseInterface->createDeckFile(folderId, userInfo->id(), data);
sqlInterface->prepareQuery("insert into {prefix}_decklist_files (id_folder, id_user, name, upload_time, " if (writeResult.result != Server_DatabaseInterface::DeckStorageResult::Ok) {
"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)) {
return Response::RespContextError; 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()) { } else if (cmd.has_deck_id()) {
QString updateQuery = "update {prefix}_decklist_files set name=:name, upload_time=NOW(), content=:content, " writeResult = databaseInterface->updateDeckFile(cmd.deck_id(), userInfo->id(), data, cmd.has_is_public());
"banner_card_name=:banner_card_name, banner_card_provider=:banner_card_provider, " if (writeResult.result == Server_DatabaseInterface::DeckStorageResult::NotFound) {
"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) {
return Response::RespNameNotFound; return Response::RespNameNotFound;
} }
if (writeResult.result != Server_DatabaseInterface::DeckStorageResult::Ok) {
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)) {
return Response::RespContextError; 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 { } else {
return Response::RespInvalidData; 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; return Response::RespOk;
} }