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 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; diff --git a/src/backend/models/moderation.cpp b/src/backend/models/moderation.cpp index c39c47fb..c68b3986 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 1131fa3f..73570a59 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -68,12 +68,14 @@ SuggestedRecipeDetails tag_invoke(json::value_to_tag /*t .name = value_to(j.at("name")), .link = value_to(j.at("sourceLink")), // 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, + .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")) + : moderation::PublicationRequestStatusStruct{.status = PublicationRequestStatus::NO_REQUEST, + .reason = std::nullopt}, + }; } @@ -86,15 +88,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 32c4c4ec..4128bc77 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -55,7 +55,7 @@ struct SuggestedRecipeDetails { std::string name; std::optional link; std::optional creator; - moderation::PublicationRequestStatus moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST; + moderation::PublicationRequestStatusStruct moderationStatus; friend SuggestedRecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); @@ -93,10 +93,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..4085d515 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,25 @@ 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"⚠️Что-то пошло не так, вероятно ваш рецепт содержит неопубликованные ингредиенты"); + bot.answerCallbackQuery(cq.id); + } // 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..4f05e7ca 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{}; } @@ -74,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/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 8f4efb51..4dbd5654 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -35,13 +35,18 @@ std::pair, std::string> renderCustomRecipe( ingredients.push_back({.id = it.id, .name = it.name}); } - 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{}; + // 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 == 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 71dbc1ed..b949e414 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -187,6 +187,7 @@ struct CustomRecipePublicationHistory { api::RecipeId recipeId; std::size_t pageNo; std::string recipeName; + std::string errorReport; }; struct RecipesSearch {