From 2b0f13a9058933ec21cfd4b20edb0cf492b25824 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Tue, 22 Jul 2025 19:40:21 +0300 Subject: [PATCH 1/5] fix: rework of moderation history for new backend signature --- src/backend/api/recipes.cpp | 5 +++++ src/backend/api/recipes.hpp | 2 ++ src/backend/models/moderation.cpp | 8 ++++---- src/backend/models/moderation.hpp | 3 +-- .../models/publication_request_status.cpp | 17 +++++++++++++++-- .../models/publication_request_status.hpp | 9 +++++++++ src/backend/models/recipe.cpp | 17 +++++++++-------- src/backend/models/recipe.hpp | 5 ++--- .../recipe/moderation_history.cpp | 19 ++++++++++++------- src/handlers/personal_account/recipe/view.cpp | 5 +++-- .../personal_account/publication_history.cpp | 6 +++--- .../recipe/moderation_history.cpp | 17 +++++++++++------ .../recipe/moderation_history.hpp | 1 + src/render/personal_account/recipe/view.cpp | 7 ++++--- src/states.hpp | 1 + 15 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 3f16fb5c..d239796e 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -77,4 +77,9 @@ std::vector RecipesApi::getRecipeRequestHistory(UserId user, std::format("/recipes/{}/publication-requests", recipe)); } +// GET /recipes/{id}/can-publish +bool RecipesApi::canPublish(UserId user, RecipeId recipe) const { + return jsonGetAuthed(user, std::format("/recipes/{}/publication-requests", recipe)); +} + } // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 187c2de0..d815237b 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -47,6 +47,8 @@ class RecipesApi : ApiBase { void delete_(UserId user, RecipeId recipe) const; void publishCustom(UserId user, RecipeId recipe) const; + + [[nodiscard]] bool canPublish(UserId user, RecipeId recipe) const; }; using RecipesApiRef = const api::RecipesApi&; diff --git a/src/backend/models/moderation.cpp b/src/backend/models/moderation.cpp index 5432f70e..972fb2fe 100644 --- a/src/backend/models/moderation.cpp +++ b/src/backend/models/moderation.cpp @@ -17,14 +17,14 @@ PublicationRequest tag_invoke(json::value_to_tag /*tag*/, co return { .name = value_to(j.at("name")), .requestType = value_to(j.at("requestType")), - .status = j.as_object().if_contains("status") ? value_to(j.at("status")) - : PublicationRequestStatus::NO_REQUEST, + .status = j.as_object().if_contains("status") + ? value_to(j.at("status")) + : PublicationRequestStatusStruct{.status = PublicationRequestStatus::NO_REQUEST, + .reason = std::nullopt}, .created = utils::parseIsoTime(value_to(j.at("createdAt"))), .updated = j.as_object().if_contains("updatedAt") ? std::optional{utils::parseIsoTime(value_to(j.at("updatedAt")))} : std::nullopt, - .reason = j.as_object().if_contains("reason") ? value_to(j.at("reason")) - : std::nullopt, }; } diff --git a/src/backend/models/moderation.hpp b/src/backend/models/moderation.hpp index c39e7308..812c567e 100644 --- a/src/backend/models/moderation.hpp +++ b/src/backend/models/moderation.hpp @@ -13,10 +13,9 @@ namespace cookcookhnya::api::models::moderation { struct PublicationRequest { std::string name; std::string requestType; - PublicationRequestStatus status = PublicationRequestStatus::NO_REQUEST; + PublicationRequestStatusStruct status; std::chrono::system_clock::time_point created; std::optional updated; - std::optional reason; friend PublicationRequest tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/backend/models/publication_request_status.cpp b/src/backend/models/publication_request_status.cpp index e21abc5f..a21cfb40 100644 --- a/src/backend/models/publication_request_status.cpp +++ b/src/backend/models/publication_request_status.cpp @@ -2,10 +2,11 @@ #include "utils/utils.hpp" +#include #include #include - -#include +#include +#include #include #include @@ -20,6 +21,18 @@ PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*unused*/, + const boost::json::value& j) { + if (j.is_object()) { + return {.status = j.as_object().if_contains("type") ? value_to(j.at("type")) + : PublicationRequestStatus::NO_REQUEST, + .reason = j.as_object().if_contains("reason") + ? value_to(j.at("reason")) + : std::nullopt}; + } + return {.status = PublicationRequestStatus::NO_REQUEST, .reason = std::nullopt}; } } // namespace cookcookhnya::api::models::moderation diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp index 49c46e72..6ad5ac68 100644 --- a/src/backend/models/publication_request_status.hpp +++ b/src/backend/models/publication_request_status.hpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace cookcookhnya::api::models::moderation { @@ -12,6 +13,14 @@ enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); +struct PublicationRequestStatusStruct { + PublicationRequestStatus status = PublicationRequestStatus::NO_REQUEST; + std::optional reason; + + friend PublicationRequestStatusStruct tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); +}; + } // namespace cookcookhnya::api::models::moderation namespace cookcookhnya::utils { diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 1b6666a9..6c6e314d 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -58,9 +58,11 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: // Deal with optionals using ternary operator .creator = j.as_object().if_contains("creator") ? value_to(j.at("creator")) : std::nullopt, - .moderationStatus = j.as_object().if_contains("moderationStatus") - ? value_to(j.at("moderationStatus")) - : PublicationRequestStatus::NO_REQUEST, + .moderationStatus = + j.as_object().if_contains("moderationStatus") + ? value_to(j.at("moderationStatus")) + : moderation::PublicationRequestStatusStruct{.status = PublicationRequestStatus::NO_REQUEST, + .reason = std::nullopt}, }; } @@ -73,15 +75,14 @@ RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/ RecipePublicationRequest tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { - .status = j.as_object().if_contains("status") ? value_to(j.at("status")) - : moderation::PublicationRequestStatus::NO_REQUEST, + .status = j.as_object().if_contains("status") + ? value_to(j.at("status")) + : moderation::PublicationRequestStatusStruct{.status = PublicationRequestStatus::NO_REQUEST, + .reason = std::nullopt}, .created = utils::parseIsoTime(value_to(j.at("createdAt"))), .updated = j.as_object().if_contains("updatedAt") ? std::optional{utils::parseIsoTime(value_to(j.at("updatedAt")))} : std::nullopt, - .reason = j.as_object().if_contains("reason") - ? value_to(j.at("reason")) - : std::nullopt, }; } diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 03d78cee..ab2d8f75 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -45,7 +45,7 @@ struct RecipeDetails { std::string name; std::optional link; std::optional creator; - moderation::PublicationRequestStatus moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST; + moderation::PublicationRequestStatusStruct moderationStatus; friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; @@ -82,10 +82,9 @@ struct RecipeSearchResponse { }; struct RecipePublicationRequest { - moderation::PublicationRequestStatus status = moderation::PublicationRequestStatus::NO_REQUEST; + moderation::PublicationRequestStatusStruct status; std::chrono::system_clock::time_point created; std::optional updated; - std::optional reason; friend RecipePublicationRequest tag_invoke(boost::json::value_to_tag, const boost::json::value& j); diff --git a/src/handlers/personal_account/recipe/moderation_history.cpp b/src/handlers/personal_account/recipe/moderation_history.cpp index 0031b92f..f8b91bf7 100644 --- a/src/handlers/personal_account/recipe/moderation_history.cpp +++ b/src/handlers/personal_account/recipe/moderation_history.cpp @@ -17,7 +17,7 @@ void handleCustomRecipePublicationHistoryCQ( if (data == "backFromRules") { auto history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); - renderPublicationHistory(userId, chatId, state.recipeName, history, bot); + renderPublicationHistory(userId, chatId, state.recipeName, state.errorReport, history, bot); bot.answerCallbackQuery(cq.id); return; } @@ -36,19 +36,24 @@ void handleCustomRecipePublicationHistoryCQ( } if (data == "confirm") { - // Peeking (if button with this data then accepted or pending) - auto history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); + auto history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); // Here check for emptiness first, thanks to lazy compilator - const bool shouldPublish = - history.empty() || (history.back().status == api::models::moderation::PublicationRequestStatus::REJECTED); + const bool shouldPublish = history.empty() || (history.back().status.status == + api::models::moderation::PublicationRequestStatus::REJECTED); if (shouldPublish) { - api.getRecipesApi().publishCustom(userId, state.recipeId); + try { + api.getRecipesApi().publishCustom(userId, state.recipeId); + state.errorReport = ""; + } catch (...) { + state.errorReport = + utils::utf8str(u8"⚠️Что-то пошло не так, вероятно ваш рецепт содержит неопубликованные ингредиенты"); + } // Get updated history history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); } - renderPublicationHistory(userId, chatId, state.recipeName, history, bot); + renderPublicationHistory(userId, chatId, state.recipeName, state.errorReport, history, bot); bot.answerCallbackQuery(cq.id); return; } diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index 409c4314..e732c116 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -51,9 +51,10 @@ void handleRecipeCustomViewCQ( if (data == "publish") { auto history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); - renderPublicationHistory(userId, chatId, state.recipeName, history, bot); + std::string temp; + renderPublicationHistory(userId, chatId, state.recipeName, temp, history, bot); stateManager.put(CustomRecipePublicationHistory{ - .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); + .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName, .errorReport = ""}); bot.answerCallbackQuery(cq.id); return; } diff --git a/src/render/personal_account/publication_history.cpp b/src/render/personal_account/publication_history.cpp index 5e18ff69..2d41bb9a 100644 --- a/src/render/personal_account/publication_history.cpp +++ b/src/render/personal_account/publication_history.cpp @@ -28,9 +28,9 @@ void renderRequestHistory(UserId userId, else rcpIngRender = utils::utf8str(u8"🥬"); toPrint += std::format( - "{} {}: *{}* статус: {} ", rcpIngRender, req.requestType, req.name, utils::to_string(req.status)); - if (req.reason.has_value()) - toPrint += std::format("по причине: {} ", req.reason.value()); + "{} {}: *{}* статус: {} ", rcpIngRender, req.requestType, req.name, utils::to_string(req.status.status)); + if (req.status.reason.has_value()) + toPrint += std::format("по причине: {} ", req.status.reason.value()); toPrint += std::format("запрос создан: {} ", utils::to_string(req.created)); if (req.updated.has_value()) { toPrint += std::format("последенее обновление: {}", utils::to_string(req.updated.value())); diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index 23190baa..9910e48d 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -19,6 +19,7 @@ using namespace std::views; void renderPublicationHistory(UserId userId, ChatId chatId, std::string& recipeName, + std::string& errorReport, std::vector history, BotRef bot) { @@ -29,11 +30,13 @@ void renderPublicationHistory(UserId userId, toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n\n"); if (!history.empty()) { // Show confirm only if in those states - isConfirm = history[history.size() - 1].status == api::models::moderation::PublicationRequestStatus::REJECTED; + isConfirm = + history[history.size() - 1].status.status == api::models::moderation::PublicationRequestStatus::REJECTED; const size_t lastUpdatedInstance = history.size() - 1; // Construct current status string - toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdatedInstance].status); - if (auto reason = history[lastUpdatedInstance].reason) { + toPrint += + utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdatedInstance].status.status); + if (auto reason = history[lastUpdatedInstance].status.reason) { toPrint += std::format(" по причине {}", reason.value()); } toPrint += " " + utils::to_string(history[lastUpdatedInstance].created) + "\n\n"; @@ -42,9 +45,9 @@ void renderPublicationHistory(UserId userId, history.erase(history.end()); for (auto& req : history | reverse) { - toPrint += std::format("Статус: {} ", utils::to_string(req.status)); - if (req.reason.has_value()) - toPrint += std::format("по причине: {} ", req.reason.value()); + toPrint += std::format("Статус: {} ", utils::to_string(req.status.status)); + if (req.status.reason.has_value()) + toPrint += std::format("по причине: {} ", req.status.reason.value()); toPrint += std::format("запрос создан: {} ", utils::to_string(req.created)); if (req.updated.has_value()) { toPrint += std::format("последенее обновление: {}", utils::to_string(req.updated.value())); @@ -53,6 +56,8 @@ void renderPublicationHistory(UserId userId, } } + toPrint += errorReport; + if (isConfirm) { keyboard << makeCallbackButton(u8"▶️Подтвердить", "confirm") << NewRow{}; } diff --git a/src/render/personal_account/recipe/moderation_history.hpp b/src/render/personal_account/recipe/moderation_history.hpp index 6965fe34..9913d1a2 100644 --- a/src/render/personal_account/recipe/moderation_history.hpp +++ b/src/render/personal_account/recipe/moderation_history.hpp @@ -8,6 +8,7 @@ namespace cookcookhnya::render::personal_account::recipe { void renderPublicationHistory(UserId userId, ChatId chatId, std::string& recipeName, + std::string& errorReport, std::vector history, BotRef bot); void renderPublicationRules(UserId userId, ChatId chatId, BotRef bot); diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 077615d2..09cbc771 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -38,13 +38,14 @@ std::pair, std::string> renderCustomRecipe( }); } - toPrint += "\n🌐 [Статус проверки] " + utils::to_string(recipeDetails.moderationStatus); + toPrint += "\n🌐 [Статус проверки] " + utils::to_string(recipeDetails.moderationStatus.status); keyboard << makeCallbackButton(u8"🚮 Удалить", "delete") << NewRow{}; keyboard << makeCallbackButton(u8"✏️ Редактировать", "change") << NewRow{}; + // Show publish button only iff the status is not emty AND not rejected - if (recipeDetails.moderationStatus == PublicationRequestStatus::NO_REQUEST || - recipeDetails.moderationStatus == PublicationRequestStatus::REJECTED) { + if (recipeDetails.moderationStatus.status == PublicationRequestStatus::NO_REQUEST || + recipeDetails.moderationStatus.status == PublicationRequestStatus::REJECTED) { keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; } else { keyboard << makeCallbackButton(u8"📢 История публикаций", "publish") << NewRow{}; diff --git a/src/states.hpp b/src/states.hpp index cbbeec0a..4d8bf83d 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -190,6 +190,7 @@ struct CustomRecipePublicationHistory { api::RecipeId recipeId; std::size_t pageNo; std::string recipeName; + std::string errorReport; }; struct TotalPublicationHistory { From 251d2a653de9e3f4cd10abff84806192e4dff89f Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Tue, 22 Jul 2025 19:41:59 +0300 Subject: [PATCH 2/5] refactor: remove redundant function --- src/backend/api/recipes.cpp | 5 ----- src/backend/api/recipes.hpp | 2 -- 2 files changed, 7 deletions(-) diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index d239796e..3f16fb5c 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -77,9 +77,4 @@ std::vector RecipesApi::getRecipeRequestHistory(UserId user, std::format("/recipes/{}/publication-requests", recipe)); } -// GET /recipes/{id}/can-publish -bool RecipesApi::canPublish(UserId user, RecipeId recipe) const { - return jsonGetAuthed(user, std::format("/recipes/{}/publication-requests", recipe)); -} - } // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index d815237b..187c2de0 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -47,8 +47,6 @@ class RecipesApi : ApiBase { void delete_(UserId user, RecipeId recipe) const; void publishCustom(UserId user, RecipeId recipe) const; - - [[nodiscard]] bool canPublish(UserId user, RecipeId recipe) const; }; using RecipesApiRef = const api::RecipesApi&; From 8a866d3d07eab44c801930c4ff175a093ee26721 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Tue, 22 Jul 2025 20:19:03 +0300 Subject: [PATCH 3/5] i forgot what i did --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 360f7be0..4212e21b 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ build/Release/CMakeCache.txt: conanfile.txt build-debug: build/Debug/CMakeCache.txt cmake --build --preset=conan-debug build-debug-j5: build/Release/CMakeCache.txt - cmake --build . --preset=conan-debug -- -j3 + cmake --build . --preset=conan-debug -- -j2 build-release: build/Release/CMakeCache.txt cmake --build --preset=conan-release From 84a63c23a9af1ecac9f54799dcb588fce08380e2 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Wed, 23 Jul 2025 14:49:04 +0300 Subject: [PATCH 4/5] fix: recipe history for new backend --- src/handlers/personal_account/recipe/moderation_history.cpp | 1 + src/render/personal_account/recipe/moderation_history.cpp | 5 +++-- src/render/personal_account/recipe/view.cpp | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/handlers/personal_account/recipe/moderation_history.cpp b/src/handlers/personal_account/recipe/moderation_history.cpp index f8b91bf7..4085d515 100644 --- a/src/handlers/personal_account/recipe/moderation_history.cpp +++ b/src/handlers/personal_account/recipe/moderation_history.cpp @@ -49,6 +49,7 @@ void handleCustomRecipePublicationHistoryCQ( } catch (...) { state.errorReport = utils::utf8str(u8"⚠️Что-то пошло не так, вероятно ваш рецепт содержит неопубликованные ингредиенты"); + bot.answerCallbackQuery(cq.id); } // Get updated history history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index 9910e48d..4f05e7ca 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -79,8 +79,9 @@ void renderPublicationRules(UserId userId, ChatId chatId, BotRef bot) { "ингредиенты, отклонённые администрацией.\n3. *Название рецепта*\n - Не должно содержать " "нецензурную лексику, оскорбления или спам;\n - Должно точно отражать содержание рецепта (например: " "\"Спагетти карбонара\", а не \"Вкуснятина\"). \n4. *Дополнительно*\n - Запрещено размещать контактные " - "данные или рекламу." + - utils::utf8str(u8"\n⚠️ Нарушение правил приведёт к отклонению рецепта "); + "данные или рекламу;\n - Вы не сможете редактировать рецепт с статусом " + + utils::utf8str(u8"\"🟢 Принят\" и \"🟡 На рассмотрении\""); + utils::utf8str(u8"\n⚠️ Нарушение правил приведёт к отклонению рецепта "); InlineKeyboardBuilder keyboard{1}; // back keyboard << makeCallbackButton(u8"↩️ Назад", "backFromRules"); diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 09cbc771..989918bd 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -41,7 +41,11 @@ std::pair, std::string> renderCustomRecipe( toPrint += "\n🌐 [Статус проверки] " + utils::to_string(recipeDetails.moderationStatus.status); keyboard << makeCallbackButton(u8"🚮 Удалить", "delete") << NewRow{}; - keyboard << makeCallbackButton(u8"✏️ Редактировать", "change") << NewRow{}; + // Allow to edit recipe only if no request was made or it was rejected + if (recipeDetails.moderationStatus.status == PublicationRequestStatus::NO_REQUEST || + recipeDetails.moderationStatus.status == PublicationRequestStatus::REJECTED) { + keyboard << makeCallbackButton(u8"✏️ Редактировать", "change") << NewRow{}; + } // Show publish button only iff the status is not emty AND not rejected if (recipeDetails.moderationStatus.status == PublicationRequestStatus::NO_REQUEST || From fa36963a0d3bb7991bca636a70465ae4f8533d2b Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Wed, 23 Jul 2025 16:21:10 +0300 Subject: [PATCH 5/5] lint v1 --- src/backend/models/ingredient.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index 7aea86ff..7d658981 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -6,8 +6,6 @@ #include #include -#include - namespace cookcookhnya::api::models::ingredient { namespace json = boost::json;