From df09b16dbc620193e98365215250e0ea0c66093e Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Wed, 16 Jul 2025 23:39:47 +0300 Subject: [PATCH 001/106] feat (WIP): shopping list improvement --- src/render/shopping_list/view.cpp | 26 ++++++++++++++++++++++---- src/states.hpp | 6 ++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 082f0515..24a1ab99 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -6,20 +6,38 @@ #include "utils/to_string.hpp" #include "utils/utils.hpp" +#include +#include +#include #include #include namespace cookcookhnya::render::shopping_list { void renderShoppingList(const states::ShoppingListView::ItemsDb::Set& items, UserId userId, ChatId chatId, BotRef bot) { - InlineKeyboard keyboard(1 + items.size()); - for (auto [i, item] : std::views::enumerate(items)) - keyboard[i].push_back(makeCallbackButton(item.name, utils::to_string(item.ingredientId))); + bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); + + InlineKeyboard keyboard; + keyboard.reserve(3 + items.size()); // add, remove and/or buy, list (n), back + auto buttonRowIter = std::back_inserter(keyboard); + + (*buttonRowIter).push_back(makeCallbackButton(u8"Поиск", "search")); + if (anySelected) { + buttonRowIter++; + keyboard[buttonRowIter].push_back(makeCallbackButton(u8"Убрать", "remove")); + keyboard[buttonRowIter++].push_back(makeCallbackButton(u8"Купить", "buy")); + } + + for (auto [i, item] : std::views::enumerate(items)) { + const char* const selectedMark = item.selected ? "[+] " : "[ ] "; + keyboard[buttonRowIter++].push_back( + makeCallbackButton(selectedMark + item.name, utils::to_string(item.ingredientId))); + } keyboard[items.size()].push_back(makeCallbackButton(u8"↩️ Назад", "back")); auto messageId = message::getMessageId(userId); if (messageId) { auto text = utils::utf8str(u8"🔖 Ваш список покупок. Нажмите на элемент, чтобы вычеркнуть из списка."); - bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); } } diff --git a/src/states.hpp b/src/states.hpp index d5a2418d..6e1b751d 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -114,8 +114,10 @@ struct RecipeIngredientsSearch { }; struct ShoppingListView { - using ItemsDb = utils::FastSortedDb; + struct SelectableItem : api::models::shopping_list::ShoppingListItem { + bool selected = false; + }; + using ItemsDb = utils::FastSortedDb; ItemsDb items; }; From dc361c368330082cba80cd48081af224de3cc57b Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Thu, 17 Jul 2025 00:18:17 +0300 Subject: [PATCH 002/106] recipe view, storage addition and shopping list creation rework --- src/backend/api/common.hpp | 10 +- src/backend/api/ingredients.cpp | 2 +- src/backend/api/ingredients.hpp | 2 +- src/backend/api/recipes.cpp | 2 +- src/backend/api/recipes.hpp | 2 +- .../recipes_list/recipe/view.cpp | 2 +- src/handlers/recipe/view.cpp | 8 +- src/handlers/recipes_suggestions/view.cpp | 59 ++++++- src/handlers/recipes_suggestions/view.hpp | 7 + src/handlers/storage/ingredients/view.cpp | 5 +- src/patched_bot.cpp | 14 +- src/patched_bot.hpp | 15 +- .../ingredients_list/publish.cpp | 2 +- .../ingredients_list/view.cpp | 2 +- .../recipe/search_ingredients.cpp | 6 +- .../recipe/search_ingredients.hpp | 2 +- .../recipes_list/recipe/view.cpp | 8 +- .../personal_account/recipes_list/view.cpp | 16 +- src/render/recipe/add_storage.cpp | 152 +++++++----------- src/render/recipe/add_storage.hpp | 24 ++- src/render/recipe/view.cpp | 136 ++++------------ src/render/recipe/view.hpp | 18 +-- src/render/recipes_suggestions/view.cpp | 14 +- src/render/storage/ingredients/view.cpp | 2 - src/render/storage/members/add.cpp | 4 +- src/render/storage/members/delete.cpp | 2 +- src/render/storage/members/view.cpp | 2 +- src/states.hpp | 1 + 28 files changed, 245 insertions(+), 274 deletions(-) diff --git a/src/backend/api/common.hpp b/src/backend/api/common.hpp index c160cfd1..8a2c1eee 100644 --- a/src/backend/api/common.hpp +++ b/src/backend/api/common.hpp @@ -5,15 +5,15 @@ #include #include -enum struct filterType : std::uint8_t { All, Custom, Public }; +enum struct FilterType : std::uint8_t { All, Custom, Public }; -inline std::string filterStr(filterType value) { - static const std::unordered_map map = { - {filterType::All, "All"}, {filterType::Custom, "Custom"}, {filterType::Public, "Public"}}; +inline std::string filterStr(FilterType value) { + static const std::unordered_map map = { + {FilterType::All, "All"}, {FilterType::Custom, "Custom"}, {FilterType::Public, "Public"}}; auto it = map.find(value); if (it != map.end()) { return it->second; } - throw std::invalid_argument("Invalid filterType value"); + throw std::invalid_argument("Invalid FilterType value"); } diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index 639c06ad..e75418ee 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -83,7 +83,7 @@ IngredientSearchResponse IngredientsApi::search(UserId user, std::size_t threshold, std::size_t size, std::size_t offset, - filterType filter) const { + FilterType filter) const { return jsonGetAuthed(user, "/ingredients", {{"query", std::move(query)}, diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index f918a55a..0eeb90d6 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -50,7 +50,7 @@ class IngredientsApi : ApiBase { std::size_t threshold = 50, // NOLINT(*magic*) std::size_t size = 2, std::size_t offset = 2, - filterType filter = filterType::All) const; + FilterType filter = FilterType::All) const; [[nodiscard]] models::ingredient::Ingredient getPublicIngredient(IngredientId ingredient) const; diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 737df48b..9de4ebd4 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -35,7 +35,7 @@ RecipeSearchResponse RecipesApi::getRecipesList(UserId user, std::size_t threshold, std::size_t size, std::size_t offset, - filterType filter) const { + FilterType filter) const { return jsonGetAuthed(user, "/recipes", {{"query", std::move(query)}, diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 8b42dc58..c5a12011 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -29,7 +29,7 @@ class RecipesApi : ApiBase { std::size_t threshold, std::size_t size, std::size_t offset, - filterType filter) const; + FilterType filter) const; [[nodiscard]] models::recipe::RecipeDetails getIngredientsInRecipe(UserId user, RecipeId recipe) const; diff --git a/src/handlers/personal_account/recipes_list/recipe/view.cpp b/src/handlers/personal_account/recipes_list/recipe/view.cpp index 0654169e..884a3181 100644 --- a/src/handlers/personal_account/recipes_list/recipe/view.cpp +++ b/src/handlers/personal_account/recipes_list/recipe/view.cpp @@ -24,7 +24,7 @@ void handleRecipeCustomViewCQ( if (data == "change") { // Reuse search render - renderStorageIngredientsSearch(chatId, userId, bot); + renderRecipeIngredientsSearch(chatId, userId, bot); stateManager.put(CustomRecipeIngredientsSearch{ .recipeId = state.recipeId, .shownIngredients = {}, .totalFound = 0, .pageNo = 0}); return; diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 9acf0ee6..9622e9b8 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/recipe/add_storage.hpp" #include "render/recipes_suggestions/view.hpp" @@ -22,10 +23,7 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe // TODO: add state of begginig of cooking return; } - if (data == "make_product_list") { - // Next lines of code is necessary preparation for making product list. Need to re-verify products which are - // certanly not in storage - // Besides we exactly need ingredient Ids, as then it will be sent on backend + if (data == "shopping_list") { const std::unordered_set storageIdsSet(state.storageIds.begin(), state.storageIds.end()); auto recipesApi = api.getRecipesApi(); @@ -63,7 +61,7 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe return; } - if (data[0] == '?') { + if (data == "add_storages") { renderStoragesSuggestion(state.storageIds, state.recipeId, userId, chatId, bot, api); stateManager.put(RecipeStorageAddition{.storageIds = state.storageIds, .recipeId = state.recipeId, diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index fdef7c22..14d7fb4e 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -1,16 +1,18 @@ #include "view.hpp" #include "backend/id_types.hpp" +#include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" #include "render/recipe/view.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/storage/view.hpp" #include "render/storages_selection/view.hpp" -#include "states.hpp" #include "utils/parsing.hpp" #include +#include #include namespace cookcookhnya::handlers::recipes_suggestions { @@ -21,6 +23,56 @@ using namespace render::storage; using namespace render::recipe; using namespace render::main_menu; +namespace { + +using namespace api; + +std::vector> inStoragesAvailability(std::vector& selectedStorages, const RecipeId recipeId, UserId userId, ApiClientRef api){ + auto allStorages = api.getStoragesApi().getStoragesList(userId); + auto recipe = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); + + std::unordered_set selectedStoragesSet(selectedStorages.begin(), selectedStorages.end()); + + std::unordered_map allStoragesMap; + for (const auto& storage : allStorages) { + allStoragesMap.emplace(storage.id, storage); + } + + std::vector> result; + + for (const auto& ingredient : recipe.ingredients) { + IngredientAvailability availability; + std::vector otherStorages; + + bool hasInSelected = false; + + for (const auto& storageId : ingredient.inStorages) { + auto it = allStoragesMap.find(storageId); + if (it == allStoragesMap.end()) + continue; + if (selectedStoragesSet.contains(storageId)) { + hasInSelected = true; + } else { + otherStorages.push_back(it->second); + } + } + + if (hasInSelected) { + availability.available = AvailabiltiyType::available; + } else if (!otherStorages.empty()) { + availability.available = AvailabiltiyType::other_storages; + availability.storages = std::move(otherStorages); + } else { + availability.available = AvailabiltiyType::not_available; + } + + result.emplace_back(ingredient, std::move(availability)); + } + + return result; +} +} // namespace + void handleSuggestedRecipeListCQ( SuggestedRecipeList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); @@ -51,7 +103,8 @@ void handleSuggestedRecipeListCQ( auto recipeId = utils::parseSafe( data.substr(data.find(' ', 0) + 1, data.size())); // +1 is to move from space and get pure number if (recipeId) { - renderRecipeView(state.storageIds, *recipeId, userId, chatId, bot, api); + auto inStorage = inStoragesAvailability(state.storageIds, *recipeId, userId, api); + renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); stateManager.put(RecipeView{.storageIds = state.storageIds, .recipeId = *recipeId, .fromStorage = state.fromStorage, @@ -62,7 +115,7 @@ void handleSuggestedRecipeListCQ( } if (data != "dont_handle") { - auto pageNo = utils::parseSafe(data); + auto pageNo = utils::parseSafe(data); if (pageNo) { state.pageNo = *pageNo; } diff --git a/src/handlers/recipes_suggestions/view.hpp b/src/handlers/recipes_suggestions/view.hpp index 79cbc54f..0a0d61ef 100644 --- a/src/handlers/recipes_suggestions/view.hpp +++ b/src/handlers/recipes_suggestions/view.hpp @@ -4,6 +4,13 @@ namespace cookcookhnya::handlers::recipes_suggestions { +enum struct AvailabiltiyType : std::uint8_t { available, not_available, other_storages }; + +struct IngredientAvailability { + AvailabiltiyType available = AvailabiltiyType::not_available; + std::vector storages; +}; + void handleSuggestedRecipeListCQ( SuggestedRecipeList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index a089afc7..f7b4ac6b 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -59,8 +59,9 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, state.searchItems.clear(); renderIngredientsListSearch(state, userId, userId, bot); } else { - const std::size_t count = 100; - auto response = api.searchForStorage(userId, state.storageId, iq.query, count, 0); + const std::size_t count = 15; + auto response = api.searchForStorage(userId, state.storageId, iq.query, 50, count); // NOLINT (*magic*) + std::cerr << response.found << response.page[0].name; if (response.found != state.totalFound || !std::ranges::equal(response.page, state.searchItems, std::ranges::equal_to{}, diff --git a/src/patched_bot.cpp b/src/patched_bot.cpp index 1b2747ad..3ed47c55 100644 --- a/src/patched_bot.cpp +++ b/src/patched_bot.cpp @@ -46,7 +46,7 @@ void appendToJson(std::string& json, std::string_view varName, std::string_view appendToJson(result, "callback_data", object->callbackData); else if (!object->url.empty()) appendToJson(result, "url", object->url); - else if (!object->switchInlineQueryCurrentChat.empty()) + else appendToJson(result, "switch_inline_query_current_chat", object->switchInlineQueryCurrentChat); removeLastComma(result); result += '}'; @@ -79,13 +79,17 @@ void appendToJson(std::string& json, std::string_view varName, std::string_view TgBot::Message::Ptr // NOLINT(*nodiscard) PatchedBot::sendMessage(tg_types::ChatId chatId, std::string_view text, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup) const { + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + std::string_view parseMode) const { std::vector args; args.reserve(3); args.emplace_back("chat_id", chatId); args.emplace_back("text", text); if (replyMarkup) args.emplace_back("reply_markup", parseInlineKeyboardMarkup(replyMarkup)); + if (!parseMode.empty()) { + args.emplace_back("parse_mode", parseMode); + } return _tgTypeParser.parseJsonAndGetMessage(sendRequest("sendMessage", args)); } @@ -93,7 +97,8 @@ TgBot::Message::Ptr // NOLINT(*nodiscard) PatchedBot::editMessageText(std::string_view text, tg_types::ChatId chatId, tg_types::MessageId messageId, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup) const { + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + std::string_view parseMode) const { std::vector args; args.reserve(4); args.emplace_back("text", text); @@ -102,6 +107,9 @@ PatchedBot::editMessageText(std::string_view text, if (replyMarkup) { args.emplace_back("reply_markup", parseInlineKeyboardMarkup(replyMarkup)); } + if (!parseMode.empty()) { + args.emplace_back("parse_mode", parseMode); + } return _tgTypeParser.parseJsonAndGetMessage(sendRequest("editMessageText", args)); } diff --git a/src/patched_bot.hpp b/src/patched_bot.hpp index 805b7f23..0ddf3de0 100644 --- a/src/patched_bot.hpp +++ b/src/patched_bot.hpp @@ -30,31 +30,34 @@ class PatchedBot : TgBot::Api { TgBot::Message::Ptr sendMessage(tg_types::ChatId chatId, // NOLINT(*nodiscard) std::string_view text, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup) const; + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + std::string_view parseMode = "") const; // For code compatibility TgBot::Message::Ptr sendMessage(tg_types::ChatId chatId, // NOLINT(*nodiscard) std::string_view text, std::nullptr_t /*unused*/, std::nullptr_t /*unused*/, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup) const { - return sendMessage(chatId, text, replyMarkup); + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + std::string_view parseMode = "") const { + return sendMessage(chatId, text, replyMarkup, parseMode); } TgBot::Message::Ptr editMessageText(std::string_view text, // NOLINT(*nodiscard) tg_types::ChatId chatId, tg_types::MessageId messageId, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup) const; + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + std::string_view parseMode = "") const; // For code compatibility TgBot::Message::Ptr editMessageText(std::string_view text, // NOLINT(*nodiscard) tg_types::ChatId chatId, tg_types::MessageId messageId, const char* /*unused*/, - const char* /*unused*/, + const char* parseMode, std::nullptr_t, const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup) const { - return editMessageText(text, chatId, messageId, replyMarkup); + return editMessageText(text, chatId, messageId, replyMarkup, parseMode); } TgBot::Message::Ptr editMessageReplyMarkup(tg_types::ChatId chatId, // NOLINT(*nodiscard*) diff --git a/src/render/personal_account/ingredients_list/publish.cpp b/src/render/personal_account/ingredients_list/publish.cpp index c2a58c6d..37c70a93 100644 --- a/src/render/personal_account/ingredients_list/publish.cpp +++ b/src/render/personal_account/ingredients_list/publish.cpp @@ -16,7 +16,7 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - auto ingredientsResp = api.search(userId, "", 0, 1000, 0, filterType::Custom); // NOLINT (*magic*) + auto ingredientsResp = api.search(userId, "", 0, 1000, 0, FilterType::Custom); // NOLINT (*magic*) // TODO: make pagination for ingredients auto ingredients = ingredientsResp.page; const std::size_t buttonRows = ((ingredients.size() + 1) / 2) + 1; diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index 9980f692..96caed51 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -17,7 +17,7 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; void renderCustomIngredientsList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - auto ingredientsResp = api.search(userId, "", 0, 100, 0, filterType::Custom); // NOLINT(*magic*) + auto ingredientsResp = api.search(userId, "", 0, 100, 0, FilterType::Custom); // NOLINT(*magic*) auto ingredients = ingredientsResp.page; const std::size_t buttonRows = ingredients.empty() ? 2 : 3; InlineKeyboard keyboard(buttonRows); diff --git a/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp b/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp index 34f27e81..2d348d24 100644 --- a/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp +++ b/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp @@ -35,7 +35,7 @@ auto makeKeyboard(const std::vector& ingredients, keyboard[0].push_back(std::move(searchButton)); for (auto [row, ing] : zip(std::ranges::views::drop(keyboard, 1), ingredients)) - row.push_back(makeCallbackButton((ing.isInRecipe ? "[+] " : "[-] ") + ing.name, utils::to_string(ing.id))); + row.push_back(makeCallbackButton((ing.isInRecipe ? "[ + ] " : "[ - ] ") + ing.name, utils::to_string(ing.id))); keyboard[1 + ingredients.size()].push_back(makeCallbackButton(u8"↩️ Назад", "back")); @@ -54,10 +54,10 @@ void renderRecipeIngredientsSearchEdit(const std::vector(std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage))); + std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - const bool ifMaxPage = amountOfRecipes - numOfRecipesOnPage * pageNo + 1 <= - 0; // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that - // will be actually shown + const bool ifMaxPage = amountOfRecipes - numOfRecipesOnPage * pageNo + 1 <= 0; + // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that will be actually shown if (offset + recipesToShow >= fullKeyBoardSize) { InlineKeyboard error(0); @@ -43,7 +41,7 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, const size_t arrowsRow = offset + recipesToShow; InlineKeyboard keyboard(fullKeyBoardSize); - int counter = 0; + std::size_t counter = 0; for (std::size_t i = 0; i < recipesToShow; i++) { // Print on button in form "1. {Recipe}" keyboard[i + offset].push_back(makeCallbackButton( @@ -109,11 +107,11 @@ constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::R InlineKeyboard keyboard = recipesList.found == 0 ? constructOnlyCreate() - : constructNavigationsMarkup(offset, numOfRows + recipesToShow, pageNo, numOfRecipesOnPage, recipesList); + : constructNavigationsMarkup(offset, numOfRows + recipesToShow, (pageNo + 1), numOfRecipesOnPage, recipesList); if (keyboard.empty()) { // If error happened return keyboard; } - keyboard[0].push_back(makeCallbackButton(u8"Создать", "custom_recipe_create")); + keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "custom_recipe_create")); keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); @@ -127,7 +125,7 @@ void renderCustomRecipesList(size_t pageNo, UserId userId, ChatId chatId, BotRef const std::size_t numOfRecipesOnPage = 5; auto recipesList = - recipesApi.getRecipesList(userId, "", 0, numOfRecipesOnPage, pageNo * numOfRecipesOnPage, filterType::Custom); + recipesApi.getRecipesList(userId, "", 0, numOfRecipesOnPage, (pageNo + 1) * numOfRecipesOnPage, FilterType::Custom); bot.editMessageText( pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index 4929ff40..bb01279d 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -2,122 +2,94 @@ #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include "view.hpp" #include -#include -#include #include #include -#include #include #include namespace cookcookhnya::render::recipe { -std::vector storagesToShow(const std::vector& ingredients, - const std::vector& storageIdsToAccount) { - std::vector storageIdsToShow; +using namespace handlers::recipes_suggestions; - std::unordered_set toAdd; // If there will be only one element of storageId then remove - bool isFound = false; - for (const auto& ingredient : ingredients) { - isFound = false; // Iterate through each ingredient - for (const api::StorageId inStorage : - ingredient.inStorages) { // Iterate through each storage where ingredient is present - for (const api::StorageId stId : storageIdsToAccount) { - if (stId == inStorage) { - isFound = true; - break; - } - } - if (isFound) { - break; +textGenInfo storageAdditionView(const std::vector>& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ApiClient api){ + auto recipeIngredients = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); + + bool isIngredientNotAvailable = false; + bool isIngredientIsOtherStorages = false; + const std::string recipeName = recipeIngredients.name; + auto text = std::format("{} Выбранные хранилища: ", utils::utf8str(u8"🍱")); + for (std::size_t i = 0; i != selectedStorages.size(); ++i){ + text += selectedStorages[i].name; + text += i != selectedStorages.size() - 1 ? ", " : "\n"; } - } - if (!isFound) { - // Proof that ingredient doesn't have "toxic" storages. Toxic storage is a storage which has some - // ingredient so because of it other storages with that ingredient are not needed - // But storages may be redeemed if they are in set of storages of ingredient where there is no toxic one - for (const api::StorageId temp : ingredient.inStorages) { - toAdd.insert(temp); + text += std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); + + for (const auto& infoPair : inStoragesAvailability){ + if (infoPair.second.available == AvailabiltiyType::available){ + text += "`[+]` " + infoPair.first.name + "\n"; + } else if (infoPair.second.available == AvailabiltiyType::other_storages){ + text += "`[?]` " + infoPair.first.name + "\n"; + isIngredientIsOtherStorages = true; + text += "__Доступно в: "; + auto storages = infoPair.second.storages; + for (std::size_t i = 0; i != storages.size(); ++i){ + text += storages[i].name; + text += i != storages.size() - 1 ? ", " : "\n"; } + } else { + text += "`[ ]` " + infoPair.first.name + "\n"; + isIngredientNotAvailable = true; } } + text += "\n🌐 Источник: " + recipeIngredients.link; - storageIdsToShow.reserve(toAdd.size()); - for (auto add : toAdd) { - storageIdsToShow.push_back(add); - } - - return storageIdsToShow; + return {.text = text, .isIngredientNotAvailable = isIngredientNotAvailable, .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; } -void renderStoragesSuggestion(const std::vector& storageIdsToAccount, // storages which are selected - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api) { - - auto storageApi = api.getStoragesApi(); - - auto recipesApi = api.getRecipesApi(); - auto recipeIngredients = recipesApi.getIngredientsInRecipe(userId, recipeId); - auto ingredients = recipeIngredients.ingredients; - - const std::vector storageIdsToShow = storagesToShow(ingredients, storageIdsToAccount); - - const textGenInfo text = textGen(storageIdsToAccount, recipeIngredients, userId, api); - auto toPrint = text.text; - - auto suggestionStrings = text.foundInStoragesStrings; - size_t counterOfSuggestionsFound = 0; - bool ifSuggestionEcountered = false; - - // This for can be moved to distinct function - for (size_t i = 0; i < toPrint.size(); i++) { // Put suggestions here - if (toPrint[i] == '\n' && ifSuggestionEcountered) { - toPrint.insert(i + 1, suggestionStrings[counterOfSuggestionsFound]); - counterOfSuggestionsFound++; - ifSuggestionEcountered = false; - } - if (toPrint[i] == '?') { - ifSuggestionEcountered = true; - } - } - // This for is similar to suggested storages can be unionaized with this part of textGen (which will be incredibly - // difficult to keep consistency of textGen fenction) To print storages which were added - std::string storagesWhichAccount = utils::utf8str(u8"Выбранные хранилища: "); - for (size_t i = 0; i < storageIdsToAccount.size(); i++) { - auto storage = storageApi.get(userId, storageIdsToAccount[i]); - storagesWhichAccount += std::format("\"{}\" ", storage.name); - if (i == storageIdsToAccount.size() - 1) { - storagesWhichAccount += "\n"; - } - } - toPrint.insert(0, storagesWhichAccount); - const int buttonRows = std::floor(((storageIdsToShow.size() + 1) / 2) + 1); // +1 for back +void renderStoragesSuggestion(const std::vector>& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + ApiClient api) { + auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); + auto storages = api.getStoragesApi().getStoragesList(userId); + const size_t buttonRows = ((storages.size() + 1) / 2) + 1; InlineKeyboard keyboard(buttonRows); + + for (std::size_t i = 0; i < storages.size(); ++i) { + if (i % 2 == 0) + keyboard[i / 2].reserve(2); + const bool isSelected = std::ranges::find(selectedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != selectedStorages.end(); - uint64_t i = 0; - for (auto storageId : storageIdsToShow) { - const std::string name = storageApi.get(userId, storageId).name; - if (i % 2 == 0) { - keyboard[std::floor(i / 2)].reserve(2); - } - keyboard[std::floor(i / 2)].push_back(makeCallbackButton(name, "+" + utils::to_string(storageId))); - i++; + std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); + const char* actionPrefix = isSelected ? "in__" : "out_"; + const std::string text = std::format("{} {}", emoji, storages[i].name); + const std::string data = actionPrefix + utils::to_string(storages[i].id); + keyboard[i / 2].push_back(makeCallbackButton(text, data)); + } + keyboard[buttonRows - 1].reserve(2); + keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "cancel")); + if (!selectedStorages.empty()) { + keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); } - keyboard[std::floor((storageIdsToShow.size() + 1) / 2)].push_back( - makeCallbackButton(u8"↩️ Назад", "back_from_adding_storages")); + auto messageId = message::getMessageId(userId); if (messageId) { - bot.editMessageText(toPrint, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); + // Only on difference between function above + bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); } } } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index a63d420b..fa22a9d9 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -2,19 +2,27 @@ #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" +#include "handlers/recipes_suggestions/view.hpp" #include "render/common.hpp" +#include "render/recipe/view.hpp" #include namespace cookcookhnya::render::recipe { -void renderStoragesSuggestion(const std::vector& storageIdsToAccount, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api); +textGenInfo storageAdditionView(const std::vector>& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ApiClient api); -std::vector storagesToShow(const std::vector& ingredients, - const std::vector& storageIdsToAccount); +void renderStoragesSuggestion(const std::vector>& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + ApiClient api); + } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index f706876f..c6aad99b 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -2,137 +2,65 @@ #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" +#include "handlers/recipes_suggestions/view.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/utils.hpp" -#include -#include #include #include -#include #include #include namespace cookcookhnya::render::recipe { -textGenInfo textGen(const std::vector& storageIds, - const api::models::recipe::RecipeDetails& recipeIngredients, - UserId userId, - ApiClient api) { // will return needed text and some additional elements - - // Get two api's from apiClient - auto storageApi = api.getStoragesApi(); - const std::unordered_set storageIdSet(storageIds.begin(), storageIds.end()); +using namespace handlers::recipes_suggestions; - std::unordered_set suggestedStorageIds; - std::vector foundInStoragesStrings; - auto ingredients = recipeIngredients.ingredients; +textGenInfo recipeView(const std::vector>& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + ApiClient api) { + auto recipeIngredients = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); + bool isIngredientNotAvailable = false; + bool isIngredientIsOtherStorages = false; const std::string recipeName = recipeIngredients.name; - auto toPrint = std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); - std::vector variants = { - "\n", " и ", ", "}; // difference is 0 -> last, difference is 1 -> предпоследний. - - bool isContains = false; - bool isSuggestionMade = false; - bool isIngredientNotWritten = true; - bool isAtLeastOneIngredientLack = false; - size_t counterOfSuggestion = 0; - for (auto& ingredient : ingredients) { // Iterate through each ingredient - isIngredientNotWritten = true; - isContains = false; - if (ingredient.inStorages.empty()) { - toPrint += std::format("`[ ]` {}\n", ingredient.name); - isAtLeastOneIngredientLack = true; - continue; - } - - for (size_t j = 0; j < ingredient.inStorages.size(); - j++) { // Iterate through each storage where ingredient is present - if (storageIdSet.contains( - ingredient.inStorages[j])) { // If it contains then ingredient is in chosen storages - toPrint += std::format("`[+]` {}\n", ingredient.name); - isContains = true; - break; - } - } - - if (isContains) { - continue; - } - - for (size_t j = 0; j < ingredient.inStorages.size(); - j++) { // Iterate through each storage where ingredient is present - isSuggestionMade = true; - if (isIngredientNotWritten) { - toPrint += std::format("`[?]` {}\n", ingredient.name); - isIngredientNotWritten = false; - - foundInStoragesStrings.emplace_back(""); // New place for string for suggestion - - if (ingredient.inStorages.size() == 1) { - - foundInStoragesStrings[counterOfSuggestion] += utils::utf8str(u8" *Найдено в хранилище: "); - } else { - foundInStoragesStrings[counterOfSuggestion] += utils::utf8str(u8" *Найдено в хранилищах: "); - } - } - auto storage = storageApi.get(userId, ingredient.inStorages[j]); - - suggestedStorageIds.insert(ingredient.inStorages[j]); // Keep set of storages which will be suggested - foundInStoragesStrings[counterOfSuggestion] += // I felt myself genious after writing that line of code - // (here is one for, if-else and if technically) - std::format("\"{}\"{}", - storage.name, - variants[std::min(variants.size() - 1, ingredient.inStorages.size() - j - 1)]); + auto text = std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); + + for (const auto& infoPair : inStoragesAvailability){ + if (infoPair.second.available == AvailabiltiyType::available){ + text += "`[+]` " + infoPair.first.name + "\n"; + } else if (infoPair.second.available == AvailabiltiyType::other_storages){ + text += "`[?]` " + infoPair.first.name + "\n"; + isIngredientIsOtherStorages = true; + } else { + text += "`[ ]` " + infoPair.first.name + "\n"; + isIngredientNotAvailable = true; } - counterOfSuggestion++; // If here then suggesiton was made } - toPrint += "\n🌐 [Источник](" + recipeIngredients.link + ")"; - return {.text = toPrint, - .isSuggestionMade = isSuggestionMade, - .suggestedStorageIds = suggestedStorageIds, - .foundInStoragesStrings = foundInStoragesStrings, - .isAtLeastOneIngredientLack = - isAtLeastOneIngredientLack}; // Many info may be needed from that function to make right markup + text += "\n🌐 Источник: " + recipeIngredients.link; + + return {.text = text, .isIngredientNotAvailable = isIngredientNotAvailable, .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; } -void renderRecipeView(const std::vector& storageIds, +void renderRecipeView(const std::vector>& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, ApiClient api) { - - auto recipesApi = api.getRecipesApi(); - auto recipeIngredients = recipesApi.getIngredientsInRecipe(userId, recipeId); - const textGenInfo text = textGen(storageIds, recipeIngredients, userId, api); - - const bool isSuggestionMade = text.isSuggestionMade; - auto suggestedStorageIds = text.suggestedStorageIds; - auto toPrint = text.text; - const bool isAtLeastOneIngredientLack = text.isAtLeastOneIngredientLack; - - // if there is no lacking ingredients then there is no need to show field of shopping list - const size_t buttonRows = isAtLeastOneIngredientLack ? 3 : 2; + auto textGen = recipeView(inStoragesAvailability, recipeId, userId, api); + const size_t buttonRows = textGen.isIngredientNotAvailable ? 3 : 2; InlineKeyboard keyboard(buttonRows); - keyboard[0].push_back(makeCallbackButton(u8"🧑‍🍳 Готовить", - "start_cooking")); // Add needed info for next states! + keyboard[0].push_back(makeCallbackButton(u8"🧑‍🍳 Готовить", "start_cooking")); - if (isSuggestionMade) { - std::string dataForSuggestion = "?"; - for (auto id : suggestedStorageIds) { - dataForSuggestion += std::format("{} ", id); - } - keyboard[0].push_back(makeCallbackButton(u8"?", dataForSuggestion)); + if (textGen.isIngredientIsOtherStorages) { + keyboard[0].push_back(makeCallbackButton(u8"?", "add_storages")); } - - if (isAtLeastOneIngredientLack) { - keyboard[1].push_back(makeCallbackButton(u8"📝 Составить список продуктов", - "make_product_list")); // Add needed info for next states! + if (textGen.isIngredientNotAvailable) { + keyboard[1].push_back(makeCallbackButton(u8"📝 Составить список продуктов", "shopping_list")); } keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back_from_recipe_view")); @@ -140,7 +68,7 @@ void renderRecipeView(const std::vector& storageIds, auto messageId = message::getMessageId(userId); if (messageId) { // Only on difference between function above - bot.editMessageText(toPrint, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); } } diff --git a/src/render/recipe/view.hpp b/src/render/recipe/view.hpp index 9e7a6344..b1b8a27c 100644 --- a/src/render/recipe/view.hpp +++ b/src/render/recipe/view.hpp @@ -1,33 +1,29 @@ #pragma once #include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" +#include "handlers/recipes_suggestions/view.hpp" #include "render/common.hpp" -#include -#include + #include namespace cookcookhnya::render::recipe { struct textGenInfo { std::string text; - bool isSuggestionMade{}; - std::unordered_set suggestedStorageIds; - std::vector foundInStoragesStrings; - bool isAtLeastOneIngredientLack; + bool isIngredientNotAvailable; + bool isIngredientIsOtherStorages; }; -void renderRecipeView(const std::vector& storageIds, +void renderRecipeView(const std::vector>& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, ApiClient api); -// Helper functions -textGenInfo textGen(const std::vector& storageIds, - const api::models::recipe::RecipeDetails& recipeIngredients, +textGenInfo recipeView(const std::vector>& inStoragesAvailability, + api::RecipeId recipeId, UserId userId, ApiClient api); diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index e5224316..76b84948 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -19,12 +19,12 @@ namespace cookcookhnya::render::recipes_suggestions { // offset is variable which defines amout of rows before beggining of paging // fullKeyBoardSize is self explanatory -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t pageNo, - size_t numOfRecipesOnPage, +InlineKeyboard constructNavigationsMarkup(std::size_t offset, + std::size_t fullKeyBoardSize, + std::size_t pageNo, + std::size_t numOfRecipesOnPage, api::models::recipe::RecipesList recipesList) { - const size_t amountOfRecipes = recipesList.found; + const std::size_t amountOfRecipes = recipesList.found; std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); @@ -38,7 +38,7 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, const size_t arrowsRow = offset + recipesToShow; InlineKeyboard keyboard(fullKeyBoardSize); - int counter = 0; + std::size_t counter = 0; for (std::size_t i = 0; i < recipesToShow; i++) { // Print on button in form "1. {Recipe}" keyboard[i + offset].push_back( @@ -118,7 +118,7 @@ void renderRecipesSuggestion(const std::vector& storageIds, const size_t numOfRecipesOnPage = 5; - auto recipesList = recipesApi.getSuggestedRecipesList(userId, storageIds, 0, pageNo * numOfRecipesOnPage); + auto recipesList = recipesApi.getSuggestedRecipesList(userId, storageIds, (pageNo + 1) * numOfRecipesOnPage); if (messageId) { bot.editMessageText(pageInfo, diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 3390e04d..9a871132 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -32,7 +32,6 @@ auto makeKeyboard(const states::StorageIngredientsList& state) { searchButton->text = utils::utf8str(u8"✏️ Редактировать"); searchButton->switchInlineQueryCurrentChat = ""; keyboard[0].push_back(std::move(searchButton)); - for (auto [row, ing] : zip(drop(keyboard, 1), state.searchItems)) row.push_back(makeCallbackButton((ing.available ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id))); keyboard[1 + state.searchItems.size()].push_back(makeCallbackButton(u8"↩️ Назад", "back")); @@ -51,7 +50,6 @@ void renderIngredientsListSearch(const states::StorageIngredientsList& state, std::string list = state.storageIngredients.getAll() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); - auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); if (auto messageId = message::getMessageId(userId)) bot.editMessageText(text, chatId, *messageId, makeKeyboard(state)); diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index 27a97565..6378b21a 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -17,7 +17,7 @@ void renderStorageMemberAddition( const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); - const int buttonRows = 2; + const std::size_t buttonRows = 2; InlineKeyboard keyboard(buttonRows); keyboard[0].push_back(makeCallbackButton(u8"🔗 Создать ссылку", "create_link")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); @@ -33,7 +33,7 @@ void renderShareLinkMemberAddition( const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); - const int buttonRows = 2; + const std::size_t buttonRows = 2; InlineKeyboard keyboard(buttonRows); auto inviteButton = std::make_shared(); diff --git a/src/render/storage/members/delete.cpp b/src/render/storage/members/delete.cpp index 2a5bff41..a5584411 100644 --- a/src/render/storage/members/delete.cpp +++ b/src/render/storage/members/delete.cpp @@ -18,7 +18,7 @@ void renderStorageMemberDeletion( auto storage = storageApi.get(userId, storageId); auto members = storageApi.getStorageMembers(userId, storageId); - const unsigned int buttonRows = members.size(); + const std::size_t buttonRows = members.size(); InlineKeyboard keyboard(buttonRows); keyboard[0].push_back(makeCallbackButton(u8"↩️ Назад", "cancel_member_deletion")); size_t k = 1; diff --git a/src/render/storage/members/view.cpp b/src/render/storage/members/view.cpp index ae2e32f1..8009c32f 100644 --- a/src/render/storage/members/view.cpp +++ b/src/render/storage/members/view.cpp @@ -22,7 +22,7 @@ void renderMemberList(bool toBeEdited, StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const bool isOwner = storage.ownerId == userId; - const int buttonRows = isOwner ? 2 : 1; + const std::size_t buttonRows = isOwner ? 2 : 1; InlineKeyboard keyboard(buttonRows); diff --git a/src/states.hpp b/src/states.hpp index d5a2418d..c560debe 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -3,6 +3,7 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "backend/models/shopping_list.hpp" +#include "handlers/recipes_suggestions/view.cpp" #include "utils/fast_sorted_db.hpp" #include From ff0f2ed2b010197e066647814bcab5982f74efb8 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Thu, 17 Jul 2025 19:07:52 +0300 Subject: [PATCH 003/106] refactor: new storage additiong, recipe view, shopping list creation --- src/backend/api/recipes.cpp | 7 +- src/backend/api/recipes.hpp | 10 ++- src/handlers/main_menu/view.cpp | 10 +-- .../personal_account/recipes_list/view.cpp | 3 +- src/handlers/recipe/add_storage.cpp | 30 +++++--- src/handlers/recipe/view.cpp | 45 +++++------ src/handlers/recipes_suggestions/view.cpp | 76 +++---------------- src/handlers/recipes_suggestions/view.hpp | 7 -- src/handlers/shopping_list/create.cpp | 56 ++++++++------ src/handlers/storage/ingredients/view.cpp | 1 - src/handlers/storage/view.cpp | 10 ++- src/handlers/storages_selection/view.cpp | 21 ++--- src/patched_bot.cpp | 4 +- src/patched_bot.hpp | 2 +- .../recipe/search_ingredients.cpp | 9 ++- .../personal_account/recipes_list/view.cpp | 12 +-- src/render/recipe/add_storage.cpp | 66 ++++++++-------- src/render/recipe/add_storage.hpp | 33 ++++---- src/render/recipe/view.cpp | 29 +++---- src/render/recipe/view.hpp | 16 ++-- src/render/recipes_suggestions/view.cpp | 28 +++---- src/render/recipes_suggestions/view.hpp | 4 +- src/render/shopping_list/create.cpp | 69 +++++++---------- src/render/shopping_list/create.hpp | 10 +-- src/render/storages_selection/view.cpp | 12 +-- src/render/storages_selection/view.hpp | 3 +- src/states.hpp | 23 ++++-- src/utils/ingredients_availability.cpp | 63 +++++++++++++++ src/utils/ingredients_availability.hpp | 25 ++++++ 29 files changed, 362 insertions(+), 322 deletions(-) create mode 100644 src/utils/ingredients_availability.cpp create mode 100644 src/utils/ingredients_availability.hpp diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 9de4ebd4..72a9579b 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -3,6 +3,7 @@ #include "backend/api/common.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" #include "utils/parsing.hpp" #include "utils/to_string.hpp" @@ -20,12 +21,12 @@ using namespace models::recipe; // GET /suggested-recipes RecipesList RecipesApi::getSuggestedRecipesList(UserId user, - const std::vector& storages, + std::vector& storages, size_t size, size_t offset) const { httplib::Params params = {{"size", utils::to_string(size)}, {"offset", std::to_string(offset)}}; - for (auto id : storages) - params.insert({"storage-id", utils::to_string(id)}); + for (const auto& storage : storages) + params.insert({"storage-id", utils::to_string(storage.id)}); return jsonGetAuthed(user, "/suggested-recipes", params); } diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index c5a12011..0013ffdf 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -3,6 +3,7 @@ #include "backend/api/base.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" #include "common.hpp" #include @@ -19,10 +20,11 @@ class RecipesApi : ApiBase { explicit RecipesApi(httplib::Client& api) : ApiBase{api} {} public: - [[nodiscard]] models::recipe::RecipesList getSuggestedRecipesList(UserId user, - const std::vector& storages, - size_t size = 2, - size_t offset = 0) const; + [[nodiscard]] models::recipe::RecipesList + getSuggestedRecipesList(UserId user, + std::vector& storages, + size_t size = 2, + size_t offset = 0) const; [[nodiscard]] models::recipe::RecipeSearchResponse getRecipesList(UserId user, std::string query, diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index fd0a9f26..5c0ee006 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -1,7 +1,7 @@ #include "view.hpp" #include "backend/api/storages.hpp" -#include "backend/id_types.hpp" +#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/personal_account/view.hpp" #include "render/recipes_suggestions/view.hpp" @@ -32,13 +32,13 @@ void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SM } if (cq.data == "wanna_eat") { if (storages.size() == 1) { - auto storageId = {storages[0].id}; - renderRecipesSuggestion(storageId, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{.pageNo = 0, .storageIds = storageId, .fromStorage = false}); + std::vector storage = {storages[0]}; + renderRecipesSuggestion(storage, 0, userId, chatId, bot, api); + stateManager.put(SuggestedRecipeList{.pageNo = 0, .storages = storage, .fromStorage = false}); return; } renderStorageSelection({}, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = std::vector{}}); + stateManager.put(StoragesSelection{.storages = std::vector{}}); return; } if (cq.data == "shopping_list") { diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index 04104d82..e8766e0f 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -38,8 +38,7 @@ void handleCustomRecipesListCQ( } if (data[0] == 'r') { - auto recipeId = utils::parseSafe( - data.substr(data.find(' ', 0) + 1, data.size())); // +1 is to move from space and get pure number + auto recipeId = utils::parseSafe(data.substr(1, data.size())); if (recipeId) { renderCustomRecipe(true, userId, chatId, recipeId.value(), bot, api); stateManager.put(RecipeCustomView{.recipeId = recipeId.value(), .pageNo = state.pageNo}); diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp index 0e47bbce..ba12fa50 100644 --- a/src/handlers/recipe/add_storage.cpp +++ b/src/handlers/recipe/add_storage.cpp @@ -1,6 +1,7 @@ #include "add_storage.hpp" #include "backend/id_types.hpp" +#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/recipe/add_storage.hpp" #include "render/recipe/view.hpp" @@ -15,28 +16,39 @@ using namespace render::recipe; void handleRecipeStorageAdditionCQ( RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + bot.answerCallbackQuery(cq.id); std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; if (data[0] == '+') { - auto newStorageIdStr = - data.substr(1, data.size()); // Here we got all selected storages and new one as last in string + auto newStorageIdStr = data.substr(1, data.size()); auto newStorageId = utils::parseSafe(newStorageIdStr); if (newStorageId) { - state.storageIds.push_back(*newStorageId); - renderStoragesSuggestion(state.storageIds, state.recipeId, userId, chatId, bot, api); + auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); + api::models::storage::StorageSummary newStorage = { + .id = *newStorageId, .name = newStorageDetails.name, .ownerId = newStorageDetails.ownerId}; + state.storages.push_back(newStorage); + renderStoragesSuggestion(state.availability, state.storages, state.recipeId, userId, chatId, bot, api); + } + } + if (data[0] == '-') { + auto newStorageIdStr = data.substr(1, data.size()); + auto newStorageId = utils::parseSafe(newStorageIdStr); + if (newStorageId) { + state.storages.erase( + std::ranges::find(state.storages, newStorageId, &api::models::storage::StorageSummary::id)); + renderStoragesSuggestion(state.availability, state.storages, state.recipeId, userId, chatId, bot, api); } - bot.answerCallbackQuery(cq.id); } - if (data == "back_from_adding_storages") { - renderRecipeView(state.storageIds, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.storageIds = state.storageIds, + if (data == "back") { + renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); + stateManager.put(RecipeView{.storages = state.storages, + .availability = state.availability, .recipeId = state.recipeId, .fromStorage = state.fromStorage, .pageNo = state.pageNo}); - bot.answerCallbackQuery(cq.id); return; } } diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 9622e9b8..3fbd3e84 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -1,12 +1,13 @@ #include "view.hpp" #include "backend/id_types.hpp" +#include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "render/recipe/add_storage.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" + #include -#include namespace cookcookhnya::handlers::recipe { @@ -24,46 +25,34 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe return; } if (data == "shopping_list") { - const std::unordered_set storageIdsSet(state.storageIds.begin(), state.storageIds.end()); - auto recipesApi = api.getRecipesApi(); - - auto ingredients = recipesApi.getIngredientsInRecipe(userId, state.recipeId).ingredients; - std::vector ingredientIds; - bool isHavingIngredient = false; - - for (auto& ingredient : ingredients) { // Iterate through each ingredient - isHavingIngredient = false; - for (const api::StorageId storage : ingredient.inStorages) { - // Then for this ingredient one of possible storages already selected - if (storageIdsSet.contains(storage)) { - isHavingIngredient = true; - break; // No need to iterate further - } - } - if (!isHavingIngredient) { - ingredientIds.push_back(ingredient.id); + std::vector selectedIngredients; + for (const auto& infoPair : state.availability) { + if (infoPair.second.available == utils::AvailabiltiyType::not_available) { + selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); } } - renderShoppingListCreation(ingredientIds, userId, chatId, bot, api); - stateManager.put(ShoppingListCreation{.storageIdsFrom = state.storageIds, - .recipeIdFrom = state.recipeId, - .ingredientIdsInList = ingredientIds, + renderShoppingListCreation(selectedIngredients, userId, chatId, bot); + stateManager.put(ShoppingListCreation{.storages = state.storages, + .availability = state.availability, + .recipeId = state.recipeId, + .selectedIngredients = selectedIngredients, .fromStorage = state.fromStorage, .pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); return; } if (data == "back_from_recipe_view") { - renderRecipesSuggestion(state.storageIds, state.pageNo, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{ - .pageNo = state.pageNo, .storageIds = state.storageIds, .fromStorage = state.fromStorage}); + renderRecipesSuggestion(state.storages, state.pageNo, userId, chatId, bot, api); + stateManager.put( + SuggestedRecipeList{.pageNo = state.pageNo, .storages = state.storages, .fromStorage = state.fromStorage}); bot.answerCallbackQuery(cq.id); return; } if (data == "add_storages") { - renderStoragesSuggestion(state.storageIds, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeStorageAddition{.storageIds = state.storageIds, + renderStoragesSuggestion(state.availability, state.storages, state.recipeId, userId, chatId, bot, api); + stateManager.put(RecipeStorageAddition{.storages = state.storages, + .availability = state.availability, .recipeId = state.recipeId, .fromStorage = state.fromStorage, .pageNo = state.pageNo}); diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index 14d7fb4e..bb438ce3 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -1,18 +1,16 @@ #include "view.hpp" #include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" -#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" #include "render/recipe/view.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/storage/view.hpp" #include "render/storages_selection/view.hpp" +#include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" #include -#include #include namespace cookcookhnya::handlers::recipes_suggestions { @@ -23,56 +21,6 @@ using namespace render::storage; using namespace render::recipe; using namespace render::main_menu; -namespace { - -using namespace api; - -std::vector> inStoragesAvailability(std::vector& selectedStorages, const RecipeId recipeId, UserId userId, ApiClientRef api){ - auto allStorages = api.getStoragesApi().getStoragesList(userId); - auto recipe = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); - - std::unordered_set selectedStoragesSet(selectedStorages.begin(), selectedStorages.end()); - - std::unordered_map allStoragesMap; - for (const auto& storage : allStorages) { - allStoragesMap.emplace(storage.id, storage); - } - - std::vector> result; - - for (const auto& ingredient : recipe.ingredients) { - IngredientAvailability availability; - std::vector otherStorages; - - bool hasInSelected = false; - - for (const auto& storageId : ingredient.inStorages) { - auto it = allStoragesMap.find(storageId); - if (it == allStoragesMap.end()) - continue; - if (selectedStoragesSet.contains(storageId)) { - hasInSelected = true; - } else { - otherStorages.push_back(it->second); - } - } - - if (hasInSelected) { - availability.available = AvailabiltiyType::available; - } else if (!otherStorages.empty()) { - availability.available = AvailabiltiyType::other_storages; - availability.storages = std::move(otherStorages); - } else { - availability.available = AvailabiltiyType::not_available; - } - - result.emplace_back(ingredient, std::move(availability)); - } - - return result; -} -} // namespace - void handleSuggestedRecipeListCQ( SuggestedRecipeList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); @@ -83,34 +31,32 @@ void handleSuggestedRecipeListCQ( if (data == "back") { if (state.fromStorage) { - renderStorageView(state.storageIds[0], cq.from->id, chatId, bot, api); - stateManager.put(StorageView{state.storageIds[0]}); // Go to the only one storage + renderStorageView(state.storages[0].id, cq.from->id, chatId, bot, api); + stateManager.put(StorageView{state.storages[0].id}); // Go to the only one storage } else { if (api.getStoragesApi().getStoragesList(userId).size() == 1) { renderMainMenu(true, userId, chatId, bot, api); stateManager.put(MainMenu{}); } else { - renderStorageSelection(state.storageIds, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = std::move(state.storageIds)}); + renderStorageSelection(state.storages, userId, chatId, bot, api); + stateManager.put(StoragesSelection{.storages = std::move(state.storages)}); } } bot.answerCallbackQuery(cq.id); return; } - if (data[0] == 'r') { // Same naive implementation: if first char is r then it's recipe - - auto recipeId = utils::parseSafe( - data.substr(data.find(' ', 0) + 1, data.size())); // +1 is to move from space and get pure number + if (data[0] == 'r') { + auto recipeId = utils::parseSafe(data.substr(data.find(' ', 0) + 1, data.size())); if (recipeId) { - auto inStorage = inStoragesAvailability(state.storageIds, *recipeId, userId, api); + auto inStorage = utils::inStoragesAvailability(state.storages, *recipeId, userId, api); renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.storageIds = state.storageIds, + stateManager.put(RecipeView{.storages = state.storages, + .availability = inStorage, .recipeId = *recipeId, .fromStorage = state.fromStorage, .pageNo = state.pageNo}); } - return; } @@ -120,7 +66,7 @@ void handleSuggestedRecipeListCQ( state.pageNo = *pageNo; } // Message is 100% exists as it was rendered by some another method - renderRecipesSuggestion(state.storageIds, *pageNo, userId, chatId, bot, api); + renderRecipesSuggestion(state.storages, *pageNo, userId, chatId, bot, api); return; } } diff --git a/src/handlers/recipes_suggestions/view.hpp b/src/handlers/recipes_suggestions/view.hpp index 0a0d61ef..79cbc54f 100644 --- a/src/handlers/recipes_suggestions/view.hpp +++ b/src/handlers/recipes_suggestions/view.hpp @@ -4,13 +4,6 @@ namespace cookcookhnya::handlers::recipes_suggestions { -enum struct AvailabiltiyType : std::uint8_t { available, not_available, other_storages }; - -struct IngredientAvailability { - AvailabiltiyType available = AvailabiltiyType::not_available; - std::vector storages; -}; - void handleSuggestedRecipeListCQ( SuggestedRecipeList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 96262d1f..10c53a75 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -1,11 +1,13 @@ #include "create.hpp" #include "backend/id_types.hpp" +#include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "render/recipe/view.hpp" #include "render/shopping_list/create.hpp" #include "utils/parsing.hpp" +#include #include namespace cookcookhnya::handlers::shopping_list { @@ -20,43 +22,49 @@ void handleShoppingListCreationCQ( auto userId = cq.from->id; if (data == "back") { - renderRecipeView(state.storageIdsFrom, state.recipeIdFrom, userId, chatId, bot, api); - stateManager.put(RecipeView{.storageIds = state.storageIdsFrom, - .recipeId = state.recipeIdFrom, + renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); + stateManager.put(RecipeView{.storages = state.storages, + .availability = state.availability, + .recipeId = state.recipeId, .fromStorage = state.fromStorage, .pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); return; } if (data == "confirm") { - // Put ingredients in list auto shoppingApi = api.getShoppingListApi(); - shoppingApi.put(userId, state.ingredientIdsInList); - - // Return to previous state - renderRecipeView(state.storageIdsFrom, state.recipeIdFrom, userId, chatId, bot, api); - stateManager.put(RecipeView{.storageIds = state.storageIdsFrom, - .recipeId = state.recipeIdFrom, + std::vector putIds; + putIds.reserve(state.selectedIngredients.size()); + for (const auto& ingredient : state.selectedIngredients) { + putIds.push_back(ingredient.id); + } + shoppingApi.put(userId, putIds); + renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); + stateManager.put(RecipeView{.storages = state.storages, + .availability = state.availability, + .recipeId = state.recipeId, .fromStorage = state.fromStorage, .pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); return; } - if (data[0] == 'i') { - auto newIngredientIdStr = - data.substr(1, data.size()); // Here we got all selected storages and new one as last in string - auto ingredientIdToRemove = utils::parseSafe(newIngredientIdStr); - // Remove ingredient which was chosen - if (ingredientIdToRemove) { - for (auto ingredientId = state.ingredientIdsInList.begin(); ingredientId < state.ingredientIdsInList.end(); - ingredientId++) { - if (*ingredientId == *ingredientIdToRemove) { - state.ingredientIdsInList.erase(ingredientId); - } - } + if (data[0] == '+') { + auto newIngredientIdStr = data.substr(1, data.size()); + auto newIngredientId = utils::parseSafe(newIngredientIdStr); + if (newIngredientId) { + auto ingredient = api.getIngredientsApi().get(userId, *newIngredientId); + state.selectedIngredients.push_back(ingredient); } - - renderShoppingListCreation(state.ingredientIdsInList, userId, chatId, bot, api); + renderShoppingListCreation(state.selectedIngredients, userId, chatId, bot); + } + if (data[0] == '-') { + auto newIngredientIdStr = data.substr(1, data.size()); + auto newIngredientId = utils::parseSafe(newIngredientIdStr); + if (newIngredientId) { + state.selectedIngredients.erase(std::ranges::find( + state.selectedIngredients, *newIngredientId, &api::models::ingredient::Ingredient::id)); + } + renderShoppingListCreation(state.selectedIngredients, userId, chatId, bot); } } } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index f7b4ac6b..76b8d8c7 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -61,7 +61,6 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, } else { const std::size_t count = 15; auto response = api.searchForStorage(userId, state.storageId, iq.query, 50, count); // NOLINT (*magic*) - std::cerr << response.found << response.page[0].name; if (response.found != state.totalFound || !std::ranges::equal(response.page, state.searchItems, std::ranges::equal_to{}, diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 5bd214fa..261e767d 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/storage/ingredients/view.hpp" @@ -34,9 +35,12 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM renderStorageList(true, userId, chatId, bot, api); stateManager.put(StorageList{}); } else if (cq.data == "wanna_eat") { - renderRecipesSuggestion({state.storageId}, 0, userId, chatId, bot, api); - stateManager.put( - SuggestedRecipeList{.pageNo = 0, .storageIds = std::vector{state.storageId}, .fromStorage = true}); + auto storageDetails = api.getStoragesApi().get(userId, state.storageId); + api::models::storage::StorageSummary storage = { + .id = state.storageId, .name = storageDetails.name, .ownerId = storageDetails.ownerId}; + std::vector storages = {storage}; + renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); + stateManager.put(SuggestedRecipeList{.pageNo = 0, .storages = std::vector{storage}, .fromStorage = true}); return; } } diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 918c5325..b6269bc3 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -1,6 +1,7 @@ #include "view.hpp" #include "backend/id_types.hpp" +#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" #include "render/recipes_suggestions/view.hpp" @@ -22,12 +23,12 @@ void handleStoragesSelectionCQ( bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; - auto selectedStorages = state.storageIds; + auto selectedStorages = state.storages; if (cq.data == "confirm") { renderRecipesSuggestion(selectedStorages, 0, userId, chatId, bot, api); stateManager.put( - SuggestedRecipeList{.pageNo = 0, .storageIds = std::move(selectedStorages), .fromStorage = false}); + SuggestedRecipeList{.pageNo = 0, .storages = std::move(selectedStorages), .fromStorage = false}); return; } if (cq.data == "cancel") { @@ -36,19 +37,21 @@ void handleStoragesSelectionCQ( return; } - auto storageId = utils::parseSafe(cq.data.substr(4)); + auto storageId = utils::parseSafe(cq.data.substr(1)); if (storageId) { - if (cq.data.starts_with("in")) { - auto it = std::ranges::find(selectedStorages, *storageId); + if (cq.data[0] == '+') { + auto it = std::ranges::find(selectedStorages, *storageId, &api::models::storage::StorageSummary::id); selectedStorages.erase(it); renderStorageSelection(selectedStorages, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = selectedStorages}); + stateManager.put(StoragesSelection{.storages = selectedStorages}); return; } - if (cq.data.starts_with("out")) { - selectedStorages.push_back(*storageId); + if (cq.data[0] == '-') { + auto storageDetails = api.getStoragesApi().get(userId, *storageId); + selectedStorages.push_back( + {.id = *storageId, .name = storageDetails.name, .ownerId = storageDetails.ownerId}); renderStorageSelection(selectedStorages, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = selectedStorages}); + stateManager.put(StoragesSelection{.storages = selectedStorages}); return; } } diff --git a/src/patched_bot.cpp b/src/patched_bot.cpp index 3ed47c55..4343c77c 100644 --- a/src/patched_bot.cpp +++ b/src/patched_bot.cpp @@ -79,7 +79,7 @@ void appendToJson(std::string& json, std::string_view varName, std::string_view TgBot::Message::Ptr // NOLINT(*nodiscard) PatchedBot::sendMessage(tg_types::ChatId chatId, std::string_view text, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, std::string_view parseMode) const { std::vector args; args.reserve(3); @@ -97,7 +97,7 @@ TgBot::Message::Ptr // NOLINT(*nodiscard) PatchedBot::editMessageText(std::string_view text, tg_types::ChatId chatId, tg_types::MessageId messageId, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, std::string_view parseMode) const { std::vector args; args.reserve(4); diff --git a/src/patched_bot.hpp b/src/patched_bot.hpp index 0ddf3de0..cc30cb8f 100644 --- a/src/patched_bot.hpp +++ b/src/patched_bot.hpp @@ -38,7 +38,7 @@ class PatchedBot : TgBot::Api { std::string_view text, std::nullptr_t /*unused*/, std::nullptr_t /*unused*/, - const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, + const TgBot::InlineKeyboardMarkup::Ptr& replyMarkup, std::string_view parseMode = "") const { return sendMessage(chatId, text, replyMarkup, parseMode); } diff --git a/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp b/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp index 2d348d24..68732660 100644 --- a/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp +++ b/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp @@ -57,10 +57,11 @@ void renderRecipeIngredientsSearchEdit(const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ApiClient api){ +textGenInfo storageAdditionView( + const std::vector>& + inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ApiClient api) { auto recipeIngredients = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); bool isIngredientNotAvailable = false; bool isIngredientIsOtherStorages = false; const std::string recipeName = recipeIngredients.name; auto text = std::format("{} Выбранные хранилища: ", utils::utf8str(u8"🍱")); - for (std::size_t i = 0; i != selectedStorages.size(); ++i){ - text += selectedStorages[i].name; - text += i != selectedStorages.size() - 1 ? ", " : "\n"; - } + for (std::size_t i = 0; i != selectedStorages.size(); ++i) { + text += selectedStorages[i].name; + text += i != selectedStorages.size() - 1 ? ", " : "\n"; + } text += std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); - for (const auto& infoPair : inStoragesAvailability){ - if (infoPair.second.available == AvailabiltiyType::available){ + for (const auto& infoPair : inStoragesAvailability) { + if (infoPair.second.available == utils::AvailabiltiyType::available) { text += "`[+]` " + infoPair.first.name + "\n"; - } else if (infoPair.second.available == AvailabiltiyType::other_storages){ + } else if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { text += "`[?]` " + infoPair.first.name + "\n"; isIngredientIsOtherStorages = true; text += "__Доступно в: "; auto storages = infoPair.second.storages; - for (std::size_t i = 0; i != storages.size(); ++i){ + for (std::size_t i = 0; i != storages.size(); ++i) { text += storages[i].name; text += i != storages.size() - 1 ? ", " : "\n"; } @@ -54,41 +55,44 @@ textGenInfo storageAdditionView(const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api) { +void renderStoragesSuggestion( + const std::vector>& + inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + ApiClient api) { auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); auto storages = api.getStoragesApi().getStoragesList(userId); const size_t buttonRows = ((storages.size() + 1) / 2) + 1; InlineKeyboard keyboard(buttonRows); - + for (std::size_t i = 0; i < storages.size(); ++i) { if (i % 2 == 0) keyboard[i / 2].reserve(2); - const bool isSelected = std::ranges::find(selectedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != selectedStorages.end(); + const bool isSelected = + std::ranges::find(selectedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != + selectedStorages.end(); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); - const char* actionPrefix = isSelected ? "in__" : "out_"; + const char* actionPrefix = isSelected ? "+" : "-"; const std::string text = std::format("{} {}", emoji, storages[i].name); const std::string data = actionPrefix + utils::to_string(storages[i].id); keyboard[i / 2].push_back(makeCallbackButton(text, data)); } - keyboard[buttonRows - 1].reserve(2); - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "cancel")); if (!selectedStorages.empty()) { - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); + keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); } - + auto messageId = message::getMessageId(userId); if (messageId) { - // Only on difference between function above bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); } } diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index fa22a9d9..3021abe6 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -1,28 +1,31 @@ #pragma once #include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" -#include "handlers/recipes_suggestions/view.hpp" #include "render/common.hpp" #include "render/recipe/view.hpp" +#include "utils/ingredients_availability.hpp" #include namespace cookcookhnya::render::recipe { -textGenInfo storageAdditionView(const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ApiClient api); +textGenInfo storageAdditionView( + const std::vector>& + inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ApiClient api); + +void renderStoragesSuggestion( + const std::vector>& + inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + ApiClient api); -void renderStoragesSuggestion(const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api); - } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index c6aad99b..9cac9359 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -2,25 +2,23 @@ #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" -#include "handlers/recipes_suggestions/view.hpp" #include "message_tracker.hpp" #include "render/common.hpp" +#include "utils/ingredients_availability.hpp" #include "utils/utils.hpp" #include #include #include -#include namespace cookcookhnya::render::recipe { -using namespace handlers::recipes_suggestions; - - -textGenInfo recipeView(const std::vector>& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ApiClient api) { +textGenInfo +recipeView(const std::vector>& + inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + ApiClient api) { auto recipeIngredients = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); bool isIngredientNotAvailable = false; @@ -28,10 +26,10 @@ textGenInfo recipeView(const std::vector>& inStoragesAvailability, +void renderRecipeView(std::vector>& + inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, diff --git a/src/render/recipe/view.hpp b/src/render/recipe/view.hpp index b1b8a27c..0dff2290 100644 --- a/src/render/recipe/view.hpp +++ b/src/render/recipe/view.hpp @@ -1,9 +1,8 @@ #pragma once #include "backend/id_types.hpp" -#include "handlers/recipes_suggestions/view.hpp" #include "render/common.hpp" - +#include "utils/ingredients_availability.hpp" #include @@ -15,16 +14,19 @@ struct textGenInfo { bool isIngredientIsOtherStorages; }; -void renderRecipeView(const std::vector>& inStoragesAvailability, +void renderRecipeView(std::vector>& + inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, ApiClient api); -textGenInfo recipeView(const std::vector>& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ApiClient api); +textGenInfo +recipeView(const std::vector>& + inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + ApiClient api); } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 76b84948..e50a7f29 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -1,7 +1,7 @@ #include "view.hpp" -#include "backend/id_types.hpp" #include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/to_string.hpp" @@ -41,13 +41,12 @@ InlineKeyboard constructNavigationsMarkup(std::size_t offset, std::size_t counter = 0; for (std::size_t i = 0; i < recipesToShow; i++) { // Print on button in form "1. {Recipe}" - keyboard[i + offset].push_back( - makeCallbackButton(std::format("{}. {} [{} из {}]", - 1 + counter + ((pageNo)*numOfRecipesOnPage), - recipesList.page[counter].name, - recipesList.page[counter].available, - recipesList.page[counter].total), - std::format("recipe: {}", recipesList.page[counter].id))); // RECIPE ID + keyboard[i + offset].push_back(makeCallbackButton(std::format("{}. {} [{} из {}]", + 1 + counter + ((pageNo)*numOfRecipesOnPage), + recipesList.page[counter].name, + recipesList.page[counter].available, + recipesList.page[counter].total), + std::format("r", recipesList.page[counter].id))); counter++; } keyboard[arrowsRow].reserve(3); @@ -106,7 +105,7 @@ constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::R return keyboard; } -void renderRecipesSuggestion(const std::vector& storageIds, +void renderRecipesSuggestion(std::vector& storages, size_t pageNo, UserId userId, ChatId chatId, @@ -118,16 +117,11 @@ void renderRecipesSuggestion(const std::vector& storageIds, const size_t numOfRecipesOnPage = 5; - auto recipesList = recipesApi.getSuggestedRecipesList(userId, storageIds, (pageNo + 1) * numOfRecipesOnPage); + auto recipesList = recipesApi.getSuggestedRecipesList(userId, storages, (pageNo + 1) * numOfRecipesOnPage); if (messageId) { - bot.editMessageText(pageInfo, - chatId, - *messageId, - "", - "", - nullptr, - makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + bot.editMessageText( + pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); } } diff --git a/src/render/recipes_suggestions/view.hpp b/src/render/recipes_suggestions/view.hpp index 278bb897..b338513a 100644 --- a/src/render/recipes_suggestions/view.hpp +++ b/src/render/recipes_suggestions/view.hpp @@ -1,7 +1,7 @@ #pragma once -#include "backend/id_types.hpp" #include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" #include "render/common.hpp" #include @@ -19,7 +19,7 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, size_t numOfRecipesOnPage, api::models::recipe::RecipesList recipesList); -void renderRecipesSuggestion(const std::vector& storageIds, +void renderRecipesSuggestion(std::vector& storages, size_t pageNo, UserId userId, ChatId chatId, diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index 89c9355a..a803f30a 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -1,62 +1,47 @@ #include "create.hpp" -#include "backend/api/ingredients.hpp" -#include "backend/id_types.hpp" +#include "backend/models/ingredient.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/to_string.hpp" #include "utils/utils.hpp" #include -#include #include #include #include namespace cookcookhnya::render::shopping_list { -std::vector renderShoppingListCreation(const std::vector& ingredientIds, - UserId userId, - ChatId chatId, - BotRef bot, - api::IngredientsApi ingredientsApi) { +void renderShoppingListCreation(std::vector selectedIngredients, + UserId userId, + ChatId chatId, + BotRef bot) { + std::string text = + std::format("{} Выберите продукты, которые хотели бы добавить в список покупок\n\n", utils::utf8str(u8"📝")); + const size_t buttonRows = ((selectedIngredients.size() + 1) / 2) + 1; + InlineKeyboard keyboard(buttonRows); - std::string text = utils::utf8str(u8"Основываясь на недостающих ингредиентах, составили для вас продукты " - u8"которые можно добавить в список покупок:\n *В самом низу выберите " - u8"ингредиенты которые вы хотите исключить из списка покупок\n"); - std::vector ingredientsName; - for (const api::IngredientId ingredientId : ingredientIds) { - // IMPORTANT!: Probably can be optimized because this data is available at the recipe page - // by Maxim Fomin - // (1) I believe that both ways are expensive: or it's run through string of textGen or it's several small - // queries to backend. While i understand that working with string is faster then sending such queries i think - // that it's better not to overengineering frontend in this aspect. - // - // (2) Besides this also lays on frontend additional work on maintaining ingredientsName vector (in this case - // deletion from it). - // by Ilia Kliantsevich - std::string name = ingredientsApi.get(userId, ingredientId).name; - ingredientsName.push_back(name); - text += std::format("- {}\n", name); + for (std::size_t i = 0; i < selectedIngredients.size(); ++i) { + if (i % 2 == 0) + keyboard[i / 2].reserve(2); + const bool isSelected = + std::ranges::find(selectedIngredients, + selectedIngredients[i].id, + &api::models::ingredient::Ingredient::id) != selectedIngredients.end(); + std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); + const char* actionPrefix = isSelected ? "+" : "-"; + const std::string text = std::format("{} {}", emoji, selectedIngredients[i].name); + const std::string data = actionPrefix + utils::to_string(selectedIngredients[i].id); + keyboard[i / 2].push_back(makeCallbackButton(text, data)); } - const std::size_t buttonRows = ((ingredientIds.size() + 1) / 2) + 2; // +1 for back, +1 for approve - - InlineKeyboard keyboard(buttonRows); - uint64_t i = 0; - for (auto ingredientId : ingredientIds) { - const std::string& name = ingredientsName[i]; - if (i % 2 == 0) { - keyboard[(i / 2)].reserve(2); - } - keyboard[i / 2].push_back( - makeCallbackButton(name, "i" + utils::to_string(ingredientId))); // i stands for ingredient - i++; + keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); + if (!selectedIngredients.empty()) { + keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); } - keyboard[((ingredientIds.size() + 1) / 2)].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); - keyboard[(((ingredientIds.size() + 1) / 2) + 1)].push_back(makeCallbackButton(u8"↩️ Назад", "back")); auto messageId = message::getMessageId(userId); - if (messageId) - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); - return ingredientIds; + if (messageId) { + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); + } } } // namespace cookcookhnya::render::shopping_list diff --git a/src/render/shopping_list/create.hpp b/src/render/shopping_list/create.hpp index 7f09d4ba..e28478fe 100644 --- a/src/render/shopping_list/create.hpp +++ b/src/render/shopping_list/create.hpp @@ -1,16 +1,14 @@ #pragma once -#include "backend/id_types.hpp" #include "render/common.hpp" #include namespace cookcookhnya::render::shopping_list { -std::vector renderShoppingListCreation(const std::vector& ingredientIds, - UserId userId, - ChatId chatId, - BotRef bot, - api::IngredientsApi ingredientsApi); +void renderShoppingListCreation(std::vector selectedIngredients, + UserId userId, + ChatId chatId, + BotRef bot); } // namespace cookcookhnya::render::shopping_list diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index 01bf062c..460082c0 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -1,6 +1,6 @@ #include "view.hpp" -#include "backend/id_types.hpp" +#include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/to_string.hpp" @@ -17,7 +17,7 @@ namespace cookcookhnya::render::select_storages { using namespace tg_types; -void renderStorageSelection(const std::vector& selected_storages, +void renderStorageSelection(const std::vector& selectedStorages, UserId userId, ChatId chatId, BotRef bot, @@ -29,17 +29,19 @@ void renderStorageSelection(const std::vector& selected_storages for (std::size_t i = 0; i < storages.size(); ++i) { if (i % 2 == 0) keyboard[i / 2].reserve(2); - const bool isSelected = std::ranges::find(selected_storages, storages[i].id) != selected_storages.end(); + const bool isSelected = + std::ranges::find(selectedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != + selectedStorages.end(); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); - const char* actionPrefix = isSelected ? "in__" : "out_"; + const char* actionPrefix = isSelected ? "+" : "-"; const std::string text = std::format("{} {}", emoji, storages[i].name); const std::string data = actionPrefix + utils::to_string(storages[i].id); keyboard[i / 2].push_back(makeCallbackButton(text, data)); } keyboard[buttonRows - 1].reserve(2); keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "cancel")); - if (!selected_storages.empty()) { + if (!selectedStorages.empty()) { keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); } auto text = utils::utf8str(u8"🍱 Откуда брать продукты?"); diff --git a/src/render/storages_selection/view.hpp b/src/render/storages_selection/view.hpp index f490b55f..e30544d7 100644 --- a/src/render/storages_selection/view.hpp +++ b/src/render/storages_selection/view.hpp @@ -1,13 +1,12 @@ #pragma once -#include "backend/id_types.hpp" #include "render/common.hpp" #include namespace cookcookhnya::render::select_storages { -void renderStorageSelection(const std::vector& selected_storages, +void renderStorageSelection(const std::vector& selectedStorages, UserId userId, ChatId chatId, BotRef bot, diff --git a/src/states.hpp b/src/states.hpp index c560debe..ab92ba26 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -3,8 +3,9 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "backend/models/shopping_list.hpp" -#include "handlers/recipes_suggestions/view.cpp" +#include "backend/models/storage.hpp" #include "utils/fast_sorted_db.hpp" +#include "utils/ingredients_availability.hpp" #include #include @@ -59,22 +60,26 @@ struct StorageIngredientsList : detail::StorageIdMixin { }; struct StoragesSelection { - std::vector storageIds; + std::vector storages; }; struct SuggestedRecipeList { std::size_t pageNo; - std::vector storageIds; + std::vector storages; bool fromStorage; }; struct RecipeView { - std::vector storageIds; + std::vector storages; + std::vector> + availability; api::RecipeId recipeId; bool fromStorage; size_t pageNo; }; struct RecipeStorageAddition { - std::vector storageIds; + std::vector storages; + std::vector> + availability; api::RecipeId recipeId; bool fromStorage; size_t pageNo; @@ -102,9 +107,11 @@ struct CreateCustomRecipe { }; struct ShoppingListCreation { - std::vector storageIdsFrom; - api::RecipeId recipeIdFrom; - std::vector ingredientIdsInList; + std::vector storages; + std::vector> + availability; + api::RecipeId recipeId; + std::vector selectedIngredients; bool fromStorage; std::size_t pageNo; }; diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp new file mode 100644 index 00000000..0c091d93 --- /dev/null +++ b/src/utils/ingredients_availability.cpp @@ -0,0 +1,63 @@ +#include "ingredients_availability.hpp" + +#include "backend/id_types.hpp" +#include "handlers/common.hpp" + +#include +#include + +namespace cookcookhnya::utils { + +using namespace api; +using namespace tg_types; + +std::vector> +inStoragesAvailability(std::vector& selectedStorages, + const RecipeId recipeId, + UserId userId, + handlers::ApiClientRef api) { + auto allStorages = api.getStoragesApi().getStoragesList(userId); + auto recipe = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); + + auto selectedStoragesSet = selectedStorages | std::views::transform(&api::models::storage::StorageSummary::id) | + std::ranges::to(); + + std::unordered_map allStoragesMap; + for (const auto& storage : allStorages) { + allStoragesMap.emplace(storage.id, storage); + } + + std::vector> result; + + for (const auto& ingredient : recipe.ingredients) { + IngredientAvailability availability; + std::vector otherStorages; + + bool hasInSelected = false; + + for (const auto& storageId : ingredient.inStorages) { + auto it = allStoragesMap.find(storageId); + if (it == allStoragesMap.end()) + continue; + if (selectedStoragesSet.contains(storageId)) { + hasInSelected = true; + } else { + otherStorages.push_back(it->second); + } + } + + if (hasInSelected) { + availability.available = AvailabiltiyType::available; + } else if (!otherStorages.empty()) { + availability.available = AvailabiltiyType::other_storages; + availability.storages = std::move(otherStorages); + } else { + availability.available = AvailabiltiyType::not_available; + } + + result.emplace_back(ingredient, std::move(availability)); + } + + return result; +} +} // namespace cookcookhnya::utils diff --git a/src/utils/ingredients_availability.hpp b/src/utils/ingredients_availability.hpp new file mode 100644 index 00000000..3c239e48 --- /dev/null +++ b/src/utils/ingredients_availability.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "backend/models/recipe.hpp" +#include "backend/models/storage.hpp" + +#include +#include + +namespace cookcookhnya::utils { + +enum struct AvailabiltiyType : std::uint8_t { available, not_available, other_storages }; + +struct IngredientAvailability { + AvailabiltiyType available = AvailabiltiyType::not_available; + std::vector storages; +}; + +std::vector> +inStoragesAvailability(std::vector& selectedStorages, + cookcookhnya::api::RecipeId recipeId, + cookcookhnya::tg_types::UserId userId, + const api::ApiClient& api); + +} // namespace cookcookhnya::utils From a7021ffba97f8bb474d09b0e2c3a79805becaba3 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Thu, 17 Jul 2025 19:29:17 +0300 Subject: [PATCH 004/106] fix: connect ingred_avail.cpp in cmake --- src/handlers/recipes_suggestions/view.cpp | 2 +- src/utils/CMakeLists.txt | 1 + src/utils/ingredients_availability.cpp | 7 +++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index bb438ce3..17a4d3ff 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -47,7 +47,7 @@ void handleSuggestedRecipeListCQ( } if (data[0] == 'r') { - auto recipeId = utils::parseSafe(data.substr(data.find(' ', 0) + 1, data.size())); + auto recipeId = utils::parseSafe(data.substr(1, data.size())); if (recipeId) { auto inStorage = utils::inStoragesAvailability(state.storages, *recipeId, userId, api); renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 77e53af3..3c20a6f7 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -3,4 +3,5 @@ target_sources(main PRIVATE src/utils/to_string.cpp src/utils/utils.cpp src/utils/uuid.cpp + src/utils/ingredients_availability.cpp ) diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index 0c091d93..cb4dc284 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -1,7 +1,6 @@ #include "ingredients_availability.hpp" #include "backend/id_types.hpp" -#include "handlers/common.hpp" #include #include @@ -12,10 +11,10 @@ using namespace api; using namespace tg_types; std::vector> -inStoragesAvailability(std::vector& selectedStorages, - const RecipeId recipeId, +inStoragesAvailability(std::vector& selectedStorages, + RecipeId recipeId, UserId userId, - handlers::ApiClientRef api) { + const api::ApiClient& api) { auto allStorages = api.getStoragesApi().getStoragesList(userId); auto recipe = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); From f2d1610b0334b54bcfeb964ca761e11e6a2288f1 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Fri, 18 Jul 2025 02:48:45 +0300 Subject: [PATCH 005/106] feat: shopping list multiselect + bug fixes --- src/backend/api/shopping_lists.cpp | 6 +- src/backend/api/shopping_lists.hpp | 3 +- src/handlers/CMakeLists.txt | 5 +- src/handlers/handlers_list.hpp | 4 +- src/handlers/main_menu/view.cpp | 30 +++++---- .../recipe/search_ingredients.cpp | 38 +++++------ .../recipe/search_ingredients.hpp | 0 .../{recipes_list => }/recipe/view.cpp | 14 ++-- .../{recipes_list => }/recipe/view.hpp | 0 .../personal_account/recipes_list/create.cpp | 2 +- .../personal_account/recipes_list/view.cpp | 2 +- src/handlers/shopping_list/view.cpp | 39 ++++++++++- src/handlers/storage/view.cpp | 14 ++-- src/render/CMakeLists.txt | 5 +- src/render/common.hpp | 35 ++++++++++ .../recipe/search_ingredients.cpp | 2 +- .../recipe/search_ingredients.hpp | 0 .../{recipes_list => }/recipe/view.cpp | 0 .../{recipes_list => }/recipe/view.hpp | 0 src/render/shopping_list/view.cpp | 33 +++++----- src/render/shopping_list/view.hpp | 2 +- src/render/storage/ingredients/view.cpp | 2 +- src/states.hpp | 33 ++++++---- src/utils/fast_sorted_db.hpp | 66 ++++++++++++++----- 24 files changed, 224 insertions(+), 111 deletions(-) rename src/handlers/personal_account/{recipes_list => }/recipe/search_ingredients.cpp (79%) rename src/handlers/personal_account/{recipes_list => }/recipe/search_ingredients.hpp (100%) rename src/handlers/personal_account/{recipes_list => }/recipe/view.cpp (81%) rename src/handlers/personal_account/{recipes_list => }/recipe/view.hpp (100%) rename src/render/personal_account/{recipes_list => }/recipe/search_ingredients.cpp (98%) rename src/render/personal_account/{recipes_list => }/recipe/search_ingredients.hpp (100%) rename src/render/personal_account/{recipes_list => }/recipe/view.cpp (100%) rename src/render/personal_account/{recipes_list => }/recipe/view.hpp (100%) diff --git a/src/backend/api/shopping_lists.cpp b/src/backend/api/shopping_lists.cpp index 7b4cbfc1..bff8a5d1 100644 --- a/src/backend/api/shopping_lists.cpp +++ b/src/backend/api/shopping_lists.cpp @@ -4,6 +4,7 @@ #include "backend/models/shopping_list.hpp" #include "utils/to_string.hpp" +#include #include #include #include @@ -13,8 +14,9 @@ namespace cookcookhnya::api { using namespace models::shopping_list; // GET /shopping-list -std::vector ShoppingListApi::get(UserId user) const { - return jsonGetAuthed>(user, "/shopping-list"); +std::vector ShoppingListApi::get(UserId user, std::size_t count, std::size_t offset) const { + return jsonGetAuthed>( + user, "/shopping-list", {{"size", utils::to_string(count)}, {"offset", utils::to_string(offset)}}); } // PUT /shopping-list diff --git a/src/backend/api/shopping_lists.hpp b/src/backend/api/shopping_lists.hpp index 37990964..52c9a2de 100644 --- a/src/backend/api/shopping_lists.hpp +++ b/src/backend/api/shopping_lists.hpp @@ -16,7 +16,8 @@ class ShoppingListApi : ApiBase { explicit ShoppingListApi(httplib::Client& api) : ApiBase{api} {} public: - [[nodiscard]] std::vector get(UserId user) const; + [[nodiscard]] std::vector + get(UserId user, std::size_t count = 500, std::size_t offset = 0) const; // NOLINT(*magic-number*) void put(UserId user, const std::vector& ingredients) const; diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index b0f6c93e..575165f4 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -8,10 +8,11 @@ target_sources(main PRIVATE src/handlers/personal_account/ingredients_list/view.cpp src/handlers/personal_account/recipes_list/create.cpp - src/handlers/personal_account/recipes_list/recipe/search_ingredients.cpp - src/handlers/personal_account/recipes_list/recipe/view.cpp src/handlers/personal_account/recipes_list/view.cpp + src/handlers/personal_account/recipe/search_ingredients.cpp + src/handlers/personal_account/recipe/view.cpp + src/handlers/personal_account/view.cpp src/handlers/recipe/add_storage.cpp diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 1ef3c95f..9a4807f7 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -9,9 +9,9 @@ #include "personal_account/ingredients_list/publish.hpp" #include "personal_account/ingredients_list/view.hpp" +#include "personal_account/recipe/search_ingredients.hpp" +#include "personal_account/recipe/view.hpp" #include "personal_account/recipes_list/create.hpp" -#include "personal_account/recipes_list/recipe/search_ingredients.hpp" -#include "personal_account/recipes_list/recipe/view.hpp" #include "personal_account/recipes_list/view.hpp" #include "personal_account/view.hpp" diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index fd0a9f26..2dad10a0 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -1,7 +1,6 @@ #include "view.hpp" #include "backend/api/storages.hpp" -#include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/personal_account/view.hpp" #include "render/recipes_suggestions/view.hpp" @@ -9,7 +8,8 @@ #include "render/storages_list/view.hpp" #include "render/storages_selection/view.hpp" -#include +#include +#include #include namespace cookcookhnya::handlers::main_menu { @@ -19,35 +19,43 @@ using namespace render::recipes_suggestions; using namespace render::select_storages; using namespace render::shopping_list; using namespace render::personal_account; +using namespace std::views; void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; auto storages = api.getStoragesApi().getStoragesList(userId); + if (cq.data == "storage_list") { renderStorageList(true, userId, chatId, bot, api); stateManager.put(StorageList{}); return; } + if (cq.data == "wanna_eat") { if (storages.size() == 1) { - auto storageId = {storages[0].id}; - renderRecipesSuggestion(storageId, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{.pageNo = 0, .storageIds = storageId, .fromStorage = false}); - return; + auto storageIds = {storages[0].id}; + renderRecipesSuggestion(storageIds, 0, userId, chatId, bot, api); + stateManager.put(SuggestedRecipeList{.pageNo = 0, .storageIds = storageIds, .fromStorage = false}); + } else { + renderStorageSelection({}, userId, chatId, bot, api); + stateManager.put(StoragesSelection{.storageIds = {}}); } - renderStorageSelection({}, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = std::vector{}}); return; } + if (cq.data == "shopping_list") { + const bool canBuy = !storages.empty(); auto items = api.getShoppingListApi().get(userId); - stateManager.put( - ShoppingListView{{{std::make_move_iterator(items.begin()), std::make_move_iterator(items.end())}}}); - renderShoppingList(std::get(*stateManager.get()).items.getAll(), userId, chatId, bot); + ShoppingListView::ItemsDb itemsDb{ + items | transform([](auto& i) { return ShoppingListView::SelectableItem{std::move(i)}; })}; + + stateManager.put(ShoppingListView{.items = std::move(itemsDb), .canBuy = canBuy}); + renderShoppingList(std::get(*stateManager.get()), userId, chatId, bot); return; } + if (cq.data == "personal_account") { renderPersonalAccountMenu(userId, chatId, bot); stateManager.put(PersonalAccountMenu{}); diff --git a/src/handlers/personal_account/recipes_list/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp similarity index 79% rename from src/handlers/personal_account/recipes_list/recipe/search_ingredients.cpp rename to src/handlers/personal_account/recipe/search_ingredients.cpp index 1d58b7e6..e72f668e 100644 --- a/src/handlers/personal_account/recipes_list/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -4,14 +4,15 @@ #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" -#include "render/personal_account/recipes_list/recipe/search_ingredients.hpp" -#include "render/personal_account/recipes_list/recipe/view.hpp" +#include "render/personal_account/recipe/search_ingredients.hpp" +#include "render/personal_account/recipe/view.hpp" #include "tg_types.hpp" #include "utils/parsing.hpp" #include #include #include +#include #include #include @@ -20,20 +21,17 @@ namespace cookcookhnya::handlers::personal_account::recipes { using namespace api::models::ingredient; using namespace render::recipe::ingredients; using namespace render::personal_account::recipes; - -// Global vars -const size_t numOfIngredientsOnPage = 5; -const size_t threshhold = 70; +using namespace std::ranges; +using namespace std::views; namespace { -void updateSearch(CustomRecipeIngredientsSearch& state, BotRef bot, tg_types::UserId userId, IngredientsApiRef api) { - auto response = api.searchForRecipe(userId, - state.recipeId, - state.inlineQuery, - threshhold, - numOfIngredientsOnPage, - state.pageNo * numOfIngredientsOnPage); +const std::size_t numOfIngredientsOnPage = 5; +const std::size_t threshhold = 70; + +void updateSearch(CustomRecipeIngredientsSearch& state, BotRef bot, tg_types::UserId userId, IngredientsApiRef api) { + auto response = api.searchForRecipe( + userId, state.recipeId, state.query, threshhold, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage); if (response.found != state.totalFound || !std::ranges::equal(response.page, state.searchItems, std::ranges::equal_to{}, @@ -45,6 +43,7 @@ void updateSearch(CustomRecipeIngredientsSearch& state, BotRef bot, tg_types::Us renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); } } + } // namespace void handleCustomRecipeIngredientsSearchCQ( @@ -55,11 +54,12 @@ void handleCustomRecipeIngredientsSearchCQ( if (cq.data == "back") { renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); - std::vector ingredient; - ingredient.assign(state.recipeIngredients.getAll().begin(), state.recipeIngredients.getAll().end()); - stateManager.put(RecipeCustomView{.recipeId = state.recipeId, .pageNo = 0, .ingredients = ingredient}); + auto ingredients = state.recipeIngredients.getValues() | as_rvalue | to(); + stateManager.put( + RecipeCustomView{.recipeId = state.recipeId, .pageNo = 0, .ingredients = std::move(ingredients)}); return; } + if (cq.data == "prev") { state.pageNo -= 1; updateSearch(state, bot, userId, api); @@ -73,7 +73,6 @@ void handleCustomRecipeIngredientsSearchCQ( } if (cq.data != "dont_handle") { - auto mIngredient = utils::parseSafe(cq.data); if (!mIngredient) return; @@ -97,12 +96,11 @@ void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, InlineQueryRef iq, BotRef bot, IngredientsApiRef api) { - const size_t numOfIngredientsOnPage = 5; - state.inlineQuery = iq.query; + state.query = iq.query; const auto userId = iq.from->id; if (iq.query.empty()) { - state.searchItems.clear(); // When query is empty then search shouldn't happen + state.searchItems.clear(); state.totalFound = 0; renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); } else { diff --git a/src/handlers/personal_account/recipes_list/recipe/search_ingredients.hpp b/src/handlers/personal_account/recipe/search_ingredients.hpp similarity index 100% rename from src/handlers/personal_account/recipes_list/recipe/search_ingredients.hpp rename to src/handlers/personal_account/recipe/search_ingredients.hpp diff --git a/src/handlers/personal_account/recipes_list/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp similarity index 81% rename from src/handlers/personal_account/recipes_list/recipe/view.cpp rename to src/handlers/personal_account/recipe/view.cpp index 3d14c577..5e5ccbab 100644 --- a/src/handlers/personal_account/recipes_list/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -1,25 +1,28 @@ #include "view.hpp" #include "handlers/common.hpp" -#include "render/personal_account/recipes_list/recipe/search_ingredients.hpp" +#include "render/personal_account/recipe/search_ingredients.hpp" #include "render/personal_account/recipes_list/view.hpp" #include "states.hpp" #include -#include +#include #include namespace cookcookhnya::handlers::personal_account::recipes { using namespace render::personal_account::recipes; using namespace render::recipe::ingredients; +using namespace std::views; + +const std::size_t numOfIngredientsOnPage = 5; void handleRecipeCustomViewCQ( RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; - const size_t numOfIngredientsOnPage = 5; + if (data == "back") { renderCustomRecipesList(state.pageNo, userId, chatId, bot, api); stateManager.put(CustomRecipesList{.pageNo = state.pageNo}); @@ -27,10 +30,7 @@ void handleRecipeCustomViewCQ( } if (data == "change") { - stateManager.put(CustomRecipeIngredientsSearch{ - state.recipeId, - {std::make_move_iterator(state.ingredients.begin()), std::make_move_iterator(state.ingredients.end())}, - ""}); + stateManager.put(CustomRecipeIngredientsSearch{state.recipeId, state.ingredients | as_rvalue, ""}); renderRecipeIngredientsSearch( std::get(*stateManager.get()), numOfIngredientsOnPage, userId, chatId, bot); return; diff --git a/src/handlers/personal_account/recipes_list/recipe/view.hpp b/src/handlers/personal_account/recipe/view.hpp similarity index 100% rename from src/handlers/personal_account/recipes_list/recipe/view.hpp rename to src/handlers/personal_account/recipe/view.hpp diff --git a/src/handlers/personal_account/recipes_list/create.cpp b/src/handlers/personal_account/recipes_list/create.cpp index e65770ef..ab2119b4 100644 --- a/src/handlers/personal_account/recipes_list/create.cpp +++ b/src/handlers/personal_account/recipes_list/create.cpp @@ -2,7 +2,7 @@ #include "backend/models/recipe.hpp" #include "handlers/common.hpp" -#include "render/personal_account/recipes_list/recipe/view.hpp" +#include "render/personal_account/recipe/view.hpp" #include "render/personal_account/recipes_list/view.hpp" namespace cookcookhnya::handlers::personal_account::recipes { diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index d8c39006..b978ec6d 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -2,8 +2,8 @@ #include "backend/id_types.hpp" #include "handlers/common.hpp" +#include "render/personal_account/recipe/view.hpp" #include "render/personal_account/recipes_list/create.hpp" -#include "render/personal_account/recipes_list/recipe/view.hpp" #include "render/personal_account/recipes_list/view.hpp" #include "render/personal_account/view.hpp" #include "states.hpp" diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index e4a8f4e0..23791849 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -6,29 +6,62 @@ #include "render/shopping_list/view.hpp" #include "utils/parsing.hpp" +#include +#include + namespace cookcookhnya::handlers::shopping_list { using namespace render::main_menu; using namespace render::shopping_list; +using namespace std::views; +using namespace std::ranges; + void handleShoppingListViewCQ( ShoppingListView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; + if (cq.data == "back") { renderMainMenu(true, userId, cq.message->chat->id, bot, api); stateManager.put(MainMenu{}); return; } + if (cq.data == "search") { + renderMainMenu(true, userId, cq.message->chat->id, bot, api); + stateManager.put(MainMenu{}); + return; + } + + if (cq.data == "remove") { + using SelectableItem = ShoppingListView::SelectableItem; + auto toDelete = state.items.getValues() | filter(&SelectableItem::selected) | + views::transform(&SelectableItem::ingredientId) | to(); + + api.getShoppingListApi().remove(userId, toDelete); + for (auto& id : toDelete) + state.items.remove(id); + + renderShoppingList(state, userId, chatId, bot); + return; + } + + if (cq.data == "buy") { + renderMainMenu(true, userId, cq.message->chat->id, bot, api); + stateManager.put(MainMenu{}); + return; + } + auto mIngredientId = utils::parseSafe(cq.data); if (!mIngredientId) return; - api.getShoppingListApi().remove(userId, {*mIngredientId}); - state.items.remove(*mIngredientId); - renderShoppingList(state.items.getAll(), userId, chatId, bot); + if (auto* mItem = state.items[*mIngredientId]) { + mItem->selected = !mItem->selected; + renderShoppingList(state, userId, chatId, bot); + } } } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 8ebe9d5b..1d10b892 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -8,8 +8,7 @@ #include "states.hpp" #include -#include -#include +#include namespace cookcookhnya::handlers::storage { @@ -17,18 +16,16 @@ using namespace render::storage::ingredients; using namespace render::storage::members; using namespace render::storages_list; using namespace render::recipes_suggestions; +using namespace std::views; void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; - const size_t numOfIngredientsOnPage = 5; + const std::size_t numOfIngredientsOnPage = 5; if (cq.data == "ingredients") { auto ingredients = api.getIngredientsApi().getStorageIngredients(userId, state.storageId); - stateManager.put(StorageIngredientsList{ - state.storageId, - {std::make_move_iterator(ingredients.begin()), std::make_move_iterator(ingredients.end())}, - ""}); + stateManager.put(StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}); renderIngredientsListSearch( std::get(*stateManager.get()), numOfIngredientsOnPage, userId, chatId, bot); } else if (cq.data == "members") { @@ -39,8 +36,7 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM stateManager.put(StorageList{}); } else if (cq.data == "wanna_eat") { renderRecipesSuggestion({state.storageId}, 0, userId, chatId, bot, api); - stateManager.put( - SuggestedRecipeList{.pageNo = 0, .storageIds = std::vector{state.storageId}, .fromStorage = true}); + stateManager.put(SuggestedRecipeList{.pageNo = 0, .storageIds = {state.storageId}, .fromStorage = true}); return; } } diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 277c63d2..22bf5a89 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -6,10 +6,11 @@ target_sources(main PRIVATE src/render/personal_account/ingredients_list/view.cpp src/render/personal_account/recipes_list/create.cpp - src/render/personal_account/recipes_list/recipe/search_ingredients.cpp - src/render/personal_account/recipes_list/recipe/view.cpp src/render/personal_account/recipes_list/view.cpp + src/render/personal_account/recipe/search_ingredients.cpp + src/render/personal_account/recipe/view.cpp + src/render/personal_account/view.cpp src/render/recipe/add_storage.cpp diff --git a/src/render/common.hpp b/src/render/common.hpp index 30140e88..c12cfd4e 100644 --- a/src/render/common.hpp +++ b/src/render/common.hpp @@ -10,6 +10,7 @@ #include "tg_types.hpp" #include "utils/utils.hpp" +#include #include #include #include @@ -71,4 +72,38 @@ inline std::shared_ptr makeKeyboardMarkup(InlineKey return markup; } +struct NewRow {}; + +class InlineKeyboardBuilder { + InlineKeyboard keyboard; + + public: + explicit InlineKeyboardBuilder(std::size_t reserve = 0) { + keyboard.reserve(reserve); + keyboard.emplace_back(); + } + + InlineKeyboardBuilder& operator<<(TgBot::InlineKeyboardButton::Ptr&& button) { + keyboard.back().push_back(std::move(button)); + return *this; + } + + InlineKeyboardBuilder& operator<<(NewRow /*tag*/) { + keyboard.emplace_back(); + return *this; + } + + void operator++(int) { + keyboard.emplace_back(); + } + + TgBot::InlineKeyboardMarkup::Ptr build() && { + return makeKeyboardMarkup(std::move(keyboard)); + } + + operator TgBot::InlineKeyboardMarkup::Ptr() && { // NOLINT(*explicit*) + return makeKeyboardMarkup(std::move(keyboard)); + } +}; + } // namespace cookcookhnya::render diff --git a/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp b/src/render/personal_account/recipe/search_ingredients.cpp similarity index 98% rename from src/render/personal_account/recipes_list/recipe/search_ingredients.cpp rename to src/render/personal_account/recipe/search_ingredients.cpp index d3fe76f2..a35519b0 100644 --- a/src/render/personal_account/recipes_list/recipe/search_ingredients.cpp +++ b/src/render/personal_account/recipe/search_ingredients.cpp @@ -130,7 +130,7 @@ void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& using namespace std::views; using std::ranges::to; - std::string list = state.recipeIngredients.getAll() | + std::string list = state.recipeIngredients.getValues() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); diff --git a/src/render/personal_account/recipes_list/recipe/search_ingredients.hpp b/src/render/personal_account/recipe/search_ingredients.hpp similarity index 100% rename from src/render/personal_account/recipes_list/recipe/search_ingredients.hpp rename to src/render/personal_account/recipe/search_ingredients.hpp diff --git a/src/render/personal_account/recipes_list/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp similarity index 100% rename from src/render/personal_account/recipes_list/recipe/view.cpp rename to src/render/personal_account/recipe/view.cpp diff --git a/src/render/personal_account/recipes_list/recipe/view.hpp b/src/render/personal_account/recipe/view.hpp similarity index 100% rename from src/render/personal_account/recipes_list/recipe/view.hpp rename to src/render/personal_account/recipe/view.hpp diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 24a1ab99..50c6855d 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -7,37 +7,36 @@ #include "utils/utils.hpp" #include -#include -#include #include #include namespace cookcookhnya::render::shopping_list { -void renderShoppingList(const states::ShoppingListView::ItemsDb::Set& items, UserId userId, ChatId chatId, BotRef bot) { +void renderShoppingList(const states::ShoppingListView& state, UserId userId, ChatId chatId, BotRef bot) { + auto items = state.items.getValues(); bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); - InlineKeyboard keyboard; - keyboard.reserve(3 + items.size()); // add, remove and/or buy, list (n), back - auto buttonRowIter = std::back_inserter(keyboard); + InlineKeyboardBuilder keyboard{3 + items.size()}; // add, remove and/or buy, list (n), back + + keyboard << makeCallbackButton(u8"Поиск", "search") << NewRow{}; - (*buttonRowIter).push_back(makeCallbackButton(u8"Поиск", "search")); if (anySelected) { - buttonRowIter++; - keyboard[buttonRowIter].push_back(makeCallbackButton(u8"Убрать", "remove")); - keyboard[buttonRowIter++].push_back(makeCallbackButton(u8"Купить", "buy")); + keyboard << makeCallbackButton(u8"Убрать", "remove"); + if (state.canBuy) + keyboard << makeCallbackButton(u8"Купить", "buy"); + keyboard << NewRow{}; } - for (auto [i, item] : std::views::enumerate(items)) { + for (const auto& item : items) { const char* const selectedMark = item.selected ? "[+] " : "[ ] "; - keyboard[buttonRowIter++].push_back( - makeCallbackButton(selectedMark + item.name, utils::to_string(item.ingredientId))); + keyboard << makeCallbackButton(selectedMark + item.name, utils::to_string(item.ingredientId)) << NewRow{}; } - keyboard[items.size()].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - auto messageId = message::getMessageId(userId); - if (messageId) { + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto messageId = message::getMessageId(userId)) { auto text = utils::utf8str(u8"🔖 Ваш список покупок. Нажмите на элемент, чтобы вычеркнуть из списка."); - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } } diff --git a/src/render/shopping_list/view.hpp b/src/render/shopping_list/view.hpp index 4da9193c..95f665ab 100644 --- a/src/render/shopping_list/view.hpp +++ b/src/render/shopping_list/view.hpp @@ -5,6 +5,6 @@ namespace cookcookhnya::render::shopping_list { -void renderShoppingList(const states::ShoppingListView::ItemsDb::Set& items, UserId userId, ChatId chatId, BotRef bot); +void renderShoppingList(const states::ShoppingListView& state, UserId userId, ChatId chatId, BotRef bot); } // namespace cookcookhnya::render::shopping_list diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 871263cd..515dbc0b 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -130,7 +130,7 @@ void renderIngredientsListSearch(const states::StorageIngredientsList& state, using namespace std::views; using std::ranges::to; - std::string list = state.storageIngredients.getAll() | + std::string list = state.storageIngredients.getValues() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); diff --git a/src/states.hpp b/src/states.hpp index 1880e2b5..1036d81e 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -8,7 +8,9 @@ #include #include +#include #include +#include #include #include #include @@ -53,8 +55,11 @@ struct StorageIngredientsList : detail::StorageIdMixin { std::size_t pageNo = 0; std::vector searchItems; std::string inlineQuery; - StorageIngredientsList(api::StorageId storageId, IngredientsDb::Set ingredients, std::string iq) - : StorageIdMixin{storageId}, storageIngredients{std::move(ingredients)}, inlineQuery(std::move(iq)) {} + + template + requires std::convertible_to, IngredientsDb::mapped_type> + StorageIngredientsList(api::StorageId storageId, R&& ingredients, std::string iq) + : StorageIdMixin{storageId}, storageIngredients{std::forward(ingredients)}, inlineQuery(std::move(iq)) {} }; struct StoragesSelection { @@ -69,42 +74,45 @@ struct RecipeView { std::vector storageIds; api::RecipeId recipeId; bool fromStorage; - size_t pageNo; + std::size_t pageNo; }; struct RecipeStorageAddition { std::vector storageIds; api::RecipeId recipeId; bool fromStorage; - size_t pageNo; + std::size_t pageNo; }; struct CustomRecipesList { - size_t pageNo; + std::size_t pageNo; }; struct CustomRecipeIngredientsSearch { using IngredientsDb = utils::FastSortedDb; + api::RecipeId recipeId; IngredientsDb recipeIngredients; + std::string query; std::size_t totalFound = 0; std::size_t pageNo = 0; std::vector searchItems; - std::string inlineQuery; - CustomRecipeIngredientsSearch(api::RecipeId recipeId, IngredientsDb::Set ingredients, std::string iq) - : recipeId(recipeId), recipeIngredients{std::move(ingredients)}, inlineQuery(std::move(iq)) {} + template + requires std::convertible_to, IngredientsDb::mapped_type> + CustomRecipeIngredientsSearch(api::RecipeId recipeId, R&& ingredients, std::string inlineQuery) + : recipeId(recipeId), recipeIngredients{std::forward(ingredients)}, query(std::move(inlineQuery)) {} }; struct RecipeCustomView { api::RecipeId recipeId; - size_t pageNo; + std::size_t pageNo; std::vector ingredients; }; struct CreateCustomRecipe { api::RecipeId recipeId; - size_t pageNo; + std::size_t pageNo; }; struct ShoppingListCreation { @@ -117,16 +125,17 @@ struct ShoppingListCreation { struct RecipeIngredientsSearch { api::RecipeId recipeId; - size_t pageNo; + std::size_t pageNo; }; -struct ShoppingListView { +struct ShoppingListView { // NOLINT(*member-init) // Strange. Flags only this struct due to ItemsDb struct SelectableItem : api::models::shopping_list::ShoppingListItem { bool selected = false; }; using ItemsDb = utils::FastSortedDb; ItemsDb items; + bool canBuy; }; using State = std::variant #include -#include +#include +#include #include #include #include @@ -11,38 +13,38 @@ namespace cookcookhnya::utils { template class FastSortedDb { using Id = std::remove_cvref_t>; - - struct Comparator { - bool operator()(const T& l, const T& r) const { - return std::ranges::less{}(std::invoke(SortProjection, l), std::invoke(SortProjection, r)); - } - }; + using SortKey = std::remove_cvref_t>; public: - using Set = std::set; + using mapped_type = T; + using Map = std::map; private: - Set items; - std::unordered_map index; + Map items; + std::unordered_map index; public: FastSortedDb() = default; - FastSortedDb(Set items) : items{std::move(items)} { // NOLINT(*explicit*) - for (auto it = this->items.begin(); it != this->items.end(); ++it) - index.try_emplace(std::invoke(IdProjection, std::as_const(*it)), it); + template + requires std::convertible_to, T> + FastSortedDb(R&& items) { // NOLINT(*explicit*) + for (auto&& item : std::ranges::views::all(std::forward(items))) + put(std::forward(item)); } void put(const T& item) { - const auto [it, inserted] = items.insert(item); + SortKey key = std::invoke(SortProjection, item); + auto [it, inserted] = this->items.emplace(std::move(key), item); if (inserted) - index.try_emplace(std::invoke(IdProjection, std::as_const(*it)), it); + index.try_emplace(std::invoke(IdProjection, std::as_const(it->second)), std::move(it)); } void put(T&& item) { - const auto [it, inserted] = items.insert(std::move(item)); + SortKey key = std::invoke(SortProjection, std::as_const(item)); + auto [it, inserted] = this->items.emplace(std::move(key), std::move(item)); if (inserted) - index.try_emplace(std::invoke(IdProjection, std::as_const(*it)), it); + index.try_emplace(std::invoke(IdProjection, std::as_const(it->second)), std::move(it)); } void remove(const Id& id) { @@ -53,9 +55,37 @@ class FastSortedDb { index.erase(it); } - const Set& getAll() const { + // as optional non-owning reference + [[nodiscard]] T* operator[](const Id& id) { + auto it = index.find(id); + if (it == index.end()) + return nullptr; + return &it->second->second; + } + + // as optional non-owning reference + [[nodiscard]] const T* operator[](const Id& id) const { + auto it = index.find(id); + if (it == index.end()) + return nullptr; + return &it->second->second; + } + + [[nodiscard]] Map& getAll() { + return items; + } + + [[nodiscard]] const Map& getAll() const { return items; } + + [[nodiscard]] auto getValues() { + return items | std::views::transform([](auto& p) -> T& { return p.second; }); + } + + [[nodiscard]] auto getValues() const { + return items | std::views::transform([](const auto& p) -> const T& { return p.second; }); + } }; } // namespace cookcookhnya::utils From 6ff3c14f9f282ebaff7d4a7e21e72e6a86123707 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Fri, 18 Jul 2025 02:58:27 +0300 Subject: [PATCH 006/106] refactor: replace insert with emplace in API --- src/backend/api/ingredients.cpp | 2 +- src/backend/api/recipes.cpp | 2 +- src/backend/api/shopping_lists.cpp | 9 +++++---- src/backend/api/shopping_lists.hpp | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index 639c06ad..3e8ea195 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -46,7 +46,7 @@ void IngredientsApi::deleteMultipleFromStorage(UserId user, const std::vector& ingredients) const { httplib::Params params = {}; for (auto id : ingredients) - params.insert({"ingredient", utils::to_string(id)}); + params.emplace("ingredient", utils::to_string(id)); jsonDeleteAuthed(user, std::format("/storages/{}/ingredients/", storage), params); } diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 737df48b..4669c45e 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -25,7 +25,7 @@ RecipesList RecipesApi::getSuggestedRecipesList(UserId user, size_t offset) const { httplib::Params params = {{"size", utils::to_string(size)}, {"offset", std::to_string(offset)}}; for (auto id : storages) - params.insert({"storage-id", utils::to_string(id)}); + params.emplace("storage-id", utils::to_string(id)); return jsonGetAuthed(user, "/suggested-recipes", params); } diff --git a/src/backend/api/shopping_lists.cpp b/src/backend/api/shopping_lists.cpp index bff8a5d1..03a9a817 100644 --- a/src/backend/api/shopping_lists.cpp +++ b/src/backend/api/shopping_lists.cpp @@ -23,7 +23,7 @@ std::vector ShoppingListApi::get(UserId user, std::size_t coun void ShoppingListApi::put(UserId user, const std::vector& ingredients) const { httplib::Params params; for (const IngredientId id : ingredients) - params.insert({"ingredient-id", utils::to_string(id)}); + params.emplace("ingredient-id", utils::to_string(id)); jsonPutAuthed(user, "/shopping-list", params); } @@ -31,15 +31,16 @@ void ShoppingListApi::put(UserId user, const std::vector& ingredie void ShoppingListApi::remove(UserId user, const std::vector& ingredients) const { httplib::Params params; for (const IngredientId id : ingredients) - params.insert({"ingredient-id", utils::to_string(id)}); + params.emplace("ingredient-id", utils::to_string(id)); jsonDeleteAuthed(user, "/shopping-list", params); } // PUT /shopping-list/buy -void ShoppingListApi::buy(UserId user, const std::vector& ingredients) const { +void ShoppingListApi::buy(UserId user, StorageId storage, const std::vector& ingredients) const { httplib::Params params; for (const IngredientId id : ingredients) - params.insert({"ingredient-id", utils::to_string(id)}); + params.emplace("ingredient-id", utils::to_string(id)); + params.emplace("storage-id", utils::to_string(storage)); jsonPutAuthed(user, "/shopping-list/buy", params); } diff --git a/src/backend/api/shopping_lists.hpp b/src/backend/api/shopping_lists.hpp index 52c9a2de..3b02b558 100644 --- a/src/backend/api/shopping_lists.hpp +++ b/src/backend/api/shopping_lists.hpp @@ -23,7 +23,7 @@ class ShoppingListApi : ApiBase { void remove(UserId user, const std::vector& ingredients) const; - void buy(UserId user, const std::vector& ingredients) const; + void buy(UserId user, StorageId storage, const std::vector& ingredients) const; }; } // namespace cookcookhnya::api From 931e46700fcd2755a893c1c46d1fbce8b3fac1f6 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Fri, 18 Jul 2025 14:47:51 +0300 Subject: [PATCH 007/106] fix: search paginations --- .../personal_account/recipe/search_ingredients.cpp | 13 +++++++++---- src/handlers/storage/ingredients/view.cpp | 12 ++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index e72f668e..233faace 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -29,7 +29,12 @@ namespace { const std::size_t numOfIngredientsOnPage = 5; const std::size_t threshhold = 70; -void updateSearch(CustomRecipeIngredientsSearch& state, BotRef bot, tg_types::UserId userId, IngredientsApiRef api) { +void updateSearch(CustomRecipeIngredientsSearch& state, + bool isQueryChanged, + BotRef bot, + tg_types::UserId userId, + IngredientsApiRef api) { + state.pageNo = isQueryChanged ? 0 : state.pageNo; auto response = api.searchForRecipe( userId, state.recipeId, state.query, threshhold, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage); if (response.found != state.totalFound || !std::ranges::equal(response.page, @@ -62,13 +67,13 @@ void handleCustomRecipeIngredientsSearchCQ( if (cq.data == "prev") { state.pageNo -= 1; - updateSearch(state, bot, userId, api); + updateSearch(state, false, bot, userId, api); return; } if (cq.data == "next") { state.pageNo += 1; - updateSearch(state, bot, userId, api); + updateSearch(state, false, bot, userId, api); return; } @@ -104,7 +109,7 @@ void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, state.totalFound = 0; renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); } else { - updateSearch(state, bot, userId, api); + updateSearch(state, true, bot, userId, api); } // Cache is not disabled on Windows and Linux desktops. Works on Android and Web bot.answerInlineQuery(iq.id, {}, 0); diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index f8fa9494..7f979f8e 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -25,8 +25,9 @@ const size_t numOfIngredientsOnPage = 5; const size_t threshhold = 70; namespace { -void updateSearch(StorageIngredientsList& state, BotRef bot, tg_types::UserId userId, IngredientsApiRef api) { - +void updateSearch( + StorageIngredientsList& state, bool isQueryChanged, BotRef bot, tg_types::UserId userId, IngredientsApiRef api) { + state.pageNo = isQueryChanged ? 0 : state.pageNo; auto response = api.searchForStorage(userId, state.storageId, state.inlineQuery, @@ -59,13 +60,13 @@ void handleStorageIngredientsListCQ( } if (cq.data == "prev") { state.pageNo -= 1; - updateSearch(state, bot, userId, api); + updateSearch(state, false, bot, userId, api); return; } if (cq.data == "next") { state.pageNo += 1; - updateSearch(state, bot, userId, api); + updateSearch(state, false, bot, userId, api); return; } @@ -94,7 +95,6 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, InlineQueryRef iq, BotRef bot, IngredientsApiRef api) { - const size_t numOfIngredientsOnPage = 5; const auto userId = iq.from->id; state.inlineQuery = iq.query; if (iq.query.empty()) { @@ -103,7 +103,7 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, state.totalFound = 0; renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, userId, bot); } else { - updateSearch(state, bot, userId, api); + updateSearch(state, true, bot, userId, api); } // Cache is not disabled on Windows and Linux desktops. Works on Android and Web bot.answerInlineQuery(iq.id, {}, 0); From 99ca049e1b126b91bc17d027c63dfd086a8764e9 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Fri, 18 Jul 2025 22:13:02 +0300 Subject: [PATCH 008/106] feat: finally done refactoring recipe view, storage addition in recipe, shopping list creation --- src/backend/models/recipe.hpp | 3 +- src/backend/models/storage.cpp | 3 +- src/backend/models/storage.hpp | 1 - src/handlers/CMakeLists.txt | 2 +- src/handlers/handlers_list.hpp | 5 ++- src/handlers/main_menu/view.cpp | 4 +- src/handlers/recipe/add_storage.cpp | 25 +++++++---- src/handlers/recipe/view.cpp | 13 +++--- src/handlers/recipes_suggestions/view.cpp | 16 +++---- src/handlers/shopping_list/create.cpp | 6 ++- .../{storages_list => storage}/delete.cpp | 10 ++--- .../{storages_list => storage}/delete.hpp | 0 src/handlers/storage/view.cpp | 10 ++++- src/handlers/storages_list/view.cpp | 7 ---- src/handlers/storages_selection/view.cpp | 21 +++++----- src/render/CMakeLists.txt | 2 +- .../personal_account/recipes_list/view.cpp | 2 +- src/render/recipe/add_storage.cpp | 24 ++++++++--- src/render/recipe/add_storage.hpp | 2 + src/render/recipe/view.cpp | 4 +- src/render/recipes_suggestions/view.cpp | 6 +-- src/render/storage/delete.cpp | 23 ++++++++++ src/render/storage/delete.hpp | 10 +++++ src/render/storage/view.cpp | 25 +++++++---- src/render/storages_list/delete.cpp | 37 ---------------- src/render/storages_list/delete.hpp | 9 ---- src/render/storages_list/view.cpp | 14 ++----- src/states.hpp | 17 ++++---- src/utils/ingredients_availability.cpp | 42 +++++++++++++++---- src/utils/ingredients_availability.hpp | 13 ++++-- 30 files changed, 202 insertions(+), 154 deletions(-) rename src/handlers/{storages_list => storage}/delete.cpp (57%) rename src/handlers/{storages_list => storage}/delete.hpp (100%) create mode 100644 src/render/storage/delete.cpp create mode 100644 src/render/storage/delete.hpp delete mode 100644 src/render/storages_list/delete.cpp delete mode 100644 src/render/storages_list/delete.hpp diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index b0f6ec23..4f51df8a 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -1,6 +1,7 @@ #pragma once #include "backend/id_types.hpp" +#include "backend/models/storage.hpp" #include "backend/models/user.hpp" #include "tg_types.hpp" @@ -32,7 +33,7 @@ struct RecipeSummaryWithIngredients { struct IngredientInRecipe { IngredientId id; std::string name; - std::vector inStorages; + std::vector inStorages; friend IngredientInRecipe tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/backend/models/storage.cpp b/src/backend/models/storage.cpp index 22b10292..a1d8c289 100644 --- a/src/backend/models/storage.cpp +++ b/src/backend/models/storage.cpp @@ -11,8 +11,7 @@ namespace json = boost::json; StorageSummary tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .id = value_to(j.at("id")), - .name = value_to(j.at("name")), - .ownerId = value_to(j.at("ownerId")), + .name = value_to(j.at("name")), }; } diff --git a/src/backend/models/storage.hpp b/src/backend/models/storage.hpp index 9cd16e9b..2aa6dec1 100644 --- a/src/backend/models/storage.hpp +++ b/src/backend/models/storage.hpp @@ -13,7 +13,6 @@ namespace cookcookhnya::api::models::storage { struct StorageSummary { StorageId id; std::string name; - tg_types::UserId ownerId; friend StorageSummary tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index b0f6c93e..7b39be9b 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -29,9 +29,9 @@ target_sources(main PRIVATE src/handlers/storage/members/view.cpp src/handlers/storage/view.cpp + src/handlers/storage/delete.cpp src/handlers/storages_list/create.cpp - src/handlers/storages_list/delete.cpp src/handlers/storages_list/view.cpp src/handlers/storages_selection/view.cpp diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 1ef3c95f..691c10ae 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -30,10 +30,11 @@ #include "storage/members/delete.hpp" #include "storage/members/view.hpp" +#include "storage/delete.hpp" #include "storage/view.hpp" + #include "storages_list/create.hpp" -#include "storages_list/delete.hpp" #include "storages_list/view.hpp" #include "storages_selection/view.hpp" @@ -84,10 +85,10 @@ using customIngredientPublishCQHandler = Handler; using storageCreationEnterNameMsgHandler = Handler; using storageCreationEnterNameCQHandler = Handler; -using storageDeletionCQHandler = Handler; // StorageView using storageViewCQHandler = Handler; +using storageDeletionCQHandler = Handler; // StorageViewMembers using storageMemberViewCQHandler = Handler; diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index 5c0ee006..5683d71e 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -34,11 +34,11 @@ void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SM if (storages.size() == 1) { std::vector storage = {storages[0]}; renderRecipesSuggestion(storage, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{.pageNo = 0, .storages = storage, .fromStorage = false}); + stateManager.put(SuggestedRecipeList{.pageNo = 0, .selectedStorages = storage, .fromStorage = false}); return; } renderStorageSelection({}, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storages = std::vector{}}); + stateManager.put(StoragesSelection{.selectedStorages = std::vector{}}); return; } if (cq.data == "shopping_list") { diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp index ba12fa50..8d49e527 100644 --- a/src/handlers/recipe/add_storage.cpp +++ b/src/handlers/recipe/add_storage.cpp @@ -6,6 +6,7 @@ #include "render/recipe/add_storage.hpp" #include "render/recipe/view.hpp" #include "states.hpp" +#include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" #include @@ -21,30 +22,36 @@ void handleRecipeStorageAdditionCQ( auto chatId = cq.message->chat->id; auto userId = cq.from->id; - if (data[0] == '+') { + if (data[0] == '-') { auto newStorageIdStr = data.substr(1, data.size()); auto newStorageId = utils::parseSafe(newStorageIdStr); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); api::models::storage::StorageSummary newStorage = { - .id = *newStorageId, .name = newStorageDetails.name, .ownerId = newStorageDetails.ownerId}; - state.storages.push_back(newStorage); - renderStoragesSuggestion(state.availability, state.storages, state.recipeId, userId, chatId, bot, api); + .id = *newStorageId, .name = newStorageDetails.name}; + state.addedStorages.push_back(newStorage); + utils::addStorage(state.availability, newStorage); + renderStoragesSuggestion(state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); } } - if (data[0] == '-') { + if (data[0] == '+') { auto newStorageIdStr = data.substr(1, data.size()); auto newStorageId = utils::parseSafe(newStorageIdStr); if (newStorageId) { - state.storages.erase( - std::ranges::find(state.storages, newStorageId, &api::models::storage::StorageSummary::id)); - renderStoragesSuggestion(state.availability, state.storages, state.recipeId, userId, chatId, bot, api); + auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); + api::models::storage::StorageSummary newStorage = { + .id = *newStorageId, .name = newStorageDetails.name}; + state.addedStorages.erase( + std::ranges::find(state.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); + utils::deleteStorage(state.availability, newStorage); + renderStoragesSuggestion(state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); } } if (data == "back") { renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.storages = state.storages, + stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, + .addedStorages = state.addedStorages, .availability = state.availability, .recipeId = state.recipeId, .fromStorage = state.fromStorage, diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 7d3d48d6..4cbb5ac7 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -20,7 +20,6 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; - if (data == "start_cooking") { // TODO: add state of begginig of cooking return; @@ -33,7 +32,8 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe } } renderShoppingListCreation(selectedIngredients, userId, chatId, bot); - stateManager.put(ShoppingListCreation{.storages = state.storages, + stateManager.put(ShoppingListCreation{.selectedStorages = state.selectedStorages, + .addedStorages = state.addedStorages, .availability = state.availability, .recipeId = state.recipeId, .selectedIngredients = selectedIngredients, @@ -43,16 +43,17 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe return; } if (data == "back_from_recipe_view") { - renderRecipesSuggestion(state.storages, state.pageNo, userId, chatId, bot, api); + renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); stateManager.put( - SuggestedRecipeList{.pageNo = state.pageNo, .storages = state.storages, .fromStorage = state.fromStorage}); + SuggestedRecipeList{.pageNo = state.pageNo, .selectedStorages = state.selectedStorages, .fromStorage = state.fromStorage}); bot.answerCallbackQuery(cq.id); return; } if (data == "add_storages") { - renderStoragesSuggestion(state.availability, state.storages, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeStorageAddition{.storages = state.storages, + renderStoragesSuggestion(state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); + stateManager.put(RecipeStorageAddition{.selectedStorages = state.selectedStorages, + .addedStorages = state.addedStorages, .availability = state.availability, .recipeId = state.recipeId, .fromStorage = state.fromStorage, diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index 17a4d3ff..30de03b6 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -31,15 +31,15 @@ void handleSuggestedRecipeListCQ( if (data == "back") { if (state.fromStorage) { - renderStorageView(state.storages[0].id, cq.from->id, chatId, bot, api); - stateManager.put(StorageView{state.storages[0].id}); // Go to the only one storage + renderStorageView(state.selectedStorages[0].id, cq.from->id, chatId, bot, api); + stateManager.put(StorageView{state.selectedStorages[0].id}); // Go to the only one storage } else { if (api.getStoragesApi().getStoragesList(userId).size() == 1) { renderMainMenu(true, userId, chatId, bot, api); stateManager.put(MainMenu{}); } else { - renderStorageSelection(state.storages, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storages = std::move(state.storages)}); + renderStorageSelection(state.selectedStorages, userId, chatId, bot, api); + stateManager.put(StoragesSelection{.selectedStorages = std::move(state.selectedStorages)}); } } bot.answerCallbackQuery(cq.id); @@ -49,9 +49,10 @@ void handleSuggestedRecipeListCQ( if (data[0] == 'r') { auto recipeId = utils::parseSafe(data.substr(1, data.size())); if (recipeId) { - auto inStorage = utils::inStoragesAvailability(state.storages, *recipeId, userId, api); + auto inStorage = utils::inStoragesAvailability(state.selectedStorages, *recipeId, userId, api); renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.storages = state.storages, + stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, + .addedStorages = {}, .availability = inStorage, .recipeId = *recipeId, .fromStorage = state.fromStorage, @@ -65,8 +66,7 @@ void handleSuggestedRecipeListCQ( if (pageNo) { state.pageNo = *pageNo; } - // Message is 100% exists as it was rendered by some another method - renderRecipesSuggestion(state.storages, *pageNo, userId, chatId, bot, api); + renderRecipesSuggestion(state.selectedStorages, *pageNo, userId, chatId, bot, api); return; } } diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 10c53a75..6d84afe1 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -23,7 +23,8 @@ void handleShoppingListCreationCQ( if (data == "back") { renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.storages = state.storages, + stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, + .addedStorages = state.addedStorages, .availability = state.availability, .recipeId = state.recipeId, .fromStorage = state.fromStorage, @@ -40,7 +41,8 @@ void handleShoppingListCreationCQ( } shoppingApi.put(userId, putIds); renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.storages = state.storages, + stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, + .addedStorages = state.addedStorages, .availability = state.availability, .recipeId = state.recipeId, .fromStorage = state.fromStorage, diff --git a/src/handlers/storages_list/delete.cpp b/src/handlers/storage/delete.cpp similarity index 57% rename from src/handlers/storages_list/delete.cpp rename to src/handlers/storage/delete.cpp index d623a17b..71dfd91c 100644 --- a/src/handlers/storages_list/delete.cpp +++ b/src/handlers/storage/delete.cpp @@ -3,20 +3,16 @@ #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/storages_list/view.hpp" -#include "utils/parsing.hpp" namespace cookcookhnya::handlers::storages_list { using namespace render::storages_list; void handleStorageDeletionCQ( - StorageDeletion& /*unused*/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); - if (cq.data.starts_with("st")) { - auto storageId = utils::parseSafe(cq.data.substr(4)); - if (storageId) { - storageApi.delete_(cq.from->id, *storageId); - } + if (cq.data.starts_with("confirm")) { + storageApi.delete_(cq.from->id, state.storageId); } renderStorageList(true, cq.from->id, cq.message->chat->id, bot, storageApi); stateManager.put(StorageList{}); diff --git a/src/handlers/storages_list/delete.hpp b/src/handlers/storage/delete.hpp similarity index 100% rename from src/handlers/storages_list/delete.hpp rename to src/handlers/storage/delete.hpp diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index c57d0088..ff94dd55 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -3,6 +3,7 @@ #include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/recipes_suggestions/view.hpp" +#include "render/storage/delete.hpp" #include "render/storage/ingredients/view.hpp" #include "render/storage/members/view.hpp" #include "render/storages_list/view.hpp" @@ -18,6 +19,7 @@ using namespace render::storage::ingredients; using namespace render::storage::members; using namespace render::storages_list; using namespace render::recipes_suggestions; +using namespace render::delete_storage; void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); @@ -41,10 +43,14 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM } else if (cq.data == "wanna_eat") { auto storageDetails = api.getStoragesApi().get(userId, state.storageId); api::models::storage::StorageSummary storage = { - .id = state.storageId, .name = storageDetails.name, .ownerId = storageDetails.ownerId}; + .id = state.storageId, .name = storageDetails.name}; std::vector storages = {storage}; renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{.pageNo = 0, .storages = std::vector{storage}, .fromStorage = true}); + stateManager.put(SuggestedRecipeList{.pageNo = 0, .selectedStorages = std::vector{storage}, .fromStorage = true}); + return; + } else if (cq.data == "delete") { + renderStorageDeletion(state.storageId, chatId, bot, cq.from->id, api); + stateManager.put(StorageDeletion{state.storageId}); return; } } diff --git a/src/handlers/storages_list/view.cpp b/src/handlers/storages_list/view.cpp index 779a1601..b00c5c88 100644 --- a/src/handlers/storages_list/view.cpp +++ b/src/handlers/storages_list/view.cpp @@ -6,7 +6,6 @@ #include "render/main_menu/view.hpp" #include "render/storage/view.hpp" #include "render/storages_list/create.hpp" -#include "render/storages_list/delete.hpp" #include "utils/parsing.hpp" #include @@ -15,7 +14,6 @@ namespace cookcookhnya::handlers::storages_list { using namespace render::main_menu; using namespace render::create_storage; -using namespace render::delete_storage; using namespace render::storage; void handleStorageListCQ( @@ -29,11 +27,6 @@ void handleStorageListCQ( stateManager.put(StorageCreationEnterName{}); return; } - if (cq.data == "delete") { - renderStorageDeletion(chatId, bot, cq.from->id, api); - stateManager.put(StorageDeletion{}); - return; - } if (cq.data == "back") { renderMainMenu(true, userId, chatId, bot, api); stateManager.put(MainMenu{}); diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index b6269bc3..0af8cdb9 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -23,12 +23,11 @@ void handleStoragesSelectionCQ( bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; - auto selectedStorages = state.storages; if (cq.data == "confirm") { - renderRecipesSuggestion(selectedStorages, 0, userId, chatId, bot, api); + renderRecipesSuggestion(state.selectedStorages, 0, userId, chatId, bot, api); stateManager.put( - SuggestedRecipeList{.pageNo = 0, .storages = std::move(selectedStorages), .fromStorage = false}); + SuggestedRecipeList{.pageNo = 0, .selectedStorages = std::move(state.selectedStorages), .fromStorage = false}); return; } if (cq.data == "cancel") { @@ -40,18 +39,18 @@ void handleStoragesSelectionCQ( auto storageId = utils::parseSafe(cq.data.substr(1)); if (storageId) { if (cq.data[0] == '+') { - auto it = std::ranges::find(selectedStorages, *storageId, &api::models::storage::StorageSummary::id); - selectedStorages.erase(it); - renderStorageSelection(selectedStorages, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storages = selectedStorages}); + auto it = std::ranges::find(state.selectedStorages, *storageId, &api::models::storage::StorageSummary::id); + state.selectedStorages.erase(it); + renderStorageSelection(state.selectedStorages, userId, chatId, bot, api); + stateManager.put(StoragesSelection{.selectedStorages = state.selectedStorages}); return; } if (cq.data[0] == '-') { auto storageDetails = api.getStoragesApi().get(userId, *storageId); - selectedStorages.push_back( - {.id = *storageId, .name = storageDetails.name, .ownerId = storageDetails.ownerId}); - renderStorageSelection(selectedStorages, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storages = selectedStorages}); + state.selectedStorages.push_back( + {.id = *storageId, .name = storageDetails.name}); + renderStorageSelection(state.selectedStorages, userId, chatId, bot, api); + stateManager.put(StoragesSelection{.selectedStorages = state.selectedStorages}); return; } } diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 277c63d2..929d3b02 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -27,9 +27,9 @@ target_sources(main PRIVATE src/render/storage/members/view.cpp src/render/storage/view.cpp + src/render/storage/delete.cpp src/render/storages_list/create.cpp - src/render/storages_list/delete.cpp src/render/storages_list/view.cpp src/render/storages_selection/view.cpp diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 94669cfc..24fc1c78 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -125,7 +125,7 @@ void renderCustomRecipesList(size_t pageNo, UserId userId, ChatId chatId, BotRef const std::size_t numOfRecipesOnPage = 5; auto recipesList = - recipesApi.getRecipesList(userId, "", 0, numOfRecipesOnPage, pageNo * numOfRecipesOnPage, filterType::Custom); + recipesApi.getRecipesList(userId, "", 0, numOfRecipesOnPage, pageNo * numOfRecipesOnPage, FilterType::Custom); if (messageId) { bot.editMessageText( pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index 1670f30c..a973bf58 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -21,6 +21,7 @@ textGenInfo storageAdditionView( const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, + const std::vector& addedStorages, api::RecipeId recipeId, UserId userId, ApiClient api) { @@ -42,7 +43,7 @@ textGenInfo storageAdditionView( } else if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { text += "`[?]` " + infoPair.first.name + "\n"; isIngredientIsOtherStorages = true; - text += "__Доступно в: "; + text += "Доступно в: "; auto storages = infoPair.second.storages; for (std::size_t i = 0; i != storages.size(); ++i) { text += storages[i].name; @@ -64,13 +65,24 @@ void renderStoragesSuggestion( const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, + const std::vector& addedStorages, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, ApiClient api) { - auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); - auto storages = api.getStoragesApi().getStoragesList(userId); + auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, addedStorages, recipeId, userId, api); + std::vector storages; + for (const auto& infoPair : inStoragesAvailability){ + if(infoPair.second.available == utils::AvailabiltiyType::other_storages){ + for (const auto& storage : infoPair.second.storages){ + if (std::ranges::find(storages, storage.id, &api::models::storage::StorageSummary::id) == storages.end()){ + storages.push_back(storage); + } + } + } + } + storages.insert(storages.end(), addedStorages.begin(), addedStorages.end()); const size_t buttonRows = ((storages.size() + 1) / 2) + 1; InlineKeyboard keyboard(buttonRows); @@ -78,8 +90,8 @@ void renderStoragesSuggestion( if (i % 2 == 0) keyboard[i / 2].reserve(2); const bool isSelected = - std::ranges::find(selectedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != - selectedStorages.end(); + std::ranges::find(addedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != + addedStorages.end(); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; @@ -87,7 +99,7 @@ void renderStoragesSuggestion( const std::string data = actionPrefix + utils::to_string(storages[i].id); keyboard[i / 2].push_back(makeCallbackButton(text, data)); } - if (!selectedStorages.empty()) { + if (!storages.empty()) { keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); } diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index 3021abe6..39c7ccd5 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -14,6 +14,7 @@ textGenInfo storageAdditionView( const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, + const std::vector& addedStorages, api::RecipeId recipeId, UserId userId, ApiClient api); @@ -22,6 +23,7 @@ void renderStoragesSuggestion( const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, + const std::vector& addedStorages, api::RecipeId recipeId, UserId userId, ChatId chatId, diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index 9cac9359..004f0047 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -37,7 +37,9 @@ recipeView(const std::vector + +namespace cookcookhnya::render::delete_storage { + +void renderStorageDeletion(api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi) { + auto storage = storageApi.get(userId, storageId); + InlineKeyboard keyboard(2); + keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); + keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + auto text = utils::utf8str(u8"🚮 Вы уверены, что хотите удалить хранилище?"); + auto messageId = message::getMessageId(userId); + if (messageId) + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); +}; + +} // namespace cookcookhnya::render::delete_storage diff --git a/src/render/storage/delete.hpp b/src/render/storage/delete.hpp new file mode 100644 index 00000000..01a6d00e --- /dev/null +++ b/src/render/storage/delete.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "backend/id_types.hpp" +#include "render/common.hpp" + +namespace cookcookhnya::render::delete_storage { + +void renderStorageDeletion(api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi); + +} // namespace cookcookhnya::render::delete_storage diff --git a/src/render/storage/view.cpp b/src/render/storage/view.cpp index e1d9eb3c..93578216 100644 --- a/src/render/storage/view.cpp +++ b/src/render/storage/view.cpp @@ -12,14 +12,25 @@ namespace cookcookhnya::render::storage { void renderStorageView(api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); - const std::size_t buttonRows = 2; + const std::size_t buttonRows = storage.ownerId == userId ? 3 : 2; InlineKeyboard keyboard(buttonRows); - keyboard[0].reserve(2); - keyboard[0].push_back(makeCallbackButton(u8"🍗 Продукты", "ingredients")); - keyboard[0].push_back(makeCallbackButton(u8"👥 Участники", "members")); - keyboard[1].reserve(2); - keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - keyboard[1].push_back(makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat")); + if (storage.ownerId == userId){ + keyboard[0].reserve(2); + keyboard[0].push_back(makeCallbackButton(u8"🍗 Продукты", "ingredients")); + keyboard[0].push_back(makeCallbackButton(u8"👥 Участники", "members")); + keyboard[1].push_back(makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat")); + keyboard[2].reserve(2); + keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + keyboard[2].push_back(makeCallbackButton(u8"🚮 Удалить", "delete")); + } else { + keyboard[0].reserve(2); + keyboard[0].push_back(makeCallbackButton(u8"🍗 Продукты", "ingredients")); + keyboard[0].push_back(makeCallbackButton(u8"👥 Участники", "members")); + keyboard[1].reserve(2); + keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + keyboard[1].push_back(makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat")); + } + auto text = utils::utf8str(u8"Вы находитесь в хранилище 🍱 ") + storage.name + "\n"; auto messageId = message::getMessageId(userId); if (messageId) { diff --git a/src/render/storages_list/delete.cpp b/src/render/storages_list/delete.cpp deleted file mode 100644 index 083b70ba..00000000 --- a/src/render/storages_list/delete.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "delete.hpp" - -#include "message_tracker.hpp" -#include "render/common.hpp" -#include "utils/to_string.hpp" -#include "utils/utils.hpp" - -#include -#include -#include - -namespace cookcookhnya::render::delete_storage { - -void renderStorageDeletion(ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi) { - auto storages = storageApi.getStoragesList(userId); - size_t numStoragesOwner = 0; - for (auto& storage : storages) { - if (userId == storage.ownerId) { - numStoragesOwner++; - } - } - InlineKeyboard keyboard(numStoragesOwner + 1); - keyboard[0].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - size_t k = 1; - for (auto& storage : storages) { - if (userId == storage.ownerId) { - keyboard[k++].push_back(makeCallbackButton(std::format("{} {}", utils::utf8str(u8"🍱"), storage.name), - "st__" + utils::to_string(storage.id))); - } - } - auto text = utils::utf8str(u8"🚮 Выберите хранилище для удаления"); - auto messageId = message::getMessageId(userId); - if (messageId) - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); -}; - -} // namespace cookcookhnya::render::delete_storage diff --git a/src/render/storages_list/delete.hpp b/src/render/storages_list/delete.hpp deleted file mode 100644 index 23f7eace..00000000 --- a/src/render/storages_list/delete.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "render/common.hpp" - -namespace cookcookhnya::render::delete_storage { - -void renderStorageDeletion(ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi); - -} // namespace cookcookhnya::render::delete_storage diff --git a/src/render/storages_list/view.cpp b/src/render/storages_list/view.cpp index bb6c657e..c2866689 100644 --- a/src/render/storages_list/view.cpp +++ b/src/render/storages_list/view.cpp @@ -15,22 +15,16 @@ using namespace tg_types; void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); - const std::size_t buttonRows = storages.empty() ? 2 : ((storages.size() + 1) / 2) + 2; // ceiling + const std::size_t buttonRows = storages.empty() ? 2 : ((storages.size() + 2) / 2) + 1; InlineKeyboard keyboard(buttonRows); - if (!storages.empty()) { - keyboard[0].reserve(2); - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); - keyboard[0].push_back(makeCallbackButton(u8"🚮 Удалить", "delete")); - } else { - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); - } for (std::size_t i = 0; i < storages.size(); i++) { if (i % 2 == 0) - keyboard[1 + (i / 2)].reserve(2); - keyboard[1 + (i / 2)].push_back(makeCallbackButton("🍱 " + storages[i].name, utils::to_string(storages[i].id))); + keyboard[i / 2].reserve(2); + keyboard[i / 2].push_back(makeCallbackButton("🍱 " + storages[i].name, utils::to_string(storages[i].id))); } + keyboard[storages.size() / 2].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); auto text = utils::utf8str(u8"🍱 Ваши хранилища"); if (toBeEdited) { diff --git a/src/states.hpp b/src/states.hpp index 960bd61b..dd79831f 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -39,10 +39,10 @@ struct CustomIngredientConfirmation { struct CustomIngredientPublish {}; struct StorageList {}; -struct StorageDeletion {}; struct StorageCreationEnterName {}; -struct StorageView : detail::StorageIdMixin {}; +struct StorageView : detail::StorageIdMixin {}; +struct StorageDeletion : detail::StorageIdMixin{}; struct StorageMemberView : detail::StorageIdMixin {}; struct StorageMemberAddition : detail::StorageIdMixin {}; struct StorageMemberDeletion : detail::StorageIdMixin {}; @@ -60,15 +60,16 @@ struct StorageIngredientsList : detail::StorageIdMixin { }; struct StoragesSelection { - std::vector storages; + std::vector selectedStorages; }; struct SuggestedRecipeList { std::size_t pageNo; - std::vector storages; + std::vector selectedStorages; bool fromStorage; }; struct RecipeView { - std::vector storages; + std::vector selectedStorages; + std::vector addedStorages; std::vector> availability; api::RecipeId recipeId; @@ -77,7 +78,8 @@ struct RecipeView { }; struct RecipeStorageAddition { - std::vector storages; + std::vector selectedStorages; + std::vector addedStorages; std::vector> availability; api::RecipeId recipeId; @@ -114,7 +116,8 @@ struct CreateCustomRecipe { }; struct ShoppingListCreation { - std::vector storages; + std::vector selectedStorages; + std::vector addedStorages; std::vector> availability; api::RecipeId recipeId; diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index cb4dc284..ba03d889 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -1,6 +1,7 @@ #include "ingredients_availability.hpp" #include "backend/id_types.hpp" +#include "backend/models/storage.hpp" #include #include @@ -30,26 +31,30 @@ inStoragesAvailability(std::vector& selectedSto for (const auto& ingredient : recipe.ingredients) { IngredientAvailability availability; - std::vector otherStorages; + std::vector storages; bool hasInSelected = false; + bool hasInOther = false; - for (const auto& storageId : ingredient.inStorages) { - auto it = allStoragesMap.find(storageId); + for (const auto& storage : ingredient.inStorages) { + auto it = allStoragesMap.find(storage.id); if (it == allStoragesMap.end()) continue; - if (selectedStoragesSet.contains(storageId)) { + if (selectedStoragesSet.contains(storage.id)) { + storages.push_back(it->second); hasInSelected = true; } else { - otherStorages.push_back(it->second); + storages.push_back(it->second); + hasInOther = true; } } if (hasInSelected) { availability.available = AvailabiltiyType::available; - } else if (!otherStorages.empty()) { + availability.storages = std::move(storages); + } else if (hasInOther) { availability.available = AvailabiltiyType::other_storages; - availability.storages = std::move(otherStorages); + availability.storages = std::move(storages); } else { availability.available = AvailabiltiyType::not_available; } @@ -59,4 +64,27 @@ inStoragesAvailability(std::vector& selectedSto return result; } + +void addStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage){ + for (auto& infoPair : availability){ + auto it = std::ranges::find(infoPair.second.storages, storage.id, &models::storage::StorageSummary::id); + if (it != infoPair.second.storages.end()){ + infoPair.second.storages.erase(it); + infoPair.second.available = AvailabiltiyType::available; + } + } +} + +void deleteStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage){ + for (auto& infoPair : availability){ + for (auto& storage_ : infoPair.first.inStorages){ + if(storage.id == storage_.id){ + infoPair.second.storages.push_back(storage); + infoPair.second.available = AvailabiltiyType::other_storages; + } + } + } +} + + } // namespace cookcookhnya::utils diff --git a/src/utils/ingredients_availability.hpp b/src/utils/ingredients_availability.hpp index 3c239e48..504f51b9 100644 --- a/src/utils/ingredients_availability.hpp +++ b/src/utils/ingredients_availability.hpp @@ -1,6 +1,7 @@ #pragma once #include "backend/api/api.hpp" +#include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" @@ -13,13 +14,17 @@ enum struct AvailabiltiyType : std::uint8_t { available, not_available, other_st struct IngredientAvailability { AvailabiltiyType available = AvailabiltiyType::not_available; - std::vector storages; + std::vector storages; }; -std::vector> +std::vector> inStoragesAvailability(std::vector& selectedStorages, - cookcookhnya::api::RecipeId recipeId, - cookcookhnya::tg_types::UserId userId, + api::RecipeId recipeId, + tg_types::UserId userId, const api::ApiClient& api); +void addStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage); + +void deleteStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage); + } // namespace cookcookhnya::utils From 33a4d8c845b54c18b6a6edbb0ecf679cfbc588b3 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Fri, 18 Jul 2025 22:24:14 +0300 Subject: [PATCH 009/106] feat: done refactoring recipe view, storage addition in recipe, shopping list creation --- src/handlers/storage/delete.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/handlers/storage/delete.cpp b/src/handlers/storage/delete.cpp index 71dfd91c..6b49772e 100644 --- a/src/handlers/storage/delete.cpp +++ b/src/handlers/storage/delete.cpp @@ -3,19 +3,27 @@ #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/storages_list/view.hpp" +#include "render/storage/view.hpp" +#include "states.hpp" namespace cookcookhnya::handlers::storages_list { using namespace render::storages_list; +using namespace render::storage; void handleStorageDeletionCQ( StorageDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); - if (cq.data.starts_with("confirm")) { + if (cq.data == "confirm") { storageApi.delete_(cq.from->id, state.storageId); + renderStorageList(true, cq.from->id, cq.message->chat->id, bot, storageApi); + stateManager.put(StorageList{}); } - renderStorageList(true, cq.from->id, cq.message->chat->id, bot, storageApi); - stateManager.put(StorageList{}); + if (cq.data == "back"){ + renderStorageView(state.storageId, cq.from->id, cq.message->chat->id, bot, storageApi); + stateManager.put(StorageView{state.storageId}); + } + }; } // namespace cookcookhnya::handlers::storages_list From f593ce7e201d3f20166f21d82495b35a053e79ae Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Fri, 18 Jul 2025 22:39:55 +0300 Subject: [PATCH 010/106] style: format fixes --- src/backend/models/storage.cpp | 2 +- src/handlers/handlers_list.hpp | 1 - src/handlers/recipe/add_storage.cpp | 24 ++++++++++++++++++------ src/handlers/recipe/view.cpp | 7 ++++--- src/handlers/shopping_list/create.cpp | 2 +- src/handlers/storage/delete.cpp | 3 +-- src/handlers/storage/view.cpp | 6 +++--- src/handlers/storages_selection/view.cpp | 7 +++---- src/render/recipe/add_storage.cpp | 12 ++++++------ src/render/recipe/add_storage.hpp | 1 - src/render/recipe/view.cpp | 2 +- src/render/recipes_suggestions/view.cpp | 3 +-- src/render/storage/delete.cpp | 3 ++- src/render/storage/delete.hpp | 3 ++- src/render/storage/view.cpp | 4 ++-- src/render/storages_list/view.cpp | 1 - src/states.hpp | 2 +- src/utils/ingredients_availability.cpp | 18 ++++++++++-------- src/utils/ingredients_availability.hpp | 7 +++++-- 19 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/backend/models/storage.cpp b/src/backend/models/storage.cpp index a1d8c289..05f2c3b5 100644 --- a/src/backend/models/storage.cpp +++ b/src/backend/models/storage.cpp @@ -11,7 +11,7 @@ namespace json = boost::json; StorageSummary tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .id = value_to(j.at("id")), - .name = value_to(j.at("name")), + .name = value_to(j.at("name")), }; } diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 746a2ad5..015ba339 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -33,7 +33,6 @@ #include "storage/delete.hpp" #include "storage/view.hpp" - #include "storages_list/create.hpp" #include "storages_list/view.hpp" diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp index 8d49e527..299f3f43 100644 --- a/src/handlers/recipe/add_storage.cpp +++ b/src/handlers/recipe/add_storage.cpp @@ -27,11 +27,17 @@ void handleRecipeStorageAdditionCQ( auto newStorageId = utils::parseSafe(newStorageIdStr); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - api::models::storage::StorageSummary newStorage = { - .id = *newStorageId, .name = newStorageDetails.name}; + api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; state.addedStorages.push_back(newStorage); utils::addStorage(state.availability, newStorage); - renderStoragesSuggestion(state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); + renderStoragesSuggestion(state.availability, + state.selectedStorages, + state.addedStorages, + state.recipeId, + userId, + chatId, + bot, + api); } } if (data[0] == '+') { @@ -39,12 +45,18 @@ void handleRecipeStorageAdditionCQ( auto newStorageId = utils::parseSafe(newStorageIdStr); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - api::models::storage::StorageSummary newStorage = { - .id = *newStorageId, .name = newStorageDetails.name}; + api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; state.addedStorages.erase( std::ranges::find(state.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); utils::deleteStorage(state.availability, newStorage); - renderStoragesSuggestion(state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); + renderStoragesSuggestion(state.availability, + state.selectedStorages, + state.addedStorages, + state.recipeId, + userId, + chatId, + bot, + api); } } diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 4cbb5ac7..4aea3191 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -44,14 +44,15 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe } if (data == "back_from_recipe_view") { renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); - stateManager.put( - SuggestedRecipeList{.pageNo = state.pageNo, .selectedStorages = state.selectedStorages, .fromStorage = state.fromStorage}); + stateManager.put(SuggestedRecipeList{ + .pageNo = state.pageNo, .selectedStorages = state.selectedStorages, .fromStorage = state.fromStorage}); bot.answerCallbackQuery(cq.id); return; } if (data == "add_storages") { - renderStoragesSuggestion(state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); + renderStoragesSuggestion( + state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); stateManager.put(RecipeStorageAddition{.selectedStorages = state.selectedStorages, .addedStorages = state.addedStorages, .availability = state.availability, diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 6d84afe1..a3c98fcb 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -24,7 +24,7 @@ void handleShoppingListCreationCQ( if (data == "back") { renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, + .addedStorages = state.addedStorages, .availability = state.availability, .recipeId = state.recipeId, .fromStorage = state.fromStorage, diff --git a/src/handlers/storage/delete.cpp b/src/handlers/storage/delete.cpp index d29746b6..e82a149a 100644 --- a/src/handlers/storage/delete.cpp +++ b/src/handlers/storage/delete.cpp @@ -19,11 +19,10 @@ void handleStorageDeletionCQ( renderStorageList(true, cq.from->id, cq.message->chat->id, bot, storageApi); stateManager.put(StorageList{}); } - if (cq.data == "back"){ + if (cq.data == "back") { renderStorageView(state.storageId, cq.from->id, cq.message->chat->id, bot, storageApi); stateManager.put(StorageView{state.storageId}); } - }; } // namespace cookcookhnya::handlers::storages_list diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index e35579ef..c537c077 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -39,11 +39,11 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM stateManager.put(StorageList{}); } else if (cq.data == "wanna_eat") { auto storageDetails = api.getStoragesApi().get(userId, state.storageId); - api::models::storage::StorageSummary storage = { - .id = state.storageId, .name = storageDetails.name}; + api::models::storage::StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; std::vector storages = {storage}; renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{.pageNo = 0, .selectedStorages = std::vector{storage}, .fromStorage = true}); + stateManager.put( + SuggestedRecipeList{.pageNo = 0, .selectedStorages = std::vector{storage}, .fromStorage = true}); return; } else if (cq.data == "delete") { renderStorageDeletion(state.storageId, chatId, bot, cq.from->id, api); diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 0af8cdb9..4fd70153 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -26,8 +26,8 @@ void handleStoragesSelectionCQ( if (cq.data == "confirm") { renderRecipesSuggestion(state.selectedStorages, 0, userId, chatId, bot, api); - stateManager.put( - SuggestedRecipeList{.pageNo = 0, .selectedStorages = std::move(state.selectedStorages), .fromStorage = false}); + stateManager.put(SuggestedRecipeList{ + .pageNo = 0, .selectedStorages = std::move(state.selectedStorages), .fromStorage = false}); return; } if (cq.data == "cancel") { @@ -47,8 +47,7 @@ void handleStoragesSelectionCQ( } if (cq.data[0] == '-') { auto storageDetails = api.getStoragesApi().get(userId, *storageId); - state.selectedStorages.push_back( - {.id = *storageId, .name = storageDetails.name}); + state.selectedStorages.push_back({.id = *storageId, .name = storageDetails.name}); renderStorageSelection(state.selectedStorages, userId, chatId, bot, api); stateManager.put(StoragesSelection{.selectedStorages = state.selectedStorages}); return; diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index a973bf58..2eb977bc 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -21,7 +21,6 @@ textGenInfo storageAdditionView( const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, - const std::vector& addedStorages, api::RecipeId recipeId, UserId userId, ApiClient api) { @@ -71,12 +70,13 @@ void renderStoragesSuggestion( ChatId chatId, BotRef bot, ApiClient api) { - auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, addedStorages, recipeId, userId, api); + auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); std::vector storages; - for (const auto& infoPair : inStoragesAvailability){ - if(infoPair.second.available == utils::AvailabiltiyType::other_storages){ - for (const auto& storage : infoPair.second.storages){ - if (std::ranges::find(storages, storage.id, &api::models::storage::StorageSummary::id) == storages.end()){ + for (const auto& infoPair : inStoragesAvailability) { + if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { + for (const auto& storage : infoPair.second.storages) { + if (std::ranges::find(storages, storage.id, &api::models::storage::StorageSummary::id) == + storages.end()) { storages.push_back(storage); } } diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index 39c7ccd5..d6fb071e 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -14,7 +14,6 @@ textGenInfo storageAdditionView( const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, - const std::vector& addedStorages, api::RecipeId recipeId, UserId userId, ApiClient api); diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index 004f0047..6d581e08 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -37,7 +37,7 @@ recipeView(const std::vector& selectedSto return result; } -void addStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage){ - for (auto& infoPair : availability){ +void addStorage(std::vector>& availability, + const api::models::storage::StorageSummary& storage) { + for (auto& infoPair : availability) { auto it = std::ranges::find(infoPair.second.storages, storage.id, &models::storage::StorageSummary::id); - if (it != infoPair.second.storages.end()){ + if (it != infoPair.second.storages.end()) { infoPair.second.storages.erase(it); infoPair.second.available = AvailabiltiyType::available; } } } -void deleteStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage){ - for (auto& infoPair : availability){ - for (auto& storage_ : infoPair.first.inStorages){ - if(storage.id == storage_.id){ +void deleteStorage( + std::vector>& availability, + const api::models::storage::StorageSummary& storage) { + for (auto& infoPair : availability) { + for (auto& storage_ : infoPair.first.inStorages) { + if (storage.id == storage_.id) { infoPair.second.storages.push_back(storage); infoPair.second.available = AvailabiltiyType::other_storages; } @@ -86,5 +89,4 @@ void deleteStorage(std::vector& select tg_types::UserId userId, const api::ApiClient& api); -void addStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage); +void addStorage(std::vector>& availability, + const api::models::storage::StorageSummary& storage); -void deleteStorage(std::vector>& availability, const api::models::storage::StorageSummary& storage); +void deleteStorage( + std::vector>& availability, + const api::models::storage::StorageSummary& storage); } // namespace cookcookhnya::utils From a997a00a9fa3afae043c95210bd5d43cc034e9d5 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sat, 19 Jul 2025 02:32:10 +0300 Subject: [PATCH 011/106] feat: shopping list buy action --- src/handlers/CMakeLists.txt | 1 + src/handlers/common.hpp | 2 + src/handlers/handlers_list.hpp | 3 ++ src/handlers/main_menu/view.cpp | 5 +- src/handlers/personal_account/recipe/view.cpp | 7 +-- src/handlers/recipes_suggestions/view.cpp | 8 ++-- .../storage_selection_to_buy.cpp | 45 ++++++++++++++++++ .../storage_selection_to_buy.hpp | 13 ++++++ src/handlers/shopping_list/view.cpp | 20 +++++++- src/handlers/storage/view.cpp | 12 +++-- src/handlers/storages_selection/view.cpp | 26 ++++------- src/main.cpp | 3 +- src/render/CMakeLists.txt | 1 + src/render/common.hpp | 9 ++++ src/render/recipes_suggestions/view.cpp | 9 +--- src/render/shopping_list/create.cpp | 6 ++- .../storage_selection_to_buy.cpp | 38 +++++++++++++++ .../storage_selection_to_buy.hpp | 13 ++++++ src/render/storages_list/view.cpp | 35 +++++++------- src/render/storages_selection/view.cpp | 46 ++++++++++--------- src/render/storages_selection/view.hpp | 11 ++--- src/states.hpp | 7 +++ 22 files changed, 234 insertions(+), 86 deletions(-) create mode 100644 src/handlers/shopping_list/storage_selection_to_buy.cpp create mode 100644 src/handlers/shopping_list/storage_selection_to_buy.hpp create mode 100644 src/render/shopping_list/storage_selection_to_buy.cpp create mode 100644 src/render/shopping_list/storage_selection_to_buy.hpp diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 575165f4..a3eb7e62 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources(main PRIVATE src/handlers/recipes_suggestions/view.cpp src/handlers/shopping_list/create.cpp + src/handlers/shopping_list/storage_selection_to_buy.cpp src/handlers/shopping_list/view.cpp src/handlers/storage/ingredients/view.cpp diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 5ad0cb3b..c04b7168 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -44,6 +44,7 @@ using states::StoragesSelection; using states::SuggestedRecipeList; using states::ShoppingListCreation; +using states::ShoppingListStorageSelectionToBuy; using states::ShoppingListView; using states::CreateCustomRecipe; @@ -57,6 +58,7 @@ using UserApiRef = const api::UsersApi&; using StorageApiRef = const api::StoragesApi&; using IngredientsApiRef = const api::IngredientsApi&; using RecipesApiRef = const api::RecipesApi&; +using ShoppingListApiRef = const api::ShoppingListApi&; using BotRef = const TgBot::Api&; using SMRef = const states::StateManager&; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 9a4807f7..dd91634d 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -22,6 +22,7 @@ #include "recipes_suggestions/view.hpp" #include "shopping_list/create.hpp" +#include "shopping_list/storage_selection_to_buy.hpp" #include "shopping_list/view.hpp" #include "storage/ingredients/view.hpp" @@ -112,6 +113,8 @@ using shoppingListCreationCQHandler = Handler; +using shoppingListStorageSelectionToBuyCQHandler = + Handler; // Personal account using personalAccountMenuCQHandler = Handler; diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index 2dad10a0..4d2a547e 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -51,8 +51,9 @@ void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SM ShoppingListView::ItemsDb itemsDb{ items | transform([](auto& i) { return ShoppingListView::SelectableItem{std::move(i)}; })}; - stateManager.put(ShoppingListView{.items = std::move(itemsDb), .canBuy = canBuy}); - renderShoppingList(std::get(*stateManager.get()), userId, chatId, bot); + auto newState = ShoppingListView{.items = std::move(itemsDb), .canBuy = canBuy}; + renderShoppingList(newState, userId, chatId, bot); + stateManager.put(std::move(newState)); return; } diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index 5e5ccbab..eea483d0 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace cookcookhnya::handlers::personal_account::recipes { @@ -30,9 +31,9 @@ void handleRecipeCustomViewCQ( } if (data == "change") { - stateManager.put(CustomRecipeIngredientsSearch{state.recipeId, state.ingredients | as_rvalue, ""}); - renderRecipeIngredientsSearch( - std::get(*stateManager.get()), numOfIngredientsOnPage, userId, chatId, bot); + auto newState = CustomRecipeIngredientsSearch{state.recipeId, state.ingredients | as_rvalue, ""}; + renderRecipeIngredientsSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); + stateManager.put(std::move(newState)); return; } diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index fdef7c22..ca304500 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -32,14 +32,15 @@ void handleSuggestedRecipeListCQ( if (data == "back") { if (state.fromStorage) { renderStorageView(state.storageIds[0], cq.from->id, chatId, bot, api); - stateManager.put(StorageView{state.storageIds[0]}); // Go to the only one storage + stateManager.put(StorageView{state.storageIds[0]}); // Go to the only storage } else { if (api.getStoragesApi().getStoragesList(userId).size() == 1) { renderMainMenu(true, userId, chatId, bot, api); stateManager.put(MainMenu{}); } else { - renderStorageSelection(state.storageIds, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = std::move(state.storageIds)}); + auto newState = StoragesSelection{.storageIds = std::move(state.storageIds)}; + renderStorageSelection(newState, userId, chatId, bot, api); + stateManager.put(std::move(newState)); } } bot.answerCallbackQuery(cq.id); @@ -47,7 +48,6 @@ void handleSuggestedRecipeListCQ( } if (data[0] == 'r') { // Same naive implementation: if first char is r then it's recipe - auto recipeId = utils::parseSafe( data.substr(data.find(' ', 0) + 1, data.size())); // +1 is to move from space and get pure number if (recipeId) { diff --git a/src/handlers/shopping_list/storage_selection_to_buy.cpp b/src/handlers/shopping_list/storage_selection_to_buy.cpp new file mode 100644 index 00000000..37acc821 --- /dev/null +++ b/src/handlers/shopping_list/storage_selection_to_buy.cpp @@ -0,0 +1,45 @@ +#include "storage_selection_to_buy.hpp" + +#include "backend/id_types.hpp" +#include "handlers/common.hpp" +#include "render/shopping_list/view.hpp" +#include "utils/parsing.hpp" + +#include +#include +#include + +namespace cookcookhnya::handlers::shopping_list { + +using render::shopping_list::renderShoppingList; + +void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy& state, + CallbackQueryRef cq, + BotRef bot, + SMRef stateManager, + ShoppingListApiRef api) { + bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + + if (cq.data == "back") { + renderShoppingList(state.prevState, userId, chatId, bot); + stateManager.put(std::move(state.prevState)); + return; + } + + const std::size_t idStrStart = sizeof("storage_") - 1; + if (cq.data.size() <= idStrStart) + return; + auto mStorageId = utils::parseSafe(std::string_view{cq.data}.substr(idStrStart)); + if (!mStorageId) + return; + + api.buy(userId, *mStorageId, state.selectedIngredients); + for (auto& id : state.selectedIngredients) + state.prevState.items.remove(id); + renderShoppingList(state.prevState, userId, chatId, bot); + stateManager.put(std::move(state.prevState)); +} + +} // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/storage_selection_to_buy.hpp b/src/handlers/shopping_list/storage_selection_to_buy.hpp new file mode 100644 index 00000000..7801f9c1 --- /dev/null +++ b/src/handlers/shopping_list/storage_selection_to_buy.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::shopping_list { + +void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy& state, + CallbackQueryRef cq, + BotRef bot, + SMRef stateManager, + ShoppingListApiRef api); + +} // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index 23791849..00447dc5 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -3,10 +3,13 @@ #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" +#include "render/shopping_list/storage_selection_to_buy.hpp" #include "render/shopping_list/view.hpp" +#include "states.hpp" #include "utils/parsing.hpp" #include +#include #include namespace cookcookhnya::handlers::shopping_list { @@ -22,6 +25,7 @@ void handleShoppingListViewCQ( bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; + auto storages = api.getStoragesApi().getStoragesList(userId); if (cq.data == "back") { renderMainMenu(true, userId, cq.message->chat->id, bot, api); @@ -49,8 +53,20 @@ void handleShoppingListViewCQ( } if (cq.data == "buy") { - renderMainMenu(true, userId, cq.message->chat->id, bot, api); - stateManager.put(MainMenu{}); + using SelectableItem = ShoppingListView::SelectableItem; + auto toBuy = state.items.getValues() | filter(&SelectableItem::selected) | + views::transform(&SelectableItem::ingredientId) | to(); + if (storages.size() == 1) { + api.getShoppingListApi().buy(userId, storages[0].id, toBuy); + for (auto& id : toBuy) + state.items.remove(id); + renderShoppingList(state, userId, chatId, bot); + } else { + auto newState = ShoppingListStorageSelectionToBuy{ + .prevState = std::move(state), .selectedIngredients = std::move(toBuy), .storages = storages}; + renderShoppingListStorageSelectionToBuy(newState, userId, chatId, bot); + stateManager.put(std::move(newState)); + } return; } diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 1d10b892..daa0ded9 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace cookcookhnya::handlers::storage { @@ -18,16 +19,18 @@ using namespace render::storages_list; using namespace render::recipes_suggestions; using namespace std::views; +const std::size_t numOfIngredientsOnPage = 5; + void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; - const std::size_t numOfIngredientsOnPage = 5; + if (cq.data == "ingredients") { auto ingredients = api.getIngredientsApi().getStorageIngredients(userId, state.storageId); - stateManager.put(StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}); - renderIngredientsListSearch( - std::get(*stateManager.get()), numOfIngredientsOnPage, userId, chatId, bot); + auto newState = StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}; + renderIngredientsListSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); + stateManager.put(std::move(newState)); } else if (cq.data == "members") { renderMemberList(true, state.storageId, userId, chatId, bot, api); stateManager.put(StorageMemberView{state.storageId}); @@ -37,7 +40,6 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM } else if (cq.data == "wanna_eat") { renderRecipesSuggestion({state.storageId}, 0, userId, chatId, bot, api); stateManager.put(SuggestedRecipeList{.pageNo = 0, .storageIds = {state.storageId}, .fromStorage = true}); - return; } } diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 918c5325..2c17f7a4 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -22,35 +22,29 @@ void handleStoragesSelectionCQ( bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; - auto selectedStorages = state.storageIds; if (cq.data == "confirm") { - renderRecipesSuggestion(selectedStorages, 0, userId, chatId, bot, api); + renderRecipesSuggestion(state.storageIds, 0, userId, chatId, bot, api); stateManager.put( - SuggestedRecipeList{.pageNo = 0, .storageIds = std::move(selectedStorages), .fromStorage = false}); + SuggestedRecipeList{.pageNo = 0, .storageIds = std::move(state.storageIds), .fromStorage = false}); return; } + if (cq.data == "cancel") { renderMainMenu(true, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } - auto storageId = utils::parseSafe(cq.data.substr(4)); - if (storageId) { + if (auto storageId = utils::parseSafe(cq.data.substr(sizeof("in__") - 1))) { if (cq.data.starts_with("in")) { - auto it = std::ranges::find(selectedStorages, *storageId); - selectedStorages.erase(it); - renderStorageSelection(selectedStorages, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = selectedStorages}); - return; - } - if (cq.data.starts_with("out")) { - selectedStorages.push_back(*storageId); - renderStorageSelection(selectedStorages, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.storageIds = selectedStorages}); - return; + if (auto it = std::ranges::find(state.storageIds, *storageId); it != state.storageIds.end()) + state.storageIds.erase(it); + } else if (cq.data.starts_with("out")) { + state.storageIds.push_back(*storageId); } + renderStorageSelection(state, userId, chatId, bot, api); + return; } } } // namespace cookcookhnya::handlers::storages_selection diff --git a/src/main.cpp b/src/main.cpp index 87eb9151..26b71e0c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -64,7 +64,8 @@ int main(int argc, char* argv[]) { createCustomRecipeCQHandler, recipeCustomViewCQHandler, customRecipeIngredientsSearchCQHandler, - customRecipeIngredientsSearchIQHandler> + customRecipeIngredientsSearchIQHandler, + shoppingListStorageSelectionToBuyCQHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 22bf5a89..a401148f 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(main PRIVATE src/render/recipes_suggestions/view.cpp src/render/shopping_list/create.cpp + src/render/shopping_list/storage_selection_to_buy.cpp src/render/shopping_list/view.cpp src/render/storage/ingredients/view.cpp diff --git a/src/render/common.hpp b/src/render/common.hpp index c12cfd4e..1fdb5bee 100644 --- a/src/render/common.hpp +++ b/src/render/common.hpp @@ -88,6 +88,10 @@ class InlineKeyboardBuilder { return *this; } + void reserveInRow(std::size_t capacity) { + keyboard.back().reserve(capacity); + } + InlineKeyboardBuilder& operator<<(NewRow /*tag*/) { keyboard.emplace_back(); return *this; @@ -97,6 +101,11 @@ class InlineKeyboardBuilder { keyboard.emplace_back(); } + void removeRowIfEmpty() { + if (keyboard.back().empty()) + keyboard.pop_back(); + } + TgBot::InlineKeyboardMarkup::Ptr build() && { return makeKeyboardMarkup(std::move(keyboard)); } diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index a49ed27a..6980371a 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -136,13 +136,8 @@ void renderRecipesSuggestion(const std::vector& storageIds, recipesApi.getSuggestedRecipesList(userId, storageIds, numOfRecipes, pageNo * numOfRecipesOnPage); if (messageId) { - bot.editMessageText(pageInfo, - chatId, - *messageId, - "", - "", - nullptr, - makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + bot.editMessageText( + pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); } } diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index 37445ac8..34d8d1d9 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -29,13 +29,17 @@ std::vector renderShoppingListCreation(const std::vector +#include + +namespace cookcookhnya::render::shopping_list { + +using namespace std::views; + +void renderShoppingListStorageSelectionToBuy(const states::ShoppingListStorageSelectionToBuy& state, + UserId userId, + ChatId chatId, + BotRef bot) { + InlineKeyboardBuilder keyboard{((state.storages.size() + 1) / 2) + 1}; // ceil(storagesCount / 2) and back + + // in two columns + for (auto chunk : state.storages | chunk(2)) { + keyboard.reserveInRow(2); + for (const auto& s : chunk) + keyboard << makeCallbackButton(utils::utf8str(u8"🍱 ") + s.name, "storage_" + utils::to_string(s.id)); + keyboard << NewRow{}; + } + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto mMessageId = message::getMessageId(userId)) { + bot.editMessageText( + utils::utf8str(u8"Выберите хранилище, куда положить продукты"), chatId, *mMessageId, std::move(keyboard)); + } +} + +} // namespace cookcookhnya::render::shopping_list diff --git a/src/render/shopping_list/storage_selection_to_buy.hpp b/src/render/shopping_list/storage_selection_to_buy.hpp new file mode 100644 index 00000000..262839c3 --- /dev/null +++ b/src/render/shopping_list/storage_selection_to_buy.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "render/common.hpp" +#include "states.hpp" + +namespace cookcookhnya::render::shopping_list { + +void renderShoppingListStorageSelectionToBuy(const states::ShoppingListStorageSelectionToBuy& state, + UserId userId, + ChatId chatId, + BotRef bot); + +} // namespace cookcookhnya::render::shopping_list diff --git a/src/render/storages_list/view.cpp b/src/render/storages_list/view.cpp index bb6c657e..c8a60a07 100644 --- a/src/render/storages_list/view.cpp +++ b/src/render/storages_list/view.cpp @@ -6,40 +6,43 @@ #include "utils/utils.hpp" #include +#include #include namespace cookcookhnya::render::storages_list { using namespace tg_types; +using namespace std::views; void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); - const std::size_t buttonRows = storages.empty() ? 2 : ((storages.size() + 1) / 2) + 2; // ceiling - InlineKeyboard keyboard(buttonRows); + const std::size_t buttonRows = ((storages.size() + 1) / 2) + 2; // ceiling + InlineKeyboardBuilder keyboard{buttonRows}; - if (!storages.empty()) { - keyboard[0].reserve(2); - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); - keyboard[0].push_back(makeCallbackButton(u8"🚮 Удалить", "delete")); - } else { - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); - } + keyboard.reserveInRow(2); + keyboard << makeCallbackButton(u8"🆕 Создать", "create"); + if (!storages.empty()) + keyboard << makeCallbackButton(u8"🚮 Удалить", "delete"); + keyboard << NewRow{}; - for (std::size_t i = 0; i < storages.size(); i++) { - if (i % 2 == 0) - keyboard[1 + (i / 2)].reserve(2); - keyboard[1 + (i / 2)].push_back(makeCallbackButton("🍱 " + storages[i].name, utils::to_string(storages[i].id))); + for (auto chunk : storages | chunk(2)) { + keyboard.reserveInRow(2); + for (auto& s : chunk) + keyboard << makeCallbackButton(utils::utf8str(u8"🍱 ") + s.name, utils::to_string(s.id)); + keyboard << NewRow{}; } - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + auto text = utils::utf8str(u8"🍱 Ваши хранилища"); if (toBeEdited) { auto messageId = message::getMessageId(userId); if (messageId) { - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } } else { - auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); + auto message = bot.sendMessage(chatId, text, std::move(keyboard)); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index 01bf062c..1da9b8b5 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -9,43 +9,47 @@ #include #include #include +#include #include #include #include namespace cookcookhnya::render::select_storages { +using namespace states; using namespace tg_types; -void renderStorageSelection(const std::vector& selected_storages, - UserId userId, - ChatId chatId, - BotRef bot, - StorageApiRef storageApi) { - auto storages = storageApi.getStoragesList(userId); - const std::size_t buttonRows = ((storages.size() + 1) / 2) + 1; - InlineKeyboard keyboard(buttonRows); +void renderStorageSelection( + const StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const auto& selectedStorages = state.storageIds; + auto allStorages = storageApi.getStoragesList(userId); - for (std::size_t i = 0; i < storages.size(); ++i) { - if (i % 2 == 0) - keyboard[i / 2].reserve(2); - const bool isSelected = std::ranges::find(selected_storages, storages[i].id) != selected_storages.end(); + const std::size_t buttonRows = ((allStorages.size() + 1) / 2) + 1; // ceil(storagesCount / 2) and back + InlineKeyboardBuilder keyboard{buttonRows}; + for (auto [i, storage] : std::views::enumerate(allStorages)) { + const bool isSelected = std::ranges::contains(selectedStorages, storage.id); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "in__" : "out_"; - const std::string text = std::format("{} {}", emoji, storages[i].name); - const std::string data = actionPrefix + utils::to_string(storages[i].id); - keyboard[i / 2].push_back(makeCallbackButton(text, data)); - } - keyboard[buttonRows - 1].reserve(2); - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "cancel")); - if (!selected_storages.empty()) { - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); + std::string text = std::format("{} {}", emoji, storage.name); + std::string data = actionPrefix + utils::to_string(storage.id); + + if (i % 2 == 0) + keyboard.reserveInRow(2); + keyboard << makeCallbackButton(text, data); + if (i % 2 == 1) + keyboard << NewRow{}; } + + keyboard.reserveInRow(2); + keyboard << makeCallbackButton(u8"↩️ Назад", "cancel"); + if (!selectedStorages.empty()) + keyboard << makeCallbackButton(u8"▶️ Подтвердить", "confirm"); + auto text = utils::utf8str(u8"🍱 Откуда брать продукты?"); auto messageId = message::getMessageId(userId); if (messageId) - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, std::move(keyboard).build()); } } // namespace cookcookhnya::render::select_storages diff --git a/src/render/storages_selection/view.hpp b/src/render/storages_selection/view.hpp index f490b55f..465b86a9 100644 --- a/src/render/storages_selection/view.hpp +++ b/src/render/storages_selection/view.hpp @@ -1,16 +1,11 @@ #pragma once -#include "backend/id_types.hpp" #include "render/common.hpp" - -#include +#include "states.hpp" namespace cookcookhnya::render::select_storages { -void renderStorageSelection(const std::vector& selected_storages, - UserId userId, - ChatId chatId, - BotRef bot, - StorageApiRef storageApi); +void renderStorageSelection( + const states::StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); } // namespace cookcookhnya::render::select_storages diff --git a/src/states.hpp b/src/states.hpp index 1036d81e..9b5a0af2 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -3,6 +3,7 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "backend/models/shopping_list.hpp" +#include "backend/models/storage.hpp" #include "utils/fast_sorted_db.hpp" #include @@ -137,6 +138,11 @@ struct ShoppingListView { // NOLINT(*member-init) // Strange. Flags only this st ItemsDb items; bool canBuy; }; +struct ShoppingListStorageSelectionToBuy { + ShoppingListView prevState; + std::vector selectedIngredients; + std::vector storages; +}; using State = std::variant Date: Sat, 19 Jul 2025 02:45:25 +0300 Subject: [PATCH 012/106] chore: add WEBHOOK_CERTIFICATE_PATH to .env.example --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index 69b57979..5e131ed9 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,4 @@ API_URL=localhost:8000 WEBHOOK_PORT=8443 WEBHOOK_HOST=https://mydomain.com WEBHOOK_SECRET=secret +WEBHOOK_CERTIFICATE_PATH=cert.pem From b78badbe67b61a728e52b4c27195640299ce7272 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sat, 19 Jul 2025 03:33:48 +0300 Subject: [PATCH 013/106] refactor: state rename and other --- src/handlers/common.hpp | 2 +- src/handlers/handlers_list.hpp | 4 ++-- src/handlers/main_menu/view.cpp | 2 +- src/handlers/recipe/view.cpp | 2 +- src/handlers/recipes_suggestions/view.cpp | 4 ++-- src/handlers/recipes_suggestions/view.hpp | 4 ++-- src/handlers/storage/view.cpp | 2 +- src/handlers/storages_selection/view.cpp | 20 +++++++------------- src/states.hpp | 4 ++-- 9 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index c04b7168..855c2ea4 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -41,7 +41,7 @@ using states::StorageMemberView; using states::StorageIngredientsList; using states::StoragesSelection; -using states::SuggestedRecipeList; +using states::SuggestedRecipesList; using states::ShoppingListCreation; using states::ShoppingListStorageSelectionToBuy; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 66eebe48..f2cee479 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -99,8 +99,8 @@ using storageMemberDeletionCQHandler = Handler; -// SuggestedRecipeList -using suggestedRecipeListCQHandler = Handler; +// SuggestedRecipesList +using suggestedRecipeListCQHandler = Handler; // StorageIngredientsList using storageIngredientsListCQHandler = Handler; diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index 00008b98..7ba6330f 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -38,7 +38,7 @@ void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SM if (storages.size() == 1) { std::vector storage = {storages[0]}; renderRecipesSuggestion(storage, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{.pageNo = 0, .selectedStorages = storage, .fromStorage = false}); + stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storage, .fromStorage = false}); return; } renderStorageSelection({}, userId, chatId, bot, api); diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 4aea3191..257e1004 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -44,7 +44,7 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe } if (data == "back_from_recipe_view") { renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{ + stateManager.put(SuggestedRecipesList{ .pageNo = state.pageNo, .selectedStorages = state.selectedStorages, .fromStorage = state.fromStorage}); bot.answerCallbackQuery(cq.id); return; diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index f0f12721..196b55ea 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -21,8 +21,8 @@ using namespace render::storage; using namespace render::recipe; using namespace render::main_menu; -void handleSuggestedRecipeListCQ( - SuggestedRecipeList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleSuggestedRecipesListCQ( + SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/recipes_suggestions/view.hpp b/src/handlers/recipes_suggestions/view.hpp index 79cbc54f..b291db03 100644 --- a/src/handlers/recipes_suggestions/view.hpp +++ b/src/handlers/recipes_suggestions/view.hpp @@ -4,7 +4,7 @@ namespace cookcookhnya::handlers::recipes_suggestions { -void handleSuggestedRecipeListCQ( - SuggestedRecipeList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleSuggestedRecipesListCQ( + SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); } // namespace cookcookhnya::handlers::recipes_suggestions diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 88df5b01..4dc2b052 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -45,7 +45,7 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM api::models::storage::StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; std::vector storages = {storage}; renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{.pageNo = 0, .selectedStorages = storages, .fromStorage = true}); + stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storages, .fromStorage = true}); return; } else if (cq.data == "delete") { renderStorageDeletion(state.storageId, chatId, bot, cq.from->id, api); diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index e733ef90..20d9daaf 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -14,6 +14,7 @@ namespace cookcookhnya::handlers::storages_selection { +using api::models::storage::StorageSummary; using namespace render::recipes_suggestions; using namespace render::select_storages; using namespace render::main_menu; @@ -26,7 +27,7 @@ void handleStoragesSelectionCQ( if (cq.data == "confirm") { renderRecipesSuggestion(state.selectedStorages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeList{ + stateManager.put(SuggestedRecipesList{ .pageNo = 0, .selectedStorages = std::move(state.selectedStorages), .fromStorage = false}); return; } @@ -37,20 +38,13 @@ void handleStoragesSelectionCQ( return; } - if (auto storageId = utils::parseSafe(cq.data.substr(1))) { + if (auto mSelectedStorageId = utils::parseSafe(cq.data.substr(1))) { if (cq.data[0] == '+') { - auto it = std::ranges::find(state.selectedStorages, *storageId, &api::models::storage::StorageSummary::id); + auto it = std::ranges::find(state.selectedStorages, *mSelectedStorageId, &StorageSummary::id); state.selectedStorages.erase(it); - renderStorageSelection(state, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.selectedStorages = state.selectedStorages}); - return; - } - if (cq.data[0] == '-') { - auto storageDetails = api.getStoragesApi().get(userId, *storageId); - state.selectedStorages.push_back({.id = *storageId, .name = storageDetails.name}); - renderStorageSelection(state, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.selectedStorages = state.selectedStorages}); - return; + } else if (cq.data[0] == '-') { + auto storageDetails = api.getStoragesApi().get(userId, *mSelectedStorageId); + state.selectedStorages.push_back({.id = *mSelectedStorageId, .name = storageDetails.name}); } renderStorageSelection(state, userId, chatId, bot, api); return; diff --git a/src/states.hpp b/src/states.hpp index 2035de58..f4aaa6d0 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -67,7 +67,7 @@ struct StorageIngredientsList : detail::StorageIdMixin { struct StoragesSelection { std::vector selectedStorages; }; -struct SuggestedRecipeList { +struct SuggestedRecipesList { std::size_t pageNo; std::vector selectedStorages; bool fromStorage; @@ -169,7 +169,7 @@ using State = std::variant Date: Sat, 19 Jul 2025 05:17:43 +0300 Subject: [PATCH 014/106] refactor: API client (remove redundant methods and refactor others) --- src/backend/CMakeLists.txt | 2 + src/backend/api/common.hpp | 19 ---- src/backend/api/ingredients.cpp | 42 ++++----- src/backend/api/ingredients.hpp | 36 +++---- src/backend/api/publicity_filter.cpp | 18 ++++ src/backend/api/publicity_filter.hpp | 12 +++ src/backend/api/recipes.cpp | 44 ++++----- src/backend/api/recipes.hpp | 39 ++++---- src/backend/models/ingredient.cpp | 9 ++ src/backend/models/ingredient.hpp | 7 ++ src/backend/models/recipe.cpp | 45 +-------- src/backend/models/recipe.hpp | 50 +++------- .../ingredients_list/create.cpp | 8 +- .../ingredients_list/publish.cpp | 6 +- .../ingredients_list/view.cpp | 13 ++- src/render/personal_account/recipe/view.cpp | 3 +- .../personal_account/recipes_list/view.cpp | 77 ++++++++------- .../personal_account/recipes_list/view.hpp | 14 +-- src/render/recipe/add_storage.cpp | 16 ++-- src/render/recipe/view.cpp | 15 ++- src/render/recipes_suggestions/view.cpp | 94 +++++++++---------- src/render/recipes_suggestions/view.hpp | 11 +-- src/utils/ingredients_availability.cpp | 2 +- 23 files changed, 260 insertions(+), 322 deletions(-) delete mode 100644 src/backend/api/common.hpp create mode 100644 src/backend/api/publicity_filter.cpp create mode 100644 src/backend/api/publicity_filter.hpp diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 3141c2cb..2b4bc513 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -1,6 +1,8 @@ target_sources(main PRIVATE src/backend/api/base.cpp + src/backend/api/publicity_filter.cpp + src/backend/api/storages.cpp src/backend/api/ingredients.cpp src/backend/api/users.cpp diff --git a/src/backend/api/common.hpp b/src/backend/api/common.hpp deleted file mode 100644 index 8a2c1eee..00000000 --- a/src/backend/api/common.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -enum struct FilterType : std::uint8_t { All, Custom, Public }; - -inline std::string filterStr(FilterType value) { - static const std::unordered_map map = { - {FilterType::All, "All"}, {FilterType::Custom, "Custom"}, {FilterType::Public, "Public"}}; - - auto it = map.find(value); - if (it != map.end()) { - return it->second; - } - throw std::invalid_argument("Invalid FilterType value"); -} diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index 76939376..c1235035 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -1,6 +1,6 @@ #include "backend/api/ingredients.hpp" -#include "backend/api/common.hpp" +#include "backend/api/publicity_filter.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "utils/parsing.hpp" @@ -23,11 +23,11 @@ Ingredient IngredientsApi::get(UserId user, IngredientId ingredient) const { // GET /storages/{storageId}/ingredients std::vector -IngredientsApi::getStorageIngredients(UserId user, StorageId storage, std::size_t size, std::size_t offset) const { +IngredientsApi::getStorageIngredients(UserId user, StorageId storage, std::size_t count, std::size_t offset) const { return jsonGetAuthed>( user, std::format("/storages/{}/ingredients", storage), - {{"size", utils::to_string(size)}, {"offset", utils::to_string(offset)}}); + {{"size", utils::to_string(count)}, {"offset", utils::to_string(offset)}}); } // PUT /storages/{storageId}/ingredients/{ingredientId} @@ -55,42 +55,38 @@ IngredientSearchForStorageResponse IngredientsApi::searchForStorage(UserId user, StorageId storage, std::string query, std::size_t threshold, - std::size_t size, + std::size_t count, std::size_t offset) const { return jsonGetAuthed(user, "/ingredients-for-storage", {{"query", std::move(query)}, {"storage-id", utils::to_string(storage)}, - {"size", utils::to_string(size)}, + {"size", utils::to_string(count)}, {"threshold", utils::to_string(threshold)}, {"offset", utils::to_string(offset)}}); } -// GET /public/ingredients -IngredientSearchResponse -IngredientsApi::publicSearch(std::string query, std::size_t threshold, std::size_t size, std::size_t offset) const { - return jsonGet("/public/ingredients", - {}, - {{"query", std::move(query)}, - {"threshold", utils::to_string(threshold)}, - {"size", utils::to_string(size)}, - {"offset", utils::to_string(offset)}}); -} - // GET /ingredients IngredientSearchResponse IngredientsApi::search(UserId user, + PublicityFilterType filter, std::string query, std::size_t threshold, - std::size_t size, - std::size_t offset, - FilterType filter) const { + std::size_t count, + std::size_t offset) const { return jsonGetAuthed(user, "/ingredients", {{"query", std::move(query)}, - {"size", utils::to_string(size)}, + {"size", utils::to_string(count)}, {"offset", utils::to_string(offset)}, {"threshold", utils::to_string(threshold)}, - {"filter", filterStr(filter)}}); + {"filter", utils::to_string(filter)}}); +} + +// GET /recipes +IngredientList +IngredientsApi::getList(UserId user, PublicityFilterType filter, std::size_t count, std::size_t offset) const { + auto result = search(user, filter, "", 0, count, offset); + return {.page = std::move(result.page), .found = result.found}; } // GET /public/ingredients/{ingredientId} @@ -113,13 +109,13 @@ IngredientSearchForRecipeResponse IngredientsApi::searchForRecipe(UserId user, RecipeId recipe, std::string query, std::size_t threshold, - std::size_t size, + std::size_t count, std::size_t offset) const { return jsonGetAuthed(user, "/ingredients-for-recipe", {{"query", std::move(query)}, {"threshold", utils::to_string(threshold)}, - {"size", utils::to_string(size)}, + {"size", utils::to_string(count)}, {"offset", utils::to_string(offset)}, {"recipe-id", utils::to_string(recipe)}}); } diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index 0eeb90d6..bf3101ef 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -1,9 +1,9 @@ #pragma once -#include "backend/api/base.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" -#include "common.hpp" +#include "base.hpp" +#include "publicity_filter.hpp" #include @@ -22,7 +22,7 @@ class IngredientsApi : ApiBase { [[nodiscard]] models::ingredient::Ingredient get(UserId user, IngredientId ingredient) const; [[nodiscard]] std::vector - getStorageIngredients(UserId user, StorageId storage, std::size_t size = 2, std::size_t offset = 0) const; + getStorageIngredients(UserId user, StorageId storage, std::size_t count = 2, std::size_t offset = 0) const; void putToStorage(UserId user, StorageId storage, IngredientId ingredient) const; @@ -35,22 +35,22 @@ class IngredientsApi : ApiBase { searchForStorage(UserId user, StorageId storage, std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic*) - std::size_t size = 2, + std::size_t threshold = 50, // NOLINT(*magic-number*) + std::size_t count = 50, // NOLINT(*magic-number*) std::size_t offset = 0) const; [[nodiscard]] models::ingredient::IngredientSearchResponse - publicSearch(std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic*) - std::size_t size = 2, - std::size_t offset = 0) const; - - [[nodiscard]] models::ingredient::IngredientSearchResponse search(UserId user, - std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic*) - std::size_t size = 2, - std::size_t offset = 2, - FilterType filter = FilterType::All) const; + search(UserId user, + PublicityFilterType filter = PublicityFilterType::All, + std::string query = "", + std::size_t threshold = 50, // NOLINT(*magic-number*) + std::size_t count = 50, // NOLINT(*magic-number*) + std::size_t offset = 0) const; + + [[nodiscard]] models::ingredient::IngredientList getList(UserId user, + PublicityFilterType filter = PublicityFilterType::All, + std::size_t count = 50, // NOLINT(*magic-number*) + std::size_t offset = 0) const; [[nodiscard]] models::ingredient::Ingredient getPublicIngredient(IngredientId ingredient) const; @@ -62,8 +62,8 @@ class IngredientsApi : ApiBase { searchForRecipe(UserId user, RecipeId recipe, std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic*) - std::size_t size = 2, + std::size_t threshold = 50, // NOLINT(*magic-number*) + std::size_t count = 50, // NOLINT(*magic-number*) std::size_t offset = 0) const; IngredientId createCustom(UserId user, // NOLINT(*-nodiscard) diff --git a/src/backend/api/publicity_filter.cpp b/src/backend/api/publicity_filter.cpp new file mode 100644 index 00000000..d7481d46 --- /dev/null +++ b/src/backend/api/publicity_filter.cpp @@ -0,0 +1,18 @@ +#include "publicity_filter.hpp" + +#include +#include +#include +#include + +namespace cookcookhnya::utils { + +std::string to_string(PublicityFilterType value) { + static constexpr std::array names = {"All", "Custom", "Public"}; + + if (static_cast(value) >= names.size()) + throw std::invalid_argument("Invalid FilterType value"); + return names[static_cast(value)]; +} + +} // namespace cookcookhnya::utils diff --git a/src/backend/api/publicity_filter.hpp b/src/backend/api/publicity_filter.hpp new file mode 100644 index 00000000..7cf91f77 --- /dev/null +++ b/src/backend/api/publicity_filter.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +enum struct PublicityFilterType : std::uint8_t { All, Custom, Public }; + +namespace cookcookhnya::utils { + +std::string to_string(PublicityFilterType value); + +} // namespace cookcookhnya::utils diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 348eec3d..055649d6 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -1,9 +1,8 @@ #include "backend/api/recipes.hpp" -#include "backend/api/common.hpp" +#include "backend/api/publicity_filter.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" -#include "backend/models/storage.hpp" #include "utils/parsing.hpp" #include "utils/to_string.hpp" @@ -20,34 +19,40 @@ namespace cookcookhnya::api { using namespace models::recipe; // GET /suggested-recipes -RecipesList RecipesApi::getSuggestedRecipesList(UserId user, - std::vector& storages, - size_t size, - size_t offset) const { +RecipesListWithIngredientsCount RecipesApi::getSuggestedRecipes(UserId user, + const std::vector& storages, + std::size_t size, + std::size_t offset) const { httplib::Params params = {{"size", utils::to_string(size)}, {"offset", std::to_string(offset)}}; - for (const auto& storage : storages) - params.emplace("storage-id", utils::to_string(storage.id)); - return jsonGetAuthed(user, "/suggested-recipes", params); + for (const StorageId& id : storages) + params.emplace("storage-id", utils::to_string(id)); + return jsonGetAuthed(user, "/suggested-recipes", params); } // GET /recipes -RecipeSearchResponse RecipesApi::getRecipesList(UserId user, - std::string query, - std::size_t threshold, - std::size_t size, - std::size_t offset, - FilterType filter) const { +RecipeSearchResponse RecipesApi::search(UserId user, + PublicityFilterType filter, + std::string query, + std::size_t threshold, + std::size_t size, + std::size_t offset) const { return jsonGetAuthed(user, "/recipes", {{"query", std::move(query)}, {"threshold", utils::to_string(threshold)}, {"size", utils::to_string(size)}, {"offset", utils::to_string(offset)}, - {"filter", filterStr(filter)}}); + {"filter", utils::to_string(filter)}}); +} + +// GET /recipes +RecipesList RecipesApi::getList(UserId user, PublicityFilterType filter, std::size_t size, std::size_t offset) const { + auto result = search(user, filter, "", 0, size, offset); + return {.page = std::move(result.page), .found = result.found}; } // GET /recipes/{recipeId} -RecipeDetails RecipesApi::getIngredientsInRecipe(UserId user, RecipeId recipe) const { +RecipeDetails RecipesApi::get(UserId user, RecipeId recipe) const { return jsonGetAuthed(user, std::format("/recipes/{}", recipe)); } @@ -61,11 +66,6 @@ void RecipesApi::delete_(UserId user, RecipeId recipeId) const { jsonDeleteAuthed(user, std::format("/recipes/{}", recipeId)); } -// GET /recipes/{recipeId} -CustomRecipeDetails RecipesApi::get(UserId user, RecipeId recipe) const { - return jsonGetAuthed(user, std::format("/recipes/{}", recipe)); -} - // POST /recipes/{recipeId}/request-publication void RecipesApi::publishCustom(UserId user, RecipeId recipe) const { jsonPostAuthed(user, std::format("my/recipes/{}/request-publication", recipe)); diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 0013ffdf..38785351 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -1,10 +1,9 @@ #pragma once -#include "backend/api/base.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" -#include "backend/models/storage.hpp" -#include "common.hpp" +#include "base.hpp" +#include "publicity_filter.hpp" #include @@ -20,27 +19,29 @@ class RecipesApi : ApiBase { explicit RecipesApi(httplib::Client& api) : ApiBase{api} {} public: - [[nodiscard]] models::recipe::RecipesList - getSuggestedRecipesList(UserId user, - std::vector& storages, - size_t size = 2, - size_t offset = 0) const; + [[nodiscard]] models::recipe::RecipesListWithIngredientsCount + getSuggestedRecipes(UserId user, + const std::vector& storages, + size_t size = 500, // NOLINT(*magic-number*) + size_t offset = 0) const; - [[nodiscard]] models::recipe::RecipeSearchResponse getRecipesList(UserId user, - std::string query, - std::size_t threshold, - std::size_t size, - std::size_t offset, - FilterType filter) const; + [[nodiscard]] models::recipe::RecipeSearchResponse search(UserId user, + PublicityFilterType filter = PublicityFilterType::All, + std::string query = "", + std::size_t threshold = 50, // NOLINT(*magic-number*) + std::size_t size = 100, // NOLINT(*magic-number*) + std::size_t offset = 0) const; - [[nodiscard]] models::recipe::RecipeDetails getIngredientsInRecipe(UserId user, RecipeId recipe) const; + [[nodiscard]] models::recipe::RecipesList getList(UserId user, + PublicityFilterType filter = PublicityFilterType::All, + std::size_t size = 100, // NOLINT(*magic-number*) + std::size_t offset = 0) const; - [[nodiscard]] RecipeId create(UserId user, // NOLINT(*-nodiscard) - const models::recipe::RecipeCreateBody& body) const; + [[nodiscard]] models::recipe::RecipeDetails get(UserId user, RecipeId recipeId) const; - void delete_(UserId user, RecipeId recipe) const; + RecipeId create(UserId user, const models::recipe::RecipeCreateBody& body) const; // NOLINT(*nodiscard) - [[nodiscard]] models::recipe::CustomRecipeDetails get(UserId user, RecipeId recipe) const; + void delete_(UserId user, RecipeId recipe) const; void publishCustom(UserId user, RecipeId recipe) const; }; diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index 771502fe..db2fff59 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -52,10 +52,19 @@ IngredientSearchForRecipeResponse tag_invoke(json::value_to_tag(j.at("found")), }; } + IngredientSearchResponse tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .page = value_to(j.at("results")), .found = value_to(j.at("found")), }; } + +IngredientList tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { + return { + .page = value_to(j.at("results")), + .found = value_to(j.at("found")), + }; +} + } // namespace cookcookhnya::api::models::ingredient diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index 977ee6bb..457d8be0 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -66,4 +66,11 @@ struct IngredientSearchResponse { const boost::json::value& j); }; +struct IngredientList { + std::vector page; + std::size_t found; + + friend IngredientList tag_invoke(boost::json::value_to_tag, const boost::json::value& j); +}; + } // namespace cookcookhnya::api::models::ingredient diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index c0b91955..d52eac10 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -26,10 +26,11 @@ RecipeSummaryWithIngredients tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { +RecipesListWithIngredientsCount tag_invoke(json::value_to_tag /*tag*/, + const json::value& j) { return { - .page = value_to(j.at("results")), - .found = value_to(j.at("found")), + .page = value_to(j.at("results")), + .found = value_to(j.at("found")), }; } @@ -45,14 +46,6 @@ IngredientInRecipe tag_invoke(json::value_to_tag /*tag*/, co }; } -RecipeCreator tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - return { - .id = value_to(j.at("id")), - .fullName = value_to(j.at("fullName")), - - }; -} - RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .ingredients = value_to(j.at("ingredients")), @@ -62,36 +55,6 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: }; } -CustomRecipeSummary tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - return { - .id = value_to(j.at("recipeId")), - .name = value_to(j.at("name")), - .link = value_to(j.at("sourceLink")), - }; -} - -CustomRecipesList tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - return { - .page = value_to(j.at("results")), - .found = value_to(j.at("found")), - }; -} - -CustomRecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - return { - .ingredients = value_to(j.at("ingredients")), - .name = value_to(j.at("name")), - .link = value_to(j.at("sourceLink")), - }; -} - -IngredientInCustomRecipe tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - return { - .id = value_to(j.at("id")), - .name = value_to(j.at("name")), - }; -} - RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .page = value_to(j.at("results")), diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 4f51df8a..17a70e37 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -4,10 +4,11 @@ #include "backend/models/storage.hpp" #include "backend/models/user.hpp" -#include "tg_types.hpp" #include #include + #include +#include #include #include @@ -38,65 +39,36 @@ struct IngredientInRecipe { friend IngredientInRecipe tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; -struct RecipeCreator { - tg_types::UserId id; - std::string fullName; - - friend RecipeCreator tag_invoke(boost::json::value_to_tag, const boost::json::value& j); -}; - struct RecipeDetails { std::vector ingredients; std::string name; - std::string link; - user::UserDetails creator; + std::optional link; + std::optional creator; friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; -struct IngredientInCustomRecipe { - IngredientId id; - std::string name; - - friend IngredientInCustomRecipe tag_invoke(boost::json::value_to_tag, - const boost::json::value& j); -}; - -struct CustomRecipeDetails { - std::vector ingredients; - std::string name; - std::string link; - - friend CustomRecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); -}; - struct RecipesList { - std::vector page; + std::vector page; std::size_t found; friend RecipesList tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; -struct CustomRecipeSummary { - RecipeId id; - std::string name; - std::string link; - - friend CustomRecipeSummary tag_invoke(boost::json::value_to_tag, const boost::json::value& j); -}; - -struct CustomRecipesList { - std::vector page; +struct RecipesListWithIngredientsCount { + std::vector page; std::size_t found; - friend CustomRecipesList tag_invoke(boost::json::value_to_tag, const boost::json::value& j); + friend RecipesListWithIngredientsCount tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); }; + struct RecipeCreateBody { std::string name; std::vector ingredients; std::string link; - friend void tag_invoke(boost::json::value_from_tag /*tag*/, boost::json::value& j, const RecipeCreateBody& body); + friend void tag_invoke(boost::json::value_from_tag, boost::json::value& j, const RecipeCreateBody& body); }; struct RecipeSearchResponse { diff --git a/src/render/personal_account/ingredients_list/create.cpp b/src/render/personal_account/ingredients_list/create.cpp index 470b058c..8d9a67b1 100644 --- a/src/render/personal_account/ingredients_list/create.cpp +++ b/src/render/personal_account/ingredients_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -18,7 +19,7 @@ void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot) { auto text = utils::utf8str(u8"🥦 Введите новое имя ингредиента"); auto messageId = message::getMessageId(userId); if (messageId) { - bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); } } @@ -28,7 +29,8 @@ void renderCustomIngredientConfirmation( keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - auto similarIngredients = api.search(userId, std::move(ingredientName), 70, 5, 0).page; // NOLINT(*magic-numbers*) + // NOLINTNEXTLINE(*magic-numbers*) + auto similarIngredients = api.search(userId, PublicityFilterType::All, std::move(ingredientName), 70, 5, 0).page; std::string text; if (!similarIngredients.empty()) { @@ -44,7 +46,7 @@ void renderCustomIngredientConfirmation( } else { text = utils::utf8str(u8"Вы уверены, что хотите добавить новый ингредиент?"); } - auto message = bot.sendMessage(chatId, text, nullptr, nullptr, makeKeyboardMarkup(std::move(keyboard))); + auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); message::addMessageId(userId, message->messageId); } diff --git a/src/render/personal_account/ingredients_list/publish.cpp b/src/render/personal_account/ingredients_list/publish.cpp index 37c70a93..3b65b9f7 100644 --- a/src/render/personal_account/ingredients_list/publish.cpp +++ b/src/render/personal_account/ingredients_list/publish.cpp @@ -1,6 +1,6 @@ #include "publish.hpp" -#include "backend/api/common.hpp" +#include "backend/api/publicity_filter.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/to_string.hpp" @@ -8,7 +8,6 @@ #include #include -#include #include namespace cookcookhnya::render::personal_account::ingredients { @@ -16,7 +15,8 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - auto ingredientsResp = api.search(userId, "", 0, 1000, 0, FilterType::Custom); // NOLINT (*magic*) + auto ingredientsResp = api.search(userId, PublicityFilterType::Custom); + // TODO: make pagination for ingredients auto ingredients = ingredientsResp.page; const std::size_t buttonRows = ((ingredients.size() + 1) / 2) + 1; diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index 96caed51..139b2e25 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -1,6 +1,6 @@ #include "view.hpp" -#include "backend/api/common.hpp" +#include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -17,7 +17,7 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; void renderCustomIngredientsList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - auto ingredientsResp = api.search(userId, "", 0, 100, 0, FilterType::Custom); // NOLINT(*magic*) + auto ingredientsResp = api.getList(userId, PublicityFilterType::Custom); auto ingredients = ingredientsResp.page; const std::size_t buttonRows = ingredients.empty() ? 2 : 3; InlineKeyboard keyboard(buttonRows); @@ -41,12 +41,11 @@ void renderCustomIngredientsList(bool toBeEdited, UserId userId, ChatId chatId, formatedIngredients); if (toBeEdited) { - auto messageId = message::getMessageId(userId); - if (messageId) { - bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); - } + if (auto messageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + } else { - auto message = bot.sendMessage(chatId, text, nullptr, nullptr, makeKeyboardMarkup(std::move(keyboard))); + auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 6d185106..e3dfe969 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -32,7 +32,8 @@ std::vector renderCustomRecipe( }); } - toPrint += recipeDetails.link; + if (recipeDetails.link) + toPrint += *recipeDetails.link; keyboard[0].push_back(makeCallbackButton(u8"🚮 Удалить", "delete")); keyboard[1].push_back(makeCallbackButton(u8"✏️ Редактировать", "change")); keyboard[2].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 24fc1c78..30a6eaa8 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -1,6 +1,6 @@ #include "view.hpp" -#include "backend/api/common.hpp" +#include "backend/api/publicity_filter.hpp" #include "backend/api/recipes.hpp" #include "backend/models/recipe.hpp" #include "message_tracker.hpp" @@ -19,19 +19,21 @@ namespace cookcookhnya::render::personal_account::recipes { +namespace { + // offset is variable which defines amout of rows before beggining of paging // fullKeyBoardSize is self explanatory -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t pageNo, - size_t numOfRecipesOnPage, - api::models::recipe::RecipeSearchResponse& recipesList) { +InlineKeyboard constructNavigationsMarkup(std::size_t offset, + std::size_t fullKeyBoardSize, + std::size_t pageNo, + std::size_t numOfRecipesOnPage, + api::models::recipe::RecipesList& recipesList) { const size_t amountOfRecipes = recipesList.found; std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - const bool ifMaxPage = amountOfRecipes - numOfRecipesOnPage * (pageNo + 1) <= 0; + const bool lastPage = amountOfRecipes - numOfRecipesOnPage * (pageNo + 1) <= 0; if (offset + recipesToShow >= fullKeyBoardSize) { InlineKeyboard error(0); @@ -39,7 +41,7 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, } const size_t arrowsRow = offset + recipesToShow; // Don't reserve for arrows if it's first page is max(im) - InlineKeyboard keyboard(pageNo == 0 && ifMaxPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); + InlineKeyboard keyboard(pageNo == 0 && lastPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); std::size_t counter = 0; for (std::size_t i = 0; i < recipesToShow; i++) { // Print on button in form "1. {Recipe}" @@ -48,42 +50,35 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, std::format("r", recipesList.page[counter].id))); // RECIPE ID counter++; } - if (pageNo == 0 && ifMaxPage) { + if (pageNo == 0 && lastPage) { // instead of arrows row keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); return keyboard; } keyboard[arrowsRow].reserve(3); - // Helps to reduce code. Power of C++ YEAH BABE! - uint8_t b = 0; - - // Simply enamurate every case - if (pageNo == 0) { - if (!ifMaxPage) { - b |= uint8_t{0b01}; - } - } else if (ifMaxPage) { - b |= uint8_t{0b10}; - } else { - b |= uint8_t{0b11}; - } + enum PageArrows : std::uint8_t { + NOTHING = 0b00U, + LEFT = 0b01U, + RIGHT = 0b10U, + }; + + PageArrows b = NOTHING; + if (pageNo != 0) + b = static_cast(b | LEFT); + if (!lastPage) + b = static_cast(b | RIGHT); + + if ((b & LEFT) != 0) + keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left + else + keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); + + if ((b & RIGHT) != 0) + keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right + else + keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - // Check from left to right due to buttons being displayed like that - for (int i = 1; i >= 0; i--) { - // Compare two bits under b mask. If 1 was on b mask then we need to place arrow somewhere - if ((b & static_cast((uint8_t{0b1} << static_cast(i)))) == - (uint8_t{0b1} << static_cast(i))) { - // if we need to place arrow then check the i, which represents bit which we are checking right now - if (i == 1) { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right - } - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - } - } // Put pageNo as button keyboard[arrowsRow].insert(keyboard[arrowsRow].begin() + 1, makeCallbackButton(std::format("{} из {}", pageNo + 1, maxPageNum), "dont_handle")); @@ -98,7 +93,7 @@ InlineKeyboard constructOnlyCreate() { } InlineKeyboard -constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::RecipeSearchResponse& recipesList) { +constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::RecipesList& recipesList) { // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 // for adding new recipe - other buttons are recipes const size_t numOfRows = 3; @@ -118,14 +113,16 @@ constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::R return keyboard; } -void renderCustomRecipesList(size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { +} // namespace + +void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { const std::string pageInfo = utils::utf8str(u8"🔪 Рецепты созданные вами"); auto messageId = message::getMessageId(userId); const std::size_t numOfRecipesOnPage = 5; auto recipesList = - recipesApi.getRecipesList(userId, "", 0, numOfRecipesOnPage, pageNo * numOfRecipesOnPage, FilterType::Custom); + recipesApi.getList(userId, PublicityFilterType::Custom, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); if (messageId) { bot.editMessageText( pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); diff --git a/src/render/personal_account/recipes_list/view.hpp b/src/render/personal_account/recipes_list/view.hpp index a78884d3..7bc978fa 100644 --- a/src/render/personal_account/recipes_list/view.hpp +++ b/src/render/personal_account/recipes_list/view.hpp @@ -1,23 +1,11 @@ #pragma once -#include "backend/models/recipe.hpp" #include "render/common.hpp" #include namespace cookcookhnya::render::personal_account::recipes { -void renderCustomRecipesList(size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi); - -InlineKeyboard -constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::RecipeSearchResponse& recipesList); - -InlineKeyboard constructOnlyCreate(); - -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t pageNo, - size_t numOfRecipesOnPage, - api::models::recipe::RecipeSearchResponse& recipesList); +void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi); } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index 2eb977bc..5ed05461 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -9,7 +9,6 @@ #include "utils/utils.hpp" #include "view.hpp" -#include #include #include #include @@ -17,18 +16,19 @@ namespace cookcookhnya::render::recipe { +using namespace api::models::recipe; + textGenInfo storageAdditionView( - const std::vector>& - inStoragesAvailability, + const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, api::RecipeId recipeId, UserId userId, ApiClient api) { - auto recipeIngredients = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); + auto recipe = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; bool isIngredientIsOtherStorages = false; - const std::string recipeName = recipeIngredients.name; + const std::string recipeName = recipe.name; auto text = std::format("{} Выбранные хранилища: ", utils::utf8str(u8"🍱")); for (std::size_t i = 0; i != selectedStorages.size(); ++i) { text += selectedStorages[i].name; @@ -53,7 +53,8 @@ textGenInfo storageAdditionView( isIngredientNotAvailable = true; } } - text += "\n🌐 Источник: " + recipeIngredients.link; + if (recipe.link) + text += utils::utf8str(u8"\n🌐 Источник: ") + *recipe.link; return {.text = text, .isIngredientNotAvailable = isIngredientNotAvailable, @@ -61,8 +62,7 @@ textGenInfo storageAdditionView( } void renderStoragesSuggestion( - const std::vector>& - inStoragesAvailability, + const std::vector>& inStoragesAvailability, const std::vector& selectedStorages, const std::vector& addedStorages, api::RecipeId recipeId, diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index 6d581e08..71df1ada 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -13,13 +13,14 @@ namespace cookcookhnya::render::recipe { +using namespace api::models::recipe; + textGenInfo -recipeView(const std::vector>& - inStoragesAvailability, +recipeView(const std::vector>& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ApiClient api) { - auto recipeIngredients = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); + auto recipeIngredients = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; bool isIngredientIsOtherStorages = false; @@ -37,17 +38,15 @@ recipeView(const std::vector>& - inStoragesAvailability, +void renderRecipeView(std::vector>& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 6ffab1b0..829fbf76 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -12,34 +12,42 @@ #include #include #include +#include #include #include namespace cookcookhnya::render::recipes_suggestions { +using namespace api::models::storage; +using namespace api::models::recipe; +using namespace std::views; +using namespace std::ranges; + +namespace { + // offset is variable which defines amout of rows before beggining of paging // fullKeyBoardSize is self explanatory InlineKeyboard constructNavigationsMarkup(std::size_t offset, std::size_t fullKeyBoardSize, std::size_t pageNo, std::size_t numOfRecipesOnPage, - api::models::recipe::RecipesList recipesList) { + RecipesListWithIngredientsCount recipesList) { const std::size_t amountOfRecipes = recipesList.found; std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that // will be actually shown - const bool ifMaxPage = + const bool lastPage = static_cast(amountOfRecipes) - static_cast(numOfRecipesOnPage) * (static_cast(pageNo) + 1) <= 0; if (offset + recipesToShow > fullKeyBoardSize) { // IN ERROR HANDLING MAY USE ASSERT InlineKeyboard error(0); return error; } - const size_t arrowsRow = offset + recipesToShow; + const std::size_t arrowsRow = offset + recipesToShow; - InlineKeyboard keyboard(pageNo == 0 && ifMaxPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); + InlineKeyboard keyboard(pageNo == 0 && lastPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); int counter = 0; for (std::size_t i = 0; i < recipesToShow; i++) { keyboard[i + offset].push_back(makeCallbackButton(std::format("{}. {} [{} из {}]", @@ -50,42 +58,35 @@ InlineKeyboard constructNavigationsMarkup(std::size_t offset, std::format("recipe_{}", recipesList.page[counter].id))); counter++; } - if (pageNo == 0 && ifMaxPage) { + if (pageNo == 0 && lastPage) { // instead of arrows row keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); return keyboard; } keyboard[arrowsRow].reserve(3); - // Helps to reduce code. Power of C++ YEAH BABE! - uint8_t b = 0; - - // Simply enamurate every case - if (pageNo == 0) { - if (!ifMaxPage) { - b |= uint8_t{0x1}; - } - } else if (ifMaxPage) { - b |= uint8_t{0x2}; - } else { - b |= uint8_t{0x3}; - } + enum PageArrows : std::uint8_t { + NOTHING = 0b00U, + LEFT = 0b01U, + RIGHT = 0b10U, + }; + + PageArrows b = NOTHING; + if (pageNo != 0) + b = static_cast(b | LEFT); + if (!lastPage) + b = static_cast(b | RIGHT); + + if ((b & LEFT) != 0) + keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left + else + keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); + + if ((b & RIGHT) != 0) + keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right + else + keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - // Check from left to right due to buttons being displayed like that - for (int i = 1; i >= 0; i--) { - // Compare two bits under b mask. If 1 was on b mask then we need to place arrow somewhere - if ((b & static_cast((uint8_t{0b1} << static_cast(i)))) == - (uint8_t{0b1} << static_cast(i))) { - // if we need to place arrow then check the i, which represents bit which we are checking right now - if (i == 1) { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right - } - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - } - } // Put pageNo as button keyboard[arrowsRow].insert(keyboard[arrowsRow].begin() + 1, makeCallbackButton(std::format("{} из {}", pageNo + 1, maxPageNum), "dont_handle")); @@ -100,11 +101,10 @@ InlineKeyboard constructOnlyBack() { } InlineKeyboard -constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::RecipesList& recipesList) { - - const size_t numOfRows = 2; // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS) - const size_t offset = 0; // Number of rows before list - const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); +constructMarkup(std::size_t pageNo, std::size_t numOfRecipesOnPage, RecipesListWithIngredientsCount& recipesList) { + const std::size_t numOfRows = 2; // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS) + const std::size_t offset = 0; // Number of rows before list + const std::size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); InlineKeyboard keyboard = recipesList.found == 0 @@ -117,22 +117,22 @@ constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::R return keyboard; } -void renderRecipesSuggestion(std::vector& storages, - size_t pageNo, +} // namespace + +void renderRecipesSuggestion(std::vector& storages, + std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { const std::string pageInfo = utils::utf8str(u8"🔪 Рецепты подобранные специально для вас"); + const std::size_t numOfRecipesOnPage = 5; + const std::size_t numOfRecipes = 500; - auto messageId = message::getMessageId(userId); - - const size_t numOfRecipesOnPage = 5; - const size_t numOfRecipes = 500; - - auto recipesList = recipesApi.getSuggestedRecipesList(userId, storages, numOfRecipes, pageNo * numOfRecipesOnPage); + auto storagesIds = storages | views::transform(&StorageSummary::id) | to(); + auto recipesList = recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipes, pageNo * numOfRecipesOnPage); - if (messageId) { + if (auto messageId = message::getMessageId(userId)) { bot.editMessageText( pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); } diff --git a/src/render/recipes_suggestions/view.hpp b/src/render/recipes_suggestions/view.hpp index ec21930b..8ba90811 100644 --- a/src/render/recipes_suggestions/view.hpp +++ b/src/render/recipes_suggestions/view.hpp @@ -1,6 +1,5 @@ #pragma once -#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" @@ -11,16 +10,8 @@ namespace cookcookhnya::render::recipes_suggestions { using namespace tg_types; -InlineKeyboard constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::RecipesList& recipesList); -InlineKeyboard constructOnlyBack(); -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t pageNo, - size_t numOfRecipesOnPage, - api::models::recipe::RecipesList recipesList); - void renderRecipesSuggestion(std::vector& storages, - size_t pageNo, + std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index 84a5d6fa..7e7f5edb 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -17,7 +17,7 @@ inStoragesAvailability(std::vector& selectedSto UserId userId, const api::ApiClient& api) { auto allStorages = api.getStoragesApi().getStoragesList(userId); - auto recipe = api.getRecipesApi().getIngredientsInRecipe(userId, recipeId); + auto recipe = api.getRecipesApi().get(userId, recipeId); auto selectedStoragesSet = selectedStorages | std::views::transform(&api::models::storage::StorageSummary::id) | std::ranges::to(); From baa887e8746c122d1cbe1079c060796265dc6140 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sat, 19 Jul 2025 13:35:22 +0300 Subject: [PATCH 015/106] fix: buttons in shopping list creation and ui improvements --- src/handlers/recipe/view.cpp | 5 ++++- src/handlers/shopping_list/create.cpp | 4 ++-- src/render/shopping_list/create.cpp | 8 ++++---- src/render/shopping_list/create.hpp | 1 + src/render/shopping_list/view.cpp | 10 +++++----- src/render/storages_selection/view.cpp | 2 +- src/states.hpp | 1 + 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 257e1004..bd94a513 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -26,17 +26,20 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe } if (data == "shopping_list") { std::vector selectedIngredients; + std::vector allIngredients; for (const auto& infoPair : state.availability) { if (infoPair.second.available == utils::AvailabiltiyType::not_available) { selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); } + allIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); } - renderShoppingListCreation(selectedIngredients, userId, chatId, bot); + renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); stateManager.put(ShoppingListCreation{.selectedStorages = state.selectedStorages, .addedStorages = state.addedStorages, .availability = state.availability, .recipeId = state.recipeId, .selectedIngredients = selectedIngredients, + .allIngredients = allIngredients, .fromStorage = state.fromStorage, .pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index a3c98fcb..843ffc04 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -57,7 +57,7 @@ void handleShoppingListCreationCQ( auto ingredient = api.getIngredientsApi().get(userId, *newIngredientId); state.selectedIngredients.push_back(ingredient); } - renderShoppingListCreation(state.selectedIngredients, userId, chatId, bot); + renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); } if (data[0] == '-') { auto newIngredientIdStr = data.substr(1, data.size()); @@ -66,7 +66,7 @@ void handleShoppingListCreationCQ( state.selectedIngredients.erase(std::ranges::find( state.selectedIngredients, *newIngredientId, &api::models::ingredient::Ingredient::id)); } - renderShoppingListCreation(state.selectedIngredients, userId, chatId, bot); + renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); } } } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index dfb68e96..1d07e52d 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -19,6 +19,7 @@ using namespace api::models::ingredient; using namespace std::views; void renderShoppingListCreation(const std::vector& selectedIngredients, + const std::vector& allIngredients, UserId userId, ChatId chatId, BotRef bot) { @@ -27,12 +28,11 @@ void renderShoppingListCreation(const std::vector& selectedIngredien const std::size_t buttonRows = ((selectedIngredients.size() + 1) / 2) + 1; // ceil(ingredientsCount / 2), back InlineKeyboardBuilder keyboard{buttonRows}; - for (auto chunk : selectedIngredients | chunk(2)) { + for (auto chunk : allIngredients | chunk(2)) { keyboard.reserveInRow(2); for (const Ingredient& ing : chunk) { - const bool isSelected = true; // idk, what is supposed to be here. I'm just refactoring - // std::ranges::contains(selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id); - std::string emoji = utils::utf8str(isSelected ? u8"[+]" : u8"[ ᅠ]"); + const bool isSelected = std::ranges::contains(selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id); + std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; std::string text = std::format("{} {}", emoji, ing.name); std::string data = actionPrefix + utils::to_string(ing.id); diff --git a/src/render/shopping_list/create.hpp b/src/render/shopping_list/create.hpp index 296a3e6a..aaf0feb1 100644 --- a/src/render/shopping_list/create.hpp +++ b/src/render/shopping_list/create.hpp @@ -8,6 +8,7 @@ namespace cookcookhnya::render::shopping_list { void renderShoppingListCreation(const std::vector& selectedIngredients, + const std::vector& allIngredients, UserId userId, ChatId chatId, BotRef bot); diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 50c6855d..03240d64 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -18,24 +18,24 @@ void renderShoppingList(const states::ShoppingListView& state, UserId userId, Ch InlineKeyboardBuilder keyboard{3 + items.size()}; // add, remove and/or buy, list (n), back - keyboard << makeCallbackButton(u8"Поиск", "search") << NewRow{}; + keyboard << makeCallbackButton(u8"🔍 Поиск", "search") << NewRow{}; if (anySelected) { - keyboard << makeCallbackButton(u8"Убрать", "remove"); + keyboard << makeCallbackButton(u8"🗑 Убрать", "remove"); if (state.canBuy) - keyboard << makeCallbackButton(u8"Купить", "buy"); + keyboard << makeCallbackButton(u8"🛒 Купить", "buy"); keyboard << NewRow{}; } for (const auto& item : items) { - const char* const selectedMark = item.selected ? "[+] " : "[ ] "; + const char* const selectedMark = item.selected ? "[ + ] " : "[ᅠ] "; keyboard << makeCallbackButton(selectedMark + item.name, utils::to_string(item.ingredientId)) << NewRow{}; } keyboard << makeCallbackButton(u8"↩️ Назад", "back"); if (auto messageId = message::getMessageId(userId)) { - auto text = utils::utf8str(u8"🔖 Ваш список покупок. Нажмите на элемент, чтобы вычеркнуть из списка."); + auto text = utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } } diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index f0c82995..9ab39884 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -34,7 +34,7 @@ void renderStorageSelection( keyboard.reserveInRow(2); for (auto& storage : chunk) { const bool isSelected = std::ranges::contains(selectedStorages, storage.id, &StorageSummary::id); - std::string emoji = utils::utf8str(isSelected ? u8"[+]" : u8"[ ᅠ]"); + std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; std::string text = std::format("{} {}", emoji, storage.name); std::string data = actionPrefix + utils::to_string(storage.id); diff --git a/src/states.hpp b/src/states.hpp index f4aaa6d0..b45e7cd1 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -130,6 +130,7 @@ struct ShoppingListCreation { availability; api::RecipeId recipeId; std::vector selectedIngredients; + std::vector allIngredients; bool fromStorage; std::size_t pageNo; }; From b26026fd28af6317c1b39be4cc4edb9b2b3c3cbf Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Sat, 19 Jul 2025 13:38:33 +0300 Subject: [PATCH 016/106] feat: recipe publication history --- src/backend/api/recipes.cpp | 5 ++ src/backend/api/recipes.hpp | 3 +- src/backend/models/recipe.cpp | 25 +++++++ src/backend/models/recipe.hpp | 18 ++++- src/handlers/CMakeLists.txt | 1 + src/handlers/common.hpp | 1 + src/handlers/handlers_list.hpp | 4 +- .../recipe/publication_history.cpp | 36 +++++++++ .../recipe/publication_history.hpp | 10 +++ src/handlers/personal_account/recipe/view.cpp | 11 ++- .../personal_account/recipes_list/view.cpp | 1 + src/handlers/personal_account/view.cpp | 1 + src/main.cpp | 3 +- src/render/CMakeLists.txt | 1 + .../recipe/publication_history.cpp | 73 +++++++++++++++++++ .../recipe/publication_history.hpp | 11 +++ src/render/personal_account/recipe/view.cpp | 30 +++++--- .../personal_account/recipes_list/view.cpp | 2 +- src/states.hpp | 8 +- 19 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 src/handlers/personal_account/recipe/publication_history.cpp create mode 100644 src/handlers/personal_account/recipe/publication_history.hpp create mode 100644 src/render/personal_account/recipe/publication_history.cpp create mode 100644 src/render/personal_account/recipe/publication_history.hpp diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 4669c45e..66d42be1 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -70,4 +70,9 @@ void RecipesApi::publishCustom(UserId user, RecipeId recipe) const { jsonPostAuthed(user, std::format("my/recipes/{}/request-publication", recipe)); } +// GET /recipe/{id}/moderation-history +std::vector RecipesApi::getModerationHistory(UserId user, RecipeId recipe) const { + return jsonGetAuthed>(user, + std::format("/recipe/{}/moderation-history", recipe)); +} } // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 8b42dc58..1d465bea 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -35,7 +35,8 @@ class RecipesApi : ApiBase { [[nodiscard]] RecipeId create(UserId user, // NOLINT(*-nodiscard) const models::recipe::RecipeCreateBody& body) const; - + [[nodiscard]] std::vector getModerationHistory(UserId user, + RecipeId recipe) const; void delete_(UserId user, RecipeId recipe) const; [[nodiscard]] models::recipe::CustomRecipeDetails get(UserId user, RecipeId recipe) const; diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index c0b91955..10aa9614 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace cookcookhnya::api::models::recipe { @@ -82,6 +83,8 @@ CustomRecipeDetails tag_invoke(json::value_to_tag /*tag*/, .ingredients = value_to(j.at("ingredients")), .name = value_to(j.at("name")), .link = value_to(j.at("sourceLink")), + // Check swagger for naming + //.moderationStatus = value_to(j.at("status")), }; } @@ -98,4 +101,26 @@ RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/ .found = value_to(j.at("found")), }; } + +CustomRecipePublication tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { + return { + .created = value_to(j.at("created")), + .updated = value_to(j.at("updated")), + .reason = j.as_object().if_contains("reason") + ? value_to(j.at("reason")) + : "", + .status = value_to(j.at("status")), + }; +} + +PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, + const boost::json::value& j) { + if (j.at("status") == "Pending") + return PublicationRequestStatus::Pending; + if (j.at("status") == "Accepted") + return PublicationRequestStatus::Pending; + if (j.at("status") == "Rejected") + return PublicationRequestStatus::Pending; + return PublicationRequestStatus::Pending; +} } // namespace cookcookhnya::api::models::recipe diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index b0f6ec23..79629ce5 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -7,11 +7,16 @@ #include #include #include +#include +#include #include #include namespace cookcookhnya::api::models::recipe { +enum class PublicationRequestStatus : std::uint8_t { Pending = 0, Accepted = 1, Rejected = 2, Idle = 3 }; +PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); + struct RecipeSummary { RecipeId id; std::string name; @@ -64,7 +69,8 @@ struct IngredientInCustomRecipe { struct CustomRecipeDetails { std::vector ingredients; std::string name; - std::string link; + std::string link; // Idk if link field still needed + PublicationRequestStatus moderationStatus; friend CustomRecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; @@ -106,4 +112,14 @@ struct RecipeSearchResponse { const boost::json::value& j); }; +struct CustomRecipePublication { + std::string created; + std::string updated; + std::optional reason; + PublicationRequestStatus status; + + friend CustomRecipePublication tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); +}; + } // namespace cookcookhnya::api::models::recipe diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 575165f4..fcc804d1 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources(main PRIVATE src/handlers/personal_account/recipe/search_ingredients.cpp src/handlers/personal_account/recipe/view.cpp + src/handlers/personal_account/recipe/publication_history.cpp src/handlers/personal_account/view.cpp diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 5ad0cb3b..2e654530 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -48,6 +48,7 @@ using states::ShoppingListView; using states::CreateCustomRecipe; using states::CustomRecipeIngredientsSearch; +using states::CustomRecipePublicationHistory; using states::CustomRecipesList; using states::RecipeCustomView; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 9a4807f7..ce79931d 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -9,6 +9,7 @@ #include "personal_account/ingredients_list/publish.hpp" #include "personal_account/ingredients_list/view.hpp" +#include "personal_account/recipe/publication_history.hpp" #include "personal_account/recipe/search_ingredients.hpp" #include "personal_account/recipe/view.hpp" #include "personal_account/recipes_list/create.hpp" @@ -58,6 +59,7 @@ using namespace storage::members; using namespace storages_list; using namespace storages_selection; using namespace recipes_suggestions; +using namespace personal_account::publication_history; using namespace tg_stater; @@ -125,7 +127,7 @@ using createCustomRecipeCQHandler = Handler; using customRecipeIngredientsSearchCQHandler = Handler; using customRecipeIngredientsSearchIQHandler = Handler; - +using customRecipePublicationHistoryHandler = Handler; } // namespace bot_handlers } // namespace cookcookhnya::handlers diff --git a/src/handlers/personal_account/recipe/publication_history.cpp b/src/handlers/personal_account/recipe/publication_history.cpp new file mode 100644 index 00000000..5c30f52f --- /dev/null +++ b/src/handlers/personal_account/recipe/publication_history.cpp @@ -0,0 +1,36 @@ +#include "publication_history.hpp" + +#include "render/personal_account/recipe/view.hpp" + +namespace cookcookhnya::handlers::personal_account::publication_history { + +using namespace render::personal_account; +using namespace render::personal_account::recipes; + +void handleCustomRecipePublicationHistoryCQ(CustomRecipePublicationHistory& state, + CallbackQueryRef cq, + BotRef bot, + SMRef stateManager, + RecipesApiRef recipesApi) { + const std::string data = cq.data; + auto chatId = cq.message->chat->id; + auto userId = cq.from->id; + + if (data == "back") { + auto ingredients = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); + stateManager.put( + RecipeCustomView{.recipeId = state.recipeId, .pageNo = state.pageNo, .ingredients = ingredients}); + return; + } + + if (data == "confirm") { + recipesApi.publishCustom(userId, state.recipeId); + // As published return back + auto ingredients = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); + stateManager.put( + RecipeCustomView{.recipeId = state.recipeId, .pageNo = state.pageNo, .ingredients = ingredients}); + return; + } +} + +} // namespace cookcookhnya::handlers::personal_account::publication_history diff --git a/src/handlers/personal_account/recipe/publication_history.hpp b/src/handlers/personal_account/recipe/publication_history.hpp new file mode 100644 index 00000000..774f9509 --- /dev/null +++ b/src/handlers/personal_account/recipe/publication_history.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::personal_account::publication_history { + +void handleCustomRecipePublicationHistoryCQ( + CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef api); + +} // namespace cookcookhnya::handlers::personal_account::publication_history diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index 5e5ccbab..68e310bd 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -1,6 +1,7 @@ #include "view.hpp" #include "handlers/common.hpp" +#include "render/personal_account/recipe/publication_history.hpp" #include "render/personal_account/recipe/search_ingredients.hpp" #include "render/personal_account/recipes_list/view.hpp" #include "states.hpp" @@ -13,6 +14,8 @@ namespace cookcookhnya::handlers::personal_account::recipes { using namespace render::personal_account::recipes; using namespace render::recipe::ingredients; +using namespace render::personal_account::publication_history; + using namespace std::views; const std::size_t numOfIngredientsOnPage = 5; @@ -33,6 +36,7 @@ void handleRecipeCustomViewCQ( stateManager.put(CustomRecipeIngredientsSearch{state.recipeId, state.ingredients | as_rvalue, ""}); renderRecipeIngredientsSearch( std::get(*stateManager.get()), numOfIngredientsOnPage, userId, chatId, bot); + bot.answerCallbackQuery(cq.id); return; } @@ -42,11 +46,14 @@ void handleRecipeCustomViewCQ( // Made to avoid bug when delete last recipe on page -> will return to the non-existent page renderCustomRecipesList(0, userId, chatId, bot, api); stateManager.put(CustomRecipesList{.pageNo = 0}); + bot.answerCallbackQuery(cq.id); return; } - if (data == "publish") { // Should also create backend endpoint to track status of publish - api.getRecipesApi().publishCustom(userId, state.recipeId); + if (data == "publish") { + renderPublicationHistory(userId, chatId, state.recipeId, bot, api); + stateManager.put(states::CustomRecipePublicationHistory{.recipeId = state.recipeId, .pageNo = state.pageNo}); + bot.answerCallbackQuery(cq.id); return; } } diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index b978ec6d..07f2734b 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -45,6 +45,7 @@ void handleCustomRecipesListCQ( stateManager.put( RecipeCustomView{.recipeId = recipeId.value(), .pageNo = state.pageNo, .ingredients = ingredients}); } + bot.answerCallbackQuery(cq.id); return; } diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 36f2af94..0e961e4a 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -23,6 +23,7 @@ void handlePersonalAccountMenuCQ( if (data == "recipes") { renderCustomRecipesList(0, userId, chatId, bot, api); stateManager.put(CustomRecipesList{.pageNo = 0}); + return; } if (data == "back") { renderMainMenu(true, userId, chatId, bot, api); diff --git a/src/main.cpp b/src/main.cpp index 87eb9151..f5c00719 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -64,7 +64,8 @@ int main(int argc, char* argv[]) { createCustomRecipeCQHandler, recipeCustomViewCQHandler, customRecipeIngredientsSearchCQHandler, - customRecipeIngredientsSearchIQHandler> + customRecipeIngredientsSearchIQHandler, + customRecipePublicationHistoryHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 22bf5a89..92808a00 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(main PRIVATE src/render/personal_account/recipe/search_ingredients.cpp src/render/personal_account/recipe/view.cpp + src/render/personal_account/recipe/publication_history.cpp src/render/personal_account/view.cpp diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp new file mode 100644 index 00000000..342583ef --- /dev/null +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -0,0 +1,73 @@ +#include "publication_history.hpp" + +#include "backend/api/recipes.hpp" +#include "backend/id_types.hpp" +#include "backend/models/recipe.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "utils/utils.hpp" + +#include +#include +#include + +namespace cookcookhnya::render::personal_account::publication_history { + +using namespace std::views; + +void renderPublicationHistory( + UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { + // auto history = recipesApi.getModerationHistory(userId, recipeId); + std::vector history = {{ + .created = "asd", + .updated = "12342", + .reason = "gddf", + .status = api::models::recipe::PublicationRequestStatus::Pending, + }}; + InlineKeyboardBuilder keyboard{2}; // confirm and back + + std::ostringstream toPrint; + toPrint << utils::utf8str(u8"История запросов на публикацию данного рецепта\n"); + const std::vector prefixes = {"Статус", "Обновлен", "Создан", "Причина"}; + const std::vector statusStr = {"На рассмотрении", "Принят", "Отклонен"}; + // What if i forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and interate + // through it but it would require "for" and i don't know what is it) + // reverse to print lastest request the last in list + auto res = + history | reverse | transform([&prefixes, &statusStr](const api::models::recipe::CustomRecipePublication& req) { + //!!!!IMPORTANT See that tie to match prefixes!!!! + auto fields = std::tie(req.status, req.updated, req.created, req.reason); + return iota(0U, prefixes.size()) | transform([fields, &prefixes, &statusStr](size_t idx) -> std::string { + switch (idx) { + case 0: + // statusStr to convert enum to string + return prefixes[0] + ": " + statusStr[static_cast(std::get<0>(fields))]; + case 1: + return prefixes[1] + ": " + std::get<1>(fields); + case 2: + return prefixes[2] + ": " + std::get<2>(fields); + case 3: { + // Check if optional field is filled + if (std::get<3>(fields).has_value()) { + return prefixes[3] + ": " + std::get<3>(fields).value(); + } + return ""; + } + default: + return ""; + } + }); + }) | + join; // Join here instead of in the loop + + std::ranges::copy(res, std::ostream_iterator(toPrint, "\n")); + + // Text is created moving to the markup + keyboard << makeCallbackButton(u8"▶️Подтвердить", "confirm") << NewRow{}; + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto messageId = message::getMessageId(userId)) { + bot.editMessageText(toPrint.str(), chatId, *messageId, std::move(keyboard)); + } +} +} // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/recipe/publication_history.hpp b/src/render/personal_account/recipe/publication_history.hpp new file mode 100644 index 00000000..9e71680c --- /dev/null +++ b/src/render/personal_account/recipe/publication_history.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "backend/id_types.hpp" +#include "render/common.hpp" + +namespace cookcookhnya::render::personal_account::publication_history { + +void renderPublicationHistory( + UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi); + +} // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 76aa2d90..e343f6fd 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -17,13 +17,17 @@ namespace cookcookhnya::render::personal_account::recipes { std::vector renderCustomRecipe( bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { + auto recipeDetails = recipesApi.get(userId, recipeId); + // REMOVE WHEN BACKEND IS READY + recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::Idle; std::vector ingredients; const std::size_t rows = 4; // 1 for publish, 1 for delete, 1 for back, 1 for change - InlineKeyboard keyboard(rows); + InlineKeyboardBuilder keyboard{rows}; std::string toPrint; - toPrint += (utils::utf8str(u8"Рецепт: ") + recipeDetails.name + "\n"); + toPrint += std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeDetails.name); + for (auto& it : recipeDetails.ingredients) { toPrint += std::format("• {}\n", it.name); ingredients.push_back({ @@ -31,19 +35,27 @@ std::vector renderCustomRecipe( .name = it.name, }); } - + // remove tommorrow + const std::vector statusStr = {"На рассмотрении", "Принят", "Отклонен", "Ничего"}; toPrint += recipeDetails.link; - keyboard[0].push_back(makeCallbackButton(u8"Удалить", "delete")); - keyboard[1].push_back(makeCallbackButton(u8"Редактировать", "change")); - keyboard[2].push_back(makeCallbackButton(u8"Опубликовать", "publish")); - keyboard[3].push_back(makeCallbackButton(u8"Назад", "back")); + toPrint += "\n🌐 [Статус проверки](" + statusStr[static_cast(recipeDetails.moderationStatus)] + ")"; + + 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 == api::models::recipe::PublicationRequestStatus::Idle || + recipeDetails.moderationStatus == api::models::recipe::PublicationRequestStatus::Rejected) { + keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; + } + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); if (toBeEdited) { auto messageId = message::getMessageId(userId); if (messageId) - bot.editMessageText(toPrint, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard)); } else { - auto message = bot.sendMessage(chatId, toPrint, makeKeyboardMarkup(std::move(keyboard))); + auto message = bot.sendMessage(chatId, toPrint, std::move(keyboard)); message::addMessageId(userId, message->messageId); } return ingredients; diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 5c7bf452..9463a6e5 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -117,7 +117,7 @@ constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::R if (keyboard.empty()) { // If error happened return keyboard; } - keyboard[0].push_back(makeCallbackButton(u8"Создать", "custom_recipe_create")); + keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "custom_recipe_create")); return keyboard; } diff --git a/src/states.hpp b/src/states.hpp index 1036d81e..ca249ad1 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -138,6 +138,11 @@ struct ShoppingListView { // NOLINT(*member-init) // Strange. Flags only this st bool canBuy; }; +struct CustomRecipePublicationHistory { + api::RecipeId recipeId; + std::size_t pageNo; +}; + using State = std::variant; + RecipeIngredientsSearch, + CustomRecipePublicationHistory>; using StateManager = tg_stater::StateProxy>; From f3852525956291332323fa22a95d6b06090b3b3b Mon Sep 17 00:00:00 2001 From: Badamshin Rashid <102172606+Rash1d1@users.noreply.github.com> Date: Sat, 19 Jul 2025 14:07:03 +0300 Subject: [PATCH 017/106] ci: fix wrong cache hit flag --- .github/workflows/ci.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79eb59d8..dc202ab9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,16 +66,12 @@ jobs: restore-keys: | ${{ runner.os }}-clang-tidy-${{ github.ref }}- ${{ runner.os }}-clang-tidy-refs/heads/main - - - name: Initialize empty clang-tidy cache - if: steps.cache-restore.outputs.cache-hit != 'true' - run: | - echo "{}" > .clang-tidy-cache.json - - - name: Copy original cache file - if: steps.cache-restore.outputs.cache-hit == 'true' + + - name: Copy original cache file if exists run: | - cp .clang-tidy-cache.json .clang-tidy-cache.json.orig + if [ -f .clang-tidy-cache.json ]; then + cp .clang-tidy-cache.json .clang-tidy-cache.json.orig + fi - name: Create clang-tidy cache script run: | From b0f727be21c7d4e48fcc7370c98a5d72e3ae1589 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Sat, 19 Jul 2025 14:16:52 +0300 Subject: [PATCH 018/106] feat: function to convert status enum to string --- src/render/personal_account/recipe/view.cpp | 8 ++++++-- src/utils/to_string.cpp | 10 ++++++++++ src/utils/to_string.hpp | 3 +++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index f134dabf..6d485392 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -21,6 +21,7 @@ std::vector renderCustomRecipe( auto recipeDetails = recipesApi.get(userId, recipeId); // REMOVE WHEN BACKEND IS READY recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::Idle; + std::vector ingredients; const std::size_t rows = 4; // 1 for publish, 1 for delete, 1 for back, 1 for change @@ -35,8 +36,11 @@ std::vector renderCustomRecipe( .name = it.name, }); } - // remove tommorrow - const std::vector statusStr = {"На рассмотрении", "Принят", "Отклонен", "Ничего"}; + // remove when to string method will be implemented for enum + const std::vector statusStr = {utils::utf8str(u8"🟡 На рассмотрении"), + utils::utf8str(u8"🟢 Принят"), + utils::utf8str(u8"🔴 Отклонен"), + utils::utf8str(u8"⚪️ Вы еще не отправили запрос")}; toPrint += "\n🌐 [Статус проверки](" + statusStr[static_cast(recipeDetails.moderationStatus)] + ")"; keyboard << makeCallbackButton(u8"🚮 Удалить", "delete") << NewRow{}; diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 38941db2..c727f366 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -1,5 +1,7 @@ #include "to_string.hpp" +#include "backend/models/recipe.hpp" +#include "utils/utils.hpp" #include "uuid.hpp" #include @@ -12,4 +14,12 @@ std::string to_string(const Uuid& u) { return boost::lexical_cast(u); } +std::string to_string(const cookcookhnya::api::models::recipe::PublicationRequestStatus status) { + const std::vector statusStr = {utf8str(u8"🟡 На рассмотрении"), + utf8str(u8"🟢 Принят"), + utf8str(u8"🔴 Отклонен"), + utf8str(u8"⚪️ Вы еще не отправили запрос")}; + return statusStr[static_cast(status)]; +} + } // namespace cookcookhnya::utils diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index 5179ec0a..eefe80f1 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/models/recipe.hpp" #include "uuid.hpp" #include @@ -16,4 +17,6 @@ std::string to_string(const T& t) { std::string to_string(const Uuid& u); +std::string to_string(cookcookhnya::api::models::recipe::PublicationRequestStatus status); + } // namespace cookcookhnya::utils From ace80061094195aba4e35a0634cc68b6641cd133 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Sat, 19 Jul 2025 14:44:39 +0300 Subject: [PATCH 019/106] feat: new format for publication request history --- src/backend/models/recipe.cpp | 9 ++- src/backend/models/recipe.hpp | 2 +- src/backend/models/user.hpp | 1 + src/handlers/personal_account/recipe/view.cpp | 2 +- .../personal_account/recipes_list/create.cpp | 5 +- .../personal_account/recipes_list/view.cpp | 8 ++- .../recipe/publication_history.cpp | 71 +++++++++---------- .../recipe/publication_history.hpp | 2 +- src/render/personal_account/recipe/view.cpp | 12 ++-- src/render/personal_account/recipe/view.hpp | 7 +- src/states.hpp | 1 + 11 files changed, 64 insertions(+), 56 deletions(-) diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 5d816aec..17f54c07 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -52,9 +52,12 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: .ingredients = value_to(j.at("ingredients")), .name = value_to(j.at("name")), .link = value_to(j.at("sourceLink")), - .creator = value_to(j.at("creator")), - // Check swagger for naming - //.moderationStatus = value_to(j.at("status")), + // Deal with optionals using ternary + .creator = j.as_object().if_contains("creator") ? value_to(j.at("creator")) + : user::UserDetails{.userId = 0, .alias = "", .fullName = ""}, + .moderationStatus = j.as_object().if_contains("status") + ? value_to(j.at("status")) + : PublicationRequestStatus::Idle, }; } diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 37cee6ce..2c37653a 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -48,7 +48,7 @@ struct RecipeDetails { std::string name; std::optional link; std::optional creator; - PublicationRequestStatus moderationStatus; + std::optional moderationStatus; friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/backend/models/user.hpp b/src/backend/models/user.hpp index 702f4dc0..ccf8e647 100644 --- a/src/backend/models/user.hpp +++ b/src/backend/models/user.hpp @@ -19,6 +19,7 @@ struct UpdateUserInfoBody { struct UserDetails { tg_types::UserId userId; + // Note: current backend doesn't have alias field (19.07.2025) std::optional alias; std::string fullName; diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index 54740f5d..3dd74db9 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -51,7 +51,7 @@ void handleRecipeCustomViewCQ( } if (data == "publish") { - renderPublicationHistory(userId, chatId, state.recipeId, bot, api); + renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, bot, api); stateManager.put(states::CustomRecipePublicationHistory{.recipeId = state.recipeId, .pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); return; diff --git a/src/handlers/personal_account/recipes_list/create.cpp b/src/handlers/personal_account/recipes_list/create.cpp index ab2119b4..d875a8a6 100644 --- a/src/handlers/personal_account/recipes_list/create.cpp +++ b/src/handlers/personal_account/recipes_list/create.cpp @@ -19,8 +19,9 @@ void handleCreateCustomRecipeMsg( renderCustomRecipe(false, m.from->id, m.chat->id, state.recipeId, bot, recipeApi); stateManager.put(RecipeCustomView{.recipeId = state.recipeId, .pageNo = 0, - .ingredients = {}}); // If it went from creation then as user will return - // from RecipeView to RecipesList on 1st page + .ingredients = {}, + .recipeName = m.text}); // If it went from creation then as user will return + // from RecipeView to RecipesList on 1st page }; void handleCreateCustomRecipeCQ( diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index 164cb59a..5a2dcf38 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -40,9 +40,11 @@ void handleCustomRecipesListCQ( if (data[0] == 'r') { auto recipeId = utils::parseSafe(data.substr(1, data.size())); if (recipeId) { - auto ingredients = renderCustomRecipe(true, userId, chatId, recipeId.value(), bot, api); - stateManager.put( - RecipeCustomView{.recipeId = recipeId.value(), .pageNo = state.pageNo, .ingredients = ingredients}); + auto ingredientsAndName = renderCustomRecipe(true, userId, chatId, recipeId.value(), bot, api); + stateManager.put(RecipeCustomView{.recipeId = recipeId.value(), + .pageNo = state.pageNo, + .ingredients = ingredientsAndName.ingredients, + .recipeName = ingredientsAndName.recipeName}); } bot.answerCallbackQuery(cq.id); return; diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp index 342583ef..002c9d95 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -15,52 +15,51 @@ namespace cookcookhnya::render::personal_account::publication_history { using namespace std::views; -void renderPublicationHistory( - UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { +void renderPublicationHistory(UserId userId, + ChatId chatId, + api::RecipeId recipeId, + std::string recipeName, + BotRef bot, + RecipesApiRef recipesApi) { // auto history = recipesApi.getModerationHistory(userId, recipeId); std::vector history = {{ - .created = "asd", - .updated = "12342", - .reason = "gddf", - .status = api::models::recipe::PublicationRequestStatus::Pending, + .created = "52 сентября", + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::Rejected, }}; InlineKeyboardBuilder keyboard{2}; // confirm and back std::ostringstream toPrint; - toPrint << utils::utf8str(u8"История запросов на публикацию данного рецепта\n"); - const std::vector prefixes = {"Статус", "Обновлен", "Создан", "Причина"}; - const std::vector statusStr = {"На рассмотрении", "Принят", "Отклонен"}; + toPrint << (utils::utf8str(u8"История запросов на публикацию ") + recipeName + "\n"); + const std::vector prefixes = { + utils::utf8str(u8"ℹ️ Текущий статус"), utils::utf8str(u8"по причине"), utils::utf8str(u8"Запрос создан:")}; + // What if i forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and interate // through it but it would require "for" and i don't know what is it) // reverse to print lastest request the last in list - auto res = - history | reverse | transform([&prefixes, &statusStr](const api::models::recipe::CustomRecipePublication& req) { - //!!!!IMPORTANT See that tie to match prefixes!!!! - auto fields = std::tie(req.status, req.updated, req.created, req.reason); - return iota(0U, prefixes.size()) | transform([fields, &prefixes, &statusStr](size_t idx) -> std::string { - switch (idx) { - case 0: - // statusStr to convert enum to string - return prefixes[0] + ": " + statusStr[static_cast(std::get<0>(fields))]; - case 1: - return prefixes[1] + ": " + std::get<1>(fields); - case 2: - return prefixes[2] + ": " + std::get<2>(fields); - case 3: { - // Check if optional field is filled - if (std::get<3>(fields).has_value()) { - return prefixes[3] + ": " + std::get<3>(fields).value(); - } - return ""; - } - default: - return ""; - } - }); - }) | - join; // Join here instead of in the loop + auto res = history | reverse | transform([&prefixes](const api::models::recipe::CustomRecipePublication& req) { + //!!!!IMPORTANT See that tie to match prefixes!!!! + auto fields = std::tie(req.status, req.reason, req.created); + return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { + switch (idx) { + case 0: + // statusStr to convert enum to string + return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)); + case 1: + if (std::get<1>(fields).has_value()) { + return prefixes[1] + ": " + std::get<1>(fields).value(); + } + case 2: + return prefixes[2] + ": " + std::get<2>(fields); + return ""; + default: + return ""; + } + }); + }) | + join; // Join here instead of in the loop - std::ranges::copy(res, std::ostream_iterator(toPrint, "\n")); + std::ranges::copy(res, std::ostream_iterator(toPrint, "----------------------\n")); // Text is created moving to the markup keyboard << makeCallbackButton(u8"▶️Подтвердить", "confirm") << NewRow{}; diff --git a/src/render/personal_account/recipe/publication_history.hpp b/src/render/personal_account/recipe/publication_history.hpp index 9e71680c..ebae6fe5 100644 --- a/src/render/personal_account/recipe/publication_history.hpp +++ b/src/render/personal_account/recipe/publication_history.hpp @@ -6,6 +6,6 @@ namespace cookcookhnya::render::personal_account::publication_history { void renderPublicationHistory( - UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi); + UserId userId, ChatId chatId, api::RecipeId recipeId, std::string recipeName, BotRef bot, RecipesApiRef recipesApi); } // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 6d485392..0b099f69 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -15,7 +15,7 @@ namespace cookcookhnya::render::personal_account::recipes { -std::vector renderCustomRecipe( +IngredientsAndRecipeName renderCustomRecipe( bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { auto recipeDetails = recipesApi.get(userId, recipeId); @@ -36,12 +36,8 @@ std::vector renderCustomRecipe( .name = it.name, }); } - // remove when to string method will be implemented for enum - const std::vector statusStr = {utils::utf8str(u8"🟡 На рассмотрении"), - utils::utf8str(u8"🟢 Принят"), - utils::utf8str(u8"🔴 Отклонен"), - utils::utf8str(u8"⚪️ Вы еще не отправили запрос")}; - toPrint += "\n🌐 [Статус проверки](" + statusStr[static_cast(recipeDetails.moderationStatus)] + ")"; + + toPrint += "\n🌐 [Статус проверки](" + utils::to_string(recipeDetails.moderationStatus.value()) + ")"; keyboard << makeCallbackButton(u8"🚮 Удалить", "delete") << NewRow{}; keyboard << makeCallbackButton(u8"✏️ Редактировать", "change") << NewRow{}; @@ -61,6 +57,6 @@ std::vector renderCustomRecipe( auto message = bot.sendMessage(chatId, toPrint, std::move(keyboard)); message::addMessageId(userId, message->messageId); } - return ingredients; + return {.ingredients = ingredients, .recipeName = recipeDetails.name}; } } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/render/personal_account/recipe/view.hpp b/src/render/personal_account/recipe/view.hpp index 52c4386a..64b434ed 100644 --- a/src/render/personal_account/recipe/view.hpp +++ b/src/render/personal_account/recipe/view.hpp @@ -7,7 +7,12 @@ namespace cookcookhnya::render::personal_account::recipes { -std::vector renderCustomRecipe( +struct IngredientsAndRecipeName { + std::vector ingredients; + std::string recipeName; +}; + +IngredientsAndRecipeName renderCustomRecipe( bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi); } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/states.hpp b/src/states.hpp index 15f06217..096f2944 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -116,6 +116,7 @@ struct RecipeCustomView { api::RecipeId recipeId; std::size_t pageNo; std::vector ingredients; + std::string recipeName; }; struct CreateCustomRecipe { From a00fe6adbb34613ecc727b74c4f6230b52e35c2a Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sat, 19 Jul 2025 17:06:58 +0300 Subject: [PATCH 020/106] feat: ingredients moderation status (not done) --- src/backend/CMakeLists.txt | 1 + src/backend/models/ingredient.cpp | 3 + src/backend/models/ingredient.hpp | 3 + .../models/publication_request_status.cpp | 16 ++++ .../models/publication_request_status.hpp | 13 +++ .../ingredients_list/create.cpp | 6 +- .../ingredients_list/publish.cpp | 2 +- .../ingredients_list/view.cpp | 9 ++ src/handlers/personal_account/view.cpp | 2 +- .../ingredients_list/view.cpp | 96 +++++++++++++++---- .../ingredients_list/view.hpp | 3 +- src/render/shopping_list/create.cpp | 3 +- src/render/shopping_list/view.cpp | 3 +- src/utils/to_string.cpp | 10 ++ src/utils/to_string.hpp | 3 + 15 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 src/backend/models/publication_request_status.cpp create mode 100644 src/backend/models/publication_request_status.hpp diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 2b4bc513..33cc4263 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -14,4 +14,5 @@ target_sources(main PRIVATE src/backend/models/user.cpp src/backend/models/recipe.cpp src/backend/models/shopping_list.cpp + src/backend/models/publication_request_status.cpp ) diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index db2fff59..1228ba30 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -12,6 +12,9 @@ Ingredient tag_invoke(json::value_to_tag /*tag*/, const json::value& return { .id = value_to(j.at("id")), .name = value_to(j.at("name")), + .status = j.as_object().if_contains("moderation_status") + ? value_to(j.at("moderation_status")) + : std::nullopt, }; } diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index 457d8be0..76a59244 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -1,11 +1,13 @@ #pragma once #include "backend/id_types.hpp" +#include "backend/models/publication_request_status.hpp" #include #include #include +#include #include #include @@ -14,6 +16,7 @@ namespace cookcookhnya::api::models::ingredient { struct Ingredient { IngredientId id; std::string name; + std::optional status = std::nullopt; friend Ingredient 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 new file mode 100644 index 00000000..17fdf0ce --- /dev/null +++ b/src/backend/models/publication_request_status.cpp @@ -0,0 +1,16 @@ +#include "backend/models/publication_request_status.hpp" + +namespace cookcookhnya::api::models::status { + +PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, + const boost::json::value& j) { + if (j.at("status") == "Pending") + return PublicationRequestStatus::Pending; + if (j.at("status") == "Accepted") + return PublicationRequestStatus::Accepted; + if (j.at("status") == "Rejected") + return PublicationRequestStatus::Rejected; + return PublicationRequestStatus::Idle; +} + +} // namespace cookcookhnya::api::models::status diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp new file mode 100644 index 00000000..8066c9bc --- /dev/null +++ b/src/backend/models/publication_request_status.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include + +namespace cookcookhnya::api::models::status { + +enum class PublicationRequestStatus : std::uint8_t { Pending = 0, Accepted = 1, Rejected = 2, Idle = 3 }; + +PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); + +} // namespace cookcookhnya::api::models::status diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 02f328fc..19d77356 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -39,7 +39,7 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName auto userId = cq.from->id; auto chatId = cq.message->chat->id; if (cq.data == "back") { - renderCustomIngredientsList(true, userId, chatId, bot, api); + renderCustomIngredientsList(true, 0, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); } } @@ -52,11 +52,11 @@ void handleCustomIngredientConfirmationCQ( auto name = state.name; if (cq.data == "confirm") { api.createCustom(userId, api::models::ingredient::IngredientCreateBody{name}); - renderCustomIngredientsList(true, userId, chatId, bot, api); + renderCustomIngredientsList(true, 0, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); } if (cq.data == "back") { - renderCustomIngredientsList(true, userId, chatId, bot, api); + renderCustomIngredientsList(true, 0, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); } } diff --git a/src/handlers/personal_account/ingredients_list/publish.cpp b/src/handlers/personal_account/ingredients_list/publish.cpp index 05aa2f98..ab78f5ab 100644 --- a/src/handlers/personal_account/ingredients_list/publish.cpp +++ b/src/handlers/personal_account/ingredients_list/publish.cpp @@ -20,7 +20,7 @@ void handleCustomIngredientPublishCQ( if (ingredientId) { api.publishCustom(userId, *ingredientId); } - renderCustomIngredientsList(true, userId, chatId, bot, api); + renderCustomIngredientsList(true, 0, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); } } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/view.cpp b/src/handlers/personal_account/ingredients_list/view.cpp index e7245bbf..f9d755f9 100644 --- a/src/handlers/personal_account/ingredients_list/view.cpp +++ b/src/handlers/personal_account/ingredients_list/view.cpp @@ -3,9 +3,12 @@ #include "handlers/common.hpp" #include "render/personal_account/ingredients_list/create.hpp" #include "render/personal_account/ingredients_list/publish.hpp" +#include "render/personal_account/ingredients_list/view.hpp" #include "render/personal_account/view.hpp" #include "states.hpp" +#include "utils/parsing.hpp" +#include namespace cookcookhnya::handlers::personal_account::ingredients { @@ -32,5 +35,11 @@ void handleCustomIngredientsListCQ( stateManager.put(PersonalAccountMenu{}); return; } + auto pageNo = utils::parseSafe(cq.data); + if (pageNo) { + renderCustomIngredientsList(true, *pageNo, userId, chatId, bot, api); + stateManager.put(CustomIngredientCreationEnterName{}); + return; + } } } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 36f2af94..65358cc6 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -30,7 +30,7 @@ void handlePersonalAccountMenuCQ( return; } if (data == "ingredients") { - renderCustomIngredientsList(true, userId, chatId, bot, api); + renderCustomIngredientsList(true, 0, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); return; } diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index 139b2e25..9fc0d253 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -4,48 +4,110 @@ #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" #include "render/common.hpp" +#include "utils/to_string.hpp" #include "utils/utils.hpp" -#include #include #include #include +#include #include +#include namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; -void renderCustomIngredientsList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - auto ingredientsResp = api.getList(userId, PublicityFilterType::Custom); - auto ingredients = ingredientsResp.page; - const std::size_t buttonRows = ingredients.empty() ? 2 : 3; - InlineKeyboard keyboard(buttonRows); +namespace { - if (ingredients.empty()) { +std::pair> constructNavigationMessage( + std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::IngredientList& ingredientsList) { + const size_t amountOfRecipes = ingredientsList.found; + const std::size_t maxPageNum = + std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); + + std::string text; + + text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); + for (const auto& ing : ingredientsList.page) { + text += std::format("• _{}_ {}", utils::to_string(*ing.status), ing.name); + } + + std::vector buttons; + if (pageNo == maxPageNum) { + buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); + buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "")); + buttons.push_back(makeCallbackButton(u8"ㅤ", "")); + } + if (pageNo == 0) { + buttons.push_back(makeCallbackButton(u8"ㅤ", "")); + buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "")); + buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); + } else { + buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); + buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "")); + buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); + } + return std::make_pair(text, buttons); +} + +std::pair constructMessage(size_t pageNo, + size_t numOfIngredientsOnPage, + api::models::ingredient::IngredientList& ingredientsList) { + std::size_t numOfRows = 0; + if (ingredientsList.found == 0) + numOfRows = 2; + else if (ingredientsList.found <= numOfIngredientsOnPage) + numOfRows = 3; + else + numOfRows = 4; + std::string text; + InlineKeyboard keyboard(numOfRows); + if (ingredientsList.found == 0) { + text = + utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. Создавайте и делитесь новыми ингредиентами\\.\n\n"); keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - } else { + } else if (ingredientsList.found <= numOfIngredientsOnPage) { + text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); + for (const auto& ing : ingredientsList.page) { + text += std::format("• _{}_ {}", utils::to_string(*ing.status), ing.name); + } keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + } else { + auto message = constructNavigationMessage(pageNo, numOfIngredientsOnPage, ingredientsList); + text = message.first; + keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); + keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); + keyboard[2].reserve(2); + for (const auto& navigButton : message.second) { + keyboard[2].push_back(navigButton); + } + keyboard[3].push_back(makeCallbackButton(u8"↩️ Назад", "back")); } + return std::make_pair(text, keyboard); +} + +} // namespace - std::string formatedIngredients; - std::ranges::for_each(ingredients, [&formatedIngredients](const api::models::ingredient::Ingredient& item) { - formatedIngredients += "• " + item.name + "\n"; - }); +void renderCustomIngredientsList( + bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + const std::size_t numOfIngredientsOnPage = 10; - auto text = std::format("{} Вы находитесь в Мои ингредиенты. \nВами созданные ингредиенты:\n{}", - utils::utf8str(u8"📋"), - formatedIngredients); + auto ingredientsList = + api.getList(userId, PublicityFilterType::Custom, numOfIngredientsOnPage, pageNo * numOfIngredientsOnPage); + auto res = constructMessage(pageNo, numOfIngredientsOnPage, ingredientsList); + auto text = res.first; + auto keyboard = res.second; if (toBeEdited) { if (auto messageId = message::getMessageId(userId)) - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); } else { - auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); + auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/personal_account/ingredients_list/view.hpp b/src/render/personal_account/ingredients_list/view.hpp index a277d5cd..6cb4a0a6 100644 --- a/src/render/personal_account/ingredients_list/view.hpp +++ b/src/render/personal_account/ingredients_list/view.hpp @@ -4,6 +4,7 @@ namespace cookcookhnya::render::personal_account::ingredients { -void renderCustomIngredientsList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); +void renderCustomIngredientsList( + bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index 1d07e52d..40adbb5d 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -31,7 +31,8 @@ void renderShoppingListCreation(const std::vector& selectedIngredien for (auto chunk : allIngredients | chunk(2)) { keyboard.reserveInRow(2); for (const Ingredient& ing : chunk) { - const bool isSelected = std::ranges::contains(selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id); + const bool isSelected = + std::ranges::contains(selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; std::string text = std::format("{} {}", emoji, ing.name); diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 03240d64..737d374a 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -35,7 +35,8 @@ void renderShoppingList(const states::ShoppingListView& state, UserId userId, Ch keyboard << makeCallbackButton(u8"↩️ Назад", "back"); if (auto messageId = message::getMessageId(userId)) { - auto text = utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); + auto text = + utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } } diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 38941db2..c7255195 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -1,5 +1,7 @@ #include "to_string.hpp" +#include "backend/models/publication_request_status.hpp" +#include "utils/utils.hpp" #include "uuid.hpp" #include @@ -12,4 +14,12 @@ std::string to_string(const Uuid& u) { return boost::lexical_cast(u); } +std::string to_string(const api::models::status::PublicationRequestStatus status) { + const std::vector statusStr = {utf8str(u8"🟡 На рассмотрении"), + utf8str(u8"🟢 Принят"), + utf8str(u8"🔴 Отклонен"), + utf8str(u8"⚪️ Вы еще не отправили запрос")}; + return statusStr[static_cast(status)]; +} + } // namespace cookcookhnya::utils diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index 5179ec0a..815ef722 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/models/publication_request_status.hpp" #include "uuid.hpp" #include @@ -16,4 +17,6 @@ std::string to_string(const T& t) { std::string to_string(const Uuid& u); +std::string to_string(api::models::status::PublicationRequestStatus status); + } // namespace cookcookhnya::utils From deff90de5cdd62d69db78ee76fc49367a9708899 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Sat, 19 Jul 2025 20:03:44 +0300 Subject: [PATCH 021/106] feat: ui for request history updated --- Makefile | 2 +- src/backend/models/recipe.cpp | 3 +- src/backend/models/recipe.hpp | 2 +- .../recipe/publication_history.cpp | 16 ++-- .../recipe/search_ingredients.cpp | 8 +- .../personal_account/recipes_list/view.cpp | 4 +- .../recipe/publication_history.cpp | 89 ++++++++++++++----- .../recipe/publication_history.hpp | 8 +- src/render/personal_account/recipe/view.cpp | 11 +-- src/render/personal_account/recipe/view.hpp | 7 +- .../personal_account/recipes_list/view.cpp | 11 +-- src/utils/CMakeLists.txt | 1 + src/utils/serialization.cpp | 14 +++ src/utils/serialization.hpp | 10 +++ 14 files changed, 134 insertions(+), 52 deletions(-) create mode 100644 src/utils/serialization.cpp create mode 100644 src/utils/serialization.hpp diff --git a/Makefile b/Makefile index d1bc165d..360f7be0 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 -- -j5 + cmake --build . --preset=conan-debug -- -j3 build-release: build/Release/CMakeCache.txt cmake --build --preset=conan-release diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 17f54c07..4e33d5f7 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -1,4 +1,5 @@ #include "backend/models/recipe.hpp" +#include "utils/serialization.hpp" #include #include @@ -77,7 +78,7 @@ RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/ CustomRecipePublication tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { - .created = value_to(j.at("created")), + .created = utils::parseIsoTime(value_to(j.at("created"))), .reason = j.as_object().if_contains("reason") ? value_to(j.at("reason")) : "", diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 2c37653a..5e1e9997 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -93,7 +93,7 @@ struct RecipeSearchResponse { }; struct CustomRecipePublication { - std::string created; + std::chrono::system_clock::time_point created; std::optional reason; PublicationRequestStatus status; diff --git a/src/handlers/personal_account/recipe/publication_history.cpp b/src/handlers/personal_account/recipe/publication_history.cpp index 5c30f52f..feb1a2e5 100644 --- a/src/handlers/personal_account/recipe/publication_history.cpp +++ b/src/handlers/personal_account/recipe/publication_history.cpp @@ -17,18 +17,22 @@ void handleCustomRecipePublicationHistoryCQ(CustomRecipePublicationHistory& stat auto userId = cq.from->id; if (data == "back") { - auto ingredients = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); - stateManager.put( - RecipeCustomView{.recipeId = state.recipeId, .pageNo = state.pageNo, .ingredients = ingredients}); + auto ingredientsAndRecipeName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); + stateManager.put(RecipeCustomView{.recipeId = state.recipeId, + .pageNo = state.pageNo, + .ingredients = std::get<0>(ingredientsAndRecipeName), + .recipeName = std::get<1>(ingredientsAndRecipeName)}); return; } if (data == "confirm") { recipesApi.publishCustom(userId, state.recipeId); // As published return back - auto ingredients = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); - stateManager.put( - RecipeCustomView{.recipeId = state.recipeId, .pageNo = state.pageNo, .ingredients = ingredients}); + auto ingredientsAndRecipeName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); + stateManager.put(RecipeCustomView{.recipeId = state.recipeId, + .pageNo = state.pageNo, + .ingredients = std::get<0>(ingredientsAndRecipeName), + .recipeName = std::get<1>(ingredientsAndRecipeName)}); return; } } diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 233faace..b858923d 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -58,10 +58,12 @@ void handleCustomRecipeIngredientsSearchCQ( const auto chatId = cq.message->chat->id; if (cq.data == "back") { - renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); + auto ingredientsAndName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); auto ingredients = state.recipeIngredients.getValues() | as_rvalue | to(); - stateManager.put( - RecipeCustomView{.recipeId = state.recipeId, .pageNo = 0, .ingredients = std::move(ingredients)}); + stateManager.put(RecipeCustomView{.recipeId = state.recipeId, + .pageNo = 0, + .ingredients = std::move(ingredients), + .recipeName = std::get<1>(ingredientsAndName)}); return; } diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index 5a2dcf38..aa09d210 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -43,8 +43,8 @@ void handleCustomRecipesListCQ( auto ingredientsAndName = renderCustomRecipe(true, userId, chatId, recipeId.value(), bot, api); stateManager.put(RecipeCustomView{.recipeId = recipeId.value(), .pageNo = state.pageNo, - .ingredients = ingredientsAndName.ingredients, - .recipeName = ingredientsAndName.recipeName}); + .ingredients = std::get<0>(ingredientsAndName), + .recipeName = std::get<1>(ingredientsAndName)}); } bot.answerCallbackQuery(cq.id); return; diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp index 002c9d95..de2a4274 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -5,38 +5,85 @@ #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" +#include "utils/to_string.hpp" #include "utils/utils.hpp" +#include +#include #include -#include #include namespace cookcookhnya::render::personal_account::publication_history { using namespace std::views; +namespace { +std::string convertTimeToStrFormat(std::chrono::system_clock::time_point time) { + const auto* moscow_tz = std::chrono::locate_zone("Europe/Moscow"); + auto moscow_time = std::chrono::zoned_time(moscow_tz, time); + return std::format("{:%d-%m-%Y %H:%M}", moscow_time.get_local_time()); +} +} // namespace + void renderPublicationHistory(UserId userId, ChatId chatId, api::RecipeId recipeId, - std::string recipeName, + std::string& recipeName, BotRef bot, RecipesApiRef recipesApi) { // auto history = recipesApi.getModerationHistory(userId, recipeId); - std::vector history = {{ - .created = "52 сентября", - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::Rejected, - }}; + std::vector history = { + { + .created = std::chrono::system_clock::now(), + .status = api::models::recipe::PublicationRequestStatus::Pending, + }, + { + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::Rejected, + }, + { + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::Rejected, + }, + { + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::Rejected, + }, + { + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::Rejected, + }, + { + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::Rejected, + }, + { + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::Rejected, + }}; InlineKeyboardBuilder keyboard{2}; // confirm and back - std::ostringstream toPrint; - toPrint << (utils::utf8str(u8"История запросов на публикацию ") + recipeName + "\n"); + std::string toPrint; + toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n"); + + toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[0].status) + + (history[0].reason.has_value() ? std::format(" по причине {}", history[0].reason.value()) : " ") + + convertTimeToStrFormat(history[0].created) + "\n\n"; + // Remove the lastest history instance as it's showed differently + history.erase(history.begin()); + const std::vector prefixes = { - utils::utf8str(u8"ℹ️ Текущий статус"), utils::utf8str(u8"по причине"), utils::utf8str(u8"Запрос создан:")}; + utils::utf8str(u8"Статус"), utils::utf8str(u8"по причине"), utils::utf8str(u8"запрос создан")}; - // What if i forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and interate - // through it but it would require "for" and i don't know what is it) - // reverse to print lastest request the last in list + // What if you forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and + // interate through it but it would require "for" and i don't know what is it) reverse to print lastest request the + // last in list auto res = history | reverse | transform([&prefixes](const api::models::recipe::CustomRecipePublication& req) { //!!!!IMPORTANT See that tie to match prefixes!!!! auto fields = std::tie(req.status, req.reason, req.created); @@ -44,14 +91,16 @@ void renderPublicationHistory(UserId userId, switch (idx) { case 0: // statusStr to convert enum to string - return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)); + return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)) + " "; case 1: if (std::get<1>(fields).has_value()) { - return prefixes[1] + ": " + std::get<1>(fields).value(); + return prefixes[1] + ": " + std::get<1>(fields).value() + " "; } - case 2: - return prefixes[2] + ": " + std::get<2>(fields); - return ""; + return ", "; + // Need to work with chrono + case 2: { + return prefixes[2] + ": " + convertTimeToStrFormat(std::get<2>(fields)) + "\n\n"; + } default: return ""; } @@ -59,14 +108,14 @@ void renderPublicationHistory(UserId userId, }) | join; // Join here instead of in the loop - std::ranges::copy(res, std::ostream_iterator(toPrint, "----------------------\n")); + std::ranges::for_each(res, [&toPrint](const std::string& s) { toPrint += s; }); // Text is created moving to the markup keyboard << makeCallbackButton(u8"▶️Подтвердить", "confirm") << NewRow{}; keyboard << makeCallbackButton(u8"↩️ Назад", "back"); if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText(toPrint.str(), chatId, *messageId, std::move(keyboard)); + bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); } } } // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/recipe/publication_history.hpp b/src/render/personal_account/recipe/publication_history.hpp index ebae6fe5..3973858a 100644 --- a/src/render/personal_account/recipe/publication_history.hpp +++ b/src/render/personal_account/recipe/publication_history.hpp @@ -5,7 +5,11 @@ namespace cookcookhnya::render::personal_account::publication_history { -void renderPublicationHistory( - UserId userId, ChatId chatId, api::RecipeId recipeId, std::string recipeName, BotRef bot, RecipesApiRef recipesApi); +void renderPublicationHistory(UserId userId, + ChatId chatId, + api::RecipeId recipeId, + std::string& recipeName, + BotRef bot, + RecipesApiRef recipesApi); } // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 0b099f69..a7d92ec4 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -10,17 +10,18 @@ #include #include #include +#include #include #include namespace cookcookhnya::render::personal_account::recipes { -IngredientsAndRecipeName renderCustomRecipe( +std::tuple, std::string> renderCustomRecipe( bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { auto recipeDetails = recipesApi.get(userId, recipeId); // REMOVE WHEN BACKEND IS READY - recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::Idle; + recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::Accepted; std::vector ingredients; @@ -37,7 +38,7 @@ IngredientsAndRecipeName renderCustomRecipe( }); } - toPrint += "\n🌐 [Статус проверки](" + utils::to_string(recipeDetails.moderationStatus.value()) + ")"; + toPrint += "\n🌐 [Статус проверки] " + utils::to_string(recipeDetails.moderationStatus.value()); keyboard << makeCallbackButton(u8"🚮 Удалить", "delete") << NewRow{}; keyboard << makeCallbackButton(u8"✏️ Редактировать", "change") << NewRow{}; @@ -52,11 +53,11 @@ IngredientsAndRecipeName renderCustomRecipe( if (toBeEdited) { auto messageId = message::getMessageId(userId); if (messageId) - bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard)); + bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); } else { auto message = bot.sendMessage(chatId, toPrint, std::move(keyboard)); message::addMessageId(userId, message->messageId); } - return {.ingredients = ingredients, .recipeName = recipeDetails.name}; + return {ingredients, recipeDetails.name}; } } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/render/personal_account/recipe/view.hpp b/src/render/personal_account/recipe/view.hpp index 64b434ed..aaaddcc6 100644 --- a/src/render/personal_account/recipe/view.hpp +++ b/src/render/personal_account/recipe/view.hpp @@ -7,12 +7,7 @@ namespace cookcookhnya::render::personal_account::recipes { -struct IngredientsAndRecipeName { - std::vector ingredients; - std::string recipeName; -}; - -IngredientsAndRecipeName renderCustomRecipe( +std::tuple, std::string> renderCustomRecipe( bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi); } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 30a6eaa8..5373ad44 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -33,7 +33,8 @@ InlineKeyboard constructNavigationsMarkup(std::size_t offset, const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - const bool lastPage = amountOfRecipes - numOfRecipesOnPage * (pageNo + 1) <= 0; + const bool lastPage = + static_cast(amountOfRecipes) - static_cast(numOfRecipesOnPage) * (static_cast(pageNo) + 1) <= 0; if (offset + recipesToShow >= fullKeyBoardSize) { InlineKeyboard error(0); @@ -47,7 +48,7 @@ InlineKeyboard constructNavigationsMarkup(std::size_t offset, // Print on button in form "1. {Recipe}" keyboard[i + offset].push_back(makeCallbackButton( std::format("{}. {}", 1 + counter + ((pageNo)*numOfRecipesOnPage), recipesList.page[counter].name), - std::format("r", recipesList.page[counter].id))); // RECIPE ID + std::format("r{}", recipesList.page[counter].id))); // RECIPE ID counter++; } if (pageNo == 0 && lastPage) { @@ -102,9 +103,9 @@ constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::R const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); InlineKeyboard keyboard = - recipesList.found == 0 ? constructOnlyCreate() - : constructNavigationsMarkup( - offset, numOfRows + recipesToShow, (pageNo + 1), numOfRecipesOnPage, recipesList); + recipesList.found == 0 + ? constructOnlyCreate() + : constructNavigationsMarkup(offset, numOfRows + recipesToShow, pageNo, numOfRecipesOnPage, recipesList); if (keyboard.empty()) { // If error happened return keyboard; } diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 3c20a6f7..669e2f22 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -4,4 +4,5 @@ target_sources(main PRIVATE src/utils/utils.cpp src/utils/uuid.cpp src/utils/ingredients_availability.cpp + src/utils/serialization.cpp ) diff --git a/src/utils/serialization.cpp b/src/utils/serialization.cpp new file mode 100644 index 00000000..18d0038d --- /dev/null +++ b/src/utils/serialization.cpp @@ -0,0 +1,14 @@ +#include "serialization.hpp" +#include +#include + +namespace cookcookhnya::utils { + +std::chrono::system_clock::time_point parseIsoTime(std::string s) { + std::istringstream ss{std::move(s)}; + std::chrono::system_clock::time_point tp; + ss >> std::chrono::parse("%FT%T%z", tp); + return ss.fail() ? throw std::runtime_error("Could not parse datetime") : tp; +} + +} // namespace cookcookhnya::utils diff --git a/src/utils/serialization.hpp b/src/utils/serialization.hpp new file mode 100644 index 00000000..72bdf417 --- /dev/null +++ b/src/utils/serialization.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace cookcookhnya::utils { + +std::chrono::system_clock::time_point parseIsoTime(std::string s); + +} // namespace cookcookhnya::utils From c439dc23b7276ae7fd433b4a8eb324a06d6a8c8b Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sat, 19 Jul 2025 20:13:31 +0300 Subject: [PATCH 022/106] feat: moder status for custom ingredients --- src/backend/models/ingredient.hpp | 3 ++- .../ingredients_list/create.cpp | 12 ++++----- .../ingredients_list/publish.cpp | 4 +-- .../ingredients_list/view.cpp | 8 +++--- src/handlers/personal_account/view.cpp | 2 +- src/render/common.hpp | 1 + .../ingredients_list/view.cpp | 25 +++++++++++-------- src/states.hpp | 13 +++++++--- 8 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index 76a59244..fac9252c 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -16,7 +16,8 @@ namespace cookcookhnya::api::models::ingredient { struct Ingredient { IngredientId id; std::string name; - std::optional status = std::nullopt; + std::optional status = status::PublicationRequestStatus::Idle; + // TODO: change to nullopt when back is ready friend Ingredient tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 19d77356..20c1fe15 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -12,7 +12,7 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; -void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& /*unused*/, +void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& state, MessageRef m, BotRef& bot, SMRef stateManager, @@ -27,10 +27,10 @@ void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterNam bot.editMessageText(text, chatId, *messageId); } renderCustomIngredientConfirmation(name, userId, chatId, bot, api); - stateManager.put(CustomIngredientConfirmation{name}); + stateManager.put(CustomIngredientConfirmation{.pageNo = state.pageNo, .name = name}); } -void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName& /*unused*/, +void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, @@ -40,7 +40,7 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName auto chatId = cq.message->chat->id; if (cq.data == "back") { renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{}); + stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); } } @@ -53,11 +53,11 @@ void handleCustomIngredientConfirmationCQ( if (cq.data == "confirm") { api.createCustom(userId, api::models::ingredient::IngredientCreateBody{name}); renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{}); + stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); } if (cq.data == "back") { renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{}); + stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); } } diff --git a/src/handlers/personal_account/ingredients_list/publish.cpp b/src/handlers/personal_account/ingredients_list/publish.cpp index ab78f5ab..8377193a 100644 --- a/src/handlers/personal_account/ingredients_list/publish.cpp +++ b/src/handlers/personal_account/ingredients_list/publish.cpp @@ -11,7 +11,7 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; void handleCustomIngredientPublishCQ( - CustomIngredientPublish& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { + CustomIngredientPublish& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -21,6 +21,6 @@ void handleCustomIngredientPublishCQ( api.publishCustom(userId, *ingredientId); } renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{}); + stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); } } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/view.cpp b/src/handlers/personal_account/ingredients_list/view.cpp index f9d755f9..52c08711 100644 --- a/src/handlers/personal_account/ingredients_list/view.cpp +++ b/src/handlers/personal_account/ingredients_list/view.cpp @@ -16,18 +16,18 @@ using namespace render::personal_account::ingredients; using namespace render::personal_account; void handleCustomIngredientsListCQ( - CustomIngredientsList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + CustomIngredientsList& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; if (cq.data == "create") { renderCustomIngredientCreation(userId, chatId, bot); - stateManager.put(CustomIngredientCreationEnterName{}); + stateManager.put(CustomIngredientCreationEnterName{.pageNo = state.pageNo}); return; } if (cq.data == "publish") { renderCustomIngredientPublication(userId, chatId, bot, api); - stateManager.put(CustomIngredientPublish{}); + stateManager.put(CustomIngredientPublish{.pageNo = state.pageNo}); return; } if (cq.data == "back") { @@ -38,7 +38,7 @@ void handleCustomIngredientsListCQ( auto pageNo = utils::parseSafe(cq.data); if (pageNo) { renderCustomIngredientsList(true, *pageNo, userId, chatId, bot, api); - stateManager.put(CustomIngredientCreationEnterName{}); + stateManager.put(CustomIngredientsList{.pageNo = *pageNo}); return; } } diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 65358cc6..82e7d664 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -31,7 +31,7 @@ void handlePersonalAccountMenuCQ( } if (data == "ingredients") { renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{}); + stateManager.put(CustomIngredientsList{.pageNo = 0}); return; } } diff --git a/src/render/common.hpp b/src/render/common.hpp index 1fdb5bee..ad35bc59 100644 --- a/src/render/common.hpp +++ b/src/render/common.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index 9fc0d253..ba558b35 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -23,29 +23,30 @@ namespace { std::pair> constructNavigationMessage( std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::IngredientList& ingredientsList) { const size_t amountOfRecipes = ingredientsList.found; - const std::size_t maxPageNum = - std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); + const std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); std::string text; text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - text += std::format("• _{}_ {}", utils::to_string(*ing.status), ing.name); + if (ing.status){ + text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); + } } std::vector buttons; if (pageNo == maxPageNum) { buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); - buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "")); - buttons.push_back(makeCallbackButton(u8"ㅤ", "")); + buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); + buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); } - if (pageNo == 0) { - buttons.push_back(makeCallbackButton(u8"ㅤ", "")); - buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "")); + else if (pageNo == 0) { + buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); + buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); } else { buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); - buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "")); + buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); } return std::make_pair(text, buttons); @@ -71,7 +72,9 @@ std::pair constructMessage(size_t pageNo, } else if (ingredientsList.found <= numOfIngredientsOnPage) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - text += std::format("• _{}_ {}", utils::to_string(*ing.status), ing.name); + if (ing.status){ + text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); + } } keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); @@ -81,7 +84,7 @@ std::pair constructMessage(size_t pageNo, text = message.first; keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); - keyboard[2].reserve(2); + keyboard[2].reserve(3); for (const auto& navigButton : message.second) { keyboard[2].push_back(navigButton); } diff --git a/src/states.hpp b/src/states.hpp index b45e7cd1..7df1f603 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -33,12 +33,19 @@ struct MainMenu {}; struct PersonalAccountMenu {}; -struct CustomIngredientsList {}; -struct CustomIngredientCreationEnterName {}; +struct CustomIngredientsList { + std::size_t pageNo; +}; +struct CustomIngredientCreationEnterName { + std::size_t pageNo; +}; struct CustomIngredientConfirmation { + std::size_t pageNo; std::string name; }; -struct CustomIngredientPublish {}; +struct CustomIngredientPublish { + std::size_t pageNo; +}; struct StorageList {}; struct StorageCreationEnterName {}; From 8fd2c0dc0bc56efb6169f7280b094bce8309ff1f Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sat, 19 Jul 2025 21:59:36 +0300 Subject: [PATCH 023/106] refactor: states compression --- src/handlers/main_menu/view.cpp | 2 +- src/handlers/recipe/add_storage.cpp | 54 +++++++++++------------ src/handlers/recipe/view.cpp | 41 ++++++++--------- src/handlers/recipes_suggestions/view.cpp | 19 ++++---- src/handlers/shopping_list/create.cpp | 26 +++++------ src/handlers/storage/view.cpp | 2 +- src/handlers/storages_selection/view.cpp | 2 +- src/render/recipe/add_storage.cpp | 6 +-- src/render/recipe/view.cpp | 4 +- src/states.hpp | 44 +++++++----------- src/utils/fast_sorted_db.hpp | 6 +-- src/utils/ingredients_availability.cpp | 10 ++--- src/utils/ingredients_availability.hpp | 4 +- src/utils/utils.hpp | 5 +++ 14 files changed, 104 insertions(+), 121 deletions(-) diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index 7ba6330f..8f225492 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -38,7 +38,7 @@ void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SM if (storages.size() == 1) { std::vector storage = {storages[0]}; renderRecipesSuggestion(storage, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storage, .fromStorage = false}); + stateManager.put(SuggestedRecipesList{.selectedStorages = storage, .pageNo = 0, .fromStorage = false}); return; } renderStorageSelection({}, userId, chatId, bot, api); diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp index 299f3f43..e86a5864 100644 --- a/src/handlers/recipe/add_storage.cpp +++ b/src/handlers/recipe/add_storage.cpp @@ -10,6 +10,8 @@ #include "utils/parsing.hpp" #include +#include +#include namespace cookcookhnya::handlers::recipe { @@ -18,58 +20,52 @@ using namespace render::recipe; void handleRecipeStorageAdditionCQ( RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); - std::string data = cq.data; + const std::string& data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; + if (data == "back") { + renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); + return; + } + if (data[0] == '-') { - auto newStorageIdStr = data.substr(1, data.size()); - auto newStorageId = utils::parseSafe(newStorageIdStr); + auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; - state.addedStorages.push_back(newStorage); - utils::addStorage(state.availability, newStorage); - renderStoragesSuggestion(state.availability, - state.selectedStorages, - state.addedStorages, - state.recipeId, + state.prevState.addedStorages.push_back(newStorage); + utils::addStorage(state.prevState.availability, newStorage); + renderStoragesSuggestion(state.prevState.availability, + state.prevState.prevState.selectedStorages, + state.prevState.addedStorages, + state.prevState.recipeId, userId, chatId, bot, api); } } + if (data[0] == '+') { - auto newStorageIdStr = data.substr(1, data.size()); - auto newStorageId = utils::parseSafe(newStorageIdStr); + auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; - state.addedStorages.erase( - std::ranges::find(state.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); - utils::deleteStorage(state.availability, newStorage); - renderStoragesSuggestion(state.availability, - state.selectedStorages, - state.addedStorages, - state.recipeId, + state.prevState.addedStorages.erase(std::ranges::find( + state.prevState.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); + utils::deleteStorage(state.prevState.availability, newStorage); + renderStoragesSuggestion(state.prevState.availability, + state.prevState.prevState.selectedStorages, + state.prevState.addedStorages, + state.prevState.recipeId, userId, chatId, bot, api); } } - - if (data == "back") { - renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); - return; - } } } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index bd94a513..9876c21c 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -8,6 +8,7 @@ #include "render/shopping_list/create.hpp" #include +#include #include namespace cookcookhnya::handlers::recipe { @@ -20,48 +21,48 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; + if (data == "start_cooking") { // TODO: add state of begginig of cooking return; } + if (data == "shopping_list") { std::vector selectedIngredients; std::vector allIngredients; for (const auto& infoPair : state.availability) { - if (infoPair.second.available == utils::AvailabiltiyType::not_available) { + if (infoPair.second.available == utils::AvailabiltiyType::NOT_AVAILABLE) { selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); } allIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); } renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); - stateManager.put(ShoppingListCreation{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .selectedIngredients = selectedIngredients, - .allIngredients = allIngredients, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + stateManager.put(ShoppingListCreation{ + .prevState = std::move(state), + .selectedIngredients = selectedIngredients, + .allIngredients = allIngredients, + }); bot.answerCallbackQuery(cq.id); return; } + if (data == "back_from_recipe_view") { - renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{ - .pageNo = state.pageNo, .selectedStorages = state.selectedStorages, .fromStorage = state.fromStorage}); + renderRecipesSuggestion(state.prevState.selectedStorages, state.prevState.pageNo, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; } if (data == "add_storages") { - renderStoragesSuggestion( - state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeStorageAddition{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + renderStoragesSuggestion(state.availability, + state.prevState.selectedStorages, + state.addedStorages, + state.recipeId, + userId, + chatId, + bot, + api); + stateManager.put(RecipeStorageAddition{.prevState = std::move(state)}); return; } } diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index 196b55ea..25a5c5d7 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -53,22 +53,21 @@ void handleSuggestedRecipesListCQ( return; auto inStorage = utils::inStoragesAvailability(state.selectedStorages, *recipeId, userId, api); renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = {}, - .availability = inStorage, - .recipeId = *recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); - + stateManager.put(RecipeView{ + .prevState = std::move(state), + .addedStorages = {}, + .availability = inStorage, + .recipeId = *recipeId, + }); return; } if (data != "dont_handle") { auto pageNo = utils::parseSafe(data); - if (pageNo) { + if (pageNo) state.pageNo = *pageNo; - } - renderRecipesSuggestion(state.selectedStorages, *pageNo, userId, chatId, bot, api); + + renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); return; } } diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 843ffc04..0d0ed0d6 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace cookcookhnya::handlers::shopping_list { @@ -17,21 +18,17 @@ using namespace render::recipe; void handleShoppingListCreationCQ( ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { - std::string data = cq.data; + const std::string& data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; if (data == "back") { - renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; } + if (data == "confirm") { auto shoppingApi = api.getShoppingListApi(); std::vector putIds; @@ -40,16 +37,12 @@ void handleShoppingListCreationCQ( putIds.push_back(ingredient.id); } shoppingApi.put(userId, putIds); - renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; } + if (data[0] == '+') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); @@ -58,7 +51,9 @@ void handleShoppingListCreationCQ( state.selectedIngredients.push_back(ingredient); } renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); + return; } + if (data[0] == '-') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); @@ -67,6 +62,7 @@ void handleShoppingListCreationCQ( state.selectedIngredients, *newIngredientId, &api::models::ingredient::Ingredient::id)); } renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); + return; } } } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 4dc2b052..c8d1cb32 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -45,7 +45,7 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM api::models::storage::StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; std::vector storages = {storage}; renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storages, .fromStorage = true}); + stateManager.put(SuggestedRecipesList{.selectedStorages = storages, .pageNo = 0, .fromStorage = true}); return; } else if (cq.data == "delete") { renderStorageDeletion(state.storageId, chatId, bot, cq.from->id, api); diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 20d9daaf..256e40b8 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -28,7 +28,7 @@ void handleStoragesSelectionCQ( if (cq.data == "confirm") { renderRecipesSuggestion(state.selectedStorages, 0, userId, chatId, bot, api); stateManager.put(SuggestedRecipesList{ - .pageNo = 0, .selectedStorages = std::move(state.selectedStorages), .fromStorage = false}); + .selectedStorages = std::move(state.selectedStorages), .pageNo = 0, .fromStorage = false}); return; } diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index 5ed05461..164e897b 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -37,9 +37,9 @@ textGenInfo storageAdditionView( text += std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.second.available == utils::AvailabiltiyType::available) { + if (infoPair.second.available == utils::AvailabiltiyType::AVAILABLE) { text += "`[+]` " + infoPair.first.name + "\n"; - } else if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { + } else if (infoPair.second.available == utils::AvailabiltiyType::OTHER_STORAGES) { text += "`[?]` " + infoPair.first.name + "\n"; isIngredientIsOtherStorages = true; text += "Доступно в: "; @@ -73,7 +73,7 @@ void renderStoragesSuggestion( auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); std::vector storages; for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { + if (infoPair.second.available == utils::AvailabiltiyType::OTHER_STORAGES) { for (const auto& storage : infoPair.second.storages) { if (std::ranges::find(storages, storage.id, &api::models::storage::StorageSummary::id) == storages.end()) { diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index 71df1ada..4b2fc444 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -28,9 +28,9 @@ recipeView(const std::vector #include -#include #include -#include #include #include #include @@ -58,8 +57,7 @@ struct StorageIngredientsList : detail::StorageIdMixin { std::vector searchItems; std::string inlineQuery; - template - requires std::convertible_to, IngredientsDb::mapped_type> + template R> StorageIngredientsList(api::StorageId storageId, R&& ingredients, std::string iq) : StorageIdMixin{storageId}, storageIngredients{std::forward(ingredients)}, inlineQuery(std::move(iq)) {} }; @@ -68,28 +66,26 @@ struct StoragesSelection { std::vector selectedStorages; }; struct SuggestedRecipesList { - std::size_t pageNo; std::vector selectedStorages; + std::size_t pageNo; bool fromStorage; }; struct RecipeView { - std::vector selectedStorages; + SuggestedRecipesList prevState; std::vector addedStorages; std::vector> availability; api::RecipeId recipeId; - bool fromStorage; - std::size_t pageNo; }; struct RecipeStorageAddition { - std::vector selectedStorages; - std::vector addedStorages; - std::vector> - availability; - api::RecipeId recipeId; - bool fromStorage; - std::size_t pageNo; + RecipeView prevState; +}; + +struct ShoppingListCreation { + RecipeView prevState; + std::vector selectedIngredients; + std::vector allIngredients; }; struct CustomRecipesList { @@ -106,8 +102,7 @@ struct CustomRecipeIngredientsSearch { std::size_t pageNo = 0; std::vector searchItems; - template - requires std::convertible_to, IngredientsDb::mapped_type> + template R> CustomRecipeIngredientsSearch(api::RecipeId recipeId, R&& ingredients, std::string inlineQuery) : recipeId(recipeId), recipeIngredients{std::forward(ingredients)}, query(std::move(inlineQuery)) {} }; @@ -123,18 +118,6 @@ struct CreateCustomRecipe { std::size_t pageNo; }; -struct ShoppingListCreation { - std::vector selectedStorages; - std::vector addedStorages; - std::vector> - availability; - api::RecipeId recipeId; - std::vector selectedIngredients; - std::vector allIngredients; - bool fromStorage; - std::size_t pageNo; -}; - struct RecipeIngredientsSearch { api::RecipeId recipeId; std::size_t pageNo; @@ -154,6 +137,9 @@ struct ShoppingListStorageSelectionToBuy { std::vector selectedIngredients; std::vector storages; }; +struct ShoppingListIngredientSearch { + ShoppingListView prevState; +}; using State = std::variant +#include "utils/utils.hpp" + #include #include #include @@ -26,8 +27,7 @@ class FastSortedDb { public: FastSortedDb() = default; - template - requires std::convertible_to, T> + template R> FastSortedDb(R&& items) { // NOLINT(*explicit*) for (auto&& item : std::ranges::views::all(std::forward(items))) put(std::forward(item)); diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index 7e7f5edb..c2e70785 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -50,13 +50,13 @@ inStoragesAvailability(std::vector& selectedSto } if (hasInSelected) { - availability.available = AvailabiltiyType::available; + availability.available = AvailabiltiyType::AVAILABLE; availability.storages = std::move(storages); } else if (hasInOther) { - availability.available = AvailabiltiyType::other_storages; + availability.available = AvailabiltiyType::OTHER_STORAGES; availability.storages = std::move(storages); } else { - availability.available = AvailabiltiyType::not_available; + availability.available = AvailabiltiyType::NOT_AVAILABLE; } result.emplace_back(ingredient, std::move(availability)); @@ -71,7 +71,7 @@ void addStorage(std::vector storages; }; diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index 25157add..15233d3d 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include #include @@ -17,4 +19,7 @@ std::shared_ptr make_shared(T&& t) { return std::make_shared>(std::forward(t)); } +template +concept range_of = std::ranges::range && std::convertible_to, ValueType>; + } // namespace cookcookhnya::utils From fb62957a1cc03bc7919e959620a40f4d6d7ed546 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Sat, 19 Jul 2025 22:36:43 +0300 Subject: [PATCH 024/106] fix: shopping list creation bug --- Makefile | 2 +- src/backend/models/recipe.cpp | 2 + src/backend/models/recipe.hpp | 3 +- src/handlers/personal_account/recipe/view.cpp | 12 ++- src/handlers/shopping_list/create.cpp | 4 +- .../recipe/publication_history.cpp | 81 ++++++++++++------- .../recipe/publication_history.hpp | 1 + src/render/personal_account/recipe/view.cpp | 4 +- 8 files changed, 76 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 360f7be0..d1bc165d 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 -- -j5 build-release: build/Release/CMakeCache.txt cmake --build --preset=conan-release diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 4e33d5f7..6e57983c 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -83,6 +83,8 @@ CustomRecipePublication tag_invoke(json::value_to_tag / ? value_to(j.at("reason")) : "", .status = value_to(j.at("status")), + .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) + : std::chrono::time_point(), }; } diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 5e1e9997..b063a31a 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -95,7 +95,8 @@ struct RecipeSearchResponse { struct CustomRecipePublication { std::chrono::system_clock::time_point created; std::optional reason; - PublicationRequestStatus status; + PublicationRequestStatus status{}; + std::optional updated; friend CustomRecipePublication tag_invoke(boost::json::value_to_tag, const boost::json::value& j); diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index 3dd74db9..5f1ab549 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -51,7 +51,17 @@ void handleRecipeCustomViewCQ( } if (data == "publish") { - renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, bot, api); + // Not peeking (if button with this data then idle or rejected) + bool isPeeking = false; + renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); + stateManager.put(states::CustomRecipePublicationHistory{.recipeId = state.recipeId, .pageNo = state.pageNo}); + bot.answerCallbackQuery(cq.id); + return; + } + if (data == "peekpublish") { + // Peeking (if button with this data then accepted or pending) + bool isPeeking = true; + renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); stateManager.put(states::CustomRecipePublicationHistory{.recipeId = state.recipeId, .pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); return; diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 843ffc04..692c10d7 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -50,7 +50,7 @@ void handleShoppingListCreationCQ( bot.answerCallbackQuery(cq.id); return; } - if (data[0] == '+') { + if (data[0] == '-') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); if (newIngredientId) { @@ -59,7 +59,7 @@ void handleShoppingListCreationCQ( } renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); } - if (data[0] == '-') { + if (data[0] == '+') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); if (newIngredientId) { diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp index de2a4274..ca658d05 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -1,6 +1,5 @@ #include "publication_history.hpp" -#include "backend/api/recipes.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "message_tracker.hpp" @@ -29,6 +28,7 @@ void renderPublicationHistory(UserId userId, ChatId chatId, api::RecipeId recipeId, std::string& recipeName, + bool isPeek, BotRef bot, RecipesApiRef recipesApi) { // auto history = recipesApi.getModerationHistory(userId, recipeId); @@ -41,6 +41,7 @@ void renderPublicationHistory(UserId userId, .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", .status = api::models::recipe::PublicationRequestStatus::Rejected, + .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), @@ -51,21 +52,25 @@ void renderPublicationHistory(UserId userId, .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", .status = api::models::recipe::PublicationRequestStatus::Rejected, + .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", .status = api::models::recipe::PublicationRequestStatus::Rejected, + .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", .status = api::models::recipe::PublicationRequestStatus::Rejected, + .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", .status = api::models::recipe::PublicationRequestStatus::Rejected, + .updated = std::chrono::system_clock::now(), }}; InlineKeyboardBuilder keyboard{2}; // confirm and back @@ -78,40 +83,50 @@ void renderPublicationHistory(UserId userId, // Remove the lastest history instance as it's showed differently history.erase(history.begin()); - const std::vector prefixes = { - utils::utf8str(u8"Статус"), utils::utf8str(u8"по причине"), utils::utf8str(u8"запрос создан")}; + const std::vector prefixes = {utils::utf8str(u8"Статус"), + utils::utf8str(u8"по причине"), + utils::utf8str(u8"запрос создан"), + utils::utf8str(u8"последенее обновление")}; // What if you forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and // interate through it but it would require "for" and i don't know what is it) reverse to print lastest request the // last in list - auto res = history | reverse | transform([&prefixes](const api::models::recipe::CustomRecipePublication& req) { - //!!!!IMPORTANT See that tie to match prefixes!!!! - auto fields = std::tie(req.status, req.reason, req.created); - return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { - switch (idx) { - case 0: - // statusStr to convert enum to string - return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)) + " "; - case 1: - if (std::get<1>(fields).has_value()) { - return prefixes[1] + ": " + std::get<1>(fields).value() + " "; - } - return ", "; - // Need to work with chrono - case 2: { - return prefixes[2] + ": " + convertTimeToStrFormat(std::get<2>(fields)) + "\n\n"; - } - default: - return ""; - } - }); - }) | - join; // Join here instead of in the loop + auto res = + history | reverse | transform([&prefixes](const api::models::recipe::CustomRecipePublication& req) { + //!!!!IMPORTANT See that tie to match prefixes!!!! + auto fields = std::tie(req.status, req.reason, req.created, req.updated); + return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { + switch (idx) { + case 0: + // statusStr to convert enum to string + return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)) + " "; + case 1: + if (std::get<1>(fields).has_value()) { + return prefixes[1] + ": " + std::get<1>(fields).value() + " "; + } + return ", "; + // Need to work with chrono + case 2: + return prefixes[2] + ": " + convertTimeToStrFormat(std::get<2>(fields)) + ", "; + case 3: + if (std::get<3>(fields).has_value()) { + return prefixes[3] + ": " + convertTimeToStrFormat(std::get<3>(fields).value()) + "\n\n"; + } + return "\n\n"; + default: + return ""; + } + }); + }) | + join; // Join here instead of in the loop std::ranges::for_each(res, [&toPrint](const std::string& s) { toPrint += s; }); // Text is created moving to the markup - keyboard << makeCallbackButton(u8"▶️Подтвердить", "confirm") << NewRow{}; + // If not peeking then show accept button + if (!isPeek) { + keyboard << makeCallbackButton(u8"▶️Подтвердить", "confirm") << NewRow{}; + } keyboard << makeCallbackButton(u8"↩️ Назад", "back"); if (auto messageId = message::getMessageId(userId)) { @@ -119,3 +134,15 @@ void renderPublicationHistory(UserId userId, } } } // namespace cookcookhnya::render::personal_account::publication_history + +// Uncomment in case of EMERGENCY (or if you know what for is) +// use instead of history | reverse | ... +/* +for (auto& req : history) { + toPrint += std::format("Статус: {} ", utils::to_string(req.status)); + if (req.reason.has_value()) + toPrint += std::format("по причине: {} ", req.reason.value()); + toPrint += std::format("запрос создан: {}\n", convertTimeToStrFormat(req.created)); + toPrint += '\n'; + } +*/ diff --git a/src/render/personal_account/recipe/publication_history.hpp b/src/render/personal_account/recipe/publication_history.hpp index 3973858a..ac4c35a6 100644 --- a/src/render/personal_account/recipe/publication_history.hpp +++ b/src/render/personal_account/recipe/publication_history.hpp @@ -9,6 +9,7 @@ void renderPublicationHistory(UserId userId, ChatId chatId, api::RecipeId recipeId, std::string& recipeName, + bool isPeek, BotRef bot, RecipesApiRef recipesApi); diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index a7d92ec4..2000df14 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -21,7 +21,7 @@ std::tuple, std::string> render auto recipeDetails = recipesApi.get(userId, recipeId); // REMOVE WHEN BACKEND IS READY - recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::Accepted; + recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::Idle; std::vector ingredients; @@ -46,6 +46,8 @@ std::tuple, std::string> render if (recipeDetails.moderationStatus == api::models::recipe::PublicationRequestStatus::Idle || recipeDetails.moderationStatus == api::models::recipe::PublicationRequestStatus::Rejected) { keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; + } else { + keyboard << makeCallbackButton(u8"📢 История публикаций", "peekpublish") << NewRow{}; } keyboard << makeCallbackButton(u8"↩️ Назад", "back"); From de5f9d0a2810dcecd5dc90a21e98627ddd84a1f7 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sat, 19 Jul 2025 23:42:59 +0300 Subject: [PATCH 025/106] feat: new commands --- src/backend/api/storages.cpp | 9 +++-- src/backend/api/storages.hpp | 3 +- src/handlers/CMakeLists.txt | 6 +++- src/handlers/commands/my_storages.cpp | 16 +++++++++ src/handlers/commands/my_storages.hpp | 9 +++++ src/handlers/commands/personal_account.cpp | 18 ++++++++++ src/handlers/commands/personal_account.hpp | 9 +++++ src/handlers/commands/shopping_list.cpp | 19 ++++++++++ src/handlers/commands/shopping_list.hpp | 9 +++++ src/handlers/{initial => commands}/start.cpp | 13 ++++--- src/handlers/{initial => commands}/start.hpp | 4 +-- src/handlers/commands/wanna_eat.cpp | 36 +++++++++++++++++++ src/handlers/commands/wanna_eat.hpp | 9 +++++ src/handlers/handlers_list.hpp | 22 +++++++++--- src/handlers/personal_account/view.cpp | 2 +- src/handlers/recipes_suggestions/view.cpp | 3 +- src/handlers/shopping_list/create.cpp | 4 +-- src/handlers/shopping_list/view.cpp | 4 +-- src/handlers/storages_list/view.cpp | 2 +- src/handlers/storages_selection/view.cpp | 2 +- src/main.cpp | 8 +++-- src/message_tracker.cpp | 4 +++ src/message_tracker.hpp | 2 ++ src/render/common.hpp | 1 - src/render/main_menu/view.cpp | 10 +++++- src/render/main_menu/view.hpp | 4 ++- .../personal_account/recipes_list/view.cpp | 6 +++- src/render/personal_account/view.cpp | 3 ++ src/render/recipes_suggestions/view.cpp | 10 ++++-- src/render/shopping_list/view.cpp | 6 ++-- src/render/storages_selection/view.cpp | 5 +++ 31 files changed, 224 insertions(+), 34 deletions(-) create mode 100644 src/handlers/commands/my_storages.cpp create mode 100644 src/handlers/commands/my_storages.hpp create mode 100644 src/handlers/commands/personal_account.cpp create mode 100644 src/handlers/commands/personal_account.hpp create mode 100644 src/handlers/commands/shopping_list.cpp create mode 100644 src/handlers/commands/shopping_list.hpp rename src/handlers/{initial => commands}/start.cpp (77%) rename src/handlers/{initial => commands}/start.hpp (65%) create mode 100644 src/handlers/commands/wanna_eat.cpp create mode 100644 src/handlers/commands/wanna_eat.hpp diff --git a/src/backend/api/storages.cpp b/src/backend/api/storages.cpp index cf9e1cb4..a8cfe535 100644 --- a/src/backend/api/storages.cpp +++ b/src/backend/api/storages.cpp @@ -6,6 +6,7 @@ #include "utils/parsing.hpp" #include +#include #include namespace cookcookhnya::api { @@ -54,8 +55,12 @@ InvitationId StoragesApi::inviteMember(UserId user, StorageId storage) const { } // POST /invitations/{invitationHash}/activate -void StoragesApi::activate(UserId user, InvitationId invitation) const { - postAuthed(user, std::format("/invitations/{}/activate", invitation)); +std::optional StoragesApi::activate(UserId user, InvitationId invitation) const { + try{ + return jsonPostAuthed(user, std::format("/invitations/{}/activate", invitation)); + } catch (...){ + return std::nullopt; + } } } // namespace cookcookhnya::api diff --git a/src/backend/api/storages.hpp b/src/backend/api/storages.hpp index 95f677d1..d77e65a2 100644 --- a/src/backend/api/storages.hpp +++ b/src/backend/api/storages.hpp @@ -7,6 +7,7 @@ #include +#include #include namespace cookcookhnya::api { @@ -26,7 +27,7 @@ class StoragesApi : ApiBase { void addMember(UserId user, StorageId storage, UserId member) const; void deleteMember(UserId user, StorageId storage, UserId member) const; [[nodiscard]] InvitationId inviteMember(UserId user, StorageId storage) const; - void activate(UserId user, InvitationId invitation) const; + [[nodiscard]] std::optional activate(UserId user, InvitationId invitation) const; }; } // namespace cookcookhnya::api diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 713d7b88..6b12b7f4 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -1,5 +1,9 @@ target_sources(main PRIVATE - src/handlers/initial/start.cpp + src/handlers/commands/start.cpp + src/handlers/commands/wanna_eat.cpp + src/handlers/commands/shopping_list.cpp + src/handlers/commands/personal_account.cpp + src/handlers/commands/my_storages.cpp src/handlers/main_menu/view.cpp diff --git a/src/handlers/commands/my_storages.cpp b/src/handlers/commands/my_storages.cpp new file mode 100644 index 00000000..efd08470 --- /dev/null +++ b/src/handlers/commands/my_storages.cpp @@ -0,0 +1,16 @@ +#include "my_storages.hpp" + +#include "handlers/common.hpp" +#include "render/storages_list/view.hpp" +#include "states.hpp" + +namespace cookcookhnya::handlers::commands { + +using namespace render::storages_list; + +void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { + renderStorageList(false, m.from->id, m.chat->id, bot, api); + stateManager.put(StorageList{}); +}; + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/my_storages.hpp b/src/handlers/commands/my_storages.hpp new file mode 100644 index 00000000..36402ad7 --- /dev/null +++ b/src/handlers/commands/my_storages.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::commands { + +void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/personal_account.cpp b/src/handlers/commands/personal_account.cpp new file mode 100644 index 00000000..38c1087c --- /dev/null +++ b/src/handlers/commands/personal_account.cpp @@ -0,0 +1,18 @@ +#include "personal_account.hpp" + +#include "handlers/common.hpp" +#include "message_tracker.hpp" +#include "render/personal_account/view.hpp" +#include "states.hpp" + +namespace cookcookhnya::handlers::commands { + +using namespace render::personal_account; + +void handlePersonalAccountCmd(MessageRef m, BotRef bot, SMRef stateManager) { + message::deleteMessageId(m.from->id); + renderPersonalAccountMenu(m.from->id, m.chat->id, bot); + stateManager.put(PersonalAccountMenu{}); +}; + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/personal_account.hpp b/src/handlers/commands/personal_account.hpp new file mode 100644 index 00000000..ac3861c0 --- /dev/null +++ b/src/handlers/commands/personal_account.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::commands { + +void handlePersonalAccountCmd(MessageRef m, BotRef bot, SMRef stateManager); + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/shopping_list.cpp b/src/handlers/commands/shopping_list.cpp new file mode 100644 index 00000000..cf435cae --- /dev/null +++ b/src/handlers/commands/shopping_list.cpp @@ -0,0 +1,19 @@ +#include "shopping_list.hpp" + +#include "handlers/common.hpp" +#include "message_tracker.hpp" +#include "render/shopping_list/view.hpp" +#include "states.hpp" + +namespace cookcookhnya::handlers::commands { + +using namespace render::shopping_list; + +void handleShoppingListCmd(MessageRef m, BotRef bot, SMRef stateManager) { + auto newState = ShoppingListView{}; + message::deleteMessageId(m.from->id); + renderShoppingList(newState, m.from->id, m.chat->id, bot); + stateManager.put(newState); +}; + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/shopping_list.hpp b/src/handlers/commands/shopping_list.hpp new file mode 100644 index 00000000..cc5aae0a --- /dev/null +++ b/src/handlers/commands/shopping_list.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::commands { + +void handleShoppingListCmd(MessageRef m, BotRef bot, SMRef stateManager); + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/initial/start.cpp b/src/handlers/commands/start.cpp similarity index 77% rename from src/handlers/initial/start.cpp rename to src/handlers/commands/start.cpp index eb4ec88c..197a9472 100644 --- a/src/handlers/initial/start.cpp +++ b/src/handlers/commands/start.cpp @@ -11,15 +11,13 @@ #include #include -namespace cookcookhnya::handlers::initial { +namespace cookcookhnya::handlers::commands { using namespace render::main_menu; using namespace std::literals; void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { auto userId = m.from->id; - renderMainMenu(false, m.from->id, m.chat->id, bot, api); - stateManager.put(MainMenu{}); std::string fullName = m.from->firstName; if (!m.from->lastName.empty()) { fullName += ' '; @@ -37,8 +35,13 @@ void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef a const int hashPos = "/start "sv.size(); if (startText.size() > hashPos - 1) { auto hash = std::string(m.text).substr(hashPos); - api.getStoragesApi().activate(userId, hash); + auto storage = api.getStoragesApi().activate(userId, hash); + renderMainMenu(false, storage->name, m.from->id, m.chat->id, bot, api); + stateManager.put(MainMenu{}); + return; } + renderMainMenu(false, std::nullopt, m.from->id, m.chat->id, bot, api); + stateManager.put(MainMenu{}); }; void handleNoState(MessageRef m, BotRef bot) { @@ -47,4 +50,4 @@ void handleNoState(MessageRef m, BotRef bot) { bot.sendMessage(m.chat->id, "Use /start please"); }; -} // namespace cookcookhnya::handlers::initial +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/initial/start.hpp b/src/handlers/commands/start.hpp similarity index 65% rename from src/handlers/initial/start.hpp rename to src/handlers/commands/start.hpp index acde30a5..feb4539c 100644 --- a/src/handlers/initial/start.hpp +++ b/src/handlers/commands/start.hpp @@ -2,10 +2,10 @@ #include "handlers/common.hpp" -namespace cookcookhnya::handlers::initial { +namespace cookcookhnya::handlers::commands { void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); void handleNoState(MessageRef m, BotRef bot); -} // namespace cookcookhnya::handlers::initial +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/wanna_eat.cpp b/src/handlers/commands/wanna_eat.cpp new file mode 100644 index 00000000..81d53e83 --- /dev/null +++ b/src/handlers/commands/wanna_eat.cpp @@ -0,0 +1,36 @@ +#include "wanna_eat.hpp" + +#include "handlers/common.hpp" +#include "message_tracker.hpp" +#include "render/main_menu/view.hpp" +#include "render/recipes_suggestions/view.hpp" +#include "render/storages_selection/view.hpp" +#include "states.hpp" +#include "utils/utils.hpp" +#include + +namespace cookcookhnya::handlers::commands { + +using namespace render::select_storages; +using namespace render::main_menu; +using namespace render::recipes_suggestions; + +void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { + auto storages = api.getStoragesApi().getStoragesList(m.from->id); + if (storages.empty()){ + bot.sendMessage(m.chat->id, utils::utf8str(u8"😔 К сожалению, у вас пока что нет хранилищ.")); + renderMainMenu(false, std::nullopt, m.from->id, m.chat->id, bot, api); + stateManager.put(MainMenu{}); + } else if (storages.size() == 1) { + message::deleteMessageId(m.from->id); + renderRecipesSuggestion({storages}, 0, m.from->id, m.chat->id, bot, api); + stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storages, .fromStorage = false,}); + } else { + message::deleteMessageId(m.from->id); + auto newState = StoragesSelection{}; + renderStorageSelection(newState, m.from->id, m.chat->id, bot, api); + stateManager.put(newState); + } +}; + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/wanna_eat.hpp b/src/handlers/commands/wanna_eat.hpp new file mode 100644 index 00000000..398424eb --- /dev/null +++ b/src/handlers/commands/wanna_eat.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::commands { + +void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); + +} // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index f2cee479..0bbd50f9 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -1,7 +1,11 @@ #pragma once // Handler callbacks -#include "initial/start.hpp" +#include "commands/my_storages.hpp" +#include "commands/personal_account.hpp" +#include "commands/shopping_list.hpp" +#include "commands/start.hpp" +#include "commands/wanna_eat.hpp" #include "main_menu/view.hpp" @@ -46,7 +50,7 @@ namespace cookcookhnya::handlers { -using namespace initial; +using namespace commands; using namespace main_menu; using namespace personal_account; using namespace personal_account::ingredients; @@ -64,10 +68,18 @@ using namespace tg_stater; namespace bot_handlers { -// Init -constexpr char startCmd[] = "start"; // NOLINT(*c-arrays) -using startCmdHandler = Handler; // NOLINT(*decay) +// Commands +constexpr char startCmd[] = "start"; // NOLINT(*c-arrays) +constexpr char myStoragesCmd[] = "my_storages"; // NOLINT(*c-arrays) +constexpr char shoppingListCmd[] = "shopping_list"; // NOLINT(*c-arrays) +constexpr char personalAccountCmd[] = "personal_account"; // NOLINT(*c-arrays) +constexpr char wannaEatCmd[] = "wanna_eat"; // NOLINT(*c-arrays) using noStateHandler = Handler; +using startCmdHandler = Handler; // NOLINT(*decay) +using myStoragesCmdHandler = Handler; // NOLINT(*decay) +using shoppingListCmdHandler = Handler; // NOLINT(*decay) +using personalAccountCmdHandler = Handler; // NOLINT(*decay) +using wannaEatCmdHandler = Handler; // NOLINT(*decay) // MainMenu using mainMenuCQHandler = Handler; diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 82e7d664..14bcbc25 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -25,7 +25,7 @@ void handlePersonalAccountMenuCQ( stateManager.put(CustomRecipesList{.pageNo = 0}); } if (data == "back") { - renderMainMenu(true, userId, chatId, bot, api); + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index 196b55ea..ed72ad40 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -10,6 +10,7 @@ #include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" +#include #include #include @@ -35,7 +36,7 @@ void handleSuggestedRecipesListCQ( stateManager.put(StorageView{state.selectedStorages[0].id}); // Go to the only one storage } else { if (api.getStoragesApi().getStoragesList(userId).size() == 1) { - renderMainMenu(true, userId, chatId, bot, api); + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); } else { auto newState = StoragesSelection{.selectedStorages = std::move(state.selectedStorages)}; diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 843ffc04..692c10d7 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -50,7 +50,7 @@ void handleShoppingListCreationCQ( bot.answerCallbackQuery(cq.id); return; } - if (data[0] == '+') { + if (data[0] == '-') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); if (newIngredientId) { @@ -59,7 +59,7 @@ void handleShoppingListCreationCQ( } renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); } - if (data[0] == '-') { + if (data[0] == '+') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); if (newIngredientId) { diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index 00447dc5..36ff3d20 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -28,13 +28,13 @@ void handleShoppingListViewCQ( auto storages = api.getStoragesApi().getStoragesList(userId); if (cq.data == "back") { - renderMainMenu(true, userId, cq.message->chat->id, bot, api); + renderMainMenu(true, std::nullopt, userId, cq.message->chat->id, bot, api); stateManager.put(MainMenu{}); return; } if (cq.data == "search") { - renderMainMenu(true, userId, cq.message->chat->id, bot, api); + renderMainMenu(true, std::nullopt, userId, cq.message->chat->id, bot, api); stateManager.put(MainMenu{}); return; } diff --git a/src/handlers/storages_list/view.cpp b/src/handlers/storages_list/view.cpp index b00c5c88..83e30aa6 100644 --- a/src/handlers/storages_list/view.cpp +++ b/src/handlers/storages_list/view.cpp @@ -28,7 +28,7 @@ void handleStorageListCQ( return; } if (cq.data == "back") { - renderMainMenu(true, userId, chatId, bot, api); + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 20d9daaf..85744e53 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -33,7 +33,7 @@ void handleStoragesSelectionCQ( } if (cq.data == "cancel") { - renderMainMenu(true, userId, chatId, bot, api); + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } diff --git a/src/main.cpp b/src/main.cpp index 26b71e0c..652d4326 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,8 +33,12 @@ int main(int argc, char* argv[]) { } try { - Setup>::Stater>::Stater getMessageId(tg_types::UserId userId); void addMessageId(tg_types::UserId userId, tg_types::MessageId messageId); +void deleteMessageId(tg_types::UserId userId); + } // namespace cookcookhnya::message diff --git a/src/render/common.hpp b/src/render/common.hpp index ad35bc59..1fdb5bee 100644 --- a/src/render/common.hpp +++ b/src/render/common.hpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include diff --git a/src/render/main_menu/view.cpp b/src/render/main_menu/view.cpp index 0add0f9d..4c58dcb1 100644 --- a/src/render/main_menu/view.cpp +++ b/src/render/main_menu/view.cpp @@ -5,13 +5,14 @@ #include "utils/utils.hpp" #include +#include #include namespace cookcookhnya::render::main_menu { using namespace tg_types; -void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { +void renderMainMenu(bool toBeEdited, std::optional> inviteStorage, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); const std::size_t buttonRows = storages.empty() ? 3 : 4; @@ -30,6 +31,13 @@ void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, S auto text = utils::utf8str( u8"🍳 Добро пожаловать в CookCookNya — ваш личный бот для быстро подбора рецептов и многого другого!"); + if (inviteStorage){ + if (*inviteStorage){ + text += utils::utf8str(u8"\n\nВы были успешно добавлены в хранилище 🍱") + **inviteStorage; + } else { + text += utils::utf8str(u8"\n\nК сожалению, данное приглашение уже было использовано 🥲"); + } + } if (toBeEdited) { if (auto messageId = message::getMessageId(userId)) bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); diff --git a/src/render/main_menu/view.hpp b/src/render/main_menu/view.hpp index 93bb21e1..6512f490 100644 --- a/src/render/main_menu/view.hpp +++ b/src/render/main_menu/view.hpp @@ -2,8 +2,10 @@ #include "render/common.hpp" +#include + namespace cookcookhnya::render::main_menu { -void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); +void renderMainMenu(bool toBeEdited, std::optional> inviteStorage, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); } // namespace cookcookhnya::render::main_menu diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 30a6eaa8..2288bc6d 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -116,13 +116,17 @@ constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::R } // namespace void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { - const std::string pageInfo = utils::utf8str(u8"🔪 Рецепты созданные вами"); + std::string pageInfo = utils::utf8str(u8"🔪 Рецепты созданные вами:"); auto messageId = message::getMessageId(userId); const std::size_t numOfRecipesOnPage = 5; auto recipesList = recipesApi.getList(userId, PublicityFilterType::Custom, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); + + if(recipesList.found == 0){ + pageInfo = utils::utf8str(u8"🔪 Вы находитесь в Мои рецепты. Создавайте и делитесь новыми рецептами.\n\n"); + } if (messageId) { bot.editMessageText( pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); diff --git a/src/render/personal_account/view.cpp b/src/render/personal_account/view.cpp index 2870dcfd..e75405c5 100644 --- a/src/render/personal_account/view.cpp +++ b/src/render/personal_account/view.cpp @@ -24,6 +24,9 @@ void renderPersonalAccountMenu(UserId userId, ChatId chatId, BotRef bot) { auto messageId = message::getMessageId(userId); if (messageId) { bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + } else { + auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); + message::addMessageId(userId, message->messageId); } } diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 829fbf76..cb88d6be 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -125,16 +125,22 @@ void renderRecipesSuggestion(std::vector& storages, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { - const std::string pageInfo = utils::utf8str(u8"🔪 Рецепты подобранные специально для вас"); + std::string pageInfo = utils::utf8str(u8"🔪 Рецепты подобранные специально для вас"); const std::size_t numOfRecipesOnPage = 5; const std::size_t numOfRecipes = 500; auto storagesIds = storages | views::transform(&StorageSummary::id) | to(); auto recipesList = recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipes, pageNo * numOfRecipesOnPage); - + + if(recipesList.found == 0){ + pageInfo = utils::utf8str(u8"😔 К сожалению, нам не удалось найти подходящие рецепты для вас..."); + } if (auto messageId = message::getMessageId(userId)) { bot.editMessageText( pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + } else { + auto message = bot.sendMessage(chatId, pageInfo, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + message::addMessageId(userId, message->messageId); } } diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 737d374a..a373fb8e 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -34,10 +34,12 @@ void renderShoppingList(const states::ShoppingListView& state, UserId userId, Ch keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + auto text = utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); if (auto messageId = message::getMessageId(userId)) { - auto text = - utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); + } else { + auto message = bot.sendMessage(chatId, text, std::move(keyboard)); + message::addMessageId(userId, message->messageId); } } diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index 9ab39884..e026d24b 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -52,6 +52,11 @@ void renderStorageSelection( auto messageId = message::getMessageId(userId); if (messageId) bot.editMessageText(text, chatId, *messageId, std::move(keyboard).build()); + else{ + auto message = bot.sendMessage(chatId, text, std::move(keyboard).build()); + message::addMessageId(userId, message->messageId); + } + } } // namespace cookcookhnya::render::select_storages From f20748c22fc6326f4eb37b2e4ff25e3d472b3d82 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sat, 19 Jul 2025 23:43:14 +0300 Subject: [PATCH 026/106] feat: new commands --- src/backend/api/storages.cpp | 4 ++-- src/backend/models/ingredient.hpp | 2 +- src/handlers/commands/wanna_eat.cpp | 10 ++++++--- src/handlers/handlers_list.hpp | 22 ++++++++++--------- .../ingredients_list/create.cpp | 7 ++---- src/render/main_menu/view.cpp | 11 +++++++--- src/render/main_menu/view.hpp | 7 +++++- .../ingredients_list/view.cpp | 10 ++++----- .../personal_account/recipes_list/view.cpp | 2 +- src/render/recipes_suggestions/view.cpp | 7 +++--- src/render/shopping_list/view.cpp | 3 ++- src/render/storages_selection/view.cpp | 3 +-- 12 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/backend/api/storages.cpp b/src/backend/api/storages.cpp index a8cfe535..ae3693e4 100644 --- a/src/backend/api/storages.cpp +++ b/src/backend/api/storages.cpp @@ -56,9 +56,9 @@ InvitationId StoragesApi::inviteMember(UserId user, StorageId storage) const { // POST /invitations/{invitationHash}/activate std::optional StoragesApi::activate(UserId user, InvitationId invitation) const { - try{ + try { return jsonPostAuthed(user, std::format("/invitations/{}/activate", invitation)); - } catch (...){ + } catch (...) { return std::nullopt; } } diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index fac9252c..58e1e447 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -16,7 +16,7 @@ namespace cookcookhnya::api::models::ingredient { struct Ingredient { IngredientId id; std::string name; - std::optional status = status::PublicationRequestStatus::Idle; + std::optional status = status::PublicationRequestStatus::Idle; // TODO: change to nullopt when back is ready friend Ingredient tag_invoke(boost::json::value_to_tag, const boost::json::value& j); diff --git a/src/handlers/commands/wanna_eat.cpp b/src/handlers/commands/wanna_eat.cpp index 81d53e83..8e246f98 100644 --- a/src/handlers/commands/wanna_eat.cpp +++ b/src/handlers/commands/wanna_eat.cpp @@ -17,18 +17,22 @@ using namespace render::recipes_suggestions; void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { auto storages = api.getStoragesApi().getStoragesList(m.from->id); - if (storages.empty()){ + if (storages.empty()) { bot.sendMessage(m.chat->id, utils::utf8str(u8"😔 К сожалению, у вас пока что нет хранилищ.")); renderMainMenu(false, std::nullopt, m.from->id, m.chat->id, bot, api); stateManager.put(MainMenu{}); } else if (storages.size() == 1) { message::deleteMessageId(m.from->id); renderRecipesSuggestion({storages}, 0, m.from->id, m.chat->id, bot, api); - stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storages, .fromStorage = false,}); + stateManager.put(SuggestedRecipesList{ + .pageNo = 0, + .selectedStorages = storages, + .fromStorage = false, + }); } else { message::deleteMessageId(m.from->id); auto newState = StoragesSelection{}; - renderStorageSelection(newState, m.from->id, m.chat->id, bot, api); + renderStorageSelection(newState, m.from->id, m.chat->id, bot, api); stateManager.put(newState); } }; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 0bbd50f9..43830c47 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -69,17 +69,19 @@ using namespace tg_stater; namespace bot_handlers { // Commands -constexpr char startCmd[] = "start"; // NOLINT(*c-arrays) -constexpr char myStoragesCmd[] = "my_storages"; // NOLINT(*c-arrays) -constexpr char shoppingListCmd[] = "shopping_list"; // NOLINT(*c-arrays) -constexpr char personalAccountCmd[] = "personal_account"; // NOLINT(*c-arrays) -constexpr char wannaEatCmd[] = "wanna_eat"; // NOLINT(*c-arrays) +constexpr char startCmd[] = "start"; // NOLINT(*c-arrays) +constexpr char myStoragesCmd[] = "my_storages"; // NOLINT(*c-arrays) +constexpr char shoppingListCmd[] = "shopping_list"; // NOLINT(*c-arrays) +constexpr char personalAccountCmd[] = "personal_account"; // NOLINT(*c-arrays) +constexpr char wannaEatCmd[] = "wanna_eat"; // NOLINT(*c-arrays) using noStateHandler = Handler; -using startCmdHandler = Handler; // NOLINT(*decay) -using myStoragesCmdHandler = Handler; // NOLINT(*decay) -using shoppingListCmdHandler = Handler; // NOLINT(*decay) -using personalAccountCmdHandler = Handler; // NOLINT(*decay) -using wannaEatCmdHandler = Handler; // NOLINT(*decay) +using startCmdHandler = Handler; // NOLINT(*decay) +using myStoragesCmdHandler = Handler; // NOLINT(*decay) +using shoppingListCmdHandler = + Handler; // NOLINT(*decay) +using personalAccountCmdHandler = + Handler; // NOLINT(*decay) +using wannaEatCmdHandler = Handler; // NOLINT(*decay) // MainMenu using mainMenuCQHandler = Handler; diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 20c1fe15..1f748adc 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -12,11 +12,8 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; -void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& state, - MessageRef m, - BotRef& bot, - SMRef stateManager, - IngredientsApiRef api) { +void handleCustomIngredientCreationEnterNameMsg( + CustomIngredientCreationEnterName& state, MessageRef m, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { auto name = m.text; auto userId = m.from->id; auto chatId = m.chat->id; diff --git a/src/render/main_menu/view.cpp b/src/render/main_menu/view.cpp index 4c58dcb1..ea7e60f4 100644 --- a/src/render/main_menu/view.cpp +++ b/src/render/main_menu/view.cpp @@ -12,7 +12,12 @@ namespace cookcookhnya::render::main_menu { using namespace tg_types; -void renderMainMenu(bool toBeEdited, std::optional> inviteStorage, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { +void renderMainMenu(bool toBeEdited, + std::optional> inviteStorage, + UserId userId, + ChatId chatId, + BotRef bot, + StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); const std::size_t buttonRows = storages.empty() ? 3 : 4; @@ -31,8 +36,8 @@ void renderMainMenu(bool toBeEdited, std::optional> i auto text = utils::utf8str( u8"🍳 Добро пожаловать в CookCookNya — ваш личный бот для быстро подбора рецептов и многого другого!"); - if (inviteStorage){ - if (*inviteStorage){ + if (inviteStorage) { + if (*inviteStorage) { text += utils::utf8str(u8"\n\nВы были успешно добавлены в хранилище 🍱") + **inviteStorage; } else { text += utils::utf8str(u8"\n\nК сожалению, данное приглашение уже было использовано 🥲"); diff --git a/src/render/main_menu/view.hpp b/src/render/main_menu/view.hpp index 6512f490..5d5d885a 100644 --- a/src/render/main_menu/view.hpp +++ b/src/render/main_menu/view.hpp @@ -6,6 +6,11 @@ namespace cookcookhnya::render::main_menu { -void renderMainMenu(bool toBeEdited, std::optional> inviteStorage, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); +void renderMainMenu(bool toBeEdited, + std::optional> inviteStorage, + UserId userId, + ChatId chatId, + BotRef bot, + StorageApiRef storageApi); } // namespace cookcookhnya::render::main_menu diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index ba558b35..a5bc7ebd 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -23,13 +23,14 @@ namespace { std::pair> constructNavigationMessage( std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::IngredientList& ingredientsList) { const size_t amountOfRecipes = ingredientsList.found; - const std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); + const std::size_t maxPageNum = + std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); std::string text; text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - if (ing.status){ + if (ing.status) { text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); } } @@ -39,8 +40,7 @@ std::pair> constructN buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - } - else if (pageNo == 0) { + } else if (pageNo == 0) { buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); @@ -72,7 +72,7 @@ std::pair constructMessage(size_t pageNo, } else if (ingredientsList.found <= numOfIngredientsOnPage) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - if (ing.status){ + if (ing.status) { text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); } } diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 2288bc6d..eed3b57c 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -124,7 +124,7 @@ void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, B auto recipesList = recipesApi.getList(userId, PublicityFilterType::Custom, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); - if(recipesList.found == 0){ + if (recipesList.found == 0) { pageInfo = utils::utf8str(u8"🔪 Вы находитесь в Мои рецепты. Создавайте и делитесь новыми рецептами.\n\n"); } if (messageId) { diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index cb88d6be..73de6998 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -131,15 +131,16 @@ void renderRecipesSuggestion(std::vector& storages, auto storagesIds = storages | views::transform(&StorageSummary::id) | to(); auto recipesList = recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipes, pageNo * numOfRecipesOnPage); - - if(recipesList.found == 0){ + + if (recipesList.found == 0) { pageInfo = utils::utf8str(u8"😔 К сожалению, нам не удалось найти подходящие рецепты для вас..."); } if (auto messageId = message::getMessageId(userId)) { bot.editMessageText( pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); } else { - auto message = bot.sendMessage(chatId, pageInfo, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + auto message = bot.sendMessage( + chatId, pageInfo, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index a373fb8e..6713f363 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -34,7 +34,8 @@ void renderShoppingList(const states::ShoppingListView& state, UserId userId, Ch keyboard << makeCallbackButton(u8"↩️ Назад", "back"); - auto text = utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); + auto text = + utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); if (auto messageId = message::getMessageId(userId)) { bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } else { diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index e026d24b..9cd31b6e 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -52,11 +52,10 @@ void renderStorageSelection( auto messageId = message::getMessageId(userId); if (messageId) bot.editMessageText(text, chatId, *messageId, std::move(keyboard).build()); - else{ + else { auto message = bot.sendMessage(chatId, text, std::move(keyboard).build()); message::addMessageId(userId, message->messageId); } - } } // namespace cookcookhnya::render::select_storages From 2fcf6976832fb499eb880d3404eb8ae48011a2e7 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Sat, 19 Jul 2025 23:45:02 +0300 Subject: [PATCH 027/106] feat: v1 all history render --- Makefile | 2 +- src/backend/api/recipes.cpp | 17 +++++++-- src/backend/api/recipes.hpp | 5 ++- src/backend/models/recipe.cpp | 18 +++++----- src/backend/models/recipe.hpp | 8 ++--- src/handlers/common.hpp | 2 ++ .../recipe/publication_history.cpp | 4 +-- .../recipe/publication_history.hpp | 4 +-- src/handlers/personal_account/recipe/view.cpp | 2 +- .../personal_account/request_history.cpp | 8 +++++ .../personal_account/request_history.hpp | 10 ++++++ src/handlers/personal_account/view.cpp | 10 +++++- .../recipe/publication_history.cpp | 36 ++++++++----------- .../recipe/publication_history.hpp | 4 +-- src/render/personal_account/recipe/view.cpp | 6 ++-- .../personal_account/request_history.cpp | 26 ++++++++++++++ .../personal_account/request_history.hpp | 8 +++++ src/render/personal_account/view.cpp | 5 +-- src/states.hpp | 7 +++- src/utils/to_string.cpp | 7 ++++ src/utils/to_string.hpp | 2 ++ 21 files changed, 138 insertions(+), 53 deletions(-) create mode 100644 src/handlers/personal_account/request_history.cpp create mode 100644 src/handlers/personal_account/request_history.hpp create mode 100644 src/render/personal_account/request_history.cpp create mode 100644 src/render/personal_account/request_history.hpp diff --git a/Makefile b/Makefile index d1bc165d..360f7be0 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 -- -j5 + cmake --build . --preset=conan-debug -- -j3 build-release: build/Release/CMakeCache.txt cmake --build --preset=conan-release diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 3e999f5b..5bee6d20 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -72,8 +72,19 @@ void RecipesApi::publishCustom(UserId user, RecipeId recipe) const { } // GET /recipe/{id}/moderation-history -std::vector RecipesApi::getModerationHistory(UserId user, RecipeId recipe) const { - return jsonGetAuthed>(user, - std::format("/recipe/{}/moderation-history", recipe)); +std::vector RecipesApi::getRequestHistory(UserId user, RecipeId recipe) const { + return jsonGetAuthed>(user, + std::format("/recipe/{}/moderation-history", recipe)); +} + +// GET /publication-requests?size={}&offset={} +[[nodiscard]] std::vector +RecipesApi::getAllRequestHistory(UserId user, std::size_t size, std::size_t offset) const { + return jsonGetAuthed>(user, + "/publication-requests", + { + {"size", utils::to_string(size)}, + {"offset", utils::to_string(offset)}, + }); } } // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 136ce47f..f0e691d0 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -39,7 +39,7 @@ class RecipesApi : ApiBase { RecipeId create(UserId user, // NOLINT(*-nodiscard) const models::recipe::RecipeCreateBody& body) const; - [[nodiscard]] std::vector getModerationHistory(UserId user, + [[nodiscard]] std::vector getRequestHistory(UserId user, RecipeId recipe) const; [[nodiscard]] models::recipe::RecipeDetails get(UserId user, RecipeId recipeId) const; @@ -47,6 +47,9 @@ class RecipesApi : ApiBase { void delete_(UserId user, RecipeId recipe) const; void publishCustom(UserId user, RecipeId recipe) const; + + [[nodiscard]] std::vector + getAllRequestHistory(UserId user, std::size_t size = 10, std::size_t offset = 0) const; // NOLINT(*magic-number*) }; } // namespace cookcookhnya::api diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 6e57983c..87e0a1c8 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -58,7 +58,7 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: : user::UserDetails{.userId = 0, .alias = "", .fullName = ""}, .moderationStatus = j.as_object().if_contains("status") ? value_to(j.at("status")) - : PublicationRequestStatus::Idle, + : PublicationRequestStatus::NO_REQUEST, }; } @@ -76,13 +76,15 @@ RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/ }; } -CustomRecipePublication tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { +PublicationHistoryInstance tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .created = utils::parseIsoTime(value_to(j.at("created"))), .reason = j.as_object().if_contains("reason") - ? value_to(j.at("reason")) + ? value_to(j.at("reason")) : "", - .status = value_to(j.at("status")), + .status = j.as_object().if_contains("status") + ? value_to(j.at("status")) + : PublicationRequestStatus::NO_REQUEST, .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) : std::chrono::time_point(), }; @@ -91,11 +93,11 @@ CustomRecipePublication tag_invoke(json::value_to_tag / PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, const boost::json::value& j) { if (j.at("status") == "Pending") - return PublicationRequestStatus::Pending; + return PublicationRequestStatus::PENDING; if (j.at("status") == "Accepted") - return PublicationRequestStatus::Pending; + return PublicationRequestStatus::ACCEPTED; if (j.at("status") == "Rejected") - return PublicationRequestStatus::Pending; - return PublicationRequestStatus::Pending; + return PublicationRequestStatus::REJECTED; + return PublicationRequestStatus::NO_REQUEST; } } // namespace cookcookhnya::api::models::recipe diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index b063a31a..8fe3d89b 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -15,7 +15,7 @@ namespace cookcookhnya::api::models::recipe { -enum class PublicationRequestStatus : std::uint8_t { Pending = 0, Accepted = 1, Rejected = 2, Idle = 3 }; +enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED, NO_REQUEST }; PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); struct RecipeSummary { @@ -92,14 +92,14 @@ struct RecipeSearchResponse { const boost::json::value& j); }; -struct CustomRecipePublication { +struct PublicationHistoryInstance { std::chrono::system_clock::time_point created; std::optional reason; PublicationRequestStatus status{}; std::optional updated; - friend CustomRecipePublication tag_invoke(boost::json::value_to_tag, - const boost::json::value& j); + friend PublicationHistoryInstance tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); }; } // namespace cookcookhnya::api::models::recipe diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 7f82ba7e..b81195bd 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -27,6 +27,8 @@ using states::CustomIngredientsList; using states::StorageList; +using states::AllPublicationHistory; + using states::RecipeStorageAddition; using states::RecipeView; diff --git a/src/handlers/personal_account/recipe/publication_history.cpp b/src/handlers/personal_account/recipe/publication_history.cpp index feb1a2e5..82e83ac4 100644 --- a/src/handlers/personal_account/recipe/publication_history.cpp +++ b/src/handlers/personal_account/recipe/publication_history.cpp @@ -2,7 +2,7 @@ #include "render/personal_account/recipe/view.hpp" -namespace cookcookhnya::handlers::personal_account::publication_history { +namespace cookcookhnya::handlers::personal_account::recipe::publication_history { using namespace render::personal_account; using namespace render::personal_account::recipes; @@ -37,4 +37,4 @@ void handleCustomRecipePublicationHistoryCQ(CustomRecipePublicationHistory& stat } } -} // namespace cookcookhnya::handlers::personal_account::publication_history +} // namespace cookcookhnya::handlers::personal_account::recipe::publication_history diff --git a/src/handlers/personal_account/recipe/publication_history.hpp b/src/handlers/personal_account/recipe/publication_history.hpp index 774f9509..c96a9e1e 100644 --- a/src/handlers/personal_account/recipe/publication_history.hpp +++ b/src/handlers/personal_account/recipe/publication_history.hpp @@ -2,9 +2,9 @@ #include "handlers/common.hpp" -namespace cookcookhnya::handlers::personal_account::publication_history { +namespace cookcookhnya::handlers::personal_account::recipe::publication_history { void handleCustomRecipePublicationHistoryCQ( CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef api); -} // namespace cookcookhnya::handlers::personal_account::publication_history +} // namespace cookcookhnya::handlers::personal_account::recipe::publication_history diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index 5f1ab549..ff331093 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -15,7 +15,7 @@ namespace cookcookhnya::handlers::personal_account::recipes { using namespace render::personal_account::recipes; using namespace render::recipe::ingredients; -using namespace render::personal_account::publication_history; +using namespace render::personal_account::recipe::publication_history; using namespace std::views; diff --git a/src/handlers/personal_account/request_history.cpp b/src/handlers/personal_account/request_history.cpp new file mode 100644 index 00000000..7772f600 --- /dev/null +++ b/src/handlers/personal_account/request_history.cpp @@ -0,0 +1,8 @@ +#include "request_history.hpp" + +namespace cookcookhnya::handlers::history { + +void handleAllPublicationHistoryCQ( + AllPublicationHistory& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + +} // namespace cookcookhnya::handlers::history diff --git a/src/handlers/personal_account/request_history.hpp b/src/handlers/personal_account/request_history.hpp new file mode 100644 index 00000000..c65f509b --- /dev/null +++ b/src/handlers/personal_account/request_history.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::history { + +void handleAllPublicationHistoryCQ( + AllPublicationHistory& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + +} // namespace cookcookhnya::handlers::history diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 0e961e4a..a7bd45b3 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -4,7 +4,8 @@ #include "render/main_menu/view.hpp" #include "render/personal_account/ingredients_list/view.hpp" #include "render/personal_account/recipes_list/view.hpp" - +#include "render/personal_account/request_history.hpp" +#include #include namespace cookcookhnya::handlers::personal_account { @@ -12,6 +13,7 @@ namespace cookcookhnya::handlers::personal_account { using namespace render::personal_account::recipes; using namespace render::main_menu; using namespace render::personal_account::ingredients; +using namespace render::personal_account::history; void handlePersonalAccountMenuCQ( PersonalAccountMenu& /**/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { @@ -19,6 +21,7 @@ void handlePersonalAccountMenuCQ( bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; + std::size_t numOfHistoryInstances = 10; if (data == "recipes") { renderCustomRecipesList(0, userId, chatId, bot, api); @@ -35,6 +38,11 @@ void handlePersonalAccountMenuCQ( stateManager.put(CustomIngredientsList{}); return; } + if (data == "history") { + renderRequestHistory(userId, 0, numOfHistoryInstances, chatId, bot, api); + stateManager.put(AllPublicationHistory{.pageNo = 0}); + return; + } } } // namespace cookcookhnya::handlers::personal_account diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp index ca658d05..92ba5d18 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -12,18 +12,10 @@ #include #include -namespace cookcookhnya::render::personal_account::publication_history { +namespace cookcookhnya::render::personal_account::recipe::publication_history { using namespace std::views; -namespace { -std::string convertTimeToStrFormat(std::chrono::system_clock::time_point time) { - const auto* moscow_tz = std::chrono::locate_zone("Europe/Moscow"); - auto moscow_time = std::chrono::zoned_time(moscow_tz, time); - return std::format("{:%d-%m-%Y %H:%M}", moscow_time.get_local_time()); -} -} // namespace - void renderPublicationHistory(UserId userId, ChatId chatId, api::RecipeId recipeId, @@ -32,44 +24,44 @@ void renderPublicationHistory(UserId userId, BotRef bot, RecipesApiRef recipesApi) { // auto history = recipesApi.getModerationHistory(userId, recipeId); - std::vector history = { + std::vector history = { { .created = std::chrono::system_clock::now(), - .status = api::models::recipe::PublicationRequestStatus::Pending, + .status = api::models::recipe::PublicationRequestStatus::PENDING, }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::Rejected, + .status = api::models::recipe::PublicationRequestStatus::REJECTED, .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::Rejected, + .status = api::models::recipe::PublicationRequestStatus::REJECTED, }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::Rejected, + .status = api::models::recipe::PublicationRequestStatus::REJECTED, .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::Rejected, + .status = api::models::recipe::PublicationRequestStatus::REJECTED, .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::Rejected, + .status = api::models::recipe::PublicationRequestStatus::REJECTED, .updated = std::chrono::system_clock::now(), }, { .created = std::chrono::system_clock::now(), .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::Rejected, + .status = api::models::recipe::PublicationRequestStatus::REJECTED, .updated = std::chrono::system_clock::now(), }}; InlineKeyboardBuilder keyboard{2}; // confirm and back @@ -79,7 +71,7 @@ void renderPublicationHistory(UserId userId, toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[0].status) + (history[0].reason.has_value() ? std::format(" по причине {}", history[0].reason.value()) : " ") + - convertTimeToStrFormat(history[0].created) + "\n\n"; + utils::to_string(history[0].created) + "\n\n"; // Remove the lastest history instance as it's showed differently history.erase(history.begin()); @@ -92,7 +84,7 @@ void renderPublicationHistory(UserId userId, // interate through it but it would require "for" and i don't know what is it) reverse to print lastest request the // last in list auto res = - history | reverse | transform([&prefixes](const api::models::recipe::CustomRecipePublication& req) { + history | reverse | transform([&prefixes](const api::models::recipe::PublicationHistoryInstance& req) { //!!!!IMPORTANT See that tie to match prefixes!!!! auto fields = std::tie(req.status, req.reason, req.created, req.updated); return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { @@ -107,10 +99,10 @@ void renderPublicationHistory(UserId userId, return ", "; // Need to work with chrono case 2: - return prefixes[2] + ": " + convertTimeToStrFormat(std::get<2>(fields)) + ", "; + return prefixes[2] + ": " + utils::to_string(std::get<2>(fields)) + ", "; case 3: if (std::get<3>(fields).has_value()) { - return prefixes[3] + ": " + convertTimeToStrFormat(std::get<3>(fields).value()) + "\n\n"; + return prefixes[3] + ": " + utils::to_string(std::get<3>(fields).value()) + "\n\n"; } return "\n\n"; default: @@ -133,7 +125,7 @@ void renderPublicationHistory(UserId userId, bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); } } -} // namespace cookcookhnya::render::personal_account::publication_history +} // namespace cookcookhnya::render::personal_account::recipe::publication_history // Uncomment in case of EMERGENCY (or if you know what for is) // use instead of history | reverse | ... diff --git a/src/render/personal_account/recipe/publication_history.hpp b/src/render/personal_account/recipe/publication_history.hpp index ac4c35a6..c8253cd7 100644 --- a/src/render/personal_account/recipe/publication_history.hpp +++ b/src/render/personal_account/recipe/publication_history.hpp @@ -3,7 +3,7 @@ #include "backend/id_types.hpp" #include "render/common.hpp" -namespace cookcookhnya::render::personal_account::publication_history { +namespace cookcookhnya::render::personal_account::recipe::publication_history { void renderPublicationHistory(UserId userId, ChatId chatId, @@ -13,4 +13,4 @@ void renderPublicationHistory(UserId userId, BotRef bot, RecipesApiRef recipesApi); -} // namespace cookcookhnya::render::personal_account::publication_history +} // namespace cookcookhnya::render::personal_account::recipe::publication_history diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 2000df14..d8e04c2d 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -21,7 +21,7 @@ std::tuple, std::string> render auto recipeDetails = recipesApi.get(userId, recipeId); // REMOVE WHEN BACKEND IS READY - recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::Idle; + recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::NO_REQUEST; std::vector ingredients; @@ -43,8 +43,8 @@ std::tuple, std::string> render 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 == api::models::recipe::PublicationRequestStatus::Idle || - recipeDetails.moderationStatus == api::models::recipe::PublicationRequestStatus::Rejected) { + if (recipeDetails.moderationStatus == api::models::recipe::PublicationRequestStatus::NO_REQUEST || + recipeDetails.moderationStatus == api::models::recipe::PublicationRequestStatus::REJECTED) { keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; } else { keyboard << makeCallbackButton(u8"📢 История публикаций", "peekpublish") << NewRow{}; diff --git a/src/render/personal_account/request_history.cpp b/src/render/personal_account/request_history.cpp new file mode 100644 index 00000000..b4173e4f --- /dev/null +++ b/src/render/personal_account/request_history.cpp @@ -0,0 +1,26 @@ +#include "request_history.hpp" + +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "utils/utils.hpp" +#include "view.hpp" + +#include +#include + +namespace cookcookhnya::render::personal_account::history { +void renderRequestHistory( + UserId userId, size_t pageNo, size_t numOfInstances, ChatId chatId, BotRef bot, RecipesApiRef recipeApi) { + InlineKeyboardBuilder keyboard{1}; + + auto history = recipeApi.getAllRequestHistory(userId, 10, pageNo * numOfInstances); + std::string toPrint; + for (auto& req : history) { + toPrint += std::format("Статус: {} ", utils::to_string(req.status)); + if (req.reason.has_value()) + toPrint += std::format("по причине: {} ", req.reason.value()); + toPrint += std::format("запрос создан: {}\n", utils::to_string(req.created)); + toPrint += '\n'; + } +} +} // namespace cookcookhnya::render::personal_account::history diff --git a/src/render/personal_account/request_history.hpp b/src/render/personal_account/request_history.hpp new file mode 100644 index 00000000..66b874ef --- /dev/null +++ b/src/render/personal_account/request_history.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "render/common.hpp" + +namespace cookcookhnya::render::personal_account::history { +void renderRequestHistory( + UserId userId, size_t pageNo, size_t numOfInstances, ChatId chatId, BotRef bot, RecipesApiRef recipeApi); +} // namespace cookcookhnya::render::personal_account::history diff --git a/src/render/personal_account/view.cpp b/src/render/personal_account/view.cpp index 2870dcfd..091f87f0 100644 --- a/src/render/personal_account/view.cpp +++ b/src/render/personal_account/view.cpp @@ -11,12 +11,13 @@ namespace cookcookhnya::render::personal_account { using namespace tg_types; void renderPersonalAccountMenu(UserId userId, ChatId chatId, BotRef bot) { - const std::size_t buttonRows = 3; + const std::size_t buttonRows = 5; InlineKeyboard keyboard(buttonRows); keyboard[0].push_back(makeCallbackButton(u8"📋 Мои ингредиенты", "ingredients")); keyboard[1].push_back(makeCallbackButton(u8"📒 Мои рецепты", "recipes")); // 🗃️ - keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + keyboard[2].push_back(makeCallbackButton(u8"🗃️ История запросов на публикацию", "history")); + keyboard[3].push_back(makeCallbackButton(u8"↩️ Назад", "back")); auto text = utils::utf8str(u8"👤 Вы находитесь в Личном Кабинете. Здесь вы можете добавить личные ингредиенты и " u8"рецепты, а также делиться ими с другими пользователями."); diff --git a/src/states.hpp b/src/states.hpp index 096f2944..b984af76 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -161,6 +161,10 @@ struct CustomRecipePublicationHistory { std::size_t pageNo; }; +struct AllPublicationHistory { + std::size_t pageNo; +}; + using State = std::variant; + CustomRecipePublicationHistory, + AllPublicationHistory>; using StateManager = tg_stater::StateProxy>; diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index c727f366..540ebf67 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -6,6 +6,7 @@ #include +#include #include namespace cookcookhnya::utils { @@ -22,4 +23,10 @@ std::string to_string(const cookcookhnya::api::models::recipe::PublicationReques return statusStr[static_cast(status)]; } +std::string to_string(std::chrono::system_clock::time_point time) { + const auto* moscow_tz = std::chrono::locate_zone("Europe/Moscow"); + auto moscow_time = std::chrono::zoned_time(moscow_tz, time); + return std::format("{:%d-%m-%Y %H:%M}", moscow_time.get_local_time()); +} + } // namespace cookcookhnya::utils diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index eefe80f1..7fcc4684 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -19,4 +19,6 @@ std::string to_string(const Uuid& u); std::string to_string(cookcookhnya::api::models::recipe::PublicationRequestStatus status); +std::string to_string(std::chrono::system_clock::time_point time); + } // namespace cookcookhnya::utils From 77aef62161ee25afb088cb97777237aee8d8a5d6 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sun, 20 Jul 2025 00:39:09 +0300 Subject: [PATCH 028/106] merge enum from ilyha --- src/backend/models/ingredient.hpp | 3 +-- src/backend/models/publication_request_status.cpp | 8 ++++---- src/backend/models/publication_request_status.hpp | 2 +- .../personal_account/ingredients_list/create.cpp | 7 ++----- .../personal_account/ingredients_list/view.cpp | 10 +++++----- src/utils/to_string.cpp | 13 +++++++------ 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index fac9252c..76a59244 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -16,8 +16,7 @@ namespace cookcookhnya::api::models::ingredient { struct Ingredient { IngredientId id; std::string name; - std::optional status = status::PublicationRequestStatus::Idle; - // TODO: change to nullopt when back is ready + std::optional status = std::nullopt; friend Ingredient 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 17fdf0ce..a6d00a90 100644 --- a/src/backend/models/publication_request_status.cpp +++ b/src/backend/models/publication_request_status.cpp @@ -5,12 +5,12 @@ namespace cookcookhnya::api::models::status { PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, const boost::json::value& j) { if (j.at("status") == "Pending") - return PublicationRequestStatus::Pending; + return PublicationRequestStatus::PENDING; if (j.at("status") == "Accepted") - return PublicationRequestStatus::Accepted; + return PublicationRequestStatus::ACCEPTED; if (j.at("status") == "Rejected") - return PublicationRequestStatus::Rejected; - return PublicationRequestStatus::Idle; + return PublicationRequestStatus::REJECTED; + return PublicationRequestStatus::NO_REQUEST; } } // namespace cookcookhnya::api::models::status diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp index 8066c9bc..c3cf284a 100644 --- a/src/backend/models/publication_request_status.hpp +++ b/src/backend/models/publication_request_status.hpp @@ -6,7 +6,7 @@ namespace cookcookhnya::api::models::status { -enum class PublicationRequestStatus : std::uint8_t { Pending = 0, Accepted = 1, Rejected = 2, Idle = 3 }; +enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED, NO_REQUEST }; PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 20c1fe15..1f748adc 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -12,11 +12,8 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; -void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& state, - MessageRef m, - BotRef& bot, - SMRef stateManager, - IngredientsApiRef api) { +void handleCustomIngredientCreationEnterNameMsg( + CustomIngredientCreationEnterName& state, MessageRef m, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { auto name = m.text; auto userId = m.from->id; auto chatId = m.chat->id; diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index ba558b35..a5bc7ebd 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -23,13 +23,14 @@ namespace { std::pair> constructNavigationMessage( std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::IngredientList& ingredientsList) { const size_t amountOfRecipes = ingredientsList.found; - const std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); + const std::size_t maxPageNum = + std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); std::string text; text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - if (ing.status){ + if (ing.status) { text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); } } @@ -39,8 +40,7 @@ std::pair> constructN buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - } - else if (pageNo == 0) { + } else if (pageNo == 0) { buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); @@ -72,7 +72,7 @@ std::pair constructMessage(size_t pageNo, } else if (ingredientsList.found <= numOfIngredientsOnPage) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - if (ing.status){ + if (ing.status) { text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); } } diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index c7255195..2ea41a16 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -4,6 +4,7 @@ #include "utils/utils.hpp" #include "uuid.hpp" +#include #include #include @@ -14,12 +15,12 @@ std::string to_string(const Uuid& u) { return boost::lexical_cast(u); } -std::string to_string(const api::models::status::PublicationRequestStatus status) { - const std::vector statusStr = {utf8str(u8"🟡 На рассмотрении"), - utf8str(u8"🟢 Принят"), - utf8str(u8"🔴 Отклонен"), - utf8str(u8"⚪️ Вы еще не отправили запрос")}; - return statusStr[static_cast(status)]; +std::string to_string(const cookcookhnya::api::models::status::PublicationRequestStatus status) { + const std::array statusStr = {utf8str(u8"🟡 На рассмотрении"), + utf8str(u8"🟢 Принят"), + utf8str(u8"🔴 Отклонен"), + utf8str(u8"⚪️ Вы еще не отправили запрос")}; + return statusStr[static_cast(status)]; } } // namespace cookcookhnya::utils From cb670108cab4fc04b158bf82d8db761888737dff Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sun, 20 Jul 2025 06:32:39 +0300 Subject: [PATCH 029/106] feat: ingredient search for shopping list --- src/handlers/CMakeLists.txt | 1 + src/handlers/common.hpp | 1 + src/handlers/handlers_list.hpp | 3 + .../personal_account/recipes_list/create.cpp | 25 +++--- .../personal_account/recipes_list/view.cpp | 3 +- src/handlers/shopping_list/search.cpp | 87 +++++++++++++++++++ src/handlers/shopping_list/search.hpp | 17 ++++ src/handlers/shopping_list/view.cpp | 7 +- src/main.cpp | 4 +- src/render/CMakeLists.txt | 1 + src/render/pagination.hpp | 80 +++++++++++++++++ src/render/shopping_list/search.cpp | 47 ++++++++++ src/render/shopping_list/search.hpp | 13 +++ src/render/storage/ingredients/view.cpp | 9 +- src/states.hpp | 12 ++- src/utils/fast_sorted_db.hpp | 4 +- 16 files changed, 286 insertions(+), 28 deletions(-) create mode 100644 src/handlers/shopping_list/search.cpp create mode 100644 src/handlers/shopping_list/search.hpp create mode 100644 src/render/pagination.hpp create mode 100644 src/render/shopping_list/search.cpp create mode 100644 src/render/shopping_list/search.hpp diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 713d7b88..820086f5 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources(main PRIVATE src/handlers/recipes_suggestions/view.cpp src/handlers/shopping_list/create.cpp + src/handlers/shopping_list/search.cpp src/handlers/shopping_list/storage_selection_to_buy.cpp src/handlers/shopping_list/view.cpp diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 855c2ea4..0c27215c 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -44,6 +44,7 @@ using states::StoragesSelection; using states::SuggestedRecipesList; using states::ShoppingListCreation; +using states::ShoppingListIngredientSearch; using states::ShoppingListStorageSelectionToBuy; using states::ShoppingListView; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index f2cee479..86b2cc73 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -22,6 +22,7 @@ #include "recipes_suggestions/view.hpp" #include "shopping_list/create.hpp" +#include "shopping_list/search.hpp" #include "shopping_list/storage_selection_to_buy.hpp" #include "shopping_list/view.hpp" @@ -115,6 +116,8 @@ using shoppingListCreationCQHandler = Handler; using shoppingListStorageSelectionToBuyCQHandler = Handler; +using shoppingListIngredientSearchCQHandler = Handler; +using shoppingListIngredientSearchIQHandler = Handler; // Personal account using personalAccountMenuCQHandler = Handler; diff --git a/src/handlers/personal_account/recipes_list/create.cpp b/src/handlers/personal_account/recipes_list/create.cpp index ab2119b4..50eaa936 100644 --- a/src/handlers/personal_account/recipes_list/create.cpp +++ b/src/handlers/personal_account/recipes_list/create.cpp @@ -10,27 +10,26 @@ namespace cookcookhnya::handlers::personal_account::recipes { using namespace render::personal_account::recipes; void handleCreateCustomRecipeMsg( - CreateCustomRecipe& state, MessageRef m, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi) { - + CreateCustomRecipe& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi) { // Init with no ingredients and link. My suggestion: to use link as author's alias - state.recipeId = recipeApi.create( - m.from->id, api::models::recipe::RecipeCreateBody{.name = m.text, .ingredients = {}, .link = ""}); // - - renderCustomRecipe(false, m.from->id, m.chat->id, state.recipeId, bot, recipeApi); - stateManager.put(RecipeCustomView{.recipeId = state.recipeId, - .pageNo = 0, - .ingredients = {}}); // If it went from creation then as user will return - // from RecipeView to RecipesList on 1st page + auto recipeId = recipeApi.create( + m.from->id, api::models::recipe::RecipeCreateBody{.name = m.text, .ingredients = {}, .link = ""}); + + renderCustomRecipe(false, m.from->id, m.chat->id, recipeId, bot, recipeApi); + // If it went from creation then as user will return + // from RecipeView to RecipesList on 1st page + stateManager.put(RecipeCustomView{.recipeId = recipeId, .pageNo = 0, .ingredients = {}}); }; void handleCreateCustomRecipeCQ( CreateCustomRecipe& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi) { bot.answerCallbackQuery(cq.id); + if (cq.data == "cancel_recipe_creation") { renderCustomRecipesList(state.pageNo, cq.from->id, cq.message->chat->id, bot, recipeApi); - stateManager.put(CustomRecipesList{ - .pageNo = state.pageNo}); // If it went from creation then as user will return from - // RecipeView to RecipesList on page from which they entered in this state; + // If it went from creation then as user will return from + // RecipeView to RecipesList on page from which they entered in this state; + stateManager.put(CustomRecipesList{.pageNo = state.pageNo}); } }; diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index 892e383f..03323bc6 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -30,9 +30,10 @@ void handleCustomRecipesListCQ( bot.answerCallbackQuery(cq.id); return; } + if (data == "custom_recipe_create") { renderRecipeCreation(chatId, userId, bot); - stateManager.put(CreateCustomRecipe{.recipeId = {}, .pageNo = state.pageNo}); + stateManager.put(CreateCustomRecipe{.pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); return; } diff --git a/src/handlers/shopping_list/search.cpp b/src/handlers/shopping_list/search.cpp new file mode 100644 index 00000000..09374d1f --- /dev/null +++ b/src/handlers/shopping_list/search.cpp @@ -0,0 +1,87 @@ +#include "search.hpp" + +#include "backend/api/publicity_filter.hpp" +#include "backend/id_types.hpp" +#include "backend/models/shopping_list.hpp" +#include "handlers/common.hpp" +#include "render/shopping_list/search.hpp" +#include "render/shopping_list/view.hpp" +#include "utils/parsing.hpp" + +#include +#include + +namespace cookcookhnya::handlers::shopping_list { + +using namespace render::shopping_list; +using namespace api; +using namespace api::models::shopping_list; + +const std::size_t searchThreshold = 70; + +void handleShoppingListIngredientSearchCQ( + ShoppingListIngredientSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + + if (cq.data == "back") { + renderShoppingList(state.prevState, userId, chatId, bot); + stateManager.put(auto{std::move(state.prevState)}); + return; + } + + if (cq.data == "page_left" || cq.data == "page_right") { + if (cq.data == "page_left") { + if (state.pagination.pageNo == 0) + return; + state.pagination.pageNo--; + } else { + if (state.pagination.totalItems <= state.pagination.pageNo * searchPageSize) + return; + state.pagination.pageNo++; + } + auto result = api.getIngredientsApi().search(userId, + PublicityFilterType::All, + state.query, + searchThreshold, + searchPageSize, + searchPageSize * state.pagination.pageNo); + state.page = result.page; + renderShoppingListIngredientSearch(state, searchPageSize, userId, chatId, bot); + return; + } + + if (cq.data.starts_with("ingredient_")) { + auto mIngredient = utils::parseSafe(std::string_view{cq.data}.substr(sizeof("ingredient_") - 1)); + if (!mIngredient) + return; + api.getShoppingListApi().put(userId, {*mIngredient}); + auto ingredientInfo = api.getIngredientsApi().get(userId, *mIngredient); + state.prevState.items.put({ShoppingListItem{.ingredientId = ingredientInfo.id, .name = ingredientInfo.name}}); + return; + } +} + +void handleShoppingListIngredientSearchIQ(ShoppingListIngredientSearch& state, + InlineQueryRef iq, + BotRef bot, + IngredientsApiRef api) { + auto userId = iq.from->id; + if (iq.query.empty()) { + state.query = ""; + state.pagination = {}; + state.page = {}; + renderShoppingListIngredientSearch(state, searchPageSize, userId, userId, bot); + return; + } + + auto result = api.search(userId, PublicityFilterType::All, iq.query, searchThreshold, searchPageSize, 0); + state.query = iq.query; + state.pagination.pageNo = 0; + state.pagination.totalItems = result.found; + state.page = result.page; + renderShoppingListIngredientSearch(state, searchPageSize, userId, userId, bot); +} + +} // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/search.hpp b/src/handlers/shopping_list/search.hpp new file mode 100644 index 00000000..78db0847 --- /dev/null +++ b/src/handlers/shopping_list/search.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::shopping_list { + +const std::size_t searchPageSize = 10; + +void handleShoppingListIngredientSearchCQ( + ShoppingListIngredientSearch&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + +void handleShoppingListIngredientSearchIQ(ShoppingListIngredientSearch& state, + InlineQueryRef iq, + BotRef bot, + IngredientsApiRef api); + +} // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index 00447dc5..61fd1ebf 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -2,7 +2,9 @@ #include "backend/id_types.hpp" #include "handlers/common.hpp" +#include "handlers/shopping_list/search.hpp" #include "render/main_menu/view.hpp" +#include "render/shopping_list/search.hpp" #include "render/shopping_list/storage_selection_to_buy.hpp" #include "render/shopping_list/view.hpp" #include "states.hpp" @@ -34,8 +36,9 @@ void handleShoppingListViewCQ( } if (cq.data == "search") { - renderMainMenu(true, userId, cq.message->chat->id, bot, api); - stateManager.put(MainMenu{}); + ShoppingListIngredientSearch newState{.prevState = std::move(state), .query = "", .pagination = {}, .page = {}}; + renderShoppingListIngredientSearch(newState, searchPageSize, userId, chatId, bot); + stateManager.put(std::move(newState)); return; } diff --git a/src/main.cpp b/src/main.cpp index 26b71e0c..9b23f8c1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -65,7 +65,9 @@ int main(int argc, char* argv[]) { recipeCustomViewCQHandler, customRecipeIngredientsSearchCQHandler, customRecipeIngredientsSearchIQHandler, - shoppingListStorageSelectionToBuyCQHandler> + shoppingListStorageSelectionToBuyCQHandler, + shoppingListIngredientSearchCQHandler, + shoppingListIngredientSearchIQHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 3b0625c8..396d3622 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(main PRIVATE src/render/recipes_suggestions/view.cpp src/render/shopping_list/create.cpp + src/render/shopping_list/search.cpp src/render/shopping_list/storage_selection_to_buy.cpp src/render/shopping_list/view.cpp diff --git a/src/render/pagination.hpp b/src/render/pagination.hpp new file mode 100644 index 00000000..8680e712 --- /dev/null +++ b/src/render/pagination.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "render/common.hpp" +#include "utils/utils.hpp" + +#include +#include +#include +#include +#include + +// forward declaration +namespace TgBot { +class InlineKeyboardButton; +} // namespace TgBot + +namespace cookcookhnya::render { + +template > ItemButtons> +struct PaginationMarkup { + ItemButtons itemButtons; + std::shared_ptr leftButton; + std::shared_ptr pageButton; + std::shared_ptr rightButton; + + friend InlineKeyboardBuilder& operator<<(InlineKeyboardBuilder& keyboard, PaginationMarkup&& markup) { + for (auto&& b : std::move(markup).itemButtons) + keyboard << std::move(b) << NewRow{}; + if (markup.leftButton) + keyboard << std::move(markup).leftButton << std::move(markup).pageButton << std::move(markup).rightButton + << NewRow{}; + return keyboard; + } +}; + +/** + * Single column list with an extra row of page switching if more than one page. + */ +template + requires std::is_invocable_r_v, + ItemButtonMaker, + std::ranges::range_reference_t> +auto constructPagination( + std::size_t pageNo, std::size_t pageSize, std::size_t totalItems, R&& page, ItemButtonMaker&& makeItemButton) { + using namespace std::views; + const std::size_t pagesCount = (totalItems + pageSize - 1) / pageSize; // ceiling + const bool lastPage = pageNo + 1 >= pagesCount; + + auto itemButtons = std::forward(page) | transform(std::forward(makeItemButton)); + std::shared_ptr leftButton = nullptr; + std::shared_ptr pageButton = nullptr; + std::shared_ptr rightButton = nullptr; + + if (pagesCount > 1) { + enum PageArrows : std::uint8_t { + NOTHING = 0b00U, + LEFT = 0b01U, + RIGHT = 0b10U, + }; + + const char8_t* const emptyText = u8"ㅤ"; // not empty! invisible symbol + PageArrows b = NOTHING; + if (pageNo != 0) + b = static_cast(b | LEFT); + if (!lastPage) + b = static_cast(b | RIGHT); + + leftButton = + (b & LEFT) != 0 ? makeCallbackButton(u8"◀️", "page_left") : makeCallbackButton(emptyText, "do_not_handle"); + rightButton = + (b & RIGHT) != 0 ? makeCallbackButton(u8"▶️", "page_right") : makeCallbackButton(emptyText, "do_not_handle"); + pageButton = + makeCallbackButton(std::format("{} {} {}", pageNo + 1, utils::utf8str(u8"из"), pagesCount), "dont_handle"); + } + + return PaginationMarkup{ + std::move(itemButtons), std::move(leftButton), std::move(pageButton), std::move(rightButton)}; +} + +} // namespace cookcookhnya::render diff --git a/src/render/shopping_list/search.cpp b/src/render/shopping_list/search.cpp new file mode 100644 index 00000000..394cab9c --- /dev/null +++ b/src/render/shopping_list/search.cpp @@ -0,0 +1,47 @@ +#include "search.hpp" + +#include "backend/models/ingredient.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "render/pagination.hpp" +#include "states.hpp" + +#include +#include +#include + +namespace TgBot { +class InlineKeyboardButton; +} // namespace TgBot + +namespace cookcookhnya::render::shopping_list { + +using namespace api::models::ingredient; +using namespace tg_types; +using namespace std::views; +using namespace std::ranges; + +void renderShoppingListIngredientSearch( + const states::ShoppingListIngredientSearch& state, std::size_t pageSize, UserId userId, ChatId chatId, BotRef bot) { + std::string text = utils::utf8str(u8"🍗 Используйте кнопку ниже, чтобы найти ингредиенты для добавления"); + + InlineKeyboardBuilder keyboard{state.page.size() + 2}; // search, items (n), back + + auto searchButton = std::make_shared(); + searchButton->text = utils::utf8str(u8"🔍 Искать"); + searchButton->switchInlineQueryCurrentChat = ""; + keyboard << std::move(searchButton) << NewRow{}; + + auto makeItemButton = [](const Ingredient& ing) { + // const auto* emptyBrackets = "[ㅤ] "; + return makeCallbackButton(ing.name, "ingredient_" + utils::to_string(ing.id)); + }; + keyboard << constructPagination( + state.pagination.pageNo, pageSize, state.pagination.totalItems, state.page, makeItemButton) + << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto messageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); +} + +} // namespace cookcookhnya::render::shopping_list diff --git a/src/render/shopping_list/search.hpp b/src/render/shopping_list/search.hpp new file mode 100644 index 00000000..f6dfed70 --- /dev/null +++ b/src/render/shopping_list/search.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "render/common.hpp" +#include "states.hpp" + +#include + +namespace cookcookhnya::render::shopping_list { + +void renderShoppingListIngredientSearch( + const states::ShoppingListIngredientSearch& state, std::size_t pageSize, UserId userId, ChatId chatId, BotRef bot); + +} // namespace cookcookhnya::render::shopping_list diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 48e3036e..149fdd68 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -133,13 +133,8 @@ void renderIngredientsListSearch(const states::StorageIngredientsList& state, transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText(text, - chatId, - *messageId, - "", - "", - nullptr, - makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); + bot.editMessageText( + text, chatId, *messageId, makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); } } diff --git a/src/states.hpp b/src/states.hpp index bee50448..d9614ab7 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -26,6 +26,11 @@ struct StorageIdMixin { StorageIdMixin(api::StorageId storageId) : storageId{storageId} {} // NOLINT(*-explicit-*) }; +struct Pagination { + std::size_t pageNo; + std::size_t totalItems; +}; + } // namespace detail struct MainMenu {}; @@ -114,7 +119,6 @@ struct RecipeCustomView { }; struct CreateCustomRecipe { - api::RecipeId recipeId; std::size_t pageNo; }; @@ -139,6 +143,9 @@ struct ShoppingListStorageSelectionToBuy { }; struct ShoppingListIngredientSearch { ShoppingListView prevState; + std::string query; + detail::Pagination pagination; + std::vector page; }; using State = std::variant; + RecipeIngredientsSearch, + ShoppingListIngredientSearch>; using StateManager = tg_stater::StateProxy>; diff --git a/src/utils/fast_sorted_db.hpp b/src/utils/fast_sorted_db.hpp index 125280c7..0ec3cb26 100644 --- a/src/utils/fast_sorted_db.hpp +++ b/src/utils/fast_sorted_db.hpp @@ -80,11 +80,11 @@ class FastSortedDb { } [[nodiscard]] auto getValues() { - return items | std::views::transform([](auto& p) -> T& { return p.second; }); + return items | std::views::values; } [[nodiscard]] auto getValues() const { - return items | std::views::transform([](const auto& p) -> const T& { return p.second; }); + return items | std::views::values; } }; From 35497d6e9da754979f0443ae95a3102f73de751b Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sun, 20 Jul 2025 06:32:53 +0300 Subject: [PATCH 030/106] fix: cache in inline searches --- src/handlers/personal_account/recipe/search_ingredients.cpp | 2 +- src/handlers/storage/ingredients/view.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 233faace..091561a7 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -112,7 +112,7 @@ void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, updateSearch(state, true, bot, userId, api); } // Cache is not disabled on Windows and Linux desktops. Works on Android and Web - bot.answerInlineQuery(iq.id, {}, 0); + // bot.answerInlineQuery(iq.id, {}, 0); } } // namespace cookcookhnya::handlers::personal_account::recipes diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 7f979f8e..ac450179 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -106,7 +106,7 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, updateSearch(state, true, bot, userId, api); } // Cache is not disabled on Windows and Linux desktops. Works on Android and Web - bot.answerInlineQuery(iq.id, {}, 0); + // bot.answerInlineQuery(iq.id, {}, 0); } } // namespace cookcookhnya::handlers::storage::ingredients From 2848b863b19cb96ac2d07908cc0562f5f5bd6159 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Sun, 20 Jul 2025 06:36:08 +0300 Subject: [PATCH 031/106] chore: remove extra character --- src/handlers/shopping_list/create.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 7bbb6be5..0d0ed0d6 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -43,7 +43,7 @@ void handleShoppingListCreationCQ( return; } - if (data[0] == '+') {= + if (data[0] == '+') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); if (newIngredientId) { From 30d5adfa9937d318f003c169db7d142a42d7112e Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Sun, 20 Jul 2025 06:39:35 +0300 Subject: [PATCH 032/106] fix: button data in shopping_list/create.hpp --- src/render/shopping_list/create.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index 40adbb5d..63914c4c 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -34,7 +34,8 @@ void renderShoppingListCreation(const std::vector& selectedIngredien const bool isSelected = std::ranges::contains(selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); - const char* actionPrefix = isSelected ? "+" : "-"; + // button data is onclick action for bot: "+" is "add", "-" is "remove" + const char* actionPrefix = isSelected ? "-" : "+"; std::string text = std::format("{} {}", emoji, ing.name); std::string data = actionPrefix + utils::to_string(ing.id); keyboard << makeCallbackButton(text, data); From cba89eb3de1f9e4204dcb307b4f0e6b491712cb2 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sun, 20 Jul 2025 16:02:09 +0300 Subject: [PATCH 033/106] refactor: fix linter errors --- .../personal_account/recipes_list/view.cpp | 10 +- src/handlers/recipes_suggestions/view.cpp | 8 +- src/render/main_menu/view.cpp | 35 ++--- src/render/pagination.hpp | 1 + .../ingredients_list/view.cpp | 1 + .../ingredients_list/view.hpp | 2 + .../personal_account/recipes_list/view.cpp | 127 ++++-------------- src/render/recipe/add_storage.cpp | 3 + src/render/recipe/add_storage.hpp | 2 + src/render/recipe/view.cpp | 6 +- src/render/recipe/view.hpp | 3 + src/render/recipes_suggestions/view.cpp | 124 ++++------------- src/render/shopping_list/view.cpp | 2 +- src/states.hpp | 1 + 14 files changed, 87 insertions(+), 238 deletions(-) diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index 03323bc6..a49d2c8a 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -49,11 +49,11 @@ void handleCustomRecipesListCQ( } if (data != "dont_handle") { - auto pageNo = utils::parseSafe(data); - if (pageNo) { - state.pageNo = *pageNo; - } - renderCustomRecipesList(*pageNo, userId, chatId, bot, api); + if (data == "page_left") + state.pageNo--; + else if (data == "page_right") + state.pageNo++; + renderCustomRecipesList(state.pageNo, userId, chatId, bot, api); return; } } diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index 4b2013d3..b992223b 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -64,10 +64,10 @@ void handleSuggestedRecipesListCQ( } if (data != "dont_handle") { - auto pageNo = utils::parseSafe(data); - if (pageNo) - state.pageNo = *pageNo; - + if (data == "page_left") + state.pageNo--; + else if (data == "page_right") + state.pageNo++; renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); return; } diff --git a/src/render/main_menu/view.cpp b/src/render/main_menu/view.cpp index ea7e60f4..b8513769 100644 --- a/src/render/main_menu/view.cpp +++ b/src/render/main_menu/view.cpp @@ -4,8 +4,8 @@ #include "render/common.hpp" #include "utils/utils.hpp" -#include #include +#include #include namespace cookcookhnya::render::main_menu { @@ -20,34 +20,27 @@ void renderMainMenu(bool toBeEdited, StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); - const std::size_t buttonRows = storages.empty() ? 3 : 4; - InlineKeyboard keyboard(buttonRows); - - if (!storages.empty()) { - keyboard[0].push_back(makeCallbackButton(u8"🍱 Хранилища", "storage_list")); - keyboard[1].push_back(makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat")); - keyboard[2].push_back(makeCallbackButton(u8"🗒 Список покупок", "shopping_list")); - keyboard[3].push_back(makeCallbackButton(u8"👤 Личный кабинет", "personal_account")); - } else { - keyboard[0].push_back(makeCallbackButton(u8"🍱 Хранилища", "storage_list")); - keyboard[1].push_back(makeCallbackButton(u8"🗒 Список покупок", "shopping_list")); - keyboard[2].push_back(makeCallbackButton(u8"👤 Личный кабинет", "personal_account")); - } - auto text = utils::utf8str( - u8"🍳 Добро пожаловать в CookCookNya — ваш личный бот для быстро подбора рецептов и многого другого!"); + u8"🍳 Добро пожаловать в CookCookNya — ваш личный бот для быстрого подбора рецептов и многого другого!"); if (inviteStorage) { - if (*inviteStorage) { + if (*inviteStorage) text += utils::utf8str(u8"\n\nВы были успешно добавлены в хранилище 🍱") + **inviteStorage; - } else { + else text += utils::utf8str(u8"\n\nК сожалению, данное приглашение уже было использовано 🥲"); - } } + + InlineKeyboardBuilder keyboard{4}; + keyboard << makeCallbackButton(u8"🍱 Хранилища", "storage_list") << NewRow{}; + if (!storages.empty()) + keyboard << makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat") << NewRow{}; + keyboard << makeCallbackButton(u8"🗒 Список покупок", "shopping_list") << NewRow{} + << makeCallbackButton(u8"👤 Личный кабинет", "personal_account"); + if (toBeEdited) { if (auto messageId = message::getMessageId(userId)) - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } else { - auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); + auto message = bot.sendMessage(chatId, text, std::move(keyboard)); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/pagination.hpp b/src/render/pagination.hpp index 8680e712..5527cfed 100644 --- a/src/render/pagination.hpp +++ b/src/render/pagination.hpp @@ -56,6 +56,7 @@ auto constructPagination( NOTHING = 0b00U, LEFT = 0b01U, RIGHT = 0b10U, + BOTH = 0b11U, }; const char8_t* const emptyText = u8"ㅤ"; // not empty! invisible symbol diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index a5bc7ebd..daa05dce 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -7,6 +7,7 @@ #include "utils/to_string.hpp" #include "utils/utils.hpp" +#include #include #include #include diff --git a/src/render/personal_account/ingredients_list/view.hpp b/src/render/personal_account/ingredients_list/view.hpp index 6cb4a0a6..a0ca2310 100644 --- a/src/render/personal_account/ingredients_list/view.hpp +++ b/src/render/personal_account/ingredients_list/view.hpp @@ -2,6 +2,8 @@ #include "render/common.hpp" +#include + namespace cookcookhnya::render::personal_account::ingredients { void renderCustomIngredientsList( diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index eed3b57c..8a7a906e 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -1,135 +1,54 @@ #include "view.hpp" #include "backend/api/publicity_filter.hpp" -#include "backend/api/recipes.hpp" #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" +#include "render/pagination.hpp" #include "render/personal_account/recipes_list/view.hpp" #include "utils/to_string.hpp" #include "utils/utils.hpp" -#include -#include #include -#include #include +#include #include -#include -namespace cookcookhnya::render::personal_account::recipes { - -namespace { - -// offset is variable which defines amout of rows before beggining of paging -// fullKeyBoardSize is self explanatory -InlineKeyboard constructNavigationsMarkup(std::size_t offset, - std::size_t fullKeyBoardSize, - std::size_t pageNo, - std::size_t numOfRecipesOnPage, - api::models::recipe::RecipesList& recipesList) { - const size_t amountOfRecipes = recipesList.found; - std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); +namespace TgBot { +class InlineKeyboardMarkup; +} // namespace TgBot - const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); +namespace cookcookhnya::render::personal_account::recipes { - const bool lastPage = amountOfRecipes - numOfRecipesOnPage * (pageNo + 1) <= 0; +using namespace api::models::recipe; - if (offset + recipesToShow >= fullKeyBoardSize) { - InlineKeyboard error(0); - return error; - } - const size_t arrowsRow = offset + recipesToShow; - // Don't reserve for arrows if it's first page is max(im) - InlineKeyboard keyboard(pageNo == 0 && lastPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); - std::size_t counter = 0; - for (std::size_t i = 0; i < recipesToShow; i++) { - // Print on button in form "1. {Recipe}" - keyboard[i + offset].push_back(makeCallbackButton( - std::format("{}. {}", 1 + counter + ((pageNo)*numOfRecipesOnPage), recipesList.page[counter].name), - std::format("r", recipesList.page[counter].id))); // RECIPE ID - counter++; - } - if (pageNo == 0 && lastPage) { - // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; - } - keyboard[arrowsRow].reserve(3); +namespace { - enum PageArrows : std::uint8_t { - NOTHING = 0b00U, - LEFT = 0b01U, - RIGHT = 0b10U, +std::shared_ptr +constructKeyboard(std::size_t pageNo, std::size_t pageSize, RecipesList& recipesList) { + InlineKeyboardBuilder keyboard; + keyboard << makeCallbackButton(u8"🆕 Создать", "custom_recipe_create") << NewRow{}; + auto makeRecipeButton = [i = (pageNo * pageSize) + 1](RecipeSummary& r) mutable { + return makeCallbackButton(std::format("{}. {}", i++, r.name), "recipe_" + utils::to_string(r.id)); }; - - PageArrows b = NOTHING; - if (pageNo != 0) - b = static_cast(b | LEFT); - if (!lastPage) - b = static_cast(b | RIGHT); - - if ((b & LEFT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - if ((b & RIGHT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - // Put pageNo as button - keyboard[arrowsRow].insert(keyboard[arrowsRow].begin() + 1, - makeCallbackButton(std::format("{} из {}", pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard constructOnlyCreate() { - InlineKeyboard keyboard(2); - keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard -constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::RecipesList& recipesList) { - // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 - // for adding new recipe - other buttons are recipes - const size_t numOfRows = 3; - const size_t offset = 1; // Number of rows before list - - const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - - InlineKeyboard keyboard = - recipesList.found == 0 ? constructOnlyCreate() - : constructNavigationsMarkup( - offset, numOfRows + recipesToShow, (pageNo + 1), numOfRecipesOnPage, recipesList); - if (keyboard.empty()) { // If error happened - return keyboard; - } - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "custom_recipe_create")); - - return keyboard; + keyboard << constructPagination(pageNo, pageSize, recipesList.found, recipesList.page, makeRecipeButton) + << makeCallbackButton(u8"↩️ Назад", "back"); + return std::move(keyboard); } } // namespace void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { - std::string pageInfo = utils::utf8str(u8"🔪 Рецепты созданные вами:"); - - auto messageId = message::getMessageId(userId); - const std::size_t numOfRecipesOnPage = 5; auto recipesList = recipesApi.getList(userId, PublicityFilterType::Custom, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); - if (recipesList.found == 0) { - pageInfo = utils::utf8str(u8"🔪 Вы находитесь в Мои рецепты. Создавайте и делитесь новыми рецептами.\n\n"); - } - if (messageId) { - bot.editMessageText( - pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + std::string pageInfo = utils::utf8str( + recipesList.found > 0 ? u8"🔪 Рецепты созданные вами:" + : u8"🔪 Вы находитесь в Мои рецепты. Создавайте и делитесь новыми рецептами.\n\n"); + + if (auto messageId = message::getMessageId(userId)) { + bot.editMessageText(pageInfo, chatId, *messageId, constructKeyboard(pageNo, numOfRecipesOnPage, recipesList)); } } } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index 164e897b..2f9b0aa9 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -6,9 +6,12 @@ #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/ingredients_availability.hpp" +#include "utils/to_string.hpp" #include "utils/utils.hpp" #include "view.hpp" +#include +#include #include #include #include diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index d6fb071e..2f1ba979 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -1,11 +1,13 @@ #pragma once #include "backend/id_types.hpp" +#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" #include "render/recipe/view.hpp" #include "utils/ingredients_availability.hpp" +#include #include namespace cookcookhnya::render::recipe { diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index 4b2fc444..aad04e72 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -7,9 +7,11 @@ #include "utils/ingredients_availability.hpp" #include "utils/utils.hpp" +#include #include #include #include +#include namespace cookcookhnya::render::recipe { @@ -24,7 +26,7 @@ recipeView(const std::vector +#include #include namespace cookcookhnya::render::recipe { diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 73de6998..97928d9f 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -4,18 +4,21 @@ #include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" +#include "render/pagination.hpp" #include "utils/utils.hpp" -#include -#include #include -#include #include +#include #include #include +#include #include +namespace TgBot { +class InlineKeyboardMarkup; +} // namespace TgBot + namespace cookcookhnya::render::recipes_suggestions { using namespace api::models::storage; @@ -25,96 +28,16 @@ using namespace std::ranges; namespace { -// offset is variable which defines amout of rows before beggining of paging -// fullKeyBoardSize is self explanatory -InlineKeyboard constructNavigationsMarkup(std::size_t offset, - std::size_t fullKeyBoardSize, - std::size_t pageNo, - std::size_t numOfRecipesOnPage, - RecipesListWithIngredientsCount recipesList) { - const std::size_t amountOfRecipes = recipesList.found; - std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); - - const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that - // will be actually shown - const bool lastPage = - static_cast(amountOfRecipes) - static_cast(numOfRecipesOnPage) * (static_cast(pageNo) + 1) <= 0; - - if (offset + recipesToShow > fullKeyBoardSize) { // IN ERROR HANDLING MAY USE ASSERT - InlineKeyboard error(0); - return error; - } - const std::size_t arrowsRow = offset + recipesToShow; - - InlineKeyboard keyboard(pageNo == 0 && lastPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); - int counter = 0; - for (std::size_t i = 0; i < recipesToShow; i++) { - keyboard[i + offset].push_back(makeCallbackButton(std::format("{}. {} [{} из {}]", - 1 + counter + ((pageNo)*numOfRecipesOnPage), - recipesList.page[counter].name, - recipesList.page[counter].available, - recipesList.page[counter].total), - std::format("recipe_{}", recipesList.page[counter].id))); - counter++; - } - if (pageNo == 0 && lastPage) { - // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; - } - keyboard[arrowsRow].reserve(3); - - enum PageArrows : std::uint8_t { - NOTHING = 0b00U, - LEFT = 0b01U, - RIGHT = 0b10U, +std::shared_ptr +constructKeyboard(std::size_t pageNo, std::size_t pageSize, RecipesListWithIngredientsCount& recipesList) { + InlineKeyboardBuilder keyboard; + auto makeRecipeButton = [i = (pageNo * pageSize) + 1](RecipeSummaryWithIngredients& r) mutable { + return makeCallbackButton(std::format("{}. {} [{} из {}]", i++, r.name, r.available, r.total), + std::format("recipe_{}", r.id)); }; - - PageArrows b = NOTHING; - if (pageNo != 0) - b = static_cast(b | LEFT); - if (!lastPage) - b = static_cast(b | RIGHT); - - if ((b & LEFT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - if ((b & RIGHT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - // Put pageNo as button - keyboard[arrowsRow].insert(keyboard[arrowsRow].begin() + 1, - makeCallbackButton(std::format("{} из {}", pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard constructOnlyBack() { - InlineKeyboard keyboard(1); - keyboard[0].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard -constructMarkup(std::size_t pageNo, std::size_t numOfRecipesOnPage, RecipesListWithIngredientsCount& recipesList) { - const std::size_t numOfRows = 2; // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS) - const std::size_t offset = 0; // Number of rows before list - const std::size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - - InlineKeyboard keyboard = - recipesList.found == 0 - ? constructOnlyBack() - : constructNavigationsMarkup(offset, numOfRows + recipesToShow, pageNo, numOfRecipesOnPage, recipesList); - if (keyboard.empty()) { // If error happened ADD PROPER ERROR HANDLING IF FUNCTION WILL BE REUSED - return keyboard; - } - - return keyboard; + keyboard << constructPagination(pageNo, pageSize, recipesList.found, recipesList.page, makeRecipeButton) + << makeCallbackButton(u8"↩️ Назад", "back"); + return std::move(keyboard); } } // namespace @@ -125,22 +48,21 @@ void renderRecipesSuggestion(std::vector& storages, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { - std::string pageInfo = utils::utf8str(u8"🔪 Рецепты подобранные специально для вас"); const std::size_t numOfRecipesOnPage = 5; const std::size_t numOfRecipes = 500; auto storagesIds = storages | views::transform(&StorageSummary::id) | to(); auto recipesList = recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipes, pageNo * numOfRecipesOnPage); - if (recipesList.found == 0) { - pageInfo = utils::utf8str(u8"😔 К сожалению, нам не удалось найти подходящие рецепты для вас..."); - } + std::string text = + utils::utf8str(recipesList.found > 0 ? u8"🔪 Рецепты подобранные специально для вас" + : u8"😔 К сожалению, нам не удалось найти подходящие рецепты для вас..."); + auto keyboardMarkup = constructKeyboard(pageNo, numOfRecipesOnPage, recipesList); + if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText( - pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + bot.editMessageText(text, chatId, *messageId, keyboardMarkup); } else { - auto message = bot.sendMessage( - chatId, pageInfo, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + auto message = bot.sendMessage(chatId, text, keyboardMarkup); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 6713f363..649287d6 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -14,7 +14,7 @@ namespace cookcookhnya::render::shopping_list { void renderShoppingList(const states::ShoppingListView& state, UserId userId, ChatId chatId, BotRef bot) { auto items = state.items.getValues(); - bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); + const bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); InlineKeyboardBuilder keyboard{3 + items.size()}; // add, remove and/or buy, list (n), back diff --git a/src/states.hpp b/src/states.hpp index 44b7297a..1688be1a 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -2,6 +2,7 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" +#include "backend/models/recipe.hpp" #include "backend/models/shopping_list.hpp" #include "backend/models/storage.hpp" #include "utils/fast_sorted_db.hpp" From 2ef20b6d99d315f3f7915677278e506edd1cd332 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sun, 20 Jul 2025 16:22:10 +0300 Subject: [PATCH 034/106] refactor: fix linter errors --- src/render/main_menu/view.hpp | 1 + src/render/recipes_suggestions/view.cpp | 2 +- src/render/shopping_list/search.cpp | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/render/main_menu/view.hpp b/src/render/main_menu/view.hpp index 5d5d885a..95c7de2a 100644 --- a/src/render/main_menu/view.hpp +++ b/src/render/main_menu/view.hpp @@ -3,6 +3,7 @@ #include "render/common.hpp" #include +#include namespace cookcookhnya::render::main_menu { diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 97928d9f..3a1b069f 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -54,7 +54,7 @@ void renderRecipesSuggestion(std::vector& storages, auto storagesIds = storages | views::transform(&StorageSummary::id) | to(); auto recipesList = recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipes, pageNo * numOfRecipesOnPage); - std::string text = + const std::string text = utils::utf8str(recipesList.found > 0 ? u8"🔪 Рецепты подобранные специально для вас" : u8"😔 К сожалению, нам не удалось найти подходящие рецепты для вас..."); auto keyboardMarkup = constructKeyboard(pageNo, numOfRecipesOnPage, recipesList); diff --git a/src/render/shopping_list/search.cpp b/src/render/shopping_list/search.cpp index 394cab9c..3e5d059f 100644 --- a/src/render/shopping_list/search.cpp +++ b/src/render/shopping_list/search.cpp @@ -5,8 +5,11 @@ #include "render/common.hpp" #include "render/pagination.hpp" #include "states.hpp" +#include "utils/to_string.hpp" +#include "utils/utils.hpp" #include +#include #include #include @@ -23,7 +26,7 @@ using namespace std::ranges; void renderShoppingListIngredientSearch( const states::ShoppingListIngredientSearch& state, std::size_t pageSize, UserId userId, ChatId chatId, BotRef bot) { - std::string text = utils::utf8str(u8"🍗 Используйте кнопку ниже, чтобы найти ингредиенты для добавления"); + const std::string text = utils::utf8str(u8"🍗 Используйте кнопку ниже, чтобы найти ингредиенты для добавления"); InlineKeyboardBuilder keyboard{state.page.size() + 2}; // search, items (n), back From 1af279271527a2033d445cb94cf55ea0447d3db9 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Sun, 20 Jul 2025 17:41:19 +0300 Subject: [PATCH 035/106] fix: took wrong class for request history --- src/backend/api/api.hpp | 18 +++- src/backend/api/recipes.cpp | 16 +--- src/backend/api/recipes.hpp | 7 +- src/backend/api/request_history.cpp | 21 +++++ src/backend/api/request_history.hpp | 23 +++++ src/backend/models/recipe.cpp | 6 +- src/backend/models/recipe.hpp | 6 +- src/backend/models/request_history.cpp | 27 ++++++ src/backend/models/request_history.hpp | 27 ++++++ src/handlers/CMakeLists.txt | 1 + src/handlers/handlers_list.hpp | 5 +- .../personal_account/request_history.cpp | 21 ++++- .../personal_account/request_history.hpp | 6 +- src/handlers/personal_account/view.cpp | 2 +- src/main.cpp | 3 +- src/render/CMakeLists.txt | 1 + src/render/common.hpp | 2 + .../recipe/publication_history.cpp | 45 +--------- .../personal_account/request_history.cpp | 84 ++++++++++++++++--- .../personal_account/request_history.hpp | 6 +- src/render/personal_account/view.cpp | 2 +- 21 files changed, 236 insertions(+), 93 deletions(-) create mode 100644 src/backend/api/request_history.cpp create mode 100644 src/backend/api/request_history.hpp create mode 100644 src/backend/models/request_history.cpp create mode 100644 src/backend/models/request_history.hpp diff --git a/src/backend/api/api.hpp b/src/backend/api/api.hpp index 2b83d07a..e93afd43 100644 --- a/src/backend/api/api.hpp +++ b/src/backend/api/api.hpp @@ -2,9 +2,11 @@ #include "backend/api/ingredients.hpp" #include "backend/api/recipes.hpp" +#include "backend/api/request_history.hpp" #include "backend/api/shopping_lists.hpp" #include "backend/api/storages.hpp" #include "backend/api/users.hpp" +#include "request_history.hpp" #include @@ -20,13 +22,16 @@ class ApiClient { IngredientsApi ingredients; RecipesApi recipes; ShoppingListApi shoppingList; + RequestHistoryApi requestHistory; public: explicit ApiClient(const std::string& apiAddress) - : api{apiAddress}, users{api}, storages{api}, ingredients{api}, recipes{api}, shoppingList{api} {} + : api{apiAddress}, users{api}, storages{api}, ingredients{api}, recipes{api}, shoppingList{api}, + requestHistory{api} {} ApiClient(const ApiClient&) = delete; ApiClient(ApiClient&& other) noexcept - : api{std::move(other.api)}, users{api}, storages{api}, ingredients{api}, recipes{api}, shoppingList{api} {} + : api{std::move(other.api)}, users{api}, storages{api}, ingredients{api}, recipes{api}, shoppingList{api}, + requestHistory{api} {} ApiClient& operator=(const ApiClient&) = delete; ApiClient& operator=(ApiClient&& other) noexcept { if (&other == this) @@ -37,6 +42,7 @@ class ApiClient { ingredients = IngredientsApi{api}; recipes = RecipesApi{api}; shoppingList = ShoppingListApi{api}; + requestHistory = RequestHistoryApi{api}; return *this; } ~ApiClient() = default; @@ -80,6 +86,14 @@ class ApiClient { operator const ShoppingListApi&() const { // NOLINT(*-explicit-*) return shoppingList; } + + [[nodiscard]] const RequestHistoryApi& getRequestHistoryApi() const { + return requestHistory; + } + + operator const RequestHistoryApi&() const { // NOLINT(*-explicit-*) + return requestHistory; + } }; } // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 5bee6d20..cb56a7eb 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -72,19 +72,9 @@ void RecipesApi::publishCustom(UserId user, RecipeId recipe) const { } // GET /recipe/{id}/moderation-history -std::vector RecipesApi::getRequestHistory(UserId user, RecipeId recipe) const { - return jsonGetAuthed>(user, - std::format("/recipe/{}/moderation-history", recipe)); +std::vector RecipesApi::getRecipeRequestHistory(UserId user, RecipeId recipe) const { + return jsonGetAuthed>(user, + std::format("/recipe/{}/moderation-history", recipe)); } -// GET /publication-requests?size={}&offset={} -[[nodiscard]] std::vector -RecipesApi::getAllRequestHistory(UserId user, std::size_t size, std::size_t offset) const { - return jsonGetAuthed>(user, - "/publication-requests", - { - {"size", utils::to_string(size)}, - {"offset", utils::to_string(offset)}, - }); -} } // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index f0e691d0..004dbd39 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -39,17 +39,14 @@ class RecipesApi : ApiBase { RecipeId create(UserId user, // NOLINT(*-nodiscard) const models::recipe::RecipeCreateBody& body) const; - [[nodiscard]] std::vector getRequestHistory(UserId user, - RecipeId recipe) const; + [[nodiscard]] std::vector getRecipeRequestHistory(UserId user, + RecipeId recipe) const; [[nodiscard]] models::recipe::RecipeDetails get(UserId user, RecipeId recipeId) const; void delete_(UserId user, RecipeId recipe) const; void publishCustom(UserId user, RecipeId recipe) const; - - [[nodiscard]] std::vector - getAllRequestHistory(UserId user, std::size_t size = 10, std::size_t offset = 0) const; // NOLINT(*magic-number*) }; } // namespace cookcookhnya::api diff --git a/src/backend/api/request_history.cpp b/src/backend/api/request_history.cpp new file mode 100644 index 00000000..7a9f7db3 --- /dev/null +++ b/src/backend/api/request_history.cpp @@ -0,0 +1,21 @@ +#include "request_history.hpp" +#include "backend/models/request_history.hpp" + +#include + +#include +#include +namespace cookcookhnya::api { + +using namespace models::request_history; +// GET /publication-requests?size={}&offset={} +[[nodiscard]] std::vector +RequestHistoryApi::getAllRequestHistory(UserId user, std::size_t size, std::size_t offset) const { + return jsonGetAuthed>(user, + "/publication-requests", + { + {"size", utils::to_string(size)}, + {"offset", utils::to_string(offset)}, + }); +} +} // namespace cookcookhnya::api diff --git a/src/backend/api/request_history.hpp b/src/backend/api/request_history.hpp new file mode 100644 index 00000000..6e938448 --- /dev/null +++ b/src/backend/api/request_history.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "backend/models/request_history.hpp" +#include "base.hpp" + +#include + +#include +#include + +namespace cookcookhnya::api { + +class RequestHistoryApi : ApiBase { + friend class ApiClient; + + explicit RequestHistoryApi(httplib::Client& api) : ApiBase{api} {} + + public: + [[nodiscard]] std::vector + getAllRequestHistory(UserId user, std::size_t size = 10, std::size_t offset = 0) const; // NOLINT(*magic-number*) +}; + +} // namespace cookcookhnya::api diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 87e0a1c8..c30d8893 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -76,14 +76,14 @@ RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/ }; } -PublicationHistoryInstance tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { +PublicationHistoryRecipe tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .created = utils::parseIsoTime(value_to(j.at("created"))), .reason = j.as_object().if_contains("reason") - ? value_to(j.at("reason")) + ? value_to(j.at("reason")) : "", .status = j.as_object().if_contains("status") - ? value_to(j.at("status")) + ? value_to(j.at("status")) : PublicationRequestStatus::NO_REQUEST, .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) : std::chrono::time_point(), diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 8fe3d89b..be5e88db 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -92,14 +92,14 @@ struct RecipeSearchResponse { const boost::json::value& j); }; -struct PublicationHistoryInstance { +struct PublicationHistoryRecipe { std::chrono::system_clock::time_point created; std::optional reason; PublicationRequestStatus status{}; std::optional updated; - friend PublicationHistoryInstance tag_invoke(boost::json::value_to_tag, - const boost::json::value& j); + friend PublicationHistoryRecipe tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); }; } // namespace cookcookhnya::api::models::recipe diff --git a/src/backend/models/request_history.cpp b/src/backend/models/request_history.cpp new file mode 100644 index 00000000..d5b9e1d5 --- /dev/null +++ b/src/backend/models/request_history.cpp @@ -0,0 +1,27 @@ +#include "request_history.hpp" +#include "backend/models/recipe.hpp" +#include "utils/serialization.hpp" +#include +#include +#include + +namespace cookcookhnya::api::models::request_history { + +namespace json = boost::json; + +PublicationHistoryInstance tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { + return { + .name = value_to(j.at("name")), + .created = utils::parseIsoTime(value_to(j.at("created"))), + .reason = j.as_object().if_contains("reason") + ? value_to(j.at("reason")) + : "", + .status = j.as_object().if_contains("status") + ? value_to(j.at("status")) + : recipe::PublicationRequestStatus::NO_REQUEST, + .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) + : std::chrono::time_point(), + }; +} + +} // namespace cookcookhnya::api::models::request_history diff --git a/src/backend/models/request_history.hpp b/src/backend/models/request_history.hpp new file mode 100644 index 00000000..ce671f05 --- /dev/null +++ b/src/backend/models/request_history.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "backend/models/recipe.hpp" +#include "request_history.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace cookcookhnya::api::models::request_history { + +struct PublicationHistoryInstance { + std::string name; + std::chrono::system_clock::time_point created; + std::optional reason; + api::models::recipe::PublicationRequestStatus status{}; + std::optional updated; + + friend PublicationHistoryInstance tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); +}; +} // namespace cookcookhnya::api::models::request_history diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index fcdbee3d..f23704ea 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -15,6 +15,7 @@ target_sources(main PRIVATE src/handlers/personal_account/recipe/publication_history.cpp src/handlers/personal_account/view.cpp + src/handlers/personal_account/request_history.cpp src/handlers/recipe/add_storage.cpp src/handlers/recipe/view.cpp diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 8be8b393..2429d101 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -15,6 +15,7 @@ #include "personal_account/recipes_list/create.hpp" #include "personal_account/recipes_list/view.hpp" +#include "personal_account/request_history.hpp" #include "personal_account/view.hpp" #include "recipe/add_storage.hpp" @@ -52,7 +53,7 @@ using namespace main_menu; using namespace personal_account; using namespace personal_account::ingredients; using namespace personal_account::recipes; -using namespace recipe; +using namespace handlers::recipe; using namespace shopping_list; using namespace storage; using namespace storage::ingredients; @@ -61,6 +62,7 @@ using namespace storages_list; using namespace storages_selection; using namespace recipes_suggestions; using namespace personal_account::publication_history; +using namespace personal_account::recipe::publication_history; using namespace tg_stater; @@ -120,6 +122,7 @@ using shoppingListStorageSelectionToBuyCQHandler = // Personal account using personalAccountMenuCQHandler = Handler; +using allPublicationHistoryHandler = Handler; // Custom Recipes List using customRecipesListCQHandler = Handler; diff --git a/src/handlers/personal_account/request_history.cpp b/src/handlers/personal_account/request_history.cpp index 7772f600..0c11caaa 100644 --- a/src/handlers/personal_account/request_history.cpp +++ b/src/handlers/personal_account/request_history.cpp @@ -1,8 +1,23 @@ #include "request_history.hpp" -namespace cookcookhnya::handlers::history { +#include "render/personal_account/view.hpp" + +namespace cookcookhnya::handlers::personal_account::publication_history { + +using namespace render::personal_account; void handleAllPublicationHistoryCQ( - AllPublicationHistory& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + AllPublicationHistory& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + const std::string data = cq.data; + bot.answerCallbackQuery(cq.id); + auto chatId = cq.message->chat->id; + auto userId = cq.from->id; + + if (data == "back") { + renderPersonalAccountMenu(userId, chatId, bot); + stateManager.put(PersonalAccountMenu{}); + return; + } +} -} // namespace cookcookhnya::handlers::history +} // namespace cookcookhnya::handlers::personal_account::publication_history diff --git a/src/handlers/personal_account/request_history.hpp b/src/handlers/personal_account/request_history.hpp index c65f509b..072d03a5 100644 --- a/src/handlers/personal_account/request_history.hpp +++ b/src/handlers/personal_account/request_history.hpp @@ -2,9 +2,9 @@ #include "handlers/common.hpp" -namespace cookcookhnya::handlers::history { +namespace cookcookhnya::handlers::personal_account::publication_history { void handleAllPublicationHistoryCQ( - AllPublicationHistory& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + AllPublicationHistory& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); -} // namespace cookcookhnya::handlers::history +} // namespace cookcookhnya::handlers::personal_account::publication_history diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index a7bd45b3..d28f7df9 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -13,7 +13,7 @@ namespace cookcookhnya::handlers::personal_account { using namespace render::personal_account::recipes; using namespace render::main_menu; using namespace render::personal_account::ingredients; -using namespace render::personal_account::history; +using namespace render::personal_account::publication_history; void handlePersonalAccountMenuCQ( PersonalAccountMenu& /**/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { diff --git a/src/main.cpp b/src/main.cpp index e5a12833..ccd4e298 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,7 +66,8 @@ int main(int argc, char* argv[]) { customRecipeIngredientsSearchCQHandler, customRecipeIngredientsSearchIQHandler, shoppingListStorageSelectionToBuyCQHandler, - customRecipePublicationHistoryHandler> + customRecipePublicationHistoryHandler, + allPublicationHistoryHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 7574d54c..462e17c5 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(main PRIVATE src/render/personal_account/recipe/publication_history.cpp src/render/personal_account/view.cpp + src/render/personal_account/request_history.cpp src/render/recipe/add_storage.cpp src/render/recipe/view.cpp diff --git a/src/render/common.hpp b/src/render/common.hpp index 1fdb5bee..d6481d56 100644 --- a/src/render/common.hpp +++ b/src/render/common.hpp @@ -3,6 +3,7 @@ #include "backend/api/api.hpp" #include "backend/api/ingredients.hpp" #include "backend/api/recipes.hpp" +#include "backend/api/request_history.hpp" #include "backend/api/shopping_lists.hpp" #include "backend/api/storages.hpp" #include "backend/api/users.hpp" @@ -33,6 +34,7 @@ using IngredientsApiRef = const api::IngredientsApi&; using UserApiRef = const api::UsersApi&; using RecipesApiRef = const api::RecipesApi&; using ShoppingListApiRef = const api::ShoppingListApi&; +using RequestHistoryApiRef = const api::RequestHistoryApi&; using UserId = tg_types::UserId; using ChatId = tg_types::ChatId; diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp index 92ba5d18..863d7cd7 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -23,47 +23,8 @@ void renderPublicationHistory(UserId userId, bool isPeek, BotRef bot, RecipesApiRef recipesApi) { - // auto history = recipesApi.getModerationHistory(userId, recipeId); - std::vector history = { - { - .created = std::chrono::system_clock::now(), - .status = api::models::recipe::PublicationRequestStatus::PENDING, - }, - { - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - }, - { - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }}; + auto history = recipesApi.getRecipeRequestHistory(userId, recipeId); + InlineKeyboardBuilder keyboard{2}; // confirm and back std::string toPrint; @@ -84,7 +45,7 @@ void renderPublicationHistory(UserId userId, // interate through it but it would require "for" and i don't know what is it) reverse to print lastest request the // last in list auto res = - history | reverse | transform([&prefixes](const api::models::recipe::PublicationHistoryInstance& req) { + history | reverse | transform([&prefixes](const api::models::recipe::PublicationHistoryRecipe& req) { //!!!!IMPORTANT See that tie to match prefixes!!!! auto fields = std::tie(req.status, req.reason, req.created, req.updated); return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { diff --git a/src/render/personal_account/request_history.cpp b/src/render/personal_account/request_history.cpp index b4173e4f..bab0a314 100644 --- a/src/render/personal_account/request_history.cpp +++ b/src/render/personal_account/request_history.cpp @@ -1,26 +1,86 @@ #include "request_history.hpp" +#include "backend/models/request_history.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/utils.hpp" -#include "view.hpp" #include -#include +#include +#include -namespace cookcookhnya::render::personal_account::history { -void renderRequestHistory( - UserId userId, size_t pageNo, size_t numOfInstances, ChatId chatId, BotRef bot, RecipesApiRef recipeApi) { +namespace cookcookhnya::render::personal_account::publication_history { +void renderRequestHistory(UserId userId, + size_t pageNo, + size_t numOfInstances, + ChatId chatId, + BotRef bot, + RequestHistoryApiRef reqHistoryApi) { InlineKeyboardBuilder keyboard{1}; - auto history = recipeApi.getAllRequestHistory(userId, 10, pageNo * numOfInstances); - std::string toPrint; + auto history = reqHistoryApi.getAllRequestHistory(userId, 10, pageNo * numOfInstances); + /*std::vector history = { + { + .name = "хуй слона", + .created = std::chrono::system_clock::now(), + .status = api::models::recipe::PublicationRequestStatus::PENDING, + }, + { + .name = "хуй asd", + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::REJECTED, + .updated = std::chrono::system_clock::now(), + }, + { + .name = "jtgg", + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::REJECTED, + }, + { + .name = "jghggf", + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::REJECTED, + .updated = std::chrono::system_clock::now(), + }, + { + .name = "hjfhg", + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::REJECTED, + .updated = std::chrono::system_clock::now(), + }, + { + .name = "loijh", + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::REJECTED, + .updated = std::chrono::system_clock::now(), + }, + { + .name = "ngsdf", + .created = std::chrono::system_clock::now(), + .reason = "ПИДАРАС", + .status = api::models::recipe::PublicationRequestStatus::REJECTED, + .updated = std::chrono::system_clock::now(), + }};*/ + std::string toPrint = utils::utf8str(u8"ℹ️История запросов на публикацию ваших рецептов и ингредиентов\n\n\n"); for (auto& req : history) { - toPrint += std::format("Статус: {} ", utils::to_string(req.status)); + toPrint += std::format("*{}* статус: {} ", req.name, utils::to_string(req.status)); if (req.reason.has_value()) toPrint += std::format("по причине: {} ", req.reason.value()); - toPrint += std::format("запрос создан: {}\n", utils::to_string(req.created)); - toPrint += '\n'; + toPrint += std::format("запрос создан: {} ", utils::to_string(req.created)); + if (req.updated.has_value()) { + toPrint += std::format("последенее обновление {}", utils::to_string(req.updated.value())); + } + toPrint += "\n\n"; + } + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + auto messageId = message::getMessageId(userId); + if (messageId) { + bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); } } -} // namespace cookcookhnya::render::personal_account::history +} // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/request_history.hpp b/src/render/personal_account/request_history.hpp index 66b874ef..2d5035a0 100644 --- a/src/render/personal_account/request_history.hpp +++ b/src/render/personal_account/request_history.hpp @@ -2,7 +2,7 @@ #include "render/common.hpp" -namespace cookcookhnya::render::personal_account::history { +namespace cookcookhnya::render::personal_account::publication_history { void renderRequestHistory( - UserId userId, size_t pageNo, size_t numOfInstances, ChatId chatId, BotRef bot, RecipesApiRef recipeApi); -} // namespace cookcookhnya::render::personal_account::history + UserId userId, size_t pageNo, size_t numOfInstances, ChatId chatId, BotRef bot, RequestHistoryApiRef reqHistoryApi); +} // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/view.cpp b/src/render/personal_account/view.cpp index 091f87f0..1f54db0c 100644 --- a/src/render/personal_account/view.cpp +++ b/src/render/personal_account/view.cpp @@ -11,7 +11,7 @@ namespace cookcookhnya::render::personal_account { using namespace tg_types; void renderPersonalAccountMenu(UserId userId, ChatId chatId, BotRef bot) { - const std::size_t buttonRows = 5; + const std::size_t buttonRows = 4; InlineKeyboard keyboard(buttonRows); keyboard[0].push_back(makeCallbackButton(u8"📋 Мои ингредиенты", "ingredients")); From 66c4c912c524a1675d9c3931179ba30375c2cbb9 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sun, 20 Jul 2025 21:55:59 +0300 Subject: [PATCH 036/106] feat: deletion menu for ingredients in storage --- src/handlers/CMakeLists.txt | 1 + src/handlers/common.hpp | 1 + src/handlers/handlers_list.hpp | 4 + .../ingredients_list/view.cpp | 2 +- src/handlers/storage/ingredients/delete.cpp | 88 +++++++++++ src/handlers/storage/ingredients/delete.hpp | 10 ++ src/handlers/storage/ingredients/view.cpp | 6 +- src/handlers/storage/view.cpp | 5 +- src/main.cpp | 1 + src/render/CMakeLists.txt | 1 + .../ingredients_list/view.cpp | 4 +- src/render/storage/ingredients/delete.cpp | 144 ++++++++++++++++++ src/render/storage/ingredients/delete.hpp | 13 ++ src/render/storage/ingredients/view.cpp | 10 +- src/render/storage/ingredients/view.hpp | 8 +- src/states.hpp | 7 + 16 files changed, 283 insertions(+), 22 deletions(-) create mode 100644 src/handlers/storage/ingredients/delete.cpp create mode 100644 src/handlers/storage/ingredients/delete.hpp create mode 100644 src/render/storage/ingredients/delete.cpp create mode 100644 src/render/storage/ingredients/delete.hpp diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 713d7b88..67ca3567 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -25,6 +25,7 @@ target_sources(main PRIVATE src/handlers/shopping_list/view.cpp src/handlers/storage/ingredients/view.cpp + src/handlers/storage/ingredients/delete.cpp src/handlers/storage/members/add.cpp src/handlers/storage/members/delete.cpp diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 855c2ea4..085c63b8 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -38,6 +38,7 @@ using states::StorageMemberAddition; using states::StorageMemberDeletion; using states::StorageMemberView; +using states::StorageIngredientsDeletion; using states::StorageIngredientsList; using states::StoragesSelection; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index f2cee479..a545b027 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -25,6 +25,7 @@ #include "shopping_list/storage_selection_to_buy.hpp" #include "shopping_list/view.hpp" +#include "storage/ingredients/delete.hpp" #include "storage/ingredients/view.hpp" #include "storage/members/add.hpp" @@ -106,6 +107,9 @@ using suggestedRecipeListCQHandler = Handler; using storageIngredientsListIQHandler = Handler; +// StorageIngredientsDeletion +using storageIngredientsDeletionCQHandler = Handler; + // RecipeView using recipeViewCQHandler = Handler; using recipeStorageAdditionCQHandler = Handler; diff --git a/src/handlers/personal_account/ingredients_list/view.cpp b/src/handlers/personal_account/ingredients_list/view.cpp index 52c08711..eb2b707b 100644 --- a/src/handlers/personal_account/ingredients_list/view.cpp +++ b/src/handlers/personal_account/ingredients_list/view.cpp @@ -5,9 +5,9 @@ #include "render/personal_account/ingredients_list/publish.hpp" #include "render/personal_account/ingredients_list/view.hpp" #include "render/personal_account/view.hpp" - #include "states.hpp" #include "utils/parsing.hpp" + #include namespace cookcookhnya::handlers::personal_account::ingredients { diff --git a/src/handlers/storage/ingredients/delete.cpp b/src/handlers/storage/ingredients/delete.cpp new file mode 100644 index 00000000..47532744 --- /dev/null +++ b/src/handlers/storage/ingredients/delete.cpp @@ -0,0 +1,88 @@ +#include "delete.hpp" + +#include "backend/id_types.hpp" +#include "backend/models/ingredient.hpp" +#include "render/storage/ingredients/delete.hpp" +#include "render/storage/ingredients/view.hpp" +#include "states.hpp" +#include "utils/parsing.hpp" +#include + +namespace cookcookhnya::handlers::storage::ingredients { + +using namespace render::storage::ingredients; +using namespace std::views; + +void handleStorageIngredientsDeletionCQ( + StorageIngredientsDeletion& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + + if (cq.data == "delete") { + for (const auto& ing : state.selectedIngredients) { + api.getIngredientsApi().deleteFromStorage(userId, state.storageId, ing.id); + state.selectedIngredients.erase( + std::ranges::find(state.selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id)); + state.storageIngredients.erase( + std::ranges::find(state.storageIngredients, ing.id, &api::models::ingredient::Ingredient::id)); + } + renderStorageIngredientsDeletion(state, userId, chatId, bot); + stateManager.put(StorageIngredientsDeletion{ + state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + return; + } + if (cq.data == "delete_w_shop") { + api.getShoppingListApi().put( + userId, + state.selectedIngredients | std::views::transform([](const api::models::ingredient::Ingredient& obj) { + return obj.id; + }) | std::ranges::to()); + for (const auto& ing : state.selectedIngredients) { + api.getIngredientsApi().deleteFromStorage(userId, state.storageId, ing.id); + state.selectedIngredients.erase( + std::ranges::find(state.selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id)); + state.storageIngredients.erase( + std::ranges::find(state.storageIngredients, ing.id, &api::models::ingredient::Ingredient::id)); + } + renderStorageIngredientsDeletion(state, userId, chatId, bot); + stateManager.put(StorageIngredientsDeletion{ + state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + return; + } + if (cq.data == "back") { + auto ingredients = api.getIngredientsApi().getStorageIngredients(userId, state.storageId); + auto newState = StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}; + renderIngredientsListSearch(newState, userId, chatId, bot); + stateManager.put(newState); + return; + } + if (cq.data == "prev") { + state.pageNo -= 1; + renderStorageIngredientsDeletion(state, userId, chatId, bot); + stateManager.put(StorageIngredientsDeletion{ + state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + return; + } + if (cq.data == "next") { + state.pageNo += 1; + renderStorageIngredientsDeletion(state, userId, chatId, bot); + stateManager.put(StorageIngredientsDeletion{ + state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + return; + } + if (cq.data != "dont_handle") { + if (auto id = utils::parseSafe(cq.data.substr(1))) { + if (cq.data[0] == '+') { + auto it = std::ranges::find(state.selectedIngredients, *id, &api::models::ingredient::Ingredient::id); + state.selectedIngredients.erase(it); + } else if (cq.data[0] == '-') { + auto ingredientDetails = api.getIngredientsApi().get(userId, *id); + state.selectedIngredients.push_back({.id = *id, .name = ingredientDetails.name}); + } + renderStorageIngredientsDeletion(state, userId, chatId, bot); + return; + } + } +} +} // namespace cookcookhnya::handlers::storage::ingredients diff --git a/src/handlers/storage/ingredients/delete.hpp b/src/handlers/storage/ingredients/delete.hpp new file mode 100644 index 00000000..fe7a2af4 --- /dev/null +++ b/src/handlers/storage/ingredients/delete.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::storage::ingredients { + +void handleStorageIngredientsDeletionCQ( + StorageIngredientsDeletion& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + +} // namespace cookcookhnya::handlers::storage::ingredients diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 7f979f8e..1b8a51c8 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -42,7 +42,7 @@ void updateSearch( state.searchItems = std::move(response.page); state.totalFound = response.found; if (auto mMessageId = message::getMessageId(userId)) - renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, userId, bot); + renderIngredientsListSearch(state, userId, userId, bot); } } } // namespace @@ -87,7 +87,7 @@ void handleStorageIngredientsListCQ( state.storageIngredients.put({.id = it->id, .name = it->name}); } it->isInStorage = !it->isInStorage; - renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, chatId, bot); + renderIngredientsListSearch(state, userId, chatId, bot); } } @@ -101,7 +101,7 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, state.searchItems.clear(); // When query is empty then search shouldn't happen state.totalFound = 0; - renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, userId, bot); + renderIngredientsListSearch(state, userId, userId, bot); } else { updateSearch(state, true, bot, userId, api); } diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 4dc2b052..176d1032 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -9,7 +9,6 @@ #include "render/storages_list/view.hpp" #include "states.hpp" -#include #include #include @@ -22,8 +21,6 @@ using namespace render::recipes_suggestions; using namespace render::delete_storage; using namespace std::views; -const std::size_t numOfIngredientsOnPage = 5; - void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; @@ -32,7 +29,7 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM if (cq.data == "ingredients") { auto ingredients = api.getIngredientsApi().getStorageIngredients(userId, state.storageId); auto newState = StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}; - renderIngredientsListSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); + renderIngredientsListSearch(newState, userId, chatId, bot); stateManager.put(std::move(newState)); } else if (cq.data == "members") { renderMemberList(true, state.storageId, userId, chatId, bot, api); diff --git a/src/main.cpp b/src/main.cpp index 26b71e0c..a9e7e422 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,6 +53,7 @@ int main(int argc, char* argv[]) { storageSelectionCQHandler, storageIngredientsListCQHandler, storageIngredientsListIQHandler, + storageIngredientsDeletionCQHandler, suggestedRecipeListCQHandler, recipeViewCQHandler, recipeStorageAdditionCQHandler, diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 3b0625c8..01b496b7 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -23,6 +23,7 @@ target_sources(main PRIVATE src/render/shopping_list/view.cpp src/render/storage/ingredients/view.cpp + src/render/storage/ingredients/delete.cpp src/render/storage/members/add.cpp src/render/storage/members/delete.cpp diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index a5bc7ebd..1f3e8255 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -16,8 +16,6 @@ namespace cookcookhnya::render::personal_account::ingredients { -using namespace tg_types; - namespace { std::pair> constructNavigationMessage( @@ -69,7 +67,7 @@ std::pair constructMessage(size_t pageNo, utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. Создавайте и делитесь новыми ингредиентами\\.\n\n"); keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - } else if (ingredientsList.found <= numOfIngredientsOnPage) { + } else if (ingredientsList.found <= numOfIngredientsOnPage && pageNo == 0) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { if (ing.status) { diff --git a/src/render/storage/ingredients/delete.cpp b/src/render/storage/ingredients/delete.cpp new file mode 100644 index 00000000..839f02db --- /dev/null +++ b/src/render/storage/ingredients/delete.cpp @@ -0,0 +1,144 @@ +#include "delete.hpp" + +#include "backend/models/ingredient.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "states.hpp" + +#include +#include +#include +#include + +namespace cookcookhnya::render::storage::ingredients { + +namespace { + +std::vector constructNavigationButtons(std::size_t pageNo, std::size_t maxPageNum) { + std::vector buttons; + auto forward = makeCallbackButton(u8"▶️", "next"); + auto backward = makeCallbackButton(u8"◀️", "prev"); + auto dont_handle = makeCallbackButton(u8"ㅤ", "dont_handle"); + auto page = makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle"); + if (pageNo == maxPageNum) { + buttons.push_back(backward); + buttons.push_back(page); + buttons.push_back(dont_handle); + } else if (pageNo == 0) { + buttons.push_back(dont_handle); + buttons.push_back(page); + buttons.push_back(forward); + } else { + buttons.push_back(backward); + buttons.push_back(page); + buttons.push_back(forward); + } + return buttons; +} + +std::vector +constructIngredientsButton(std::vector& selectedIngredients, + std::vector& storageIngredients, + std::size_t size, + std::size_t offset) { + size = std::min(offset + size, storageIngredients.size()) - offset; + std::vector buttons; + for (std::size_t i = offset; i != size + offset; ++i) { + const bool isSelected = std::ranges::contains( + selectedIngredients, storageIngredients[i].id, &api::models::ingredient::Ingredient::id); + std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); + const char* actionPrefix = isSelected ? "+" : "-"; + std::string text = std::format("{} {}", emoji, storageIngredients[i].name); + std::string data = actionPrefix + utils::to_string(storageIngredients[i].id); + buttons.push_back(makeCallbackButton(text, data)); + } + return buttons; +} + +std::pair +constructMessage(std::vector& selectedIngredients, + std::vector& storageIngredients, + std::size_t pageNo, + std::size_t numOfIngredientsOnPage, + std::vector& ingredientsList) { + std::size_t ingSize = ingredientsList.size(); + const std::size_t maxPageNum = + std::ceil(static_cast(ingSize) / static_cast(numOfIngredientsOnPage)); + std::size_t buttonRows = std::min(ingSize, numOfIngredientsOnPage); + if (selectedIngredients.empty()) { + if (ingSize <= numOfIngredientsOnPage) { + buttonRows += 1; // + back + } else { + buttonRows += 2; // + back + navig + } + } else { + if (ingredientsList.size() <= numOfIngredientsOnPage) { + buttonRows += 2; // + back & delete + delete with shop + } else { + buttonRows += 3; // + back & delete + navig + delete with shop + } + } + + std::string text = utils::utf8str(u8"🍅 Выберите ингредиенты для удаления\\.\n\n"); + InlineKeyboardBuilder keyboard{buttonRows}; + + for (auto& b : constructIngredientsButton( + selectedIngredients, storageIngredients, numOfIngredientsOnPage, pageNo * numOfIngredientsOnPage)) { + keyboard << std::move(b); + keyboard << NewRow{}; + } + + auto backButton = makeCallbackButton(u8"↩️ Назад", "back"); + auto deleteButton = makeCallbackButton(u8"🗑 Удалить", "delete"); + auto deleteWithShopButton = makeCallbackButton(u8"🛒 Удалить (и добавить в 🗒 Список покупок)", "delete_w_shop"); + if (selectedIngredients.empty()) { + if (ingredientsList.size() <= numOfIngredientsOnPage && pageNo == 0) { + keyboard << std::move(backButton); + } else { + for (auto& b : constructNavigationButtons(pageNo, maxPageNum)) { + keyboard << std::move(b); + } + keyboard << NewRow{}; + keyboard << std::move(backButton); + } + } else { + if (ingredientsList.size() <= numOfIngredientsOnPage && pageNo == 0) { + keyboard << std::move(deleteWithShopButton); + keyboard << NewRow{}; + keyboard << std::move(backButton); + keyboard << std::move(deleteButton); + } else { + for (auto& b : constructNavigationButtons(pageNo, maxPageNum)) { + keyboard << std::move(b); + } + keyboard << NewRow{}; + keyboard << std::move(deleteWithShopButton); + keyboard << NewRow{}; + keyboard << std::move(backButton); + keyboard << std::move(deleteButton); + } + } + return std::make_pair(text, keyboard); +} + +} // namespace + +void renderStorageIngredientsDeletion(states::StorageIngredientsDeletion& state, + UserId userId, + ChatId chatId, + BotRef bot) { + + const std::size_t numOfIngredientsOnPage = 7; + + auto res = constructMessage(state.selectedIngredients, + state.storageIngredients, + state.pageNo, + numOfIngredientsOnPage, + state.storageIngredients); + auto text = res.first; + auto keyboard = res.second; + if (auto messageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *messageId, std::move(keyboard), "MarkdownV2"); +} + +} // namespace cookcookhnya::render::storage::ingredients diff --git a/src/render/storage/ingredients/delete.hpp b/src/render/storage/ingredients/delete.hpp new file mode 100644 index 00000000..b4a64ebd --- /dev/null +++ b/src/render/storage/ingredients/delete.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "render/common.hpp" +#include "states.hpp" + +namespace cookcookhnya::render::storage::ingredients { + +void renderStorageIngredientsDeletion(states::StorageIngredientsDeletion& state, + UserId userId, + ChatId chatId, + BotRef bot); + +} // namespace cookcookhnya::render::storage::ingredients diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 48e3036e..833d3e14 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -99,14 +99,15 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, keyboard[arrowsRow].insert( keyboard[arrowsRow].begin() + 1, makeCallbackButton(std::format("{} из {}", state.pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"🗑 Удалить из хранилища", "delete")); + keyboard[arrowsRow + 2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); return keyboard; } InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::StorageIngredientsList& state) { // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 - // for editing - other buttons are ingredients - const size_t numOfRows = 3; + // for editing, 1 for delete - other buttons are ingredients + const size_t numOfRows = 4; const size_t offset = 1; // Number of rows before list const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); @@ -122,13 +123,12 @@ InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::StorageI } // namespace void renderIngredientsListSearch(const states::StorageIngredientsList& state, - size_t numOfIngredientsOnPage, UserId userId, ChatId chatId, BotRef bot) { using namespace std::views; using std::ranges::to; - + const std::size_t numOfIngredientsOnPage = 5; std::string list = state.storageIngredients.getValues() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); diff --git a/src/render/storage/ingredients/view.hpp b/src/render/storage/ingredients/view.hpp index 6cfe491d..95d12f54 100644 --- a/src/render/storage/ingredients/view.hpp +++ b/src/render/storage/ingredients/view.hpp @@ -1,14 +1,10 @@ #pragma once + #include "render/common.hpp" #include "states.hpp" -#include namespace cookcookhnya::render::storage::ingredients { -void renderIngredientsListSearch(const states::StorageIngredientsList& state, - size_t numOfIngredientsOnPage, - UserId userId, - ChatId chatId, - BotRef bot); +void renderIngredientsListSearch(const states::StorageIngredientsList& state, UserId userId, ChatId chatId, BotRef bot); } // namespace cookcookhnya::render::storage::ingredients diff --git a/src/states.hpp b/src/states.hpp index 7df1f603..b808927e 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -71,6 +71,12 @@ struct StorageIngredientsList : detail::StorageIdMixin { : StorageIdMixin{storageId}, storageIngredients{std::forward(ingredients)}, inlineQuery(std::move(iq)) {} }; +struct StorageIngredientsDeletion : detail::StorageIdMixin { + std::vector selectedIngredients; + std::vector storageIngredients; + std::size_t pageNo; +}; + struct StoragesSelection { std::vector selectedStorages; }; @@ -176,6 +182,7 @@ using State = std::variant Date: Sun, 20 Jul 2025 22:38:27 +0300 Subject: [PATCH 037/106] feat: now suggests to make custom ingredient if wasn't found --- src/backend/CMakeLists.txt | 2 + src/backend/api/recipes.cpp | 4 +- src/backend/models/recipe.cpp | 15 ++- src/backend/models/request_history.hpp | 4 - .../ingredients_list/create.cpp | 33 +++++- .../ingredients_list/create.hpp | 7 +- .../recipe/search_ingredients.cpp | 24 +++- .../personal_account/request_history.cpp | 2 +- src/handlers/storage/ingredients/view.cpp | 23 +++- .../ingredients_list/create.cpp | 13 ++- .../ingredients_list/create.hpp | 2 +- .../recipe/publication_history.cpp | 109 +++++++++--------- .../recipe/search_ingredients.cpp | 25 +++- .../recipe/search_ingredients.hpp | 9 +- src/render/personal_account/recipe/view.cpp | 2 - .../personal_account/request_history.cpp | 3 +- src/render/storage/ingredients/view.cpp | 24 ++++ src/render/storage/ingredients/view.hpp | 7 ++ src/states.hpp | 17 +++ src/utils/to_string.cpp | 11 +- src/utils/to_string.hpp | 2 +- 21 files changed, 238 insertions(+), 100 deletions(-) diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 2b4bc513..51ec90fb 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -8,10 +8,12 @@ target_sources(main PRIVATE src/backend/api/users.cpp src/backend/api/recipes.cpp src/backend/api/shopping_lists.cpp + src/backend/api/request_history.cpp src/backend/models/storage.cpp src/backend/models/ingredient.cpp src/backend/models/user.cpp src/backend/models/recipe.cpp src/backend/models/shopping_list.cpp + src/backend/models/request_history.cpp ) diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index cb56a7eb..16683269 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -68,13 +68,13 @@ void RecipesApi::delete_(UserId user, RecipeId recipeId) const { // POST /recipes/{recipeId}/request-publication void RecipesApi::publishCustom(UserId user, RecipeId recipe) const { - jsonPostAuthed(user, std::format("my/recipes/{}/request-publication", recipe)); + jsonPostAuthed(user, std::format("recipes/{}/request-publication", recipe)); } // GET /recipe/{id}/moderation-history std::vector RecipesApi::getRecipeRequestHistory(UserId user, RecipeId recipe) const { return jsonGetAuthed>(user, - std::format("/recipe/{}/moderation-history", recipe)); + std::format("/recipes/{}/moderation-history", recipe)); } } // namespace cookcookhnya::api diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index c30d8893..708467cf 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -78,25 +78,24 @@ RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/ PublicationHistoryRecipe tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { - .created = utils::parseIsoTime(value_to(j.at("created"))), + .created = utils::parseIsoTime(value_to(j.at("createdAt"))), .reason = j.as_object().if_contains("reason") ? value_to(j.at("reason")) : "", - .status = j.as_object().if_contains("status") - ? value_to(j.at("status")) - : PublicationRequestStatus::NO_REQUEST, - .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) + .status = j.as_object().if_contains("status") ? value_to(j.at("status")) + : PublicationRequestStatus::NO_REQUEST, + .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updatedAt"))) : std::chrono::time_point(), }; } PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, const boost::json::value& j) { - if (j.at("status") == "Pending") + if (j == "pending") return PublicationRequestStatus::PENDING; - if (j.at("status") == "Accepted") + if (j == "accepted") return PublicationRequestStatus::ACCEPTED; - if (j.at("status") == "Rejected") + if (j == "rejected") return PublicationRequestStatus::REJECTED; return PublicationRequestStatus::NO_REQUEST; } diff --git a/src/backend/models/request_history.hpp b/src/backend/models/request_history.hpp index ce671f05..fe005e6a 100644 --- a/src/backend/models/request_history.hpp +++ b/src/backend/models/request_history.hpp @@ -1,16 +1,12 @@ #pragma once #include "backend/models/recipe.hpp" -#include "request_history.hpp" #include #include -#include -#include #include #include -#include namespace cookcookhnya::api::models::request_history { diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 02f328fc..4d5f7940 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -3,15 +3,19 @@ #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" +#include "ranges" #include "render/personal_account/ingredients_list/create.hpp" #include "render/personal_account/ingredients_list/view.hpp" +#include "render/personal_account/recipe/search_ingredients.hpp" +#include "render/storage/ingredients/view.hpp" #include "states.hpp" #include "utils/utils.hpp" namespace cookcookhnya::handlers::personal_account::ingredients { - +using namespace render::recipe::ingredients; using namespace render::personal_account::ingredients; - +using namespace std::views; +using namespace render::storage::ingredients; void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& /*unused*/, MessageRef m, BotRef& bot, @@ -26,7 +30,7 @@ void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterNam if (messageId) { bot.editMessageText(text, chatId, *messageId); } - renderCustomIngredientConfirmation(name, userId, chatId, bot, api); + renderCustomIngredientConfirmation(false, name, userId, chatId, bot, api); stateManager.put(CustomIngredientConfirmation{name}); } @@ -45,19 +49,38 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName } void handleCustomIngredientConfirmationCQ( - CustomIngredientConfirmation& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { + CustomIngredientConfirmation& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; auto name = state.name; if (cq.data == "confirm") { - api.createCustom(userId, api::models::ingredient::IngredientCreateBody{name}); + api.getIngredientsApi().createCustom(userId, api::models::ingredient::IngredientCreateBody{name}); renderCustomIngredientsList(true, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); } if (cq.data == "back") { + size_t numOfIngredientsOnPage = 5; + + if (state.recipeFrom.has_value()) { + auto newState = + CustomRecipeIngredientsSearch{state.recipeFrom.value(), state.ingredients.value() | as_rvalue, ""}; + renderRecipeIngredientsSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); + stateManager.put(std::move(newState)); + return; + } + + if (state.storageFrom.has_value()) { + auto ingredients = api.getIngredientsApi().getStorageIngredients(userId, state.storageFrom.value()); + auto newState = StorageIngredientsList{state.storageFrom.value(), ingredients | as_rvalue, ""}; + renderIngredientsListSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); + stateManager.put(std::move(newState)); + return; + } + renderCustomIngredientsList(true, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); + return; } } diff --git a/src/handlers/personal_account/ingredients_list/create.hpp b/src/handlers/personal_account/ingredients_list/create.hpp index 27a10e34..64600c5d 100644 --- a/src/handlers/personal_account/ingredients_list/create.hpp +++ b/src/handlers/personal_account/ingredients_list/create.hpp @@ -16,10 +16,7 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName SMRef stateManager, IngredientsApiRef api); -void handleCustomIngredientConfirmationCQ(CustomIngredientConfirmation& /*unused*/, - CallbackQueryRef cq, - BotRef& bot, - SMRef stateManager, - IngredientsApiRef api); +void handleCustomIngredientConfirmationCQ( + CustomIngredientConfirmation& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index b858923d..21571fd8 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -4,6 +4,7 @@ #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" +#include "render/personal_account/ingredients_list/create.hpp" #include "render/personal_account/recipe/search_ingredients.hpp" #include "render/personal_account/recipe/view.hpp" #include "tg_types.hpp" @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -23,7 +25,8 @@ using namespace render::recipe::ingredients; using namespace render::personal_account::recipes; using namespace std::ranges; using namespace std::views; - +using namespace render::personal_account::ingredients; +using namespace render::suggest_custom_ingredient; namespace { const std::size_t numOfIngredientsOnPage = 5; @@ -44,9 +47,14 @@ void updateSearch(CustomRecipeIngredientsSearch& state, &IngredientSearchForRecipeItem::id)) { state.searchItems = std::move(response.page); state.totalFound = response.found; - if (auto mMessageId = message::getMessageId(userId)) - renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); + if (auto mMessageId = message::getMessageId(userId)) { + if (state.totalFound != 0) { + renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); + return; + } + } } + renderSuggestIngredientCustomisation(state, userId, userId, bot); } } // namespace @@ -61,7 +69,7 @@ void handleCustomRecipeIngredientsSearchCQ( auto ingredientsAndName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); auto ingredients = state.recipeIngredients.getValues() | as_rvalue | to(); stateManager.put(RecipeCustomView{.recipeId = state.recipeId, - .pageNo = 0, + .pageNo = state.pageNo, .ingredients = std::move(ingredients), .recipeName = std::get<1>(ingredientsAndName)}); return; @@ -79,6 +87,14 @@ void handleCustomRecipeIngredientsSearchCQ( return; } + if (cq.data[0] == 'i') { + auto ingredientName = cq.data.substr(1); + renderCustomIngredientConfirmation(true, ingredientName, userId, chatId, bot, api); + auto ingredients = state.recipeIngredients.getValues() | as_rvalue | to(); + stateManager.put( + CustomIngredientConfirmation{ingredientName, state.recipeId, state.pageNo, ingredients, std::nullopt}); + } + if (cq.data != "dont_handle") { auto mIngredient = utils::parseSafe(cq.data); if (!mIngredient) diff --git a/src/handlers/personal_account/request_history.cpp b/src/handlers/personal_account/request_history.cpp index 0c11caaa..2e9c3dbf 100644 --- a/src/handlers/personal_account/request_history.cpp +++ b/src/handlers/personal_account/request_history.cpp @@ -7,7 +7,7 @@ namespace cookcookhnya::handlers::personal_account::publication_history { using namespace render::personal_account; void handleAllPublicationHistoryCQ( - AllPublicationHistory& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + AllPublicationHistory& /**/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef /**/) { const std::string data = cq.data; bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 7f979f8e..9bc1f9a9 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -5,6 +5,9 @@ #include "handlers/common.hpp" #include "message_tracker.hpp" #include "render/storage/ingredients/view.hpp" + +#include "render/personal_account/ingredients_list/create.hpp" + #include "render/storage/view.hpp" #include "tg_types.hpp" #include "utils/parsing.hpp" @@ -12,6 +15,7 @@ #include #include #include +#include #include namespace cookcookhnya::handlers::storage::ingredients { @@ -19,7 +23,8 @@ namespace cookcookhnya::handlers::storage::ingredients { using namespace render::storage; using namespace render::storage::ingredients; using namespace api::models::ingredient; - +using namespace render::suggest_custom_ingredient; +using namespace render::personal_account::ingredients; // Global vars const size_t numOfIngredientsOnPage = 5; const size_t threshhold = 70; @@ -41,9 +46,14 @@ void updateSearch( &IngredientSearchForStorageItem::id)) { state.searchItems = std::move(response.page); state.totalFound = response.found; - if (auto mMessageId = message::getMessageId(userId)) - renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, userId, bot); + if (auto mMessageId = message::getMessageId(userId)) { + if (state.totalFound != 0) { + renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, userId, bot); + return; + } + } } + renderSuggestIngredientCustomisation(state, userId, userId, bot); } } // namespace @@ -70,6 +80,13 @@ void handleStorageIngredientsListCQ( return; } + if (cq.data[0] == 'i') { + auto ingredientName = cq.data.substr(1); + renderCustomIngredientConfirmation(true, ingredientName, userId, chatId, bot, api); + stateManager.put( + CustomIngredientConfirmation{ingredientName, std::nullopt, std::nullopt, std::nullopt, state.storageId}); + } + if (cq.data != "dont_handle") { auto mIngredient = utils::parseSafe(cq.data); diff --git a/src/render/personal_account/ingredients_list/create.cpp b/src/render/personal_account/ingredients_list/create.cpp index 8d9a67b1..1b391ccf 100644 --- a/src/render/personal_account/ingredients_list/create.cpp +++ b/src/render/personal_account/ingredients_list/create.cpp @@ -24,9 +24,10 @@ void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot) { } void renderCustomIngredientConfirmation( - std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + bool toBeEdited, std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { InlineKeyboard keyboard(2); keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); + keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); // NOLINTNEXTLINE(*magic-numbers*) @@ -46,8 +47,14 @@ void renderCustomIngredientConfirmation( } else { text = utils::utf8str(u8"Вы уверены, что хотите добавить новый ингредиент?"); } - auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); - message::addMessageId(userId, message->messageId); + if (toBeEdited) { + if (auto messageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + + } else { + auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); + message::addMessageId(userId, message->messageId); + } } } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/ingredients_list/create.hpp b/src/render/personal_account/ingredients_list/create.hpp index 06d4e87e..ce3d2c89 100644 --- a/src/render/personal_account/ingredients_list/create.hpp +++ b/src/render/personal_account/ingredients_list/create.hpp @@ -9,6 +9,6 @@ namespace cookcookhnya::render::personal_account::ingredients { void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot); void renderCustomIngredientConfirmation( - std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); + bool toBeEdited, std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp index 863d7cd7..e75c293e 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -9,13 +9,10 @@ #include #include -#include #include namespace cookcookhnya::render::personal_account::recipe::publication_history { -using namespace std::views; - void renderPublicationHistory(UserId userId, ChatId chatId, api::RecipeId recipeId, @@ -29,51 +26,24 @@ void renderPublicationHistory(UserId userId, std::string toPrint; toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n"); + if (!history.empty()) { + toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[0].status) + + (history[0].reason.has_value() ? std::format(" по причине {}", history[0].reason.value()) : " ") + + utils::to_string(history[0].created) + "\n\n"; + // Remove the lastest history instance as it's showed differently + history.erase(history.begin()); - toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[0].status) + - (history[0].reason.has_value() ? std::format(" по причине {}", history[0].reason.value()) : " ") + - utils::to_string(history[0].created) + "\n\n"; - // Remove the lastest history instance as it's showed differently - history.erase(history.begin()); - - const std::vector prefixes = {utils::utf8str(u8"Статус"), - utils::utf8str(u8"по причине"), - utils::utf8str(u8"запрос создан"), - utils::utf8str(u8"последенее обновление")}; - - // What if you forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and - // interate through it but it would require "for" and i don't know what is it) reverse to print lastest request the - // last in list - auto res = - history | reverse | transform([&prefixes](const api::models::recipe::PublicationHistoryRecipe& req) { - //!!!!IMPORTANT See that tie to match prefixes!!!! - auto fields = std::tie(req.status, req.reason, req.created, req.updated); - return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { - switch (idx) { - case 0: - // statusStr to convert enum to string - return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)) + " "; - case 1: - if (std::get<1>(fields).has_value()) { - return prefixes[1] + ": " + std::get<1>(fields).value() + " "; - } - return ", "; - // Need to work with chrono - case 2: - return prefixes[2] + ": " + utils::to_string(std::get<2>(fields)) + ", "; - case 3: - if (std::get<3>(fields).has_value()) { - return prefixes[3] + ": " + utils::to_string(std::get<3>(fields).value()) + "\n\n"; - } - return "\n\n"; - default: - return ""; - } - }); - }) | - join; // Join here instead of in the loop - - std::ranges::for_each(res, [&toPrint](const std::string& s) { toPrint += s; }); + for (auto& req : history) { + 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.created)); + if (req.updated.has_value()) { + toPrint += std::format("последенее обновление: {}", utils::to_string(req.updated.value())); + } + toPrint += "\n\n"; + } + } // Text is created moving to the markup // If not peeking then show accept button @@ -91,11 +61,44 @@ void renderPublicationHistory(UserId userId, // Uncomment in case of EMERGENCY (or if you know what for is) // use instead of history | reverse | ... /* -for (auto& req : history) { - toPrint += std::format("Статус: {} ", utils::to_string(req.status)); - if (req.reason.has_value()) - toPrint += std::format("по причине: {} ", req.reason.value()); - toPrint += std::format("запрос создан: {}\n", convertTimeToStrFormat(req.created)); - toPrint += '\n'; +const std::vector prefixes = {utils::utf8str(u8"Статус"), + utils::utf8str(u8"по причине"), + utils::utf8str(u8"запрос создан"), + utils::utf8str(u8"последенее обновление")}; + + // What if you forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and + // interate through it but it would require "for" and i don't know what is it) reverse to print lastest request + the + // last in list + auto res = + history | reverse | transform([&prefixes](const api::models::recipe::PublicationHistoryRecipe& req) { + //!!!!IMPORTANT See that tie to match prefixes!!!! + auto fields = std::tie(req.status, req.reason, req.created, req.updated); + return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { + switch (idx) { + case 0: + // statusStr to convert enum to string + return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)) + " "; + case 1: + if (std::get<1>(fields).has_value()) { + return prefixes[1] + ": " + std::get<1>(fields).value() + " "; + } + return ", "; + // Need to work with chrono + case 2: + return prefixes[2] + ": " + utils::to_string(std::get<2>(fields)) + ", "; + case 3: + if (std::get<3>(fields).has_value()) { + return prefixes[3] + ": " + utils::to_string(std::get<3>(fields).value()) + "\n\n"; + } + return "\n\n"; + default: + return ""; + } + }); + }) | + join; // Join here instead of in the loop + + std::ranges::for_each(res, [&toPrint](const std::string& s) { toPrint += s; }); } */ diff --git a/src/render/personal_account/recipe/search_ingredients.cpp b/src/render/personal_account/recipe/search_ingredients.cpp index 62b74e9a..b57a0fff 100644 --- a/src/render/personal_account/recipe/search_ingredients.cpp +++ b/src/render/personal_account/recipe/search_ingredients.cpp @@ -145,5 +145,28 @@ void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); } } - } // namespace cookcookhnya::render::recipe::ingredients + +namespace cookcookhnya::render::suggest_custom_ingredient { + +void renderSuggestIngredientCustomisation(const states::CustomRecipeIngredientsSearch& state, + UserId userId, + ChatId chatId, + BotRef bot) { + InlineKeyboard keyboard(3); + std::string text = utils::utf8str(u8"📝 Продолжите редактирование запроса или объявите личный ингредиент"); + + auto searchButton = std::make_shared(); + searchButton->text = utils::utf8str(u8"✏️ Редактировать"); + searchButton->switchInlineQueryCurrentChat = ""; + keyboard[0].push_back(std::move(searchButton)); + // Mark as ingredient + keyboard[1].push_back( + makeCallbackButton(std::format("Создать личный ингредиент: {}", state.query), "i" + state.query)); + keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + + if (auto messageId = message::getMessageId(userId)) { + bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); + } +} +} // namespace cookcookhnya::render::suggest_custom_ingredient diff --git a/src/render/personal_account/recipe/search_ingredients.hpp b/src/render/personal_account/recipe/search_ingredients.hpp index b07672cb..7b7561f6 100644 --- a/src/render/personal_account/recipe/search_ingredients.hpp +++ b/src/render/personal_account/recipe/search_ingredients.hpp @@ -10,5 +10,12 @@ void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& UserId userId, ChatId chatId, BotRef bot); - } // namespace cookcookhnya::render::recipe::ingredients + +namespace cookcookhnya::render::suggest_custom_ingredient { + +void renderSuggestIngredientCustomisation(const states::CustomRecipeIngredientsSearch& state, + UserId userId, + ChatId chatId, + BotRef bot); +} // namespace cookcookhnya::render::suggest_custom_ingredient diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index d8e04c2d..f8b469a4 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -20,8 +20,6 @@ std::tuple, std::string> render bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { auto recipeDetails = recipesApi.get(userId, recipeId); - // REMOVE WHEN BACKEND IS READY - recipeDetails.moderationStatus = api::models::recipe::PublicationRequestStatus::NO_REQUEST; std::vector ingredients; diff --git a/src/render/personal_account/request_history.cpp b/src/render/personal_account/request_history.cpp index bab0a314..5f666a57 100644 --- a/src/render/personal_account/request_history.cpp +++ b/src/render/personal_account/request_history.cpp @@ -6,7 +6,6 @@ #include #include -#include namespace cookcookhnya::render::personal_account::publication_history { void renderRequestHistory(UserId userId, @@ -72,7 +71,7 @@ void renderRequestHistory(UserId userId, toPrint += std::format("по причине: {} ", req.reason.value()); toPrint += std::format("запрос создан: {} ", utils::to_string(req.created)); if (req.updated.has_value()) { - toPrint += std::format("последенее обновление {}", utils::to_string(req.updated.value())); + toPrint += std::format("последенее обновление: {}", utils::to_string(req.updated.value())); } toPrint += "\n\n"; } diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 48e3036e..ee7ecd7c 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -144,3 +144,27 @@ void renderIngredientsListSearch(const states::StorageIngredientsList& state, } } // namespace cookcookhnya::render::storage::ingredients + +namespace cookcookhnya::render::suggest_custom_ingredient { + +void renderSuggestIngredientCustomisation(const states::StorageIngredientsList& state, + UserId userId, + ChatId chatId, + BotRef bot) { + InlineKeyboard keyboard(3); + std::string text = utils::utf8str(u8"📝 Продолжите редактирование запроса или объявите личный ингредиент"); + + auto searchButton = std::make_shared(); + searchButton->text = utils::utf8str(u8"✏️ Редактировать"); + searchButton->switchInlineQueryCurrentChat = ""; + keyboard[0].push_back(std::move(searchButton)); + // Mark as ingredient + keyboard[1].push_back( + makeCallbackButton(std::format("Создать личный ингредиент: {}", state.inlineQuery), "i" + state.inlineQuery)); + keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + + if (auto messageId = message::getMessageId(userId)) { + bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); + } +} +} // namespace cookcookhnya::render::suggest_custom_ingredient diff --git a/src/render/storage/ingredients/view.hpp b/src/render/storage/ingredients/view.hpp index 6cfe491d..3019eab6 100644 --- a/src/render/storage/ingredients/view.hpp +++ b/src/render/storage/ingredients/view.hpp @@ -12,3 +12,10 @@ void renderIngredientsListSearch(const states::StorageIngredientsList& state, BotRef bot); } // namespace cookcookhnya::render::storage::ingredients + +namespace cookcookhnya::render::suggest_custom_ingredient { +void renderSuggestIngredientCustomisation(const states::StorageIngredientsList& state, + UserId userId, + ChatId chatId, + BotRef bot); +} // namespace cookcookhnya::render::suggest_custom_ingredient diff --git a/src/states.hpp b/src/states.hpp index b984af76..7bb49aba 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -7,6 +7,7 @@ #include "utils/fast_sorted_db.hpp" #include "utils/ingredients_availability.hpp" +#include #include #include @@ -37,6 +38,22 @@ struct CustomIngredientsList {}; struct CustomIngredientCreationEnterName {}; struct CustomIngredientConfirmation { std::string name; + + // All optionals are for "back" from this menu, so this state won't erase all info + std::optional recipeFrom; + std::optional pageNo; + std::optional> ingredients; + + std::optional storageFrom; + + explicit CustomIngredientConfirmation( + std::string name, + std::optional recipeId = std::nullopt, + std::optional pageNo = std::nullopt, + std::optional> ingredients = std::nullopt, + std::optional storageId = std::nullopt) + : name(std::move(name)), recipeFrom(recipeId), pageNo(pageNo), ingredients(std::move(ingredients)), + storageFrom(storageId) {}; }; struct CustomIngredientPublish {}; diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 540ebf67..f618a6d4 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -23,10 +23,13 @@ std::string to_string(const cookcookhnya::api::models::recipe::PublicationReques return statusStr[static_cast(status)]; } -std::string to_string(std::chrono::system_clock::time_point time) { - const auto* moscow_tz = std::chrono::locate_zone("Europe/Moscow"); - auto moscow_time = std::chrono::zoned_time(moscow_tz, time); - return std::format("{:%d-%m-%Y %H:%M}", moscow_time.get_local_time()); +std::string to_string(std::chrono::system_clock::time_point tp) { + std::time_t time = std::chrono::system_clock::to_time_t(tp); + std::tm tm = *std::gmtime(&time); + std::ostringstream oss; + oss << std::put_time(&tm, "%Y-%m-%dT%H:%M"); + + return oss.str(); } } // namespace cookcookhnya::utils diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index 7fcc4684..2be2a4f3 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -19,6 +19,6 @@ std::string to_string(const Uuid& u); std::string to_string(cookcookhnya::api::models::recipe::PublicationRequestStatus status); -std::string to_string(std::chrono::system_clock::time_point time); +std::string to_string(std::chrono::system_clock::time_point tp); } // namespace cookcookhnya::utils From 233d097c8a0c278cf44bbf758b4c8d9316b440c3 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sun, 20 Jul 2025 23:21:38 +0300 Subject: [PATCH 038/106] revision of buttons, their position and user flow --- src/handlers/storage/ingredients/delete.cpp | 23 +++++------ src/handlers/storage/ingredients/view.cpp | 15 +++++++ src/render/main_menu/view.cpp | 4 +- src/render/shopping_list/create.cpp | 2 +- src/render/shopping_list/view.cpp | 2 +- src/render/storage/ingredients/delete.cpp | 45 +++++++++++++-------- src/render/storage/ingredients/view.cpp | 30 ++++++++++---- src/states.hpp | 1 + 8 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/handlers/storage/ingredients/delete.cpp b/src/handlers/storage/ingredients/delete.cpp index 47532744..6f8e7665 100644 --- a/src/handlers/storage/ingredients/delete.cpp +++ b/src/handlers/storage/ingredients/delete.cpp @@ -27,27 +27,22 @@ void handleStorageIngredientsDeletionCQ( state.storageIngredients.erase( std::ranges::find(state.storageIngredients, ing.id, &api::models::ingredient::Ingredient::id)); } - renderStorageIngredientsDeletion(state, userId, chatId, bot); - stateManager.put(StorageIngredientsDeletion{ - state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + auto ingredients = api.getIngredientsApi().getStorageIngredients(userId, state.storageId); + auto newState = StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}; + renderIngredientsListSearch(newState, userId, chatId, bot); + stateManager.put(newState); return; } - if (cq.data == "delete_w_shop") { + if (cq.data == "put_to_shop") { api.getShoppingListApi().put( userId, state.selectedIngredients | std::views::transform([](const api::models::ingredient::Ingredient& obj) { return obj.id; }) | std::ranges::to()); - for (const auto& ing : state.selectedIngredients) { - api.getIngredientsApi().deleteFromStorage(userId, state.storageId, ing.id); - state.selectedIngredients.erase( - std::ranges::find(state.selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id)); - state.storageIngredients.erase( - std::ranges::find(state.storageIngredients, ing.id, &api::models::ingredient::Ingredient::id)); - } + state.addedToShopList = true; renderStorageIngredientsDeletion(state, userId, chatId, bot); stateManager.put(StorageIngredientsDeletion{ - state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + state.storageId, state.selectedIngredients, state.storageIngredients, state.addedToShopList, state.pageNo}); return; } if (cq.data == "back") { @@ -61,14 +56,14 @@ void handleStorageIngredientsDeletionCQ( state.pageNo -= 1; renderStorageIngredientsDeletion(state, userId, chatId, bot); stateManager.put(StorageIngredientsDeletion{ - state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + state.storageId, state.selectedIngredients, state.storageIngredients, state.addedToShopList, state.pageNo}); return; } if (cq.data == "next") { state.pageNo += 1; renderStorageIngredientsDeletion(state, userId, chatId, bot); stateManager.put(StorageIngredientsDeletion{ - state.storageId, state.selectedIngredients, state.storageIngredients, state.pageNo}); + state.storageId, state.selectedIngredients, state.storageIngredients, state.addedToShopList, state.pageNo}); return; } if (cq.data != "dont_handle") { diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 1b8a51c8..8f304c18 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -4,8 +4,10 @@ #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" +#include "render/storage/ingredients/delete.hpp" #include "render/storage/ingredients/view.hpp" #include "render/storage/view.hpp" +#include "states.hpp" #include "tg_types.hpp" #include "utils/parsing.hpp" @@ -13,6 +15,7 @@ #include #include #include +#include namespace cookcookhnya::handlers::storage::ingredients { @@ -58,6 +61,18 @@ void handleStorageIngredientsListCQ( stateManager.put(StorageView{state.storageId}); return; } + + if (cq.data == "delete") { + std::vector ingredients; + for (auto& ing : state.storageIngredients.getValues()) { + ingredients.push_back(ing); + } + auto newState = StorageIngredientsDeletion{state.storageId, {}, ingredients, false, 0}; + renderStorageIngredientsDeletion(newState, userId, chatId, bot); + stateManager.put(newState); + return; + } + if (cq.data == "prev") { state.pageNo -= 1; updateSearch(state, false, bot, userId, api); diff --git a/src/render/main_menu/view.cpp b/src/render/main_menu/view.cpp index 0add0f9d..7e76d8d7 100644 --- a/src/render/main_menu/view.cpp +++ b/src/render/main_menu/view.cpp @@ -20,11 +20,11 @@ void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, S if (!storages.empty()) { keyboard[0].push_back(makeCallbackButton(u8"🍱 Хранилища", "storage_list")); keyboard[1].push_back(makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat")); - keyboard[2].push_back(makeCallbackButton(u8"🗒 Список покупок", "shopping_list")); + keyboard[2].push_back(makeCallbackButton(u8"🧾 Список покупок", "shopping_list")); keyboard[3].push_back(makeCallbackButton(u8"👤 Личный кабинет", "personal_account")); } else { keyboard[0].push_back(makeCallbackButton(u8"🍱 Хранилища", "storage_list")); - keyboard[1].push_back(makeCallbackButton(u8"🗒 Список покупок", "shopping_list")); + keyboard[1].push_back(makeCallbackButton(u8"🧾 Список покупок", "shopping_list")); keyboard[2].push_back(makeCallbackButton(u8"👤 Личный кабинет", "personal_account")); } diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index 40adbb5d..660808d2 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -23,7 +23,7 @@ void renderShoppingListCreation(const std::vector& selectedIngredien UserId userId, ChatId chatId, BotRef bot) { - std::string text = utils::utf8str(u8"📝 Выберите продукты, которые хотели бы добавить в список покупок\n\n"); + std::string text = utils::utf8str(u8"🧾 Выберите продукты, которые хотели бы добавить в список покупок\n\n"); const std::size_t buttonRows = ((selectedIngredients.size() + 1) / 2) + 1; // ceil(ingredientsCount / 2), back InlineKeyboardBuilder keyboard{buttonRows}; diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 737d374a..bffafa4f 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -36,7 +36,7 @@ void renderShoppingList(const states::ShoppingListView& state, UserId userId, Ch if (auto messageId = message::getMessageId(userId)) { auto text = - utils::utf8str(u8"🔖 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); + utils::utf8str(u8"🧾 Ваш список покупок. Выбирайте, чтобы добавить в хранилище или вычеркнуть из списка."); bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } } diff --git a/src/render/storage/ingredients/delete.cpp b/src/render/storage/ingredients/delete.cpp index 839f02db..f891b1a7 100644 --- a/src/render/storage/ingredients/delete.cpp +++ b/src/render/storage/ingredients/delete.cpp @@ -56,26 +56,31 @@ constructIngredientsButton(std::vector& sel } std::pair -constructMessage(std::vector& selectedIngredients, +constructMessage(std::vector& selectedIngredients, // NOLINT(*complexity*) std::vector& storageIngredients, std::size_t pageNo, std::size_t numOfIngredientsOnPage, - std::vector& ingredientsList) { - std::size_t ingSize = ingredientsList.size(); + bool withoutPutToShoppingListButton) { + std::size_t ingSize = storageIngredients.size(); const std::size_t maxPageNum = std::ceil(static_cast(ingSize) / static_cast(numOfIngredientsOnPage)); std::size_t buttonRows = std::min(ingSize, numOfIngredientsOnPage); if (selectedIngredients.empty()) { - if (ingSize <= numOfIngredientsOnPage) { + if (ingSize <= numOfIngredientsOnPage) buttonRows += 1; // + back - } else { + else buttonRows += 2; // + back + navig - } } else { - if (ingredientsList.size() <= numOfIngredientsOnPage) { - buttonRows += 2; // + back & delete + delete with shop + if (ingSize <= numOfIngredientsOnPage) { + if (withoutPutToShoppingListButton) + buttonRows += 2; // + back + delete + else + buttonRows += 3; // + back + delete + shop } else { - buttonRows += 3; // + back & delete + navig + delete with shop + if (withoutPutToShoppingListButton) + buttonRows += 3; // + back + navig + delete + else + buttonRows += 4; } } @@ -90,9 +95,9 @@ constructMessage(std::vector& selectedIngre auto backButton = makeCallbackButton(u8"↩️ Назад", "back"); auto deleteButton = makeCallbackButton(u8"🗑 Удалить", "delete"); - auto deleteWithShopButton = makeCallbackButton(u8"🛒 Удалить (и добавить в 🗒 Список покупок)", "delete_w_shop"); + auto shopButton = makeCallbackButton(u8"🧾 Добавить в Список покупок", "put_to_shop"); if (selectedIngredients.empty()) { - if (ingredientsList.size() <= numOfIngredientsOnPage && pageNo == 0) { + if (ingSize <= numOfIngredientsOnPage && pageNo == 0) { keyboard << std::move(backButton); } else { for (auto& b : constructNavigationButtons(pageNo, maxPageNum)) { @@ -102,20 +107,26 @@ constructMessage(std::vector& selectedIngre keyboard << std::move(backButton); } } else { - if (ingredientsList.size() <= numOfIngredientsOnPage && pageNo == 0) { - keyboard << std::move(deleteWithShopButton); + if (ingSize <= numOfIngredientsOnPage && pageNo == 0) { + if (!withoutPutToShoppingListButton) { + keyboard << std::move(shopButton); + keyboard << NewRow{}; + } + keyboard << std::move(deleteButton); keyboard << NewRow{}; keyboard << std::move(backButton); - keyboard << std::move(deleteButton); } else { for (auto& b : constructNavigationButtons(pageNo, maxPageNum)) { keyboard << std::move(b); } keyboard << NewRow{}; - keyboard << std::move(deleteWithShopButton); + if (!withoutPutToShoppingListButton) { + keyboard << std::move(shopButton); + keyboard << NewRow{}; + } + keyboard << std::move(deleteButton); keyboard << NewRow{}; keyboard << std::move(backButton); - keyboard << std::move(deleteButton); } } return std::make_pair(text, keyboard); @@ -134,7 +145,7 @@ void renderStorageIngredientsDeletion(states::StorageIngredientsDeletion& state, state.storageIngredients, state.pageNo, numOfIngredientsOnPage, - state.storageIngredients); + state.addedToShopList); auto text = res.first; auto keyboard = res.second; if (auto messageId = message::getMessageId(userId)) diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 833d3e14..1262f6fe 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -28,7 +28,7 @@ using namespace tg_types; namespace { -InlineKeyboard constructNavigationsMarkup(size_t offset, +InlineKeyboard constructNavigationsMarkup(size_t offset, // NOLINT(*complexity*) size_t fullKeyBoardSize, size_t numOfRecipesOnPage, const states::StorageIngredientsList& state) { @@ -53,7 +53,7 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, InlineKeyboard keyboard(state.pageNo == 0 && ifMaxPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); auto searchButton = std::make_shared(); - searchButton->text = utils::utf8str(u8"✏️ Редактировать"); + searchButton->text = utils::utf8str(u8"🛒 Добавить"); searchButton->switchInlineQueryCurrentChat = ""; keyboard[0].push_back(std::move(searchButton)); for (auto [row, ing] : zip(drop(keyboard, 1), state.searchItems)) @@ -61,7 +61,13 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, if (state.pageNo == 0 && ifMaxPage) { // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + if (!state.storageIngredients.getValues().empty()) { + keyboard[arrowsRow].push_back(makeCallbackButton(u8"🗑 Удалить", "delete")); + keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + } else { + keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + } + return keyboard; } keyboard[arrowsRow].reserve(3); @@ -99,15 +105,19 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, keyboard[arrowsRow].insert( keyboard[arrowsRow].begin() + 1, makeCallbackButton(std::format("{} из {}", state.pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"🗑 Удалить из хранилища", "delete")); - keyboard[arrowsRow + 2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + if (state.storageIngredients.getValues().empty()) { + keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + } else { + keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"🗑 Удалить", "delete")); + keyboard[arrowsRow + 2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + } return keyboard; } InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::StorageIngredientsList& state) { // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 // for editing, 1 for delete - other buttons are ingredients - const size_t numOfRows = 4; + const size_t numOfRows = state.storageIngredients.getValues().empty() ? 3 : 4; const size_t offset = 1; // Number of rows before list const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); @@ -131,7 +141,13 @@ void renderIngredientsListSearch(const states::StorageIngredientsList& state, const std::size_t numOfIngredientsOnPage = 5; std::string list = state.storageIngredients.getValues() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); - auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); + + auto text = + state.storageIngredients.getValues().empty() + ? utils::utf8str(u8"🍗 Кажется, в вашем хранилище пока нет никаких продуктов. Чтобы добавить новый, " + u8"нажмите на кнопку 🛒 Добавить и начните вводить название продукта...\n\n") + : utils::utf8str(u8"🍗 Ваши продукты:\n\n"); + text += list; if (auto messageId = message::getMessageId(userId)) { bot.editMessageText(text, chatId, diff --git a/src/states.hpp b/src/states.hpp index b808927e..f1ce597b 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -74,6 +74,7 @@ struct StorageIngredientsList : detail::StorageIdMixin { struct StorageIngredientsDeletion : detail::StorageIdMixin { std::vector selectedIngredients; std::vector storageIngredients; + bool addedToShopList; std::size_t pageNo; }; From b48261586ef91ab025320cb760791e892dc546b2 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Sun, 20 Jul 2025 23:30:55 +0300 Subject: [PATCH 039/106] fix: create button position in storage list --- src/render/storages_list/view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/storages_list/view.cpp b/src/render/storages_list/view.cpp index 28409e88..05d75d22 100644 --- a/src/render/storages_list/view.cpp +++ b/src/render/storages_list/view.cpp @@ -27,7 +27,7 @@ void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot keyboard << NewRow{}; } - keyboard << makeCallbackButton(u8"🆕 Создать", "create") << makeCallbackButton(u8"↩️ Назад", "back"); + keyboard << makeCallbackButton(u8"↩️ Назад", "back") << makeCallbackButton(u8"🆕 Создать", "create"); auto text = utils::utf8str(u8"🍱 Ваши хранилища"); if (toBeEdited) { From 242678c32474135c7cc761c2357d8c359ce7e882 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Sun, 20 Jul 2025 23:45:49 +0300 Subject: [PATCH 040/106] refactor: fix linter errors --- src/backend/api/shopping_lists.hpp | 1 + src/backend/models/ingredient.cpp | 2 + .../models/publication_request_status.cpp | 3 ++ .../models/publication_request_status.hpp | 1 + src/handlers/commands/start.cpp | 18 ++++++--- src/handlers/commands/wanna_eat.cpp | 3 +- src/handlers/common.hpp | 1 + src/handlers/personal_account/view.cpp | 1 + src/handlers/recipe/add_storage.cpp | 6 ++- src/handlers/recipe/view.cpp | 8 ++-- src/handlers/shopping_list/create.cpp | 1 + src/handlers/shopping_list/search.cpp | 1 + src/handlers/shopping_list/search.hpp | 2 + src/handlers/shopping_list/view.cpp | 2 +- src/handlers/storage/view.cpp | 23 ++++++++--- src/handlers/storages_selection/view.cpp | 1 + src/render/pagination.hpp | 1 + .../personal_account/recipes_list/view.cpp | 3 +- src/render/shopping_list/create.cpp | 12 +++--- src/render/storage/members/add.cpp | 16 ++++---- src/render/storage/members/view.cpp | 8 ++-- src/render/storages_selection/view.cpp | 4 +- src/utils/ingredients_availability.cpp | 38 +++++++++++-------- src/utils/ingredients_availability.hpp | 2 + src/utils/to_string.cpp | 12 +++--- 25 files changed, 108 insertions(+), 62 deletions(-) diff --git a/src/backend/api/shopping_lists.hpp b/src/backend/api/shopping_lists.hpp index 3b02b558..67469f06 100644 --- a/src/backend/api/shopping_lists.hpp +++ b/src/backend/api/shopping_lists.hpp @@ -6,6 +6,7 @@ #include +#include #include namespace cookcookhnya::api { diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index 1228ba30..259ccc1c 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace cookcookhnya::api::models::ingredient { namespace json = boost::json; diff --git a/src/backend/models/publication_request_status.cpp b/src/backend/models/publication_request_status.cpp index a6d00a90..ebbed02e 100644 --- a/src/backend/models/publication_request_status.cpp +++ b/src/backend/models/publication_request_status.cpp @@ -1,5 +1,8 @@ #include "backend/models/publication_request_status.hpp" +#include +#include + namespace cookcookhnya::api::models::status { PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp index c3cf284a..9ce2a739 100644 --- a/src/backend/models/publication_request_status.hpp +++ b/src/backend/models/publication_request_status.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include diff --git a/src/handlers/commands/start.cpp b/src/handlers/commands/start.cpp index 197a9472..dbaa941a 100644 --- a/src/handlers/commands/start.cpp +++ b/src/handlers/commands/start.cpp @@ -6,6 +6,7 @@ #include "render/main_menu/view.hpp" #include "states.hpp" +#include #include #include #include @@ -14,33 +15,38 @@ namespace cookcookhnya::handlers::commands { using namespace render::main_menu; +using namespace api::models::user; using namespace std::literals; void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { auto userId = m.from->id; + auto chatId = m.chat->id; + std::string fullName = m.from->firstName; if (!m.from->lastName.empty()) { fullName += ' '; fullName += m.from->lastName; } + std::optional alias; if (!m.from->username.empty()) alias = m.from->username; - api.getUsersApi().updateInfo( - userId, - api::models::user::UpdateUserInfoBody{.alias = std::move(m.from->username), .fullName = std::move(fullName)}); + api.getUsersApi().updateInfo(userId, + UpdateUserInfoBody{.alias = std::move(alias), .fullName = std::move(fullName)}); auto startText = m.text; - const int hashPos = "/start "sv.size(); + const std::size_t hashPos = "/start "sv.size(); if (startText.size() > hashPos - 1) { auto hash = std::string(m.text).substr(hashPos); auto storage = api.getStoragesApi().activate(userId, hash); - renderMainMenu(false, storage->name, m.from->id, m.chat->id, bot, api); + if (!storage) + return; + renderMainMenu(false, storage->name, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } - renderMainMenu(false, std::nullopt, m.from->id, m.chat->id, bot, api); + renderMainMenu(false, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); }; diff --git a/src/handlers/commands/wanna_eat.cpp b/src/handlers/commands/wanna_eat.cpp index 8e246f98..c99364cd 100644 --- a/src/handlers/commands/wanna_eat.cpp +++ b/src/handlers/commands/wanna_eat.cpp @@ -7,6 +7,7 @@ #include "render/storages_selection/view.hpp" #include "states.hpp" #include "utils/utils.hpp" + #include namespace cookcookhnya::handlers::commands { @@ -25,8 +26,8 @@ void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRe message::deleteMessageId(m.from->id); renderRecipesSuggestion({storages}, 0, m.from->id, m.chat->id, bot, api); stateManager.put(SuggestedRecipesList{ - .pageNo = 0, .selectedStorages = storages, + .pageNo = 0, .fromStorage = false, }); } else { diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 0c27215c..dcf5c492 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -3,6 +3,7 @@ #include "backend/api/api.hpp" #include "backend/api/ingredients.hpp" #include "backend/api/recipes.hpp" +#include "backend/api/shopping_lists.hpp" #include "backend/api/storages.hpp" #include "backend/api/users.hpp" #include "states.hpp" diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 14bcbc25..fe43109c 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -5,6 +5,7 @@ #include "render/personal_account/ingredients_list/view.hpp" #include "render/personal_account/recipes_list/view.hpp" +#include #include namespace cookcookhnya::handlers::personal_account { diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp index e86a5864..9e5fc6ee 100644 --- a/src/handlers/recipe/add_storage.cpp +++ b/src/handlers/recipe/add_storage.cpp @@ -9,6 +9,7 @@ #include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" +#include #include #include #include @@ -16,6 +17,7 @@ namespace cookcookhnya::handlers::recipe { using namespace render::recipe; +using namespace api::models::storage; void handleRecipeStorageAdditionCQ( RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { @@ -34,7 +36,7 @@ void handleRecipeStorageAdditionCQ( auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; + const StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; state.prevState.addedStorages.push_back(newStorage); utils::addStorage(state.prevState.availability, newStorage); renderStoragesSuggestion(state.prevState.availability, @@ -52,7 +54,7 @@ void handleRecipeStorageAdditionCQ( auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; + const StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; state.prevState.addedStorages.erase(std::ranges::find( state.prevState.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); utils::deleteStorage(state.prevState.availability, newStorage); diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 9876c21c..24f9af18 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -6,6 +6,7 @@ #include "render/recipe/add_storage.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" +#include "utils/ingredients_availability.hpp" #include #include @@ -16,9 +17,10 @@ namespace cookcookhnya::handlers::recipe { using namespace render::recipes_suggestions; using namespace render::shopping_list; using namespace render::recipe; +using namespace api::models::ingredient; void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { - std::string data = cq.data; + const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; @@ -28,8 +30,8 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe } if (data == "shopping_list") { - std::vector selectedIngredients; - std::vector allIngredients; + std::vector selectedIngredients; + std::vector allIngredients; for (const auto& infoPair : state.availability) { if (infoPair.second.available == utils::AvailabiltiyType::NOT_AVAILABLE) { selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 0d0ed0d6..357c1c87 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace cookcookhnya::handlers::shopping_list { diff --git a/src/handlers/shopping_list/search.cpp b/src/handlers/shopping_list/search.cpp index 09374d1f..86f075af 100644 --- a/src/handlers/shopping_list/search.cpp +++ b/src/handlers/shopping_list/search.cpp @@ -8,6 +8,7 @@ #include "render/shopping_list/view.hpp" #include "utils/parsing.hpp" +#include #include #include diff --git a/src/handlers/shopping_list/search.hpp b/src/handlers/shopping_list/search.hpp index 78db0847..2ff85fd2 100644 --- a/src/handlers/shopping_list/search.hpp +++ b/src/handlers/shopping_list/search.hpp @@ -2,6 +2,8 @@ #include "handlers/common.hpp" +#include + namespace cookcookhnya::handlers::shopping_list { const std::size_t searchPageSize = 10; diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index 3b9ae05c..d30ca6cd 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -10,6 +10,7 @@ #include "states.hpp" #include "utils/parsing.hpp" +#include #include #include #include @@ -18,7 +19,6 @@ namespace cookcookhnya::handlers::shopping_list { using namespace render::main_menu; using namespace render::shopping_list; - using namespace std::views; using namespace std::ranges; diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index c8d1cb32..913cf69b 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace cookcookhnya::handlers::storage { @@ -20,6 +21,7 @@ using namespace render::storage::members; using namespace render::storages_list; using namespace render::recipes_suggestions; using namespace render::delete_storage; +using namespace api::models::storage; using namespace std::views; const std::size_t numOfIngredientsOnPage = 5; @@ -34,20 +36,31 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM auto newState = StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}; renderIngredientsListSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); stateManager.put(std::move(newState)); - } else if (cq.data == "members") { + return; + } + + if (cq.data == "members") { renderMemberList(true, state.storageId, userId, chatId, bot, api); stateManager.put(StorageMemberView{state.storageId}); - } else if (cq.data == "back") { + return; + } + + if (cq.data == "back") { renderStorageList(true, userId, chatId, bot, api); stateManager.put(StorageList{}); - } else if (cq.data == "wanna_eat") { + return; + } + + if (cq.data == "wanna_eat") { auto storageDetails = api.getStoragesApi().get(userId, state.storageId); - api::models::storage::StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; + const StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; std::vector storages = {storage}; renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); stateManager.put(SuggestedRecipesList{.selectedStorages = storages, .pageNo = 0, .fromStorage = true}); return; - } else if (cq.data == "delete") { + } + + if (cq.data == "delete") { renderStorageDeletion(state.storageId, chatId, bot, cq.from->id, api); stateManager.put(StorageDeletion{state.storageId}); return; diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 24037402..d10db71b 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -9,6 +9,7 @@ #include "utils/parsing.hpp" #include +#include #include #include diff --git a/src/render/pagination.hpp b/src/render/pagination.hpp index 5527cfed..7dadff50 100644 --- a/src/render/pagination.hpp +++ b/src/render/pagination.hpp @@ -4,6 +4,7 @@ #include "utils/utils.hpp" #include +#include #include #include #include diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 8a7a906e..00c746f0 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace TgBot { class InlineKeyboardMarkup; @@ -43,7 +44,7 @@ void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, B auto recipesList = recipesApi.getList(userId, PublicityFilterType::Custom, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); - std::string pageInfo = utils::utf8str( + const std::string pageInfo = utils::utf8str( recipesList.found > 0 ? u8"🔪 Рецепты созданные вами:" : u8"🔪 Вы находитесь в Мои рецепты. Создавайте и делитесь новыми рецептами.\n\n"); diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index 63914c4c..a75bb221 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -6,6 +6,7 @@ #include "utils/to_string.hpp" #include "utils/utils.hpp" +#include #include #include #include @@ -23,7 +24,7 @@ void renderShoppingListCreation(const std::vector& selectedIngredien UserId userId, ChatId chatId, BotRef bot) { - std::string text = utils::utf8str(u8"📝 Выберите продукты, которые хотели бы добавить в список покупок\n\n"); + const std::string text = utils::utf8str(u8"📝 Выберите продукты, которые хотели бы добавить в список покупок\n\n"); const std::size_t buttonRows = ((selectedIngredients.size() + 1) / 2) + 1; // ceil(ingredientsCount / 2), back InlineKeyboardBuilder keyboard{buttonRows}; @@ -31,13 +32,12 @@ void renderShoppingListCreation(const std::vector& selectedIngredien for (auto chunk : allIngredients | chunk(2)) { keyboard.reserveInRow(2); for (const Ingredient& ing : chunk) { - const bool isSelected = - std::ranges::contains(selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id); - std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); + const bool isSelected = std::ranges::contains(selectedIngredients, ing.id, &Ingredient::id); + std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); // second is not empty but invisible! // button data is onclick action for bot: "+" is "add", "-" is "remove" const char* actionPrefix = isSelected ? "-" : "+"; - std::string text = std::format("{} {}", emoji, ing.name); - std::string data = actionPrefix + utils::to_string(ing.id); + const std::string text = std::format("{} {}", emoji, ing.name); + const std::string data = actionPrefix + utils::to_string(ing.id); keyboard << makeCallbackButton(text, data); } keyboard << NewRow{}; diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index 6378b21a..7dde3c8a 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -24,9 +25,8 @@ void renderStorageMemberAddition( auto text = utils::utf8str(u8"📩 Создайте ссылку или перешлите сообщение пользователя, чтобы добавить его в хранилище.\n"); auto messageId = message::getMessageId(userId); - if (messageId) { - bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); - } + if (messageId) + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); }; void renderShareLinkMemberAddition( @@ -34,14 +34,14 @@ void renderShareLinkMemberAddition( auto storage = storageApi.get(userId, storageId); const std::size_t buttonRows = 2; - InlineKeyboard keyboard(buttonRows); + InlineKeyboard keyboard{buttonRows}; auto inviteButton = std::make_shared(); inviteButton->text = utils::utf8str(u8"📤 Поделиться"); - auto hash = storageApi.inviteMember(userId, storageId); - const auto telegramBotAlias = bot.getUnderlying().getMe()->username; - auto inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + - "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=" + hash; + api::InvitationId hash = storageApi.inviteMember(userId, storageId); + const std::string telegramBotAlias = bot.getUnderlying().getMe()->username; + std::string inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + + "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=" + hash; inviteButton->url = "https://t.me/share/url?url=" + inviteText; keyboard[0].push_back(std::move(inviteButton)); diff --git a/src/render/storage/members/view.cpp b/src/render/storage/members/view.cpp index 8009c32f..0ddfaea5 100644 --- a/src/render/storage/members/view.cpp +++ b/src/render/storage/members/view.cpp @@ -5,6 +5,7 @@ #include "render/common.hpp" #include "utils/utils.hpp" +#include #include #include #include @@ -28,9 +29,8 @@ void renderMemberList(bool toBeEdited, if (isOwner) { keyboard[0].push_back(makeCallbackButton(u8"🔐 Добавить", "add")); - if (storageApi.getStorageMembers(userId, storageId).size() > 1) { + if (storageApi.getStorageMembers(userId, storageId).size() > 1) keyboard[0].push_back(makeCallbackButton(u8"🔒 Удалить", "delete")); - } keyboard[1].push_back(makeCallbackButton(u8"↩️Назад", "back")); } else { keyboard[0].push_back(makeCallbackButton(u8"↩️Назад", "back")); @@ -44,9 +44,9 @@ void renderMemberList(bool toBeEdited, for (auto [i, name] : std::views::enumerate(memberNames)) std::format_to(std::back_inserter(list), " {}. {}\n", i + 1, name); auto text = utils::utf8str(u8"👥 Список участников\n") + list; + if (toBeEdited) { - auto messageId = message::getMessageId(userId); - if (messageId) + if (auto messageId = message::getMessageId(userId)) bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); } else { auto messageId = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index 9cd31b6e..a3fffb33 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -36,8 +36,8 @@ void renderStorageSelection( const bool isSelected = std::ranges::contains(selectedStorages, storage.id, &StorageSummary::id); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; - std::string text = std::format("{} {}", emoji, storage.name); - std::string data = actionPrefix + utils::to_string(storage.id); + const std::string text = std::format("{} {}", emoji, storage.name); + const std::string data = actionPrefix + utils::to_string(storage.id); keyboard << makeCallbackButton(text, data); } keyboard << NewRow{}; diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index c2e70785..9b0b0703 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -1,37 +1,44 @@ #include "ingredients_availability.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" +#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" +#include "tg_types.hpp" +#include #include #include +#include +#include +#include namespace cookcookhnya::utils { using namespace api; +using namespace api::models::recipe; +using namespace api::models::storage; using namespace tg_types; +using namespace std::views; +using namespace std::ranges; -std::vector> -inStoragesAvailability(std::vector& selectedStorages, - RecipeId recipeId, - UserId userId, - const api::ApiClient& api) { +std::vector> inStoragesAvailability( + std::vector& selectedStorages, RecipeId recipeId, UserId userId, const api::ApiClient& api) { auto allStorages = api.getStoragesApi().getStoragesList(userId); auto recipe = api.getRecipesApi().get(userId, recipeId); - auto selectedStoragesSet = selectedStorages | std::views::transform(&api::models::storage::StorageSummary::id) | - std::ranges::to(); + auto selectedStoragesSet = selectedStorages | views::transform(&StorageSummary::id) | to(); - std::unordered_map allStoragesMap; + std::unordered_map allStoragesMap; for (const auto& storage : allStorages) { allStoragesMap.emplace(storage.id, storage); } - std::vector> result; + std::vector> result; for (const auto& ingredient : recipe.ingredients) { IngredientAvailability availability; - std::vector storages; + std::vector storages; bool hasInSelected = false; bool hasInOther = false; @@ -65,10 +72,10 @@ inStoragesAvailability(std::vector& selectedSto return result; } -void addStorage(std::vector>& availability, - const api::models::storage::StorageSummary& storage) { +void addStorage(std::vector>& availability, + const StorageSummary& storage) { for (auto& infoPair : availability) { - auto it = std::ranges::find(infoPair.second.storages, storage.id, &models::storage::StorageSummary::id); + auto it = std::ranges::find(infoPair.second.storages, storage.id, &StorageSummary::id); if (it != infoPair.second.storages.end()) { infoPair.second.storages.erase(it); infoPair.second.available = AvailabiltiyType::AVAILABLE; @@ -76,9 +83,8 @@ void addStorage(std::vector>& availability, - const api::models::storage::StorageSummary& storage) { +void deleteStorage(std::vector>& availability, + const StorageSummary& storage) { for (auto& infoPair : availability) { for (auto& storage_ : infoPair.first.inStorages) { if (storage.id == storage_.id) { diff --git a/src/utils/ingredients_availability.hpp b/src/utils/ingredients_availability.hpp index 787525fc..5f205621 100644 --- a/src/utils/ingredients_availability.hpp +++ b/src/utils/ingredients_availability.hpp @@ -4,8 +4,10 @@ #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" +#include "tg_types.hpp" #include +#include #include namespace cookcookhnya::utils { diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 2ea41a16..964cef46 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -4,9 +4,9 @@ #include "utils/utils.hpp" #include "uuid.hpp" -#include #include +#include #include namespace cookcookhnya::utils { @@ -15,12 +15,10 @@ std::string to_string(const Uuid& u) { return boost::lexical_cast(u); } -std::string to_string(const cookcookhnya::api::models::status::PublicationRequestStatus status) { - const std::array statusStr = {utf8str(u8"🟡 На рассмотрении"), - utf8str(u8"🟢 Принят"), - utf8str(u8"🔴 Отклонен"), - utf8str(u8"⚪️ Вы еще не отправили запрос")}; - return statusStr[static_cast(status)]; +std::string to_string(const api::models::status::PublicationRequestStatus status) { + static constexpr std::array statusStr = { + u8"🟡 На рассмотрении", u8"🟢 Принят", u8"🔴 Отклонен", u8"⚪️ Вы еще не отправили запрос"}; + return utf8str(statusStr[static_cast(status)]); } } // namespace cookcookhnya::utils From 8acefc911ad5674819593932f85ca08d0a403781 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 00:01:36 +0300 Subject: [PATCH 041/106] refactor: move utils::to_string overloads to their files --- .../models/publication_request_status.cpp | 18 +++++++++++++- .../models/publication_request_status.hpp | 7 ++++++ src/utils/CMakeLists.txt | 1 - src/utils/to_string.cpp | 24 ------------------- src/utils/to_string.hpp | 7 ------ src/utils/uuid.cpp | 10 ++++++++ src/utils/uuid.hpp | 6 +++++ 7 files changed, 40 insertions(+), 33 deletions(-) delete mode 100644 src/utils/to_string.cpp diff --git a/src/backend/models/publication_request_status.cpp b/src/backend/models/publication_request_status.cpp index ebbed02e..d4e0c396 100644 --- a/src/backend/models/publication_request_status.cpp +++ b/src/backend/models/publication_request_status.cpp @@ -1,8 +1,14 @@ -#include "backend/models/publication_request_status.hpp" +#include "publication_request_status.hpp" + +#include "utils/utils.hpp" #include #include +#include +#include +#include + namespace cookcookhnya::api::models::status { PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, @@ -17,3 +23,13 @@ PublicationRequestStatus tag_invoke(boost::json::value_to_tag(status)]); +} + +} // namespace cookcookhnya::utils diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp index 9ce2a739..0920d883 100644 --- a/src/backend/models/publication_request_status.hpp +++ b/src/backend/models/publication_request_status.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace cookcookhnya::api::models::status { @@ -12,3 +13,9 @@ enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); } // namespace cookcookhnya::api::models::status + +namespace cookcookhnya::utils { + +std::string to_string(api::models::status::PublicationRequestStatus status); + +} // namespace cookcookhnya::utils diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 3c20a6f7..c6cf0dea 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -1,6 +1,5 @@ target_sources(main PRIVATE src/utils/parsing.cpp - src/utils/to_string.cpp src/utils/utils.cpp src/utils/uuid.cpp src/utils/ingredients_availability.cpp diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp deleted file mode 100644 index 964cef46..00000000 --- a/src/utils/to_string.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "to_string.hpp" - -#include "backend/models/publication_request_status.hpp" -#include "utils/utils.hpp" -#include "uuid.hpp" - -#include - -#include -#include - -namespace cookcookhnya::utils { - -std::string to_string(const Uuid& u) { - return boost::lexical_cast(u); -} - -std::string to_string(const api::models::status::PublicationRequestStatus status) { - static constexpr std::array statusStr = { - u8"🟡 На рассмотрении", u8"🟢 Принят", u8"🔴 Отклонен", u8"⚪️ Вы еще не отправили запрос"}; - return utf8str(statusStr[static_cast(status)]); -} - -} // namespace cookcookhnya::utils diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index 815ef722..42986d1c 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -1,8 +1,5 @@ #pragma once -#include "backend/models/publication_request_status.hpp" -#include "uuid.hpp" - #include namespace cookcookhnya::utils { @@ -15,8 +12,4 @@ std::string to_string(const T& t) { return std::to_string(t); } -std::string to_string(const Uuid& u); - -std::string to_string(api::models::status::PublicationRequestStatus status); - } // namespace cookcookhnya::utils diff --git a/src/utils/uuid.cpp b/src/utils/uuid.cpp index 94fa5072..daeae6d5 100644 --- a/src/utils/uuid.cpp +++ b/src/utils/uuid.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace boost::uuids { uuid tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { @@ -12,3 +14,11 @@ uuid tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { } } // namespace boost::uuids + +namespace cookcookhnya::utils { + +std::string to_string(const Uuid& u) { + return boost::lexical_cast(u); +} + +} // namespace cookcookhnya::utils diff --git a/src/utils/uuid.hpp b/src/utils/uuid.hpp index 1343dd0f..eec3d1b8 100644 --- a/src/utils/uuid.hpp +++ b/src/utils/uuid.hpp @@ -22,6 +22,12 @@ using Uuid = boost::uuids::uuid; } // namespace cookcookhnya +namespace cookcookhnya::utils { + +std::string to_string(const Uuid& u); + +} // namespace cookcookhnya::utils + template <> struct std::formatter : formatter { template From 6a333441ee19f3b34048d5785712940771858ad0 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 00:03:03 +0300 Subject: [PATCH 042/106] refactor: fix linter errors --- src/render/pagination.hpp | 1 + src/render/storage/members/add.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/render/pagination.hpp b/src/render/pagination.hpp index 7dadff50..92725d5d 100644 --- a/src/render/pagination.hpp +++ b/src/render/pagination.hpp @@ -4,6 +4,7 @@ #include "utils/utils.hpp" #include +#include #include #include #include diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index 7dde3c8a..dbf46305 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -38,10 +38,10 @@ void renderShareLinkMemberAddition( auto inviteButton = std::make_shared(); inviteButton->text = utils::utf8str(u8"📤 Поделиться"); - api::InvitationId hash = storageApi.inviteMember(userId, storageId); + const api::InvitationId hash = storageApi.inviteMember(userId, storageId); const std::string telegramBotAlias = bot.getUnderlying().getMe()->username; - std::string inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + - "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=" + hash; + const std::string inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + + "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=" + hash; inviteButton->url = "https://t.me/share/url?url=" + inviteText; keyboard[0].push_back(std::move(inviteButton)); From e5bffb3dde14a0745a72ecd0f56ecac66098d3c3 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 00:04:17 +0300 Subject: [PATCH 043/106] fix: for recipe history and search enhancement --- src/backend/models/recipe.cpp | 9 +++--- src/backend/models/request_history.cpp | 5 ++-- .../recipe/publication_history.cpp | 28 +++++++++---------- .../recipe/publication_history.hpp | 2 +- .../recipe/search_ingredients.cpp | 3 +- src/handlers/personal_account/recipe/view.cpp | 6 ++-- src/handlers/storage/ingredients/view.cpp | 3 +- .../recipe/publication_history.cpp | 14 ++++++---- src/render/personal_account/recipe/view.cpp | 6 ++-- src/states.hpp | 1 + src/utils/to_string.cpp | 2 +- 11 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 708467cf..e3614156 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -56,8 +56,8 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: // Deal with optionals using ternary .creator = j.as_object().if_contains("creator") ? value_to(j.at("creator")) : user::UserDetails{.userId = 0, .alias = "", .fullName = ""}, - .moderationStatus = j.as_object().if_contains("status") - ? value_to(j.at("status")) + .moderationStatus = j.as_object().if_contains("moderationStatus") + ? value_to(j.at("moderationStatus")) : PublicationRequestStatus::NO_REQUEST, }; } @@ -84,8 +84,9 @@ PublicationHistoryRecipe tag_invoke(json::value_to_tag : "", .status = j.as_object().if_contains("status") ? value_to(j.at("status")) : PublicationRequestStatus::NO_REQUEST, - .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updatedAt"))) - : std::chrono::time_point(), + .updated = j.as_object().if_contains("updatedAt") + ? utils::parseIsoTime(value_to(j.at("updatedAt"))) + : std::chrono::time_point(), }; } diff --git a/src/backend/models/request_history.cpp b/src/backend/models/request_history.cpp index d5b9e1d5..b7782511 100644 --- a/src/backend/models/request_history.cpp +++ b/src/backend/models/request_history.cpp @@ -16,9 +16,8 @@ PublicationHistoryInstance tag_invoke(json::value_to_tag(j.at("reason")) : "", - .status = j.as_object().if_contains("status") - ? value_to(j.at("status")) - : recipe::PublicationRequestStatus::NO_REQUEST, + .status = j.as_object().if_contains("status") ? value_to(j.at("status")) + : recipe::PublicationRequestStatus::NO_REQUEST, .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) : std::chrono::time_point(), }; diff --git a/src/handlers/personal_account/recipe/publication_history.cpp b/src/handlers/personal_account/recipe/publication_history.cpp index 82e83ac4..f3a6ca01 100644 --- a/src/handlers/personal_account/recipe/publication_history.cpp +++ b/src/handlers/personal_account/recipe/publication_history.cpp @@ -1,23 +1,21 @@ #include "publication_history.hpp" +#include "handlers/common.hpp" +#include "render/personal_account/recipe/publication_history.hpp" #include "render/personal_account/recipe/view.hpp" - namespace cookcookhnya::handlers::personal_account::recipe::publication_history { using namespace render::personal_account; using namespace render::personal_account::recipes; - -void handleCustomRecipePublicationHistoryCQ(CustomRecipePublicationHistory& state, - CallbackQueryRef cq, - BotRef bot, - SMRef stateManager, - RecipesApiRef recipesApi) { +using namespace render::personal_account::recipe::publication_history; +void handleCustomRecipePublicationHistoryCQ( + CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; if (data == "back") { - auto ingredientsAndRecipeName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); + auto ingredientsAndRecipeName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); stateManager.put(RecipeCustomView{.recipeId = state.recipeId, .pageNo = state.pageNo, .ingredients = std::get<0>(ingredientsAndRecipeName), @@ -26,13 +24,13 @@ void handleCustomRecipePublicationHistoryCQ(CustomRecipePublicationHistory& stat } if (data == "confirm") { - recipesApi.publishCustom(userId, state.recipeId); - // As published return back - auto ingredientsAndRecipeName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, recipesApi); - stateManager.put(RecipeCustomView{.recipeId = state.recipeId, - .pageNo = state.pageNo, - .ingredients = std::get<0>(ingredientsAndRecipeName), - .recipeName = std::get<1>(ingredientsAndRecipeName)}); + // Peeking (if button with this data then accepted or pending) + api.getRecipesApi().publishCustom(userId, state.recipeId); + bool isPeeking = true; + renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); + stateManager.put(states::CustomRecipePublicationHistory{ + .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); + bot.answerCallbackQuery(cq.id); return; } } diff --git a/src/handlers/personal_account/recipe/publication_history.hpp b/src/handlers/personal_account/recipe/publication_history.hpp index c96a9e1e..c12ea275 100644 --- a/src/handlers/personal_account/recipe/publication_history.hpp +++ b/src/handlers/personal_account/recipe/publication_history.hpp @@ -5,6 +5,6 @@ namespace cookcookhnya::handlers::personal_account::recipe::publication_history { void handleCustomRecipePublicationHistoryCQ( - CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef api); + CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account::recipe::publication_history diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 21571fd8..3aa276d2 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -54,7 +54,8 @@ void updateSearch(CustomRecipeIngredientsSearch& state, } } } - renderSuggestIngredientCustomisation(state, userId, userId, bot); + if (state.totalFound == 0) + renderSuggestIngredientCustomisation(state, userId, userId, bot); } } // namespace diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index ff331093..21c299a0 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -54,7 +54,8 @@ void handleRecipeCustomViewCQ( // Not peeking (if button with this data then idle or rejected) bool isPeeking = false; renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); - stateManager.put(states::CustomRecipePublicationHistory{.recipeId = state.recipeId, .pageNo = state.pageNo}); + stateManager.put(states::CustomRecipePublicationHistory{ + .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); bot.answerCallbackQuery(cq.id); return; } @@ -62,7 +63,8 @@ void handleRecipeCustomViewCQ( // Peeking (if button with this data then accepted or pending) bool isPeeking = true; renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); - stateManager.put(states::CustomRecipePublicationHistory{.recipeId = state.recipeId, .pageNo = state.pageNo}); + stateManager.put(states::CustomRecipePublicationHistory{ + .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); bot.answerCallbackQuery(cq.id); return; } diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 9bc1f9a9..a15d88a7 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -53,7 +53,8 @@ void updateSearch( } } } - renderSuggestIngredientCustomisation(state, userId, userId, bot); + if (state.totalFound == 0) + renderSuggestIngredientCustomisation(state, userId, userId, bot); } } // namespace diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/publication_history.cpp index e75c293e..cbf87a65 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/publication_history.cpp @@ -9,9 +9,11 @@ #include #include +#include #include namespace cookcookhnya::render::personal_account::recipe::publication_history { +using namespace std::views; void renderPublicationHistory(UserId userId, ChatId chatId, @@ -27,13 +29,15 @@ void renderPublicationHistory(UserId userId, std::string toPrint; toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n"); if (!history.empty()) { - toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[0].status) + - (history[0].reason.has_value() ? std::format(" по причине {}", history[0].reason.value()) : " ") + - utils::to_string(history[0].created) + "\n\n"; + toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[history.size() - 1].status) + + (history[history.size() - 1].reason.has_value() + ? std::format(" по причине {} ", history[history.size() - 1].reason.value()) + : " ") + + utils::to_string(history[history.size() - 1].created) + "\n\n"; // Remove the lastest history instance as it's showed differently - history.erase(history.begin()); + history.erase(history.end()); - for (auto& req : history) { + for (auto& req : history | reverse) { toPrint += std::format("Статус: {} ", utils::to_string(req.status)); if (req.reason.has_value()) toPrint += std::format("по причине: {} ", req.reason.value()); diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index f8b469a4..581b4859 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -41,8 +41,8 @@ std::tuple, std::string> render 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 == api::models::recipe::PublicationRequestStatus::NO_REQUEST || - recipeDetails.moderationStatus == api::models::recipe::PublicationRequestStatus::REJECTED) { + if (recipeDetails.moderationStatus.value() == api::models::recipe::PublicationRequestStatus::NO_REQUEST || + recipeDetails.moderationStatus.value() == api::models::recipe::PublicationRequestStatus::REJECTED) { keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; } else { keyboard << makeCallbackButton(u8"📢 История публикаций", "peekpublish") << NewRow{}; @@ -55,7 +55,7 @@ std::tuple, std::string> render if (messageId) bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); } else { - auto message = bot.sendMessage(chatId, toPrint, std::move(keyboard)); + auto message = bot.sendMessage(chatId, toPrint, std::move(keyboard), "Markdown"); message::addMessageId(userId, message->messageId); } return {ingredients, recipeDetails.name}; diff --git a/src/states.hpp b/src/states.hpp index 7bb49aba..f1a59fce 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -176,6 +176,7 @@ struct ShoppingListStorageSelectionToBuy { struct CustomRecipePublicationHistory { api::RecipeId recipeId; std::size_t pageNo; + std::string recipeName; }; struct AllPublicationHistory { diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index f618a6d4..edb49c99 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -27,7 +27,7 @@ std::string to_string(std::chrono::system_clock::time_point tp) { std::time_t time = std::chrono::system_clock::to_time_t(tp); std::tm tm = *std::gmtime(&time); std::ostringstream oss; - oss << std::put_time(&tm, "%Y-%m-%dT%H:%M"); + oss << std::put_time(&tm, "%Y-%m-%d %H:%M"); return oss.str(); } From d718eed52130f03f87a11cf69410db8d38beb1c6 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 00:51:55 +0300 Subject: [PATCH 044/106] fix: after merge --- .../models/publication_request_status.cpp | 6 +++--- src/backend/models/recipe.cpp | 18 ++++-------------- src/backend/models/recipe.hpp | 6 ++---- src/backend/models/request_history.cpp | 6 +++--- src/backend/models/request_history.hpp | 4 ++-- .../ingredients_list/create.cpp | 6 ++---- .../recipe/search_ingredients.cpp | 5 ++--- src/handlers/storage/ingredients/view.cpp | 3 +-- .../personal_account/ingredients_list/view.cpp | 4 ++-- src/states.hpp | 7 ++----- src/utils/to_string.cpp | 5 +---- src/utils/to_string.hpp | 4 +--- 12 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/backend/models/publication_request_status.cpp b/src/backend/models/publication_request_status.cpp index a6d00a90..7f85b07b 100644 --- a/src/backend/models/publication_request_status.cpp +++ b/src/backend/models/publication_request_status.cpp @@ -4,11 +4,11 @@ namespace cookcookhnya::api::models::status { PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, const boost::json::value& j) { - if (j.at("status") == "Pending") + if (j == "pending") return PublicationRequestStatus::PENDING; - if (j.at("status") == "Accepted") + if (j == "accepted") return PublicationRequestStatus::ACCEPTED; - if (j.at("status") == "Rejected") + if (j == "rejected") return PublicationRequestStatus::REJECTED; return PublicationRequestStatus::NO_REQUEST; } diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index e3614156..fe1dbf7f 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -57,8 +57,8 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: .creator = j.as_object().if_contains("creator") ? value_to(j.at("creator")) : user::UserDetails{.userId = 0, .alias = "", .fullName = ""}, .moderationStatus = j.as_object().if_contains("moderationStatus") - ? value_to(j.at("moderationStatus")) - : PublicationRequestStatus::NO_REQUEST, + ? value_to(j.at("moderationStatus")) + : status::PublicationRequestStatus::NO_REQUEST, }; } @@ -82,22 +82,12 @@ PublicationHistoryRecipe tag_invoke(json::value_to_tag .reason = j.as_object().if_contains("reason") ? value_to(j.at("reason")) : "", - .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")) + : status::PublicationRequestStatus::NO_REQUEST, .updated = j.as_object().if_contains("updatedAt") ? utils::parseIsoTime(value_to(j.at("updatedAt"))) : std::chrono::time_point(), }; } -PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, - const boost::json::value& j) { - if (j == "pending") - return PublicationRequestStatus::PENDING; - if (j == "accepted") - return PublicationRequestStatus::ACCEPTED; - if (j == "rejected") - return PublicationRequestStatus::REJECTED; - return PublicationRequestStatus::NO_REQUEST; -} } // namespace cookcookhnya::api::models::recipe diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index be5e88db..fda00cbf 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -3,20 +3,18 @@ #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "backend/models/user.hpp" +#include "publication_request_status.hpp" #include #include #include -#include #include #include #include namespace cookcookhnya::api::models::recipe { - -enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED, NO_REQUEST }; -PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); +using namespace status; struct RecipeSummary { RecipeId id; diff --git a/src/backend/models/request_history.cpp b/src/backend/models/request_history.cpp index b7782511..db760fe7 100644 --- a/src/backend/models/request_history.cpp +++ b/src/backend/models/request_history.cpp @@ -1,5 +1,5 @@ #include "request_history.hpp" -#include "backend/models/recipe.hpp" +#include "publication_request_status.hpp" #include "utils/serialization.hpp" #include #include @@ -16,8 +16,8 @@ PublicationHistoryInstance tag_invoke(json::value_to_tag(j.at("reason")) : "", - .status = j.as_object().if_contains("status") ? value_to(j.at("status")) - : recipe::PublicationRequestStatus::NO_REQUEST, + .status = j.as_object().if_contains("status") ? value_to(j.at("status")) + : status::PublicationRequestStatus::NO_REQUEST, .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) : std::chrono::time_point(), }; diff --git a/src/backend/models/request_history.hpp b/src/backend/models/request_history.hpp index fe005e6a..d7d8a139 100644 --- a/src/backend/models/request_history.hpp +++ b/src/backend/models/request_history.hpp @@ -1,6 +1,6 @@ #pragma once -#include "backend/models/recipe.hpp" +#include "publication_request_status.hpp" #include #include @@ -14,7 +14,7 @@ struct PublicationHistoryInstance { std::string name; std::chrono::system_clock::time_point created; std::optional reason; - api::models::recipe::PublicationRequestStatus status{}; + api::models::status::PublicationRequestStatus status{}; std::optional updated; friend PublicationHistoryInstance tag_invoke(boost::json::value_to_tag, diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 360737db..a677ca58 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -33,7 +33,6 @@ void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterNam } renderCustomIngredientConfirmation(false, name, userId, chatId, bot, api); stateManager.put(CustomIngredientConfirmation{name}); - } void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName& state, @@ -58,7 +57,7 @@ void handleCustomIngredientConfirmationCQ( auto name = state.name; if (cq.data == "confirm") { api.getIngredientsApi().createCustom(userId, api::models::ingredient::IngredientCreateBody{name}); - renderCustomIngredientsList(true, userId, chatId, bot, api); + renderCustomIngredientsList(true, state.pageNo, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); } if (cq.data == "back") { @@ -80,10 +79,9 @@ void handleCustomIngredientConfirmationCQ( return; } - renderCustomIngredientsList(true, userId, chatId, bot, api); + renderCustomIngredientsList(true, state.pageNo, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); return; - } } diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 3aa276d2..1e6658f5 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -70,7 +70,7 @@ void handleCustomRecipeIngredientsSearchCQ( auto ingredientsAndName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); auto ingredients = state.recipeIngredients.getValues() | as_rvalue | to(); stateManager.put(RecipeCustomView{.recipeId = state.recipeId, - .pageNo = state.pageNo, + .pageNo = 0, .ingredients = std::move(ingredients), .recipeName = std::get<1>(ingredientsAndName)}); return; @@ -92,8 +92,7 @@ void handleCustomRecipeIngredientsSearchCQ( auto ingredientName = cq.data.substr(1); renderCustomIngredientConfirmation(true, ingredientName, userId, chatId, bot, api); auto ingredients = state.recipeIngredients.getValues() | as_rvalue | to(); - stateManager.put( - CustomIngredientConfirmation{ingredientName, state.recipeId, state.pageNo, ingredients, std::nullopt}); + stateManager.put(CustomIngredientConfirmation{ingredientName, state.recipeId, ingredients, std::nullopt}); } if (cq.data != "dont_handle") { diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index a15d88a7..0b88215d 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -84,8 +84,7 @@ void handleStorageIngredientsListCQ( if (cq.data[0] == 'i') { auto ingredientName = cq.data.substr(1); renderCustomIngredientConfirmation(true, ingredientName, userId, chatId, bot, api); - stateManager.put( - CustomIngredientConfirmation{ingredientName, std::nullopt, std::nullopt, std::nullopt, state.storageId}); + stateManager.put(CustomIngredientConfirmation{ingredientName, std::nullopt, std::nullopt, state.storageId}); } if (cq.data != "dont_handle") { diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index a5bc7ebd..1765863e 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -29,9 +29,9 @@ std::pair> constructN std::string text; text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); - for (const auto& ing : ingredientsList.page) { + for (auto& ing : ingredientsList.page) { if (ing.status) { - text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); + text += std::format("• {}, Статус: {}\n", utils::to_string(ing.status.value()), ing.name); } } diff --git a/src/states.hpp b/src/states.hpp index bb4b1d6e..1deacf17 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -41,12 +41,11 @@ struct CustomIngredientCreationEnterName { std::size_t pageNo; }; struct CustomIngredientConfirmation { - std::size_t pageNo; + std::size_t pageNo{}; std::string name; // All optionals are for "back" from this menu, so this state won't erase all info std::optional recipeFrom; - std::optional pageNo; std::optional> ingredients; std::optional storageFrom; @@ -54,11 +53,9 @@ struct CustomIngredientConfirmation { explicit CustomIngredientConfirmation( std::string name, std::optional recipeId = std::nullopt, - std::optional pageNo = std::nullopt, std::optional> ingredients = std::nullopt, std::optional storageId = std::nullopt) - : name(std::move(name)), recipeFrom(recipeId), pageNo(pageNo), ingredients(std::move(ingredients)), - storageFrom(storageId) {}; + : name(std::move(name)), recipeFrom(recipeId), ingredients(std::move(ingredients)), storageFrom(storageId) {}; }; struct CustomIngredientPublish { std::size_t pageNo; diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 6969789f..86927817 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -1,12 +1,10 @@ #include "to_string.hpp" -#include "backend/models/recipe.hpp" #include "backend/models/publication_request_status.hpp" #include "utils/utils.hpp" #include "uuid.hpp" -#include #include #include @@ -18,7 +16,7 @@ std::string to_string(const Uuid& u) { return boost::lexical_cast(u); } -std::string to_string(const cookcookhnya::api::models::recipe::PublicationRequestStatus status) { +std::string to_string(const cookcookhnya::api::models::status::PublicationRequestStatus status) { const std::vector statusStr = {utf8str(u8"🟡 На рассмотрении"), utf8str(u8"🟢 Принят"), utf8str(u8"🔴 Отклонен"), @@ -33,7 +31,6 @@ std::string to_string(std::chrono::system_clock::time_point tp) { oss << std::put_time(&tm, "%Y-%m-%d %H:%M"); return oss.str(); - } } // namespace cookcookhnya::utils diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index 16fa8c01..469d288d 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -1,6 +1,5 @@ #pragma once -#include "backend/models/recipe.hpp" #include "backend/models/publication_request_status.hpp" #include "uuid.hpp" @@ -19,9 +18,8 @@ std::string to_string(const T& t) { std::string to_string(const Uuid& u); -std::string to_string(cookcookhnya::api::models::recipe::PublicationRequestStatus status); +std::string to_string(cookcookhnya::api::models::status::PublicationRequestStatus status); std::string to_string(std::chrono::system_clock::time_point tp); - } // namespace cookcookhnya::utils From 5b075b55a2b269497739d80e3244cc7cc90d8838 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 01:27:47 +0300 Subject: [PATCH 045/106] fix: nullopt creator in recipe details --- src/backend/models/recipe.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index fe1dbf7f..a4fea8e1 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace cookcookhnya::api::models::recipe { @@ -55,7 +56,7 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: .link = value_to(j.at("sourceLink")), // Deal with optionals using ternary .creator = j.as_object().if_contains("creator") ? value_to(j.at("creator")) - : user::UserDetails{.userId = 0, .alias = "", .fullName = ""}, + : std::nullopt, .moderationStatus = j.as_object().if_contains("moderationStatus") ? value_to(j.at("moderationStatus")) : status::PublicationRequestStatus::NO_REQUEST, From 2aa7b715bf5b78fd32b9174fc8e1b8ddd45e5565 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 01:38:14 +0300 Subject: [PATCH 046/106] refactor: remove redundant structure --- src/backend/models/recipe.cpp | 7 ------- src/backend/models/recipe.hpp | 8 -------- 2 files changed, 15 deletions(-) diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index a4fea8e1..6a4ca359 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -63,13 +63,6 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: }; } -IngredientInCustomRecipe tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - return { - .id = value_to(j.at("id")), - .name = value_to(j.at("name")), - }; -} - RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { .page = value_to(j.at("results")), diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index fda00cbf..d32486a7 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -51,14 +51,6 @@ struct RecipeDetails { friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; -struct IngredientInCustomRecipe { - IngredientId id; - std::string name; - - friend IngredientInCustomRecipe tag_invoke(boost::json::value_to_tag, - const boost::json::value& j); -}; - struct RecipesList { std::vector page; std::size_t found; From bf98a48430f4f6b00f10a274f4a76078a8d006ce Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 02:24:18 +0300 Subject: [PATCH 047/106] refactor: move ingredient availability structures to states.hpp --- src/handlers/recipe/view.cpp | 12 +++--- src/render/recipe/add_storage.cpp | 60 +++++++++++++------------- src/render/recipe/add_storage.hpp | 34 ++++++--------- src/render/recipe/view.cpp | 29 +++++++------ src/render/recipe/view.hpp | 17 +++----- src/states.hpp | 12 ++++-- src/utils/ingredients_availability.cpp | 48 +++++++++++---------- src/utils/ingredients_availability.hpp | 20 +++------ 8 files changed, 110 insertions(+), 122 deletions(-) diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 24f9af18..cda8fc72 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -6,7 +6,7 @@ #include "render/recipe/add_storage.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" -#include "utils/ingredients_availability.hpp" +#include "states.hpp" #include #include @@ -18,6 +18,8 @@ using namespace render::recipes_suggestions; using namespace render::shopping_list; using namespace render::recipe; using namespace api::models::ingredient; +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { const std::string data = cq.data; @@ -32,11 +34,11 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe if (data == "shopping_list") { std::vector selectedIngredients; std::vector allIngredients; - for (const auto& infoPair : state.availability) { - if (infoPair.second.available == utils::AvailabiltiyType::NOT_AVAILABLE) { - selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); + for (const auto& av : state.availability) { + if (av.available == AvailabilityType::NOT_AVAILABLE) { + selectedIngredients.push_back({.id = av.ingredient.id, .name = av.ingredient.name}); } - allIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); + allIngredients.push_back({.id = av.ingredient.id, .name = av.ingredient.name}); } renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); stateManager.put(ShoppingListCreation{ diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index 2f9b0aa9..f82a3bb7 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -5,8 +5,7 @@ #include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/ingredients_availability.hpp" -#include "utils/to_string.hpp" +#include "states.hpp" #include "utils/utils.hpp" #include "view.hpp" @@ -20,13 +19,15 @@ namespace cookcookhnya::render::recipe { using namespace api::models::recipe; +using namespace api::models::storage; +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; -textGenInfo storageAdditionView( - const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ApiClient api) { +textGenInfo storageAdditionView(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ApiClient api) { auto recipe = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; @@ -40,19 +41,19 @@ textGenInfo storageAdditionView( text += std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.second.available == utils::AvailabiltiyType::AVAILABLE) { - text += "`[+]` " + infoPair.first.name + "\n"; - } else if (infoPair.second.available == utils::AvailabiltiyType::OTHER_STORAGES) { - text += "`[?]` " + infoPair.first.name + "\n"; + if (infoPair.available == AvailabilityType::AVAILABLE) { + text += "`[+]` " + infoPair.ingredient.name + "\n"; + } else if (infoPair.available == AvailabilityType::OTHER_STORAGES) { + text += "`[?]` " + infoPair.ingredient.name + "\n"; isIngredientIsOtherStorages = true; text += "Доступно в: "; - auto storages = infoPair.second.storages; + auto storages = infoPair.storages; for (std::size_t i = 0; i != storages.size(); ++i) { text += storages[i].name; text += i != storages.size() - 1 ? ", " : "\n"; } } else { - text += "`[ ]` " + infoPair.first.name + "\n"; + text += "`[ ]` " + infoPair.ingredient.name + "\n"; isIngredientNotAvailable = true; } } @@ -64,22 +65,20 @@ textGenInfo storageAdditionView( .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; } -void renderStoragesSuggestion( - const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - const std::vector& addedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api) { +void renderStoragesSuggestion(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + const std::vector& addedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + ApiClient api) { auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); - std::vector storages; + std::vector storages; for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.second.available == utils::AvailabiltiyType::OTHER_STORAGES) { - for (const auto& storage : infoPair.second.storages) { - if (std::ranges::find(storages, storage.id, &api::models::storage::StorageSummary::id) == - storages.end()) { + if (infoPair.available == AvailabilityType::OTHER_STORAGES) { + for (const auto& storage : infoPair.storages) { + if (std::ranges::find(storages, storage.id, &StorageSummary::id) == storages.end()) { storages.push_back(storage); } } @@ -93,8 +92,7 @@ void renderStoragesSuggestion( if (i % 2 == 0) keyboard[i / 2].reserve(2); const bool isSelected = - std::ranges::find(addedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != - addedStorages.end(); + std::ranges::find(addedStorages, storages[i].id, &StorageSummary::id) != addedStorages.end(); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; @@ -108,7 +106,7 @@ void renderStoragesSuggestion( auto messageId = message::getMessageId(userId); if (messageId) { - bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); + bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); } } } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index 2f1ba979..c692d8e7 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -1,34 +1,28 @@ #pragma once #include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" #include "render/recipe/view.hpp" -#include "utils/ingredients_availability.hpp" +#include "states.hpp" -#include #include namespace cookcookhnya::render::recipe { -textGenInfo storageAdditionView( - const std::vector>& - inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ApiClient api); +textGenInfo storageAdditionView(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + ApiClient api); -void renderStoragesSuggestion( - const std::vector>& - inStoragesAvailability, - const std::vector& selectedStorages, - const std::vector& addedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api); +void renderStoragesSuggestion(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + const std::vector& addedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + ApiClient api); } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index aad04e72..d691beaf 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -4,7 +4,7 @@ #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/ingredients_availability.hpp" +#include "states.hpp" #include "utils/utils.hpp" #include @@ -16,12 +16,13 @@ namespace cookcookhnya::render::recipe { using namespace api::models::recipe; +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; -textGenInfo -recipeView(const std::vector>& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ApiClient api) { +textGenInfo recipeView(const std::vector& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + ApiClient api) { auto recipeIngredients = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; @@ -29,14 +30,14 @@ recipeView(const std::vector>& inStoragesAvailability, +void renderRecipeView(std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, @@ -72,7 +73,7 @@ void renderRecipeView(std::vector -#include #include namespace cookcookhnya::render::recipe { @@ -17,19 +15,16 @@ struct textGenInfo { bool isIngredientIsOtherStorages; }; -void renderRecipeView(std::vector>& - inStoragesAvailability, +void renderRecipeView(std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, ApiClient api); -textGenInfo -recipeView(const std::vector>& - inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ApiClient api); +textGenInfo recipeView(const std::vector& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + ApiClient api); } // namespace cookcookhnya::render::recipe diff --git a/src/states.hpp b/src/states.hpp index 1688be1a..9da0078e 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -6,7 +6,6 @@ #include "backend/models/shopping_list.hpp" #include "backend/models/storage.hpp" #include "utils/fast_sorted_db.hpp" -#include "utils/ingredients_availability.hpp" #include "utils/utils.hpp" #include @@ -84,10 +83,17 @@ struct SuggestedRecipesList { bool fromStorage; }; struct RecipeView { + enum struct AvailabilityType : std::uint8_t { NOT_AVAILABLE, AVAILABLE, OTHER_STORAGES }; + + struct IngredientAvailability { + cookcookhnya::api::models::recipe::IngredientInRecipe ingredient; + AvailabilityType available = AvailabilityType::NOT_AVAILABLE; + std::vector storages; + }; + SuggestedRecipesList prevState; std::vector addedStorages; - std::vector> - availability; + std::vector availability; api::RecipeId recipeId; }; diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index 9b0b0703..449b400c 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -2,8 +2,8 @@ #include "backend/api/api.hpp" #include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" +#include "states.hpp" #include "tg_types.hpp" #include @@ -16,14 +16,17 @@ namespace cookcookhnya::utils { using namespace api; -using namespace api::models::recipe; using namespace api::models::storage; using namespace tg_types; +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; using namespace std::views; using namespace std::ranges; -std::vector> inStoragesAvailability( - std::vector& selectedStorages, RecipeId recipeId, UserId userId, const api::ApiClient& api) { +std::vector inStoragesAvailability(std::vector& selectedStorages, + RecipeId recipeId, + UserId userId, + const api::ApiClient& api) { auto allStorages = api.getStoragesApi().getStoragesList(userId); auto recipe = api.getRecipesApi().get(userId, recipeId); @@ -34,16 +37,16 @@ std::vector> inStoragesAva allStoragesMap.emplace(storage.id, storage); } - std::vector> result; + std::vector result; - for (const auto& ingredient : recipe.ingredients) { + for (auto& ingredient : recipe.ingredients) { IngredientAvailability availability; std::vector storages; bool hasInSelected = false; bool hasInOther = false; - for (const auto& storage : ingredient.inStorages) { + for (auto& storage : ingredient.inStorages) { auto it = allStoragesMap.find(storage.id); if (it == allStoragesMap.end()) continue; @@ -56,40 +59,39 @@ std::vector> inStoragesAva } } + availability.ingredient = std::move(ingredient); if (hasInSelected) { - availability.available = AvailabiltiyType::AVAILABLE; + availability.available = AvailabilityType::AVAILABLE; availability.storages = std::move(storages); } else if (hasInOther) { - availability.available = AvailabiltiyType::OTHER_STORAGES; + availability.available = AvailabilityType::OTHER_STORAGES; availability.storages = std::move(storages); } else { - availability.available = AvailabiltiyType::NOT_AVAILABLE; + availability.available = AvailabilityType::NOT_AVAILABLE; } - result.emplace_back(ingredient, std::move(availability)); + result.push_back(std::move(availability)); } return result; } -void addStorage(std::vector>& availability, - const StorageSummary& storage) { - for (auto& infoPair : availability) { - auto it = std::ranges::find(infoPair.second.storages, storage.id, &StorageSummary::id); - if (it != infoPair.second.storages.end()) { - infoPair.second.storages.erase(it); - infoPair.second.available = AvailabiltiyType::AVAILABLE; +void addStorage(std::vector& availability, const StorageSummary& storage) { + for (auto& info : availability) { + auto it = std::ranges::find(info.storages, storage.id, &StorageSummary::id); + if (it != info.storages.end()) { + info.storages.erase(it); + info.available = AvailabilityType::AVAILABLE; } } } -void deleteStorage(std::vector>& availability, - const StorageSummary& storage) { +void deleteStorage(std::vector& availability, const StorageSummary& storage) { for (auto& infoPair : availability) { - for (auto& storage_ : infoPair.first.inStorages) { + for (auto& storage_ : infoPair.ingredient.inStorages) { if (storage.id == storage_.id) { - infoPair.second.storages.push_back(storage); - infoPair.second.available = AvailabiltiyType::OTHER_STORAGES; + infoPair.storages.push_back(storage); + infoPair.available = AvailabilityType::OTHER_STORAGES; } } } diff --git a/src/utils/ingredients_availability.hpp b/src/utils/ingredients_availability.hpp index 5f205621..56acee0b 100644 --- a/src/utils/ingredients_availability.hpp +++ b/src/utils/ingredients_availability.hpp @@ -2,34 +2,24 @@ #include "backend/api/api.hpp" #include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" +#include "states.hpp" #include "tg_types.hpp" -#include -#include #include namespace cookcookhnya::utils { -enum struct AvailabiltiyType : std::uint8_t { AVAILABLE, NOT_AVAILABLE, OTHER_STORAGES }; - -struct IngredientAvailability { - AvailabiltiyType available = AvailabiltiyType::NOT_AVAILABLE; - std::vector storages; -}; - -std::vector> +std::vector inStoragesAvailability(std::vector& selectedStorages, api::RecipeId recipeId, tg_types::UserId userId, const api::ApiClient& api); -void addStorage(std::vector>& availability, +void addStorage(std::vector& availability, const api::models::storage::StorageSummary& storage); -void deleteStorage( - std::vector>& availability, - const api::models::storage::StorageSummary& storage); +void deleteStorage(std::vector& availability, + const api::models::storage::StorageSummary& storage); } // namespace cookcookhnya::utils From 3e728a8f8a803f7ae0f529d81548756baa97d899 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 03:14:01 +0300 Subject: [PATCH 048/106] refactor: remove API imports from commons --- src/backend/api/api.hpp | 2 + src/backend/api/ingredients.hpp | 2 + src/backend/api/recipes.hpp | 2 + src/backend/api/shopping_lists.hpp | 2 + src/backend/api/storages.hpp | 6 + src/backend/api/users.hpp | 2 + src/handlers/commands/my_storages.cpp | 3 +- src/handlers/commands/my_storages.hpp | 3 +- src/handlers/commands/start.cpp | 3 +- src/handlers/commands/start.hpp | 3 +- src/handlers/commands/wanna_eat.cpp | 3 +- src/handlers/commands/wanna_eat.hpp | 3 +- src/handlers/common.hpp | 16 --- src/handlers/handlers_list.hpp | 11 +- src/handlers/main_menu/view.cpp | 4 +- src/handlers/main_menu/view.hpp | 4 +- .../ingredients_list/create.cpp | 17 ++- .../ingredients_list/create.hpp | 7 +- .../ingredients_list/publish.cpp | 3 +- .../ingredients_list/publish.hpp | 8 +- .../ingredients_list/view.cpp | 3 +- .../ingredients_list/view.hpp | 3 +- .../recipe/search_ingredients.cpp | 8 +- .../recipe/search_ingredients.hpp | 6 +- src/handlers/personal_account/recipe/view.cpp | 3 +- src/handlers/personal_account/recipe/view.hpp | 3 +- .../personal_account/recipes_list/create.cpp | 5 +- .../personal_account/recipes_list/create.hpp | 5 +- .../personal_account/recipes_list/view.cpp | 3 +- .../personal_account/recipes_list/view.hpp | 3 +- src/handlers/personal_account/view.cpp | 3 +- src/handlers/personal_account/view.hpp | 3 +- src/handlers/recipe/add_storage.cpp | 3 +- src/handlers/recipe/add_storage.hpp | 3 +- src/handlers/recipe/view.cpp | 3 +- src/handlers/recipe/view.hpp | 3 +- src/handlers/recipes_suggestions/view.cpp | 3 +- src/handlers/recipes_suggestions/view.hpp | 3 +- src/handlers/shopping_list/create.cpp | 3 +- src/handlers/shopping_list/create.hpp | 3 +- src/handlers/shopping_list/search.cpp | 5 +- src/handlers/shopping_list/search.hpp | 5 +- .../storage_selection_to_buy.cpp | 3 +- .../storage_selection_to_buy.hpp | 3 +- src/handlers/shopping_list/view.cpp | 3 +- src/handlers/shopping_list/view.hpp | 4 +- src/handlers/storage/delete.cpp | 3 +- src/handlers/storage/delete.hpp | 3 +- src/handlers/storage/ingredients/view.cpp | 21 ++-- src/handlers/storage/ingredients/view.hpp | 5 +- src/handlers/storage/members/add.cpp | 5 +- src/handlers/storage/members/add.hpp | 5 +- src/handlers/storage/members/delete.cpp | 3 +- src/handlers/storage/members/delete.hpp | 3 +- src/handlers/storage/members/view.cpp | 3 +- src/handlers/storage/members/view.hpp | 3 +- src/handlers/storage/view.cpp | 4 +- src/handlers/storage/view.hpp | 4 +- src/handlers/storages_list/create.cpp | 5 +- src/handlers/storages_list/create.hpp | 5 +- src/handlers/storages_list/view.cpp | 7 +- src/handlers/storages_list/view.hpp | 3 +- src/handlers/storages_selection/view.cpp | 3 +- src/handlers/storages_selection/view.hpp | 3 +- src/render/common.hpp | 14 --- src/render/main_menu/view.cpp | 3 +- src/render/main_menu/view.hpp | 3 +- .../ingredients_list/create.cpp | 3 +- .../ingredients_list/create.hpp | 3 +- .../ingredients_list/publish.cpp | 4 +- .../ingredients_list/publish.hpp | 3 +- .../ingredients_list/view.cpp | 15 ++- .../ingredients_list/view.hpp | 3 +- .../recipe/search_ingredients.cpp | 23 ++-- .../recipe/search_ingredients.hpp | 2 + src/render/personal_account/recipe/view.cpp | 3 +- src/render/personal_account/recipe/view.hpp | 4 +- .../personal_account/recipes_list/view.cpp | 8 +- .../personal_account/recipes_list/view.hpp | 4 +- src/render/recipe/add_storage.cpp | 5 +- src/render/recipe/add_storage.hpp | 5 +- src/render/recipe/view.cpp | 5 +- src/render/recipe/view.hpp | 5 +- src/render/recipes_suggestions/view.cpp | 6 +- src/render/recipes_suggestions/view.hpp | 3 +- src/render/storage/delete.cpp | 3 +- src/render/storage/delete.hpp | 3 +- src/render/storage/ingredients/view.cpp | 114 +++--------------- src/render/storage/ingredients/view.hpp | 2 + src/render/storage/members/add.cpp | 5 +- src/render/storage/members/add.hpp | 5 +- src/render/storage/members/delete.cpp | 3 +- src/render/storage/members/delete.hpp | 3 +- src/render/storage/members/view.cpp | 3 +- src/render/storage/members/view.hpp | 3 +- src/render/storage/view.cpp | 4 +- src/render/storage/view.hpp | 4 +- src/render/storages_list/view.cpp | 4 +- src/render/storages_list/view.hpp | 3 +- src/render/storages_selection/view.cpp | 4 +- src/render/storages_selection/view.hpp | 3 +- 101 files changed, 295 insertions(+), 282 deletions(-) diff --git a/src/backend/api/api.hpp b/src/backend/api/api.hpp index 2b83d07a..b2754170 100644 --- a/src/backend/api/api.hpp +++ b/src/backend/api/api.hpp @@ -82,4 +82,6 @@ class ApiClient { } }; +using ApiClientRef = const api::ApiClient&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index bf3101ef..b3901c1d 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -72,4 +72,6 @@ class IngredientsApi : ApiBase { void publishCustom(UserId user, IngredientId ingredient) const; }; +using IngredientsApiRef = const api::IngredientsApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 38785351..e45dcc26 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -46,4 +46,6 @@ class RecipesApi : ApiBase { void publishCustom(UserId user, RecipeId recipe) const; }; +using RecipesApiRef = const api::RecipesApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/shopping_lists.hpp b/src/backend/api/shopping_lists.hpp index 67469f06..143437d4 100644 --- a/src/backend/api/shopping_lists.hpp +++ b/src/backend/api/shopping_lists.hpp @@ -27,4 +27,6 @@ class ShoppingListApi : ApiBase { void buy(UserId user, StorageId storage, const std::vector& ingredients) const; }; +using ShoppingListApiRef = const api::ShoppingListApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/storages.hpp b/src/backend/api/storages.hpp index d77e65a2..c10c1d42 100644 --- a/src/backend/api/storages.hpp +++ b/src/backend/api/storages.hpp @@ -19,15 +19,21 @@ class StoragesApi : ApiBase { public: [[nodiscard]] std::vector getStoragesList(UserId user) const; + [[nodiscard]] models::storage::StorageDetails get(UserId user, StorageId storage) const; + StorageId create(UserId user, // NOLINT(*-nodiscard) const models::storage::StorageCreateBody& body) const; void delete_(UserId user, StorageId storage) const; + [[nodiscard]] std::vector getStorageMembers(UserId user, StorageId storage) const; void addMember(UserId user, StorageId storage, UserId member) const; void deleteMember(UserId user, StorageId storage, UserId member) const; + [[nodiscard]] InvitationId inviteMember(UserId user, StorageId storage) const; [[nodiscard]] std::optional activate(UserId user, InvitationId invitation) const; }; +using StorageApiRef = const api::StoragesApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/users.hpp b/src/backend/api/users.hpp index 21c22299..e353d6ec 100644 --- a/src/backend/api/users.hpp +++ b/src/backend/api/users.hpp @@ -17,4 +17,6 @@ class UsersApi : ApiBase { const models::user::UpdateUserInfoBody& body) const; }; +using UserApiRef = const api::UsersApi&; + } // namespace cookcookhnya::api diff --git a/src/handlers/commands/my_storages.cpp b/src/handlers/commands/my_storages.cpp index efd08470..58ffe49e 100644 --- a/src/handlers/commands/my_storages.cpp +++ b/src/handlers/commands/my_storages.cpp @@ -1,5 +1,6 @@ #include "my_storages.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "render/storages_list/view.hpp" #include "states.hpp" @@ -8,7 +9,7 @@ namespace cookcookhnya::handlers::commands { using namespace render::storages_list; -void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { renderStorageList(false, m.from->id, m.chat->id, bot, api); stateManager.put(StorageList{}); }; diff --git a/src/handlers/commands/my_storages.hpp b/src/handlers/commands/my_storages.hpp index 36402ad7..87719af2 100644 --- a/src/handlers/commands/my_storages.hpp +++ b/src/handlers/commands/my_storages.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::commands { -void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/start.cpp b/src/handlers/commands/start.cpp index dbaa941a..bc86772f 100644 --- a/src/handlers/commands/start.cpp +++ b/src/handlers/commands/start.cpp @@ -1,5 +1,6 @@ #include "start.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/user.hpp" #include "handlers/common.hpp" @@ -18,7 +19,7 @@ using namespace render::main_menu; using namespace api::models::user; using namespace std::literals; -void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { auto userId = m.from->id; auto chatId = m.chat->id; diff --git a/src/handlers/commands/start.hpp b/src/handlers/commands/start.hpp index feb4539c..cccae238 100644 --- a/src/handlers/commands/start.hpp +++ b/src/handlers/commands/start.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::commands { -void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api); void handleNoState(MessageRef m, BotRef bot); diff --git a/src/handlers/commands/wanna_eat.cpp b/src/handlers/commands/wanna_eat.cpp index c99364cd..a33c15ae 100644 --- a/src/handlers/commands/wanna_eat.cpp +++ b/src/handlers/commands/wanna_eat.cpp @@ -1,5 +1,6 @@ #include "wanna_eat.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" #include "render/main_menu/view.hpp" @@ -16,7 +17,7 @@ using namespace render::select_storages; using namespace render::main_menu; using namespace render::recipes_suggestions; -void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { auto storages = api.getStoragesApi().getStoragesList(m.from->id); if (storages.empty()) { bot.sendMessage(m.chat->id, utils::utf8str(u8"😔 К сожалению, у вас пока что нет хранилищ.")); diff --git a/src/handlers/commands/wanna_eat.hpp b/src/handlers/commands/wanna_eat.hpp index 398424eb..ff67da45 100644 --- a/src/handlers/commands/wanna_eat.hpp +++ b/src/handlers/commands/wanna_eat.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::commands { -void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index dcf5c492..dd47c404 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -1,11 +1,5 @@ #pragma once -#include "backend/api/api.hpp" -#include "backend/api/ingredients.hpp" -#include "backend/api/recipes.hpp" -#include "backend/api/shopping_lists.hpp" -#include "backend/api/storages.hpp" -#include "backend/api/users.hpp" #include "states.hpp" #include @@ -55,20 +49,10 @@ using states::CustomRecipesList; using states::RecipeCustomView; // Type aliases -using ApiClientRef = const api::ApiClient&; -using UserApiRef = const api::UsersApi&; -using StorageApiRef = const api::StoragesApi&; -using IngredientsApiRef = const api::IngredientsApi&; -using RecipesApiRef = const api::RecipesApi&; -using ShoppingListApiRef = const api::ShoppingListApi&; - using BotRef = const TgBot::Api&; using SMRef = const states::StateManager&; using MessageRef = const TgBot::Message&; using CallbackQueryRef = const TgBot::CallbackQuery&; using InlineQueryRef = const TgBot::InlineQuery&; -using NoState = tg_stater::HandlerTypes::NoState; -using AnyState = tg_stater::HandlerTypes::AnyState; - } // namespace cookcookhnya::handlers diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index da1660db..74056a98 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -44,12 +44,10 @@ #include "storages_selection/view.hpp" -#include "handlers/common.hpp" - #include #include -namespace cookcookhnya::handlers { +namespace cookcookhnya::handlers::bot_handlers { using namespace commands; using namespace main_menu; @@ -67,7 +65,8 @@ using namespace recipes_suggestions; using namespace tg_stater; -namespace bot_handlers { +using NoState = tg_stater::HandlerTypes::NoState; +using AnyState = tg_stater::HandlerTypes::AnyState; // Commands constexpr char startCmd[] = "start"; // NOLINT(*c-arrays) @@ -146,6 +145,4 @@ using recipeCustomViewCQHandler = Handler; using customRecipeIngredientsSearchIQHandler = Handler; -} // namespace bot_handlers - -} // namespace cookcookhnya::handlers +} // namespace cookcookhnya::handlers::bot_handlers diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index 8f225492..e5225668 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/api/storages.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" @@ -22,7 +23,8 @@ using namespace render::shopping_list; using namespace render::personal_account; using namespace std::views; -void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { +void handleMainMenuCQ( + MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/main_menu/view.hpp b/src/handlers/main_menu/view.hpp index 9bc51877..d6cdf664 100644 --- a/src/handlers/main_menu/view.hpp +++ b/src/handlers/main_menu/view.hpp @@ -1,9 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::main_menu { -void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); +void handleMainMenuCQ( + MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::main_menu diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 1f748adc..4a0ba1b0 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/ingredients.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" @@ -12,8 +13,11 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; -void handleCustomIngredientCreationEnterNameMsg( - CustomIngredientCreationEnterName& state, MessageRef m, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { +void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& state, + MessageRef m, + BotRef& bot, + SMRef stateManager, + api::IngredientsApiRef api) { auto name = m.text; auto userId = m.from->id; auto chatId = m.chat->id; @@ -31,7 +35,7 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName CallbackQueryRef cq, BotRef& bot, SMRef stateManager, - IngredientsApiRef api) { + api::IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -41,8 +45,11 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName } } -void handleCustomIngredientConfirmationCQ( - CustomIngredientConfirmation& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { +void handleCustomIngredientConfirmationCQ(CustomIngredientConfirmation& state, + CallbackQueryRef cq, + BotRef& bot, + SMRef stateManager, + api::IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/personal_account/ingredients_list/create.hpp b/src/handlers/personal_account/ingredients_list/create.hpp index 27a10e34..4ef2cf32 100644 --- a/src/handlers/personal_account/ingredients_list/create.hpp +++ b/src/handlers/personal_account/ingredients_list/create.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::ingredients { @@ -8,18 +9,18 @@ void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterNam MessageRef m, BotRef& bot, SMRef stateManager, - IngredientsApiRef api); + api::IngredientsApiRef api); void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, - IngredientsApiRef api); + api::IngredientsApiRef api); void handleCustomIngredientConfirmationCQ(CustomIngredientConfirmation& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, - IngredientsApiRef api); + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/publish.cpp b/src/handlers/personal_account/ingredients_list/publish.cpp index 8377193a..648ddb87 100644 --- a/src/handlers/personal_account/ingredients_list/publish.cpp +++ b/src/handlers/personal_account/ingredients_list/publish.cpp @@ -1,5 +1,6 @@ #include "publish.hpp" +#include "backend/api/ingredients.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/personal_account/ingredients_list/view.hpp" @@ -11,7 +12,7 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; void handleCustomIngredientPublishCQ( - CustomIngredientPublish& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { + CustomIngredientPublish& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/personal_account/ingredients_list/publish.hpp b/src/handlers/personal_account/ingredients_list/publish.hpp index aaf2b74b..2c8c9956 100644 --- a/src/handlers/personal_account/ingredients_list/publish.hpp +++ b/src/handlers/personal_account/ingredients_list/publish.hpp @@ -1,10 +1,14 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::ingredients { -void handleCustomIngredientPublishCQ( - CustomIngredientPublish& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api); +void handleCustomIngredientPublishCQ(CustomIngredientPublish& /*unused*/, + CallbackQueryRef cq, + BotRef& bot, + SMRef stateManager, + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/view.cpp b/src/handlers/personal_account/ingredients_list/view.cpp index 52c08711..74bb3181 100644 --- a/src/handlers/personal_account/ingredients_list/view.cpp +++ b/src/handlers/personal_account/ingredients_list/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "render/personal_account/ingredients_list/create.hpp" #include "render/personal_account/ingredients_list/publish.hpp" @@ -16,7 +17,7 @@ using namespace render::personal_account::ingredients; using namespace render::personal_account; void handleCustomIngredientsListCQ( - CustomIngredientsList& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + CustomIngredientsList& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/personal_account/ingredients_list/view.hpp b/src/handlers/personal_account/ingredients_list/view.hpp index c28eb38a..ef1fd4bf 100644 --- a/src/handlers/personal_account/ingredients_list/view.hpp +++ b/src/handlers/personal_account/ingredients_list/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::ingredients { void handleCustomIngredientsListCQ( - CustomIngredientsList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + CustomIngredientsList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 091561a7..44db0d16 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -1,5 +1,7 @@ #include "search_ingredients.hpp" +#include "backend/api/api.hpp" +#include "backend/api/ingredients.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" @@ -33,7 +35,7 @@ void updateSearch(CustomRecipeIngredientsSearch& state, bool isQueryChanged, BotRef bot, tg_types::UserId userId, - IngredientsApiRef api) { + api::IngredientsApiRef api) { state.pageNo = isQueryChanged ? 0 : state.pageNo; auto response = api.searchForRecipe( userId, state.recipeId, state.query, threshhold, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage); @@ -52,7 +54,7 @@ void updateSearch(CustomRecipeIngredientsSearch& state, } // namespace void handleCustomRecipeIngredientsSearchCQ( - CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); const auto userId = cq.from->id; const auto chatId = cq.message->chat->id; @@ -100,7 +102,7 @@ void handleCustomRecipeIngredientsSearchCQ( void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api) { + api::IngredientsApiRef api) { state.query = iq.query; const auto userId = iq.from->id; if (iq.query.empty()) { diff --git a/src/handlers/personal_account/recipe/search_ingredients.hpp b/src/handlers/personal_account/recipe/search_ingredients.hpp index 065e154b..45101c39 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.hpp +++ b/src/handlers/personal_account/recipe/search_ingredients.hpp @@ -1,15 +1,17 @@ #pragma once +#include "backend/api/api.hpp" +#include "backend/api/ingredients.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::recipes { void handleCustomRecipeIngredientsSearchCQ( - CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api); + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::personal_account::recipes diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index eea483d0..e67686e7 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "render/personal_account/recipe/search_ingredients.hpp" #include "render/personal_account/recipes_list/view.hpp" @@ -19,7 +20,7 @@ using namespace std::views; const std::size_t numOfIngredientsOnPage = 5; void handleRecipeCustomViewCQ( - RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/personal_account/recipe/view.hpp b/src/handlers/personal_account/recipe/view.hpp index aac53df8..48026478 100644 --- a/src/handlers/personal_account/recipe/view.hpp +++ b/src/handlers/personal_account/recipe/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::recipes { void handleRecipeCustomViewCQ( - RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account::recipes diff --git a/src/handlers/personal_account/recipes_list/create.cpp b/src/handlers/personal_account/recipes_list/create.cpp index 50eaa936..d515b6a2 100644 --- a/src/handlers/personal_account/recipes_list/create.cpp +++ b/src/handlers/personal_account/recipes_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/recipes.hpp" #include "backend/models/recipe.hpp" #include "handlers/common.hpp" #include "render/personal_account/recipe/view.hpp" @@ -10,7 +11,7 @@ namespace cookcookhnya::handlers::personal_account::recipes { using namespace render::personal_account::recipes; void handleCreateCustomRecipeMsg( - CreateCustomRecipe& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi) { + CreateCustomRecipe& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi) { // Init with no ingredients and link. My suggestion: to use link as author's alias auto recipeId = recipeApi.create( m.from->id, api::models::recipe::RecipeCreateBody{.name = m.text, .ingredients = {}, .link = ""}); @@ -22,7 +23,7 @@ void handleCreateCustomRecipeMsg( }; void handleCreateCustomRecipeCQ( - CreateCustomRecipe& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi) { + CreateCustomRecipe& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi) { bot.answerCallbackQuery(cq.id); if (cq.data == "cancel_recipe_creation") { diff --git a/src/handlers/personal_account/recipes_list/create.hpp b/src/handlers/personal_account/recipes_list/create.hpp index e96edc7b..438a7ad1 100644 --- a/src/handlers/personal_account/recipes_list/create.hpp +++ b/src/handlers/personal_account/recipes_list/create.hpp @@ -1,13 +1,14 @@ #pragma once +#include "backend/api/recipes.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::recipes { void handleCreateCustomRecipeMsg( - CreateCustomRecipe&, MessageRef m, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi); + CreateCustomRecipe&, MessageRef m, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi); void handleCreateCustomRecipeCQ( - CreateCustomRecipe&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi); + CreateCustomRecipe&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi); } // namespace cookcookhnya::handlers::personal_account::recipes diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index a49d2c8a..94585ad8 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/personal_account/recipe/view.hpp" @@ -14,7 +15,7 @@ namespace cookcookhnya::handlers::personal_account::recipes { void handleCustomRecipesListCQ( - CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { using namespace render::personal_account; using namespace render::personal_account::recipes; diff --git a/src/handlers/personal_account/recipes_list/view.hpp b/src/handlers/personal_account/recipes_list/view.hpp index c9194ccb..388570d2 100644 --- a/src/handlers/personal_account/recipes_list/view.hpp +++ b/src/handlers/personal_account/recipes_list/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::recipes { void handleCustomRecipesListCQ( - CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account::recipes diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index fe43109c..4edaf73f 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -1,5 +1,6 @@ #include "handlers/personal_account/view.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" #include "render/personal_account/ingredients_list/view.hpp" @@ -15,7 +16,7 @@ using namespace render::main_menu; using namespace render::personal_account::ingredients; void handlePersonalAccountMenuCQ( - PersonalAccountMenu& /**/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + PersonalAccountMenu& /**/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string data = cq.data; bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; diff --git a/src/handlers/personal_account/view.hpp b/src/handlers/personal_account/view.hpp index 3d31fdcd..ce94f2a0 100644 --- a/src/handlers/personal_account/view.hpp +++ b/src/handlers/personal_account/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account { void handlePersonalAccountMenuCQ( - PersonalAccountMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + PersonalAccountMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp index 9e5fc6ee..29be33ed 100644 --- a/src/handlers/recipe/add_storage.cpp +++ b/src/handlers/recipe/add_storage.cpp @@ -1,5 +1,6 @@ #include "add_storage.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" @@ -20,7 +21,7 @@ using namespace render::recipe; using namespace api::models::storage; void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); const std::string& data = cq.data; auto chatId = cq.message->chat->id; diff --git a/src/handlers/recipe/add_storage.hpp b/src/handlers/recipe/add_storage.hpp index 3267f272..ac477b12 100644 --- a/src/handlers/recipe/add_storage.hpp +++ b/src/handlers/recipe/add_storage.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::recipe { void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index cda8fc72..12f3de20 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" @@ -21,7 +22,7 @@ using namespace api::models::ingredient; using IngredientAvailability = states::RecipeView::IngredientAvailability; using AvailabilityType = states::RecipeView::AvailabilityType; -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/recipe/view.hpp b/src/handlers/recipe/view.hpp index a425acfc..6cefa542 100644 --- a/src/handlers/recipe/view.hpp +++ b/src/handlers/recipe/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::recipe { -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index b992223b..d8e96e97 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" @@ -23,7 +24,7 @@ using namespace render::recipe; using namespace render::main_menu; void handleSuggestedRecipesListCQ( - SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/recipes_suggestions/view.hpp b/src/handlers/recipes_suggestions/view.hpp index b291db03..be415b79 100644 --- a/src/handlers/recipes_suggestions/view.hpp +++ b/src/handlers/recipes_suggestions/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::recipes_suggestions { void handleSuggestedRecipesListCQ( - SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::recipes_suggestions diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 357c1c87..6bdfc354 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" @@ -18,7 +19,7 @@ using namespace render::shopping_list; using namespace render::recipe; void handleShoppingListCreationCQ( - ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string& data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/shopping_list/create.hpp b/src/handlers/shopping_list/create.hpp index 2feaac37..bf8d6b62 100644 --- a/src/handlers/shopping_list/create.hpp +++ b/src/handlers/shopping_list/create.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::shopping_list { void handleShoppingListCreationCQ( - ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/search.cpp b/src/handlers/shopping_list/search.cpp index 86f075af..fcb6b55a 100644 --- a/src/handlers/shopping_list/search.cpp +++ b/src/handlers/shopping_list/search.cpp @@ -1,5 +1,6 @@ #include "search.hpp" +#include "backend/api/api.hpp" #include "backend/api/publicity_filter.hpp" #include "backend/id_types.hpp" #include "backend/models/shopping_list.hpp" @@ -21,7 +22,7 @@ using namespace api::models::shopping_list; const std::size_t searchThreshold = 70; void handleShoppingListIngredientSearchCQ( - ShoppingListIngredientSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + ShoppingListIngredientSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -67,7 +68,7 @@ void handleShoppingListIngredientSearchCQ( void handleShoppingListIngredientSearchIQ(ShoppingListIngredientSearch& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api) { + api::IngredientsApiRef api) { auto userId = iq.from->id; if (iq.query.empty()) { state.query = ""; diff --git a/src/handlers/shopping_list/search.hpp b/src/handlers/shopping_list/search.hpp index 2ff85fd2..20c5f72e 100644 --- a/src/handlers/shopping_list/search.hpp +++ b/src/handlers/shopping_list/search.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include @@ -9,11 +10,11 @@ namespace cookcookhnya::handlers::shopping_list { const std::size_t searchPageSize = 10; void handleShoppingListIngredientSearchCQ( - ShoppingListIngredientSearch&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + ShoppingListIngredientSearch&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); void handleShoppingListIngredientSearchIQ(ShoppingListIngredientSearch& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api); + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/storage_selection_to_buy.cpp b/src/handlers/shopping_list/storage_selection_to_buy.cpp index 37acc821..76394b33 100644 --- a/src/handlers/shopping_list/storage_selection_to_buy.cpp +++ b/src/handlers/shopping_list/storage_selection_to_buy.cpp @@ -1,5 +1,6 @@ #include "storage_selection_to_buy.hpp" +#include "backend/api/shopping_lists.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/shopping_list/view.hpp" @@ -17,7 +18,7 @@ void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy CallbackQueryRef cq, BotRef bot, SMRef stateManager, - ShoppingListApiRef api) { + api::ShoppingListApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/shopping_list/storage_selection_to_buy.hpp b/src/handlers/shopping_list/storage_selection_to_buy.hpp index 7801f9c1..3bb7ea03 100644 --- a/src/handlers/shopping_list/storage_selection_to_buy.hpp +++ b/src/handlers/shopping_list/storage_selection_to_buy.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/shopping_lists.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::shopping_list { @@ -8,6 +9,6 @@ void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy CallbackQueryRef cq, BotRef bot, SMRef stateManager, - ShoppingListApiRef api); + api::ShoppingListApiRef api); } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index d30ca6cd..c7120375 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "handlers/shopping_list/search.hpp" @@ -23,7 +24,7 @@ using namespace std::views; using namespace std::ranges; void handleShoppingListViewCQ( - ShoppingListView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + ShoppingListView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/shopping_list/view.hpp b/src/handlers/shopping_list/view.hpp index cf9d5d60..095f139e 100644 --- a/src/handlers/shopping_list/view.hpp +++ b/src/handlers/shopping_list/view.hpp @@ -1,9 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::shopping_list { -void handleShoppingListViewCQ(ShoppingListView&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleShoppingListViewCQ( + ShoppingListView&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/storage/delete.cpp b/src/handlers/storage/delete.cpp index e82a149a..55753556 100644 --- a/src/handlers/storage/delete.cpp +++ b/src/handlers/storage/delete.cpp @@ -1,5 +1,6 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/storage/view.hpp" @@ -12,7 +13,7 @@ using namespace render::storages_list; using namespace render::storage; void handleStorageDeletionCQ( - StorageDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); if (cq.data == "confirm") { storageApi.delete_(cq.from->id, state.storageId); diff --git a/src/handlers/storage/delete.hpp b/src/handlers/storage/delete.hpp index 28e25044..a644d3b1 100644 --- a/src/handlers/storage/delete.hpp +++ b/src/handlers/storage/delete.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storages_list { void handleStorageDeletionCQ( - StorageDeletion&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageDeletion&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storages_list diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index ac450179..b2f057f9 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" @@ -20,13 +21,16 @@ using namespace render::storage; using namespace render::storage::ingredients; using namespace api::models::ingredient; -// Global vars +namespace { + const size_t numOfIngredientsOnPage = 5; const size_t threshhold = 70; -namespace { -void updateSearch( - StorageIngredientsList& state, bool isQueryChanged, BotRef bot, tg_types::UserId userId, IngredientsApiRef api) { +void updateSearch(StorageIngredientsList& state, + bool isQueryChanged, + BotRef bot, + tg_types::UserId userId, + api::IngredientsApiRef api) { state.pageNo = isQueryChanged ? 0 : state.pageNo; auto response = api.searchForStorage(userId, state.storageId, @@ -48,7 +52,7 @@ void updateSearch( } // namespace void handleStorageIngredientsListCQ( - StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); const auto userId = cq.from->id; const auto chatId = cq.message->chat->id; @@ -58,20 +62,19 @@ void handleStorageIngredientsListCQ( stateManager.put(StorageView{state.storageId}); return; } - if (cq.data == "prev") { + if (cq.data == "page_left") { state.pageNo -= 1; updateSearch(state, false, bot, userId, api); return; } - if (cq.data == "next") { + if (cq.data == "page_right") { state.pageNo += 1; updateSearch(state, false, bot, userId, api); return; } if (cq.data != "dont_handle") { - auto mIngredient = utils::parseSafe(cq.data); if (!mIngredient) return; @@ -94,7 +97,7 @@ void handleStorageIngredientsListCQ( void handleStorageIngredientsListIQ(StorageIngredientsList& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api) { + api::IngredientsApiRef api) { const auto userId = iq.from->id; state.inlineQuery = iq.query; if (iq.query.empty()) { diff --git a/src/handlers/storage/ingredients/view.hpp b/src/handlers/storage/ingredients/view.hpp index ca6ab044..bdcaa212 100644 --- a/src/handlers/storage/ingredients/view.hpp +++ b/src/handlers/storage/ingredients/view.hpp @@ -1,15 +1,16 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::ingredients { void handleStorageIngredientsListCQ( - StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); void handleStorageIngredientsListIQ(StorageIngredientsList& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api); + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::storage::ingredients diff --git a/src/handlers/storage/members/add.cpp b/src/handlers/storage/members/add.cpp index 317c4725..43a80c1d 100644 --- a/src/handlers/storage/members/add.cpp +++ b/src/handlers/storage/members/add.cpp @@ -1,5 +1,6 @@ #include "add.hpp" +#include "backend/api/storages.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" #include "render/storage/members/add.hpp" @@ -17,7 +18,7 @@ namespace cookcookhnya::handlers::storage::members { using namespace render::storage::members; void handleStorageMemberAdditionMsg( - StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { auto chatId = m.chat->id; auto userId = m.from->id; @@ -50,7 +51,7 @@ void handleStorageMemberAdditionMsg( }; void handleStorageMemberAdditionCQ( - StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/storage/members/add.hpp b/src/handlers/storage/members/add.hpp index a767890f..7aa24d70 100644 --- a/src/handlers/storage/members/add.hpp +++ b/src/handlers/storage/members/add.hpp @@ -1,13 +1,14 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::members { void handleStorageMemberAdditionMsg( - StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); void handleStorageMemberAdditionCQ( - StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storage::members diff --git a/src/handlers/storage/members/delete.cpp b/src/handlers/storage/members/delete.cpp index 5ffabb33..f5595225 100644 --- a/src/handlers/storage/members/delete.cpp +++ b/src/handlers/storage/members/delete.cpp @@ -1,5 +1,6 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "handlers/common.hpp" #include "render/storage/members/view.hpp" #include "tg_types.hpp" @@ -10,7 +11,7 @@ namespace cookcookhnya::handlers::storage::members { using namespace render::storage::members; void handleStorageMemberDeletionCQ( - StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; diff --git a/src/handlers/storage/members/delete.hpp b/src/handlers/storage/members/delete.hpp index 562321dc..de4464fd 100644 --- a/src/handlers/storage/members/delete.hpp +++ b/src/handlers/storage/members/delete.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::members { void handleStorageMemberDeletionCQ( - StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storage::members diff --git a/src/handlers/storage/members/view.cpp b/src/handlers/storage/members/view.cpp index 411100cb..ffa8156e 100644 --- a/src/handlers/storage/members/view.cpp +++ b/src/handlers/storage/members/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "handlers/common.hpp" #include "render/storage/members/add.hpp" #include "render/storage/members/delete.hpp" @@ -11,7 +12,7 @@ using namespace render::storage::members; using namespace render::storage; void handleStorageMemberViewCQ( - StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/storage/members/view.hpp b/src/handlers/storage/members/view.hpp index 4fbe3ec4..2ad670ef 100644 --- a/src/handlers/storage/members/view.hpp +++ b/src/handlers/storage/members/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::members { void handleStorageMemberViewCQ( - StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storage::members diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 913cf69b..f7232ec7 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/recipes_suggestions/view.hpp" @@ -26,7 +27,8 @@ using namespace std::views; const std::size_t numOfIngredientsOnPage = 5; -void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleStorageViewCQ( + StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/storage/view.hpp b/src/handlers/storage/view.hpp index 8d537a92..d1143a61 100644 --- a/src/handlers/storage/view.hpp +++ b/src/handlers/storage/view.hpp @@ -1,9 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage { -void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleStorageViewCQ( + StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::storage diff --git a/src/handlers/storages_list/create.cpp b/src/handlers/storages_list/create.cpp index 42188ec2..d2798bc7 100644 --- a/src/handlers/storages_list/create.cpp +++ b/src/handlers/storages_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/storages.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" @@ -13,7 +14,7 @@ namespace cookcookhnya::handlers::storages_list { using namespace render::storages_list; void handleStorageCreationEnterNameMsg( - StorageCreationEnterName& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageCreationEnterName& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { storageApi.create(m.from->id, api::models::storage::StorageCreateBody{m.text}); // Create storage body with new name auto text = utils::utf8str(u8"🏷 Введите новое имя хранилища"); auto messageId = message::getMessageId(m.from->id); @@ -28,7 +29,7 @@ void handleStorageCreationEnterNameCQ(StorageCreationEnterName& /*unused*/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, - StorageApiRef storageApi) { + api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); if (cq.data == "back") { renderStorageList(true, cq.from->id, cq.message->chat->id, bot, storageApi); diff --git a/src/handlers/storages_list/create.hpp b/src/handlers/storages_list/create.hpp index 32b834a3..29b45b63 100644 --- a/src/handlers/storages_list/create.hpp +++ b/src/handlers/storages_list/create.hpp @@ -1,13 +1,14 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storages_list { void handleStorageCreationEnterNameMsg( - StorageCreationEnterName&, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageCreationEnterName&, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); void handleStorageCreationEnterNameCQ( - StorageCreationEnterName&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageCreationEnterName&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storages_list diff --git a/src/handlers/storages_list/view.cpp b/src/handlers/storages_list/view.cpp index 83e30aa6..23f6d752 100644 --- a/src/handlers/storages_list/view.cpp +++ b/src/handlers/storages_list/view.cpp @@ -1,6 +1,6 @@ #include "view.hpp" -#include "backend/api/storages.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" @@ -17,21 +17,24 @@ using namespace render::create_storage; using namespace render::storage; void handleStorageListCQ( - StorageList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + StorageList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; auto storages = api.getStoragesApi().getStoragesList(userId); + if (cq.data == "create") { renderStorageCreation(chatId, userId, bot); stateManager.put(StorageCreationEnterName{}); return; } + if (cq.data == "back") { renderMainMenu(true, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } + auto storageId = utils::parseSafe(cq.data); if (storageId) { renderStorageView(*storageId, cq.from->id, chatId, bot, api); diff --git a/src/handlers/storages_list/view.hpp b/src/handlers/storages_list/view.hpp index 73594b68..37fcfb38 100644 --- a/src/handlers/storages_list/view.hpp +++ b/src/handlers/storages_list/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storages_list { -void handleStorageListCQ(StorageList&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleStorageListCQ(StorageList&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::storages_list diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index d10db71b..271cbba6 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" @@ -21,7 +22,7 @@ using namespace render::select_storages; using namespace render::main_menu; void handleStoragesSelectionCQ( - StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/storages_selection/view.hpp b/src/handlers/storages_selection/view.hpp index f8638f12..654cd486 100644 --- a/src/handlers/storages_selection/view.hpp +++ b/src/handlers/storages_selection/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storages_selection { void handleStoragesSelectionCQ( - StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::storages_selection diff --git a/src/render/common.hpp b/src/render/common.hpp index 1fdb5bee..4866cdba 100644 --- a/src/render/common.hpp +++ b/src/render/common.hpp @@ -1,11 +1,5 @@ #pragma once -#include "backend/api/api.hpp" -#include "backend/api/ingredients.hpp" -#include "backend/api/recipes.hpp" -#include "backend/api/shopping_lists.hpp" -#include "backend/api/storages.hpp" -#include "backend/api/users.hpp" #include "patched_bot.hpp" #include "tg_types.hpp" #include "utils/utils.hpp" @@ -26,14 +20,6 @@ class InlineKeyboardMarkup; namespace cookcookhnya::render { -// API -using ApiClient = const api::ApiClient&; -using StorageApiRef = const api::StoragesApi&; -using IngredientsApiRef = const api::IngredientsApi&; -using UserApiRef = const api::UsersApi&; -using RecipesApiRef = const api::RecipesApi&; -using ShoppingListApiRef = const api::ShoppingListApi&; - using UserId = tg_types::UserId; using ChatId = tg_types::ChatId; using MessageId = tg_types::MessageId; diff --git a/src/render/main_menu/view.cpp b/src/render/main_menu/view.cpp index b8513769..cc9e38ea 100644 --- a/src/render/main_menu/view.cpp +++ b/src/render/main_menu/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/utils.hpp" @@ -17,7 +18,7 @@ void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi) { + api::StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); auto text = utils::utf8str( diff --git a/src/render/main_menu/view.hpp b/src/render/main_menu/view.hpp index 95c7de2a..f2e4b772 100644 --- a/src/render/main_menu/view.hpp +++ b/src/render/main_menu/view.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/storages.hpp" #include "render/common.hpp" #include @@ -12,6 +13,6 @@ void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi); + api::StorageApiRef storageApi); } // namespace cookcookhnya::render::main_menu diff --git a/src/render/personal_account/ingredients_list/create.cpp b/src/render/personal_account/ingredients_list/create.cpp index 8d9a67b1..1f160294 100644 --- a/src/render/personal_account/ingredients_list/create.cpp +++ b/src/render/personal_account/ingredients_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/ingredients.hpp" #include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" @@ -24,7 +25,7 @@ void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot) { } void renderCustomIngredientConfirmation( - std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api) { InlineKeyboard keyboard(2); keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); diff --git a/src/render/personal_account/ingredients_list/create.hpp b/src/render/personal_account/ingredients_list/create.hpp index 06d4e87e..72ee4694 100644 --- a/src/render/personal_account/ingredients_list/create.hpp +++ b/src/render/personal_account/ingredients_list/create.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "render/common.hpp" #include @@ -9,6 +10,6 @@ namespace cookcookhnya::render::personal_account::ingredients { void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot); void renderCustomIngredientConfirmation( - std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); + std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/ingredients_list/publish.cpp b/src/render/personal_account/ingredients_list/publish.cpp index 3b65b9f7..9d2b5ee4 100644 --- a/src/render/personal_account/ingredients_list/publish.cpp +++ b/src/render/personal_account/ingredients_list/publish.cpp @@ -1,9 +1,9 @@ #include "publish.hpp" +#include "backend/api/ingredients.hpp" #include "backend/api/publicity_filter.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -14,7 +14,7 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; -void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { +void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api) { auto ingredientsResp = api.search(userId, PublicityFilterType::Custom); // TODO: make pagination for ingredients diff --git a/src/render/personal_account/ingredients_list/publish.hpp b/src/render/personal_account/ingredients_list/publish.hpp index e8f28a86..66e8b9c9 100644 --- a/src/render/personal_account/ingredients_list/publish.hpp +++ b/src/render/personal_account/ingredients_list/publish.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "render/common.hpp" namespace cookcookhnya::render::personal_account::ingredients { -void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); +void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index daa05dce..b6c66bfd 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/ingredients.hpp" #include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" @@ -7,6 +8,8 @@ #include "utils/to_string.hpp" #include "utils/utils.hpp" +#include + #include #include #include @@ -17,12 +20,13 @@ namespace cookcookhnya::render::personal_account::ingredients { +using namespace api::models::ingredient; using namespace tg_types; namespace { -std::pair> constructNavigationMessage( - std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::IngredientList& ingredientsList) { +std::pair> +constructNavigationMessage(std::size_t pageNo, std::size_t numOfRecipesOnPage, IngredientList& ingredientsList) { const size_t amountOfRecipes = ingredientsList.found; const std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); @@ -53,9 +57,8 @@ std::pair> constructN return std::make_pair(text, buttons); } -std::pair constructMessage(size_t pageNo, - size_t numOfIngredientsOnPage, - api::models::ingredient::IngredientList& ingredientsList) { +std::pair +constructMessage(size_t pageNo, size_t numOfIngredientsOnPage, IngredientList& ingredientsList) { std::size_t numOfRows = 0; if (ingredientsList.found == 0) numOfRows = 2; @@ -97,7 +100,7 @@ std::pair constructMessage(size_t pageNo, } // namespace void renderCustomIngredientsList( - bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api) { const std::size_t numOfIngredientsOnPage = 10; auto ingredientsList = diff --git a/src/render/personal_account/ingredients_list/view.hpp b/src/render/personal_account/ingredients_list/view.hpp index a0ca2310..d535d955 100644 --- a/src/render/personal_account/ingredients_list/view.hpp +++ b/src/render/personal_account/ingredients_list/view.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "render/common.hpp" #include @@ -7,6 +8,6 @@ namespace cookcookhnya::render::personal_account::ingredients { void renderCustomIngredientsList( - bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); + bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/recipe/search_ingredients.cpp b/src/render/personal_account/recipe/search_ingredients.cpp index 62b74e9a..c6742a24 100644 --- a/src/render/personal_account/recipe/search_ingredients.cpp +++ b/src/render/personal_account/recipe/search_ingredients.cpp @@ -28,9 +28,9 @@ using namespace tg_types; namespace { -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t numOfRecipesOnPage, +InlineKeyboard constructNavigationsMarkup(std::size_t offset, + std::size_t fullKeyBoardSize, + std::size_t numOfRecipesOnPage, const states::CustomRecipeIngredientsSearch& state) { using namespace std::views; const size_t amountOfRecipes = state.totalFound; @@ -107,10 +107,10 @@ InlineKeyboard constructNavigationsMarkup(size_t offset, InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::CustomRecipeIngredientsSearch& state) { // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 // for editing - other buttons are ingredients - const size_t numOfRows = 3; - const size_t offset = 1; // Number of rows before list + const std::size_t numOfRows = 3; + const std::size_t offset = 1; // Number of rows before list - const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); + const std::size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); InlineKeyboard keyboard = constructNavigationsMarkup(offset, numOfRows + recipesToShow, numOfRecipesOnPage, state); if (keyboard.empty()) { // If error happened @@ -123,7 +123,7 @@ InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::CustomRe } // namespace void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& state, - size_t numOfIngredientsOnPage, + std::size_t numOfIngredientsOnPage, UserId userId, ChatId chatId, BotRef bot) { @@ -136,13 +136,8 @@ void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& auto text = utils::utf8str(u8"📝Нажмите на кнопку ✏️ Редактировать и начните вводить названия продуктов:\n\n") + std::move(list); if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText(text, - chatId, - *messageId, - "", - "", - nullptr, - makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); + bot.editMessageText( + text, chatId, *messageId, makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); } } diff --git a/src/render/personal_account/recipe/search_ingredients.hpp b/src/render/personal_account/recipe/search_ingredients.hpp index b07672cb..6fa73b9b 100644 --- a/src/render/personal_account/recipe/search_ingredients.hpp +++ b/src/render/personal_account/recipe/search_ingredients.hpp @@ -2,7 +2,9 @@ #include "render/common.hpp" #include "states.hpp" + #include + namespace cookcookhnya::render::recipe::ingredients { void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& state, diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index e3dfe969..00637b63 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/recipes.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "backend/models/recipe.hpp" @@ -16,7 +17,7 @@ namespace cookcookhnya::render::personal_account::recipes { std::vector renderCustomRecipe( - bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { + bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, api::RecipesApiRef recipesApi) { auto recipeDetails = recipesApi.get(userId, recipeId); std::vector ingredients; diff --git a/src/render/personal_account/recipe/view.hpp b/src/render/personal_account/recipe/view.hpp index 52c4386a..70060905 100644 --- a/src/render/personal_account/recipe/view.hpp +++ b/src/render/personal_account/recipe/view.hpp @@ -1,13 +1,15 @@ #pragma once +#include "backend/api/recipes.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "render/common.hpp" + #include namespace cookcookhnya::render::personal_account::recipes { std::vector renderCustomRecipe( - bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi); + bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, api::RecipesApiRef recipesApi); } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 00c746f0..c38837a7 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -1,19 +1,18 @@ #include "view.hpp" #include "backend/api/publicity_filter.hpp" +#include "backend/api/recipes.hpp" #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "render/pagination.hpp" #include "render/personal_account/recipes_list/view.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include #include #include #include -#include namespace TgBot { class InlineKeyboardMarkup; @@ -34,12 +33,13 @@ constructKeyboard(std::size_t pageNo, std::size_t pageSize, RecipesList& recipes }; keyboard << constructPagination(pageNo, pageSize, recipesList.found, recipesList.page, makeRecipeButton) << makeCallbackButton(u8"↩️ Назад", "back"); - return std::move(keyboard); + return keyboard; } } // namespace -void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { +void renderCustomRecipesList( + std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::RecipesApiRef recipesApi) { const std::size_t numOfRecipesOnPage = 5; auto recipesList = recipesApi.getList(userId, PublicityFilterType::Custom, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); diff --git a/src/render/personal_account/recipes_list/view.hpp b/src/render/personal_account/recipes_list/view.hpp index 7bc978fa..4330d93b 100644 --- a/src/render/personal_account/recipes_list/view.hpp +++ b/src/render/personal_account/recipes_list/view.hpp @@ -1,11 +1,13 @@ #pragma once +#include "backend/api/recipes.hpp" #include "render/common.hpp" #include namespace cookcookhnya::render::personal_account::recipes { -void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi); +void renderCustomRecipesList( + std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::RecipesApiRef recipesApi); } // namespace cookcookhnya::render::personal_account::recipes diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index f82a3bb7..70c9ef0e 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -1,5 +1,6 @@ #include "add_storage.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" @@ -27,7 +28,7 @@ textGenInfo storageAdditionView(const std::vector& inSto const std::vector& selectedStorages, api::RecipeId recipeId, UserId userId, - ApiClient api) { + api::ApiClientRef api) { auto recipe = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; @@ -72,7 +73,7 @@ void renderStoragesSuggestion(const std::vector& inStora UserId userId, ChatId chatId, BotRef bot, - ApiClient api) { + api::ApiClientRef api) { auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); std::vector storages; for (const auto& infoPair : inStoragesAvailability) { diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index c692d8e7..2f417f3a 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" @@ -14,7 +15,7 @@ textGenInfo storageAdditionView(const std::vector& selectedStorages, api::RecipeId recipeId, UserId userId, - ApiClient api); + api::ApiClientRef api); void renderStoragesSuggestion(const std::vector& inStoragesAvailability, const std::vector& selectedStorages, @@ -23,6 +24,6 @@ void renderStoragesSuggestion(const std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, - ApiClient api) { + api::ApiClientRef api) { auto recipeIngredients = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; @@ -54,7 +55,7 @@ void renderRecipeView(std::vector& inStoragesAvailabilit UserId userId, ChatId chatId, BotRef bot, - ApiClient api) { + api::ApiClientRef api) { auto textGen = recipeView(inStoragesAvailability, recipeId, userId, api); const std::size_t buttonRows = textGen.isIngredientNotAvailable ? 3 : 2; InlineKeyboard keyboard(buttonRows); diff --git a/src/render/recipe/view.hpp b/src/render/recipe/view.hpp index d84d038b..56a89917 100644 --- a/src/render/recipe/view.hpp +++ b/src/render/recipe/view.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" #include "states.hpp" @@ -20,11 +21,11 @@ void renderRecipeView(std::vector& i UserId userId, ChatId chatId, BotRef bot, - ApiClient api); + api::ApiClientRef api); textGenInfo recipeView(const std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, - ApiClient api); + api::ApiClientRef api); } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 3a1b069f..a05b6507 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" #include "message_tracker.hpp" @@ -12,7 +13,6 @@ #include #include #include -#include #include namespace TgBot { @@ -37,7 +37,7 @@ constructKeyboard(std::size_t pageNo, std::size_t pageSize, RecipesListWithIngre }; keyboard << constructPagination(pageNo, pageSize, recipesList.found, recipesList.page, makeRecipeButton) << makeCallbackButton(u8"↩️ Назад", "back"); - return std::move(keyboard); + return keyboard; } } // namespace @@ -47,7 +47,7 @@ void renderRecipesSuggestion(std::vector& storages, UserId userId, ChatId chatId, BotRef bot, - RecipesApiRef recipesApi) { + api::RecipesApiRef recipesApi) { const std::size_t numOfRecipesOnPage = 5; const std::size_t numOfRecipes = 500; diff --git a/src/render/recipes_suggestions/view.hpp b/src/render/recipes_suggestions/view.hpp index 8ba90811..dd3720ea 100644 --- a/src/render/recipes_suggestions/view.hpp +++ b/src/render/recipes_suggestions/view.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/api.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" @@ -15,6 +16,6 @@ void renderRecipesSuggestion(std::vector& UserId userId, ChatId chatId, BotRef bot, - RecipesApiRef recipesApi); + api::RecipesApiRef recipesApi); } // namespace cookcookhnya::render::recipes_suggestions diff --git a/src/render/storage/delete.cpp b/src/render/storage/delete.cpp index 6c5ec1d5..9c9379cb 100644 --- a/src/render/storage/delete.cpp +++ b/src/render/storage/delete.cpp @@ -1,5 +1,6 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -10,7 +11,7 @@ namespace cookcookhnya::render::delete_storage { void renderStorageDeletion( - api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi) { + api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); InlineKeyboard keyboard(2); keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); diff --git a/src/render/storage/delete.hpp b/src/render/storage/delete.hpp index 12a463b8..00d2f27c 100644 --- a/src/render/storage/delete.hpp +++ b/src/render/storage/delete.hpp @@ -1,11 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::delete_storage { void renderStorageDeletion( - api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi); + api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::delete_storage diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 149fdd68..273589b1 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -2,139 +2,61 @@ #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" -#include "patched_bot.hpp" #include "render/common.hpp" +#include "render/pagination.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" -#include - -#include -#include #include -#include #include #include #include #include #include -#include + +namespace TgBot { +class InlineKeyboardMarkup; +} // namespace TgBot namespace cookcookhnya::render::storage::ingredients { using namespace api::models::ingredient; using namespace tg_types; +using namespace std::views; +using std::ranges::to; namespace { -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t numOfRecipesOnPage, - const states::StorageIngredientsList& state) { - using namespace std::views; - const size_t amountOfRecipes = state.totalFound; - int maxPageNum = - static_cast(std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage))); - - const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); - // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that - // will be actually shown - const bool ifMaxPage = static_cast(amountOfRecipes) - - static_cast(numOfRecipesOnPage) * (static_cast(state.pageNo) + 1) <= - 0; - - if (offset + recipesToShow >= fullKeyBoardSize) { - InlineKeyboard error(0); - return error; - } - const size_t arrowsRow = offset + recipesToShow; - // Don't reserve for arrows if it's first page is max(im) - InlineKeyboard keyboard(state.pageNo == 0 && ifMaxPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); +std::shared_ptr +constructKeyboard(std::size_t pageNo, std::size_t pageSize, const states::StorageIngredientsList& state) { + InlineKeyboardBuilder keyboard; auto searchButton = std::make_shared(); searchButton->text = utils::utf8str(u8"✏️ Редактировать"); searchButton->switchInlineQueryCurrentChat = ""; - keyboard[0].push_back(std::move(searchButton)); - for (auto [row, ing] : zip(drop(keyboard, 1), state.searchItems)) - row.push_back(makeCallbackButton((ing.isInStorage ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id))); - - if (state.pageNo == 0 && ifMaxPage) { - // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; - } - keyboard[arrowsRow].reserve(3); - - // Helps to reduce code. Power of C++ YEAH BABE! - uint8_t b = 0; - - // Simply enamurate every case - if (state.pageNo == 0) { - if (!ifMaxPage) { - b |= uint8_t{0b01}; - } - } else if (ifMaxPage) { - b |= uint8_t{0b10}; - } else { - b |= uint8_t{0b11}; - } - - // Check from left to right due to buttons being displayed like that - for (int i = 1; i >= 0; i--) { - // Compare two bits under b mask. If 1 was on b mask then we need to place arrow somewhere - if ((b & static_cast((uint8_t{0b1} << static_cast(i)))) == - (uint8_t{0b1} << static_cast(i))) { - // if we need to place arrow then check the i, which represents bit which we are checking right now - if (i == 1) { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", "prev")); // left - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", "next")); // right - } - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - } - } - // Put state.pageNo as button - keyboard[arrowsRow].insert( - keyboard[arrowsRow].begin() + 1, - makeCallbackButton(std::format("{} из {}", state.pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::StorageIngredientsList& state) { - // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 - // for editing - other buttons are ingredients - const size_t numOfRows = 3; - const size_t offset = 1; // Number of rows before list - - const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); - - InlineKeyboard keyboard = constructNavigationsMarkup(offset, numOfRows + recipesToShow, numOfRecipesOnPage, state); - if (keyboard.empty()) { // If error happened - return keyboard; - } + keyboard << std::move(searchButton) << NewRow{}; + auto makeIngredientButton = [](const IngredientSearchForStorageItem& ing) { + return makeCallbackButton((ing.isInStorage ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id)); + }; + keyboard << constructPagination(pageNo, pageSize, state.totalFound, state.searchItems, makeIngredientButton) + << makeCallbackButton(u8"↩️ Назад", "back"); return keyboard; } } // namespace void renderIngredientsListSearch(const states::StorageIngredientsList& state, - size_t numOfIngredientsOnPage, + std::size_t numOfIngredientsOnPage, UserId userId, ChatId chatId, BotRef bot) { - using namespace std::views; - using std::ranges::to; std::string list = state.storageIngredients.getValues() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText( - text, chatId, *messageId, makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); + bot.editMessageText(text, chatId, *messageId, constructKeyboard(state.pageNo, numOfIngredientsOnPage, state)); } } diff --git a/src/render/storage/ingredients/view.hpp b/src/render/storage/ingredients/view.hpp index 6cfe491d..559c2e75 100644 --- a/src/render/storage/ingredients/view.hpp +++ b/src/render/storage/ingredients/view.hpp @@ -1,6 +1,8 @@ #pragma once + #include "render/common.hpp" #include "states.hpp" + #include namespace cookcookhnya::render::storage::ingredients { diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index dbf46305..a32c8f94 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -1,5 +1,6 @@ #include "add.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -15,7 +16,7 @@ namespace cookcookhnya::render::storage::members { void renderStorageMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const std::size_t buttonRows = 2; @@ -30,7 +31,7 @@ void renderStorageMemberAddition( }; void renderShareLinkMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const std::size_t buttonRows = 2; diff --git a/src/render/storage/members/add.hpp b/src/render/storage/members/add.hpp index 18462217..6bcabd1f 100644 --- a/src/render/storage/members/add.hpp +++ b/src/render/storage/members/add.hpp @@ -1,14 +1,15 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storage::members { void renderStorageMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); void renderShareLinkMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage::members diff --git a/src/render/storage/members/delete.cpp b/src/render/storage/members/delete.cpp index a5584411..f557205f 100644 --- a/src/render/storage/members/delete.cpp +++ b/src/render/storage/members/delete.cpp @@ -1,5 +1,6 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -14,7 +15,7 @@ namespace cookcookhnya::render::storage::members { void renderStorageMemberDeletion( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); auto members = storageApi.getStorageMembers(userId, storageId); diff --git a/src/render/storage/members/delete.hpp b/src/render/storage/members/delete.hpp index 71dae8c0..ef17bc5b 100644 --- a/src/render/storage/members/delete.hpp +++ b/src/render/storage/members/delete.hpp @@ -1,11 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storage::members { void renderStorageMemberDeletion( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage::members diff --git a/src/render/storage/members/view.cpp b/src/render/storage/members/view.cpp index 0ddfaea5..f1fd4a2a 100644 --- a/src/render/storage/members/view.cpp +++ b/src/render/storage/members/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -20,7 +21,7 @@ void renderMemberList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi) { + api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const bool isOwner = storage.ownerId == userId; const std::size_t buttonRows = isOwner ? 2 : 1; diff --git a/src/render/storage/members/view.hpp b/src/render/storage/members/view.hpp index 8ab807a8..fd16b370 100644 --- a/src/render/storage/members/view.hpp +++ b/src/render/storage/members/view.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" @@ -10,6 +11,6 @@ void renderMemberList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi); + api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage::members diff --git a/src/render/storage/view.cpp b/src/render/storage/view.cpp index a9c7244e..e5b4b60f 100644 --- a/src/render/storage/view.cpp +++ b/src/render/storage/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -10,7 +11,8 @@ namespace cookcookhnya::render::storage { -void renderStorageView(api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { +void renderStorageView( + api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const std::size_t buttonRows = storage.ownerId == userId ? 3 : 2; InlineKeyboard keyboard(buttonRows); diff --git a/src/render/storage/view.hpp b/src/render/storage/view.hpp index 15240b5e..b4d1245b 100644 --- a/src/render/storage/view.hpp +++ b/src/render/storage/view.hpp @@ -1,10 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storage { -void renderStorageView(api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); +void renderStorageView( + api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage diff --git a/src/render/storages_list/view.cpp b/src/render/storages_list/view.cpp index 28409e88..ffdbae21 100644 --- a/src/render/storages_list/view.cpp +++ b/src/render/storages_list/view.cpp @@ -1,8 +1,8 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -14,7 +14,7 @@ namespace cookcookhnya::render::storages_list { using namespace tg_types; using namespace std::views; -void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { +void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); const std::size_t buttonRows = ((storages.size() + 1) / 2) + 1; // ceil(storagesCount / 2) and back diff --git a/src/render/storages_list/view.hpp b/src/render/storages_list/view.hpp index 2e5ea72b..30204116 100644 --- a/src/render/storages_list/view.hpp +++ b/src/render/storages_list/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/storages.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storages_list { -void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); +void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storages_list diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index a3fffb33..48205181 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -1,10 +1,10 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -23,7 +23,7 @@ using namespace tg_types; using namespace std::views; void renderStorageSelection( - const StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { const auto& selectedStorages = state.selectedStorages; auto allStorages = storageApi.getStoragesList(userId); diff --git a/src/render/storages_selection/view.hpp b/src/render/storages_selection/view.hpp index 465b86a9..4fe026f2 100644 --- a/src/render/storages_selection/view.hpp +++ b/src/render/storages_selection/view.hpp @@ -1,11 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "render/common.hpp" #include "states.hpp" namespace cookcookhnya::render::select_storages { void renderStorageSelection( - const states::StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const states::StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::select_storages From e8bb5d6a50fd201404f4b33aa9dd996ca21e673a Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 03:16:33 +0300 Subject: [PATCH 049/106] fix: only 2 ingredients shown in the storage --- src/backend/api/ingredients.hpp | 5 ++++- src/handlers/main_menu/view.hpp | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index b3901c1d..2297cc6c 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -22,7 +22,10 @@ class IngredientsApi : ApiBase { [[nodiscard]] models::ingredient::Ingredient get(UserId user, IngredientId ingredient) const; [[nodiscard]] std::vector - getStorageIngredients(UserId user, StorageId storage, std::size_t count = 2, std::size_t offset = 0) const; + getStorageIngredients(UserId user, + StorageId storage, + std::size_t count = 200, // NOLINT(*magic-number*) + std::size_t offset = 0) const; void putToStorage(UserId user, StorageId storage, IngredientId ingredient) const; diff --git a/src/handlers/main_menu/view.hpp b/src/handlers/main_menu/view.hpp index d6cdf664..56a9086f 100644 --- a/src/handlers/main_menu/view.hpp +++ b/src/handlers/main_menu/view.hpp @@ -5,7 +5,6 @@ namespace cookcookhnya::handlers::main_menu { -void handleMainMenuCQ( - MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); +void handleMainMenuCQ(MainMenu&, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::main_menu From 23c25a20c3609fe4b8ecface9436a026748d75a8 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Mon, 21 Jul 2025 04:30:54 +0300 Subject: [PATCH 050/106] chore: turn off misc-include-cleaner tidy check --- .clang-tidy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 7b682391..0d54c5f0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -30,7 +30,9 @@ Checks: "*, -cppcoreguidelines-avoid-do-while, -bugprone-easily-swappable-parameters, -misc-non-private-member-variables-in-classes, - -llvm-header-guard," + -llvm-header-guard, + + -misc-include-cleaner" CheckOptions: cppcoreguidelines-pro-type-member-init.IgnoreArrays: true readability-implicit-bool-conversion.AllowPointerConditions: true From 4f04ab721fc551c04d33c0a1519a33b2fafc8100 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Mon, 21 Jul 2025 05:27:49 +0300 Subject: [PATCH 051/106] fix: api refactor --- src/handlers/storage/ingredients/delete.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/storage/ingredients/delete.hpp b/src/handlers/storage/ingredients/delete.hpp index fe7a2af4..a3a26754 100644 --- a/src/handlers/storage/ingredients/delete.hpp +++ b/src/handlers/storage/ingredients/delete.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp> #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::ingredients { void handleStorageIngredientsDeletionCQ( - StorageIngredientsDeletion& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + StorageIngredientsDeletion& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::storage::ingredients From 99e26cd113a52d9f83c727e3235cc7bd7e9287c6 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Mon, 21 Jul 2025 05:28:12 +0300 Subject: [PATCH 052/106] fix: api refactor --- src/handlers/storage/ingredients/delete.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/storage/ingredients/delete.cpp b/src/handlers/storage/ingredients/delete.cpp index 6f8e7665..068f5a18 100644 --- a/src/handlers/storage/ingredients/delete.cpp +++ b/src/handlers/storage/ingredients/delete.cpp @@ -6,6 +6,7 @@ #include "render/storage/ingredients/view.hpp" #include "states.hpp" #include "utils/parsing.hpp" + #include namespace cookcookhnya::handlers::storage::ingredients { @@ -14,7 +15,7 @@ using namespace render::storage::ingredients; using namespace std::views; void handleStorageIngredientsDeletionCQ( - StorageIngredientsDeletion& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + StorageIngredientsDeletion& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; From 5faa71b147d9b787c72c9cdbb0a14f5e9d8e4f1d Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Mon, 21 Jul 2025 05:32:40 +0300 Subject: [PATCH 053/106] fix: max down --- src/handlers/storage/ingredients/delete.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/storage/ingredients/delete.hpp b/src/handlers/storage/ingredients/delete.hpp index a3a26754..09ddd6a3 100644 --- a/src/handlers/storage/ingredients/delete.hpp +++ b/src/handlers/storage/ingredients/delete.hpp @@ -1,6 +1,6 @@ #pragma once -#include "backend/api/api.hpp> +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::ingredients { From 9c1b10353fa93b79c12815bcbfaf515de2017155 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Mon, 21 Jul 2025 05:39:40 +0300 Subject: [PATCH 054/106] fix: api refactor consequences --- src/handlers/storage/view.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 552bd5aa..9cd5fb02 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -24,9 +24,7 @@ using namespace render::delete_storage; using namespace api::models::storage; using namespace std::views; -void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { - const std::size_t numOfIngredientsOnPage = 5; - +void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; From 9912470189f1b2652395302702709ae254dffc09 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 07:04:27 +0300 Subject: [PATCH 055/106] refactor: fix linter errors --- src/render/storage/ingredients/delete.cpp | 13 ++++++------- src/render/storage/ingredients/view.cpp | 9 +++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/render/storage/ingredients/delete.cpp b/src/render/storage/ingredients/delete.cpp index f891b1a7..7f2fa25a 100644 --- a/src/render/storage/ingredients/delete.cpp +++ b/src/render/storage/ingredients/delete.cpp @@ -46,10 +46,10 @@ constructIngredientsButton(std::vector& sel for (std::size_t i = offset; i != size + offset; ++i) { const bool isSelected = std::ranges::contains( selectedIngredients, storageIngredients[i].id, &api::models::ingredient::Ingredient::id); - std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); + const std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; - std::string text = std::format("{} {}", emoji, storageIngredients[i].name); - std::string data = actionPrefix + utils::to_string(storageIngredients[i].id); + const std::string text = std::format("{} {}", emoji, storageIngredients[i].name); + const std::string data = actionPrefix + utils::to_string(storageIngredients[i].id); buttons.push_back(makeCallbackButton(text, data)); } return buttons; @@ -61,7 +61,7 @@ constructMessage(std::vector& selectedIngre std::size_t pageNo, std::size_t numOfIngredientsOnPage, bool withoutPutToShoppingListButton) { - std::size_t ingSize = storageIngredients.size(); + const std::size_t ingSize = storageIngredients.size(); const std::size_t maxPageNum = std::ceil(static_cast(ingSize) / static_cast(numOfIngredientsOnPage)); std::size_t buttonRows = std::min(ingSize, numOfIngredientsOnPage); @@ -84,13 +84,12 @@ constructMessage(std::vector& selectedIngre } } - std::string text = utils::utf8str(u8"🍅 Выберите ингредиенты для удаления\\.\n\n"); + const std::string text = utils::utf8str(u8"🍅 Выберите ингредиенты для удаления\\.\n\n"); InlineKeyboardBuilder keyboard{buttonRows}; for (auto& b : constructIngredientsButton( selectedIngredients, storageIngredients, numOfIngredientsOnPage, pageNo * numOfIngredientsOnPage)) { - keyboard << std::move(b); - keyboard << NewRow{}; + keyboard << std::move(b) << NewRow{}; } auto backButton = makeCallbackButton(u8"↩️ Назад", "back"); diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 5f0e50b0..e0d4f9b5 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -40,11 +40,11 @@ constructKeyboard(std::size_t pageNo, std::size_t pageSize, const states::Storag return makeCallbackButton((ing.isInStorage ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id)); }; keyboard << constructPagination(pageNo, pageSize, state.totalFound, state.searchItems, makeIngredientButton); - + if (!state.storageIngredients.getValues().empty()) keyboard << makeCallbackButton(u8"🗑 Удалить", "delete") << NewRow{}; keyboard << makeCallbackButton(u8"↩️ Назад", "back"); - + return keyboard; } @@ -55,8 +55,9 @@ void renderIngredientsListSearch(const states::StorageIngredientsList& state, ChatId chatId, BotRef bot) { const std::size_t numOfIngredientsOnPage = 5; - std::string list = state.storageIngredients.getValues() | - transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); + const std::string list = state.storageIngredients.getValues() | + transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | + to(); auto text = state.storageIngredients.getValues().empty() From d5f646f33ca2dc5412cc2d931082982decb6fba5 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 07:18:15 +0300 Subject: [PATCH 056/106] style: format code --- src/handlers/storage/ingredients/view.cpp | 2 +- src/handlers/storage/view.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 4df9d69a..db27ba79 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -76,7 +76,7 @@ void handleStorageIngredientsListCQ( stateManager.put(newState); return; } - + if (cq.data == "page_left") { state.pageNo -= 1; updateSearch(state, false, bot, userId, api); diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 9cd5fb02..d3e88451 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -24,7 +24,8 @@ using namespace render::delete_storage; using namespace api::models::storage; using namespace std::views; -void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { +void handleStorageViewCQ( + StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; From 8b11bcb84dc1a8901e8efcf1c9bfec5aa44f6f0a Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 08:31:40 +0300 Subject: [PATCH 057/106] Merge origin/dev into ilyha-dev/ingredient_search_enhancement --- .clang-tidy | 2 +- src/backend/CMakeLists.txt | 5 +- src/backend/api/api.hpp | 31 ++-- src/backend/api/ingredients.hpp | 7 +- src/backend/api/moderation.cpp | 25 ++++ src/backend/api/moderation.hpp | 25 ++++ src/backend/api/recipes.cpp | 4 +- src/backend/api/recipes.hpp | 4 +- src/backend/api/request_history.cpp | 21 --- src/backend/api/request_history.hpp | 23 --- src/backend/api/shopping_lists.hpp | 3 + src/backend/api/storages.hpp | 6 + src/backend/api/users.hpp | 2 + src/backend/models/ingredient.cpp | 2 + src/backend/models/ingredient.hpp | 2 +- src/backend/models/moderation.cpp | 30 ++++ src/backend/models/moderation.hpp | 23 +++ .../models/publication_request_status.cpp | 25 +++- .../models/publication_request_status.hpp | 14 +- src/backend/models/recipe.cpp | 25 ++-- src/backend/models/recipe.hpp | 15 +- src/backend/models/request_history.cpp | 26 ---- src/backend/models/request_history.hpp | 23 --- src/handlers/CMakeLists.txt | 5 +- src/handlers/commands/my_storages.cpp | 3 +- src/handlers/commands/my_storages.hpp | 3 +- src/handlers/commands/start.cpp | 21 ++- src/handlers/commands/start.hpp | 3 +- src/handlers/commands/wanna_eat.cpp | 6 +- src/handlers/commands/wanna_eat.hpp | 3 +- src/handlers/common.hpp | 18 +-- src/handlers/handlers_list.hpp | 71 ++++----- src/handlers/main_menu/view.cpp | 6 +- src/handlers/main_menu/view.hpp | 3 +- .../ingredients_list/create.cpp | 22 +-- .../ingredients_list/create.hpp | 13 +- .../ingredients_list/publish.cpp | 3 +- .../ingredients_list/publish.hpp | 8 +- .../ingredients_list/view.cpp | 3 +- .../ingredients_list/view.hpp | 3 +- ...st_history.cpp => publication_history.cpp} | 10 +- .../personal_account/publication_history.hpp | 11 ++ ...ion_history.cpp => moderation_history.cpp} | 20 +-- .../recipe/moderation_history.hpp | 11 ++ .../recipe/publication_history.hpp | 10 -- .../recipe/search_ingredients.cpp | 23 +-- .../recipe/search_ingredients.hpp | 10 +- src/handlers/personal_account/recipe/view.cpp | 19 ++- src/handlers/personal_account/recipe/view.hpp | 7 +- .../personal_account/recipes_list/create.cpp | 36 ++--- .../personal_account/recipes_list/create.hpp | 9 +- .../personal_account/recipes_list/view.cpp | 37 ++--- .../personal_account/recipes_list/view.hpp | 7 +- .../personal_account/request_history.hpp | 10 -- src/handlers/personal_account/view.cpp | 18 ++- src/handlers/personal_account/view.hpp | 3 +- src/handlers/recipe/add_storage.cpp | 63 ++++---- src/handlers/recipe/add_storage.hpp | 3 +- src/handlers/recipe/view.cpp | 60 ++++---- src/handlers/recipe/view.hpp | 3 +- src/handlers/recipes_suggestions/view.cpp | 26 ++-- src/handlers/recipes_suggestions/view.hpp | 3 +- src/handlers/shopping_list/create.cpp | 34 +++-- src/handlers/shopping_list/create.hpp | 3 +- src/handlers/shopping_list/search.cpp | 89 ++++++++++++ src/handlers/shopping_list/search.hpp | 20 +++ .../storage_selection_to_buy.cpp | 3 +- .../storage_selection_to_buy.hpp | 3 +- src/handlers/shopping_list/view.cpp | 12 +- src/handlers/shopping_list/view.hpp | 4 +- src/handlers/storage/delete.cpp | 7 +- src/handlers/storage/delete.hpp | 7 +- src/handlers/storage/ingredients/view.cpp | 26 ++-- src/handlers/storage/ingredients/view.hpp | 5 +- src/handlers/storage/members/add.cpp | 5 +- src/handlers/storage/members/add.hpp | 5 +- src/handlers/storage/members/delete.cpp | 3 +- src/handlers/storage/members/delete.hpp | 3 +- src/handlers/storage/members/view.cpp | 3 +- src/handlers/storage/members/view.hpp | 3 +- src/handlers/storage/view.cpp | 29 +++- src/handlers/storage/view.hpp | 4 +- src/handlers/storages_list/create.cpp | 5 +- src/handlers/storages_list/create.hpp | 5 +- src/handlers/storages_list/view.cpp | 9 +- src/handlers/storages_list/view.hpp | 3 +- src/handlers/storages_selection/view.cpp | 6 +- src/handlers/storages_selection/view.hpp | 3 +- src/main.cpp | 6 +- src/render/CMakeLists.txt | 5 +- src/render/common.hpp | 16 --- src/render/main_menu/view.cpp | 38 +++-- src/render/main_menu/view.hpp | 4 +- src/render/pagination.hpp | 83 +++++++++++ .../ingredients_list/create.cpp | 3 +- .../ingredients_list/create.hpp | 3 +- .../ingredients_list/publish.cpp | 4 +- .../ingredients_list/publish.hpp | 3 +- .../ingredients_list/view.cpp | 16 ++- .../ingredients_list/view.hpp | 5 +- .../personal_account/publication_history.cpp | 41 ++++++ .../personal_account/publication_history.hpp | 15 ++ ...ion_history.cpp => moderation_history.cpp} | 9 +- ...ion_history.hpp => moderation_history.hpp} | 7 +- .../recipe/search_ingredients.cpp | 135 ++++-------------- .../recipe/search_ingredients.hpp | 10 +- src/render/personal_account/recipe/view.cpp | 21 +-- src/render/personal_account/recipe/view.hpp | 12 +- .../personal_account/recipes_list/create.cpp | 4 +- .../personal_account/recipes_list/create.hpp | 4 +- .../personal_account/recipes_list/view.cpp | 131 ++++------------- .../personal_account/recipes_list/view.hpp | 8 +- .../personal_account/request_history.cpp | 85 ----------- .../personal_account/request_history.hpp | 8 -- src/render/recipe/add_storage.cpp | 62 ++++---- src/render/recipe/add_storage.hpp | 33 ++--- src/render/recipe/view.cpp | 38 ++--- src/render/recipe/view.hpp | 19 ++- src/render/recipes_suggestions/view.cpp | 128 ++++------------- src/render/recipes_suggestions/view.hpp | 3 +- src/render/shopping_list/create.cpp | 15 +- src/render/shopping_list/search.cpp | 50 +++++++ src/render/shopping_list/search.hpp | 13 ++ src/render/shopping_list/view.cpp | 2 +- src/render/storage/delete.cpp | 3 +- src/render/storage/delete.hpp | 3 +- src/render/storage/ingredients/view.cpp | 119 +++------------ src/render/storage/ingredients/view.hpp | 2 + src/render/storage/members/add.cpp | 21 +-- src/render/storage/members/add.hpp | 5 +- src/render/storage/members/delete.cpp | 3 +- src/render/storage/members/delete.hpp | 3 +- src/render/storage/members/view.cpp | 11 +- src/render/storage/members/view.hpp | 3 +- src/render/storage/view.cpp | 4 +- src/render/storage/view.hpp | 4 +- src/render/storages_list/create.cpp | 4 +- src/render/storages_list/create.hpp | 4 +- src/render/storages_list/view.cpp | 4 +- src/render/storages_list/view.hpp | 3 +- src/render/storages_selection/view.cpp | 8 +- src/render/storages_selection/view.hpp | 3 +- src/states.hpp | 73 +++++----- src/utils/fast_sorted_db.hpp | 10 +- src/utils/ingredients_availability.cpp | 68 +++++---- src/utils/ingredients_availability.hpp | 20 +-- src/utils/to_string.cpp | 20 +-- src/utils/to_string.hpp | 9 +- src/utils/utils.hpp | 5 + src/utils/uuid.cpp | 10 ++ src/utils/uuid.hpp | 6 + 151 files changed, 1384 insertions(+), 1318 deletions(-) create mode 100644 src/backend/api/moderation.cpp create mode 100644 src/backend/api/moderation.hpp delete mode 100644 src/backend/api/request_history.cpp delete mode 100644 src/backend/api/request_history.hpp create mode 100644 src/backend/models/moderation.cpp create mode 100644 src/backend/models/moderation.hpp delete mode 100644 src/backend/models/request_history.cpp delete mode 100644 src/backend/models/request_history.hpp rename src/handlers/personal_account/{request_history.cpp => publication_history.cpp} (54%) create mode 100644 src/handlers/personal_account/publication_history.hpp rename src/handlers/personal_account/recipe/{publication_history.cpp => moderation_history.cpp} (62%) create mode 100644 src/handlers/personal_account/recipe/moderation_history.hpp delete mode 100644 src/handlers/personal_account/recipe/publication_history.hpp delete mode 100644 src/handlers/personal_account/request_history.hpp create mode 100644 src/handlers/shopping_list/search.cpp create mode 100644 src/handlers/shopping_list/search.hpp create mode 100644 src/render/pagination.hpp create mode 100644 src/render/personal_account/publication_history.cpp create mode 100644 src/render/personal_account/publication_history.hpp rename src/render/personal_account/recipe/{publication_history.cpp => moderation_history.cpp} (95%) rename src/render/personal_account/recipe/{publication_history.hpp => moderation_history.hpp} (62%) delete mode 100644 src/render/personal_account/request_history.cpp delete mode 100644 src/render/personal_account/request_history.hpp create mode 100644 src/render/shopping_list/search.cpp create mode 100644 src/render/shopping_list/search.hpp diff --git a/.clang-tidy b/.clang-tidy index 7b682391..58e27008 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -35,5 +35,5 @@ CheckOptions: cppcoreguidelines-pro-type-member-init.IgnoreArrays: true readability-implicit-bool-conversion.AllowPointerConditions: true readability-braces-around-statements.ShortStatementLines: 3 + misc-include-cleaner.MissingIncludes: false FormatStyle: none - diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 57448ac5..ea1b03ce 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -8,14 +8,13 @@ target_sources(main PRIVATE src/backend/api/users.cpp src/backend/api/recipes.cpp src/backend/api/shopping_lists.cpp - src/backend/api/request_history.cpp + src/backend/api/moderation.cpp src/backend/models/storage.cpp src/backend/models/ingredient.cpp src/backend/models/user.cpp src/backend/models/recipe.cpp src/backend/models/shopping_list.cpp - src/backend/models/request_history.cpp src/backend/models/publication_request_status.cpp - + src/backend/models/moderation.cpp ) diff --git a/src/backend/api/api.hpp b/src/backend/api/api.hpp index e93afd43..f1992290 100644 --- a/src/backend/api/api.hpp +++ b/src/backend/api/api.hpp @@ -1,12 +1,11 @@ #pragma once -#include "backend/api/ingredients.hpp" -#include "backend/api/recipes.hpp" -#include "backend/api/request_history.hpp" -#include "backend/api/shopping_lists.hpp" -#include "backend/api/storages.hpp" -#include "backend/api/users.hpp" -#include "request_history.hpp" +#include "ingredients.hpp" +#include "moderation.hpp" +#include "recipes.hpp" +#include "shopping_lists.hpp" +#include "storages.hpp" +#include "users.hpp" #include @@ -22,16 +21,16 @@ class ApiClient { IngredientsApi ingredients; RecipesApi recipes; ShoppingListApi shoppingList; - RequestHistoryApi requestHistory; + ModerationApi moderation; public: explicit ApiClient(const std::string& apiAddress) : api{apiAddress}, users{api}, storages{api}, ingredients{api}, recipes{api}, shoppingList{api}, - requestHistory{api} {} + moderation{api} {} ApiClient(const ApiClient&) = delete; ApiClient(ApiClient&& other) noexcept : api{std::move(other.api)}, users{api}, storages{api}, ingredients{api}, recipes{api}, shoppingList{api}, - requestHistory{api} {} + moderation{api} {} ApiClient& operator=(const ApiClient&) = delete; ApiClient& operator=(ApiClient&& other) noexcept { if (&other == this) @@ -42,7 +41,7 @@ class ApiClient { ingredients = IngredientsApi{api}; recipes = RecipesApi{api}; shoppingList = ShoppingListApi{api}; - requestHistory = RequestHistoryApi{api}; + moderation = ModerationApi{api}; return *this; } ~ApiClient() = default; @@ -87,13 +86,15 @@ class ApiClient { return shoppingList; } - [[nodiscard]] const RequestHistoryApi& getRequestHistoryApi() const { - return requestHistory; + [[nodiscard]] const ModerationApi& getModerationApi() const { + return moderation; } - operator const RequestHistoryApi&() const { // NOLINT(*-explicit-*) - return requestHistory; + operator const ModerationApi&() const { // NOLINT(*-explicit-*) + return moderation; } }; +using ApiClientRef = const api::ApiClient&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index bf3101ef..2297cc6c 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -22,7 +22,10 @@ class IngredientsApi : ApiBase { [[nodiscard]] models::ingredient::Ingredient get(UserId user, IngredientId ingredient) const; [[nodiscard]] std::vector - getStorageIngredients(UserId user, StorageId storage, std::size_t count = 2, std::size_t offset = 0) const; + getStorageIngredients(UserId user, + StorageId storage, + std::size_t count = 200, // NOLINT(*magic-number*) + std::size_t offset = 0) const; void putToStorage(UserId user, StorageId storage, IngredientId ingredient) const; @@ -72,4 +75,6 @@ class IngredientsApi : ApiBase { void publishCustom(UserId user, IngredientId ingredient) const; }; +using IngredientsApiRef = const api::IngredientsApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/moderation.cpp b/src/backend/api/moderation.cpp new file mode 100644 index 00000000..fe41e50a --- /dev/null +++ b/src/backend/api/moderation.cpp @@ -0,0 +1,25 @@ +#include "moderation.hpp" + +#include "backend/models/moderation.hpp" + +#include + +#include +#include + +namespace cookcookhnya::api { + +using namespace models::moderation; + +// GET /publication-requests?size={}&offset={} +[[nodiscard]] std::vector +ModerationApi::getAllPublicationRequests(UserId user, std::size_t size, std::size_t offset) const { + return jsonGetAuthed>(user, + "/publication-requests", + { + {"size", utils::to_string(size)}, + {"offset", utils::to_string(offset)}, + }); +} + +} // namespace cookcookhnya::api diff --git a/src/backend/api/moderation.hpp b/src/backend/api/moderation.hpp new file mode 100644 index 00000000..70ffb0c8 --- /dev/null +++ b/src/backend/api/moderation.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "backend/models/moderation.hpp" +#include "base.hpp" + +#include + +#include +#include + +namespace cookcookhnya::api { + +class ModerationApi : ApiBase { + friend class ApiClient; + + explicit ModerationApi(httplib::Client& api) : ApiBase{api} {} + + public: + [[nodiscard]] std::vector getAllPublicationRequests( + UserId user, std::size_t size = 30, std::size_t offset = 0) const; // NOLINT(*magic-number*) +}; + +using ModerationApiRef = const ModerationApi&; + +} // namespace cookcookhnya::api diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 16683269..13f79185 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -72,8 +72,8 @@ void RecipesApi::publishCustom(UserId user, RecipeId recipe) const { } // GET /recipe/{id}/moderation-history -std::vector RecipesApi::getRecipeRequestHistory(UserId user, RecipeId recipe) const { - return jsonGetAuthed>(user, +std::vector RecipesApi::getRecipeRequestHistory(UserId user, RecipeId recipe) const { + return jsonGetAuthed>(user, std::format("/recipes/{}/moderation-history", recipe)); } diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 004dbd39..187c2de0 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -39,7 +39,7 @@ class RecipesApi : ApiBase { RecipeId create(UserId user, // NOLINT(*-nodiscard) const models::recipe::RecipeCreateBody& body) const; - [[nodiscard]] std::vector getRecipeRequestHistory(UserId user, + [[nodiscard]] std::vector getRecipeRequestHistory(UserId user, RecipeId recipe) const; [[nodiscard]] models::recipe::RecipeDetails get(UserId user, RecipeId recipeId) const; @@ -49,4 +49,6 @@ class RecipesApi : ApiBase { void publishCustom(UserId user, RecipeId recipe) const; }; +using RecipesApiRef = const api::RecipesApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/request_history.cpp b/src/backend/api/request_history.cpp deleted file mode 100644 index 7a9f7db3..00000000 --- a/src/backend/api/request_history.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "request_history.hpp" -#include "backend/models/request_history.hpp" - -#include - -#include -#include -namespace cookcookhnya::api { - -using namespace models::request_history; -// GET /publication-requests?size={}&offset={} -[[nodiscard]] std::vector -RequestHistoryApi::getAllRequestHistory(UserId user, std::size_t size, std::size_t offset) const { - return jsonGetAuthed>(user, - "/publication-requests", - { - {"size", utils::to_string(size)}, - {"offset", utils::to_string(offset)}, - }); -} -} // namespace cookcookhnya::api diff --git a/src/backend/api/request_history.hpp b/src/backend/api/request_history.hpp deleted file mode 100644 index 6e938448..00000000 --- a/src/backend/api/request_history.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "backend/models/request_history.hpp" -#include "base.hpp" - -#include - -#include -#include - -namespace cookcookhnya::api { - -class RequestHistoryApi : ApiBase { - friend class ApiClient; - - explicit RequestHistoryApi(httplib::Client& api) : ApiBase{api} {} - - public: - [[nodiscard]] std::vector - getAllRequestHistory(UserId user, std::size_t size = 10, std::size_t offset = 0) const; // NOLINT(*magic-number*) -}; - -} // namespace cookcookhnya::api diff --git a/src/backend/api/shopping_lists.hpp b/src/backend/api/shopping_lists.hpp index 3b02b558..143437d4 100644 --- a/src/backend/api/shopping_lists.hpp +++ b/src/backend/api/shopping_lists.hpp @@ -6,6 +6,7 @@ #include +#include #include namespace cookcookhnya::api { @@ -26,4 +27,6 @@ class ShoppingListApi : ApiBase { void buy(UserId user, StorageId storage, const std::vector& ingredients) const; }; +using ShoppingListApiRef = const api::ShoppingListApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/storages.hpp b/src/backend/api/storages.hpp index d77e65a2..c10c1d42 100644 --- a/src/backend/api/storages.hpp +++ b/src/backend/api/storages.hpp @@ -19,15 +19,21 @@ class StoragesApi : ApiBase { public: [[nodiscard]] std::vector getStoragesList(UserId user) const; + [[nodiscard]] models::storage::StorageDetails get(UserId user, StorageId storage) const; + StorageId create(UserId user, // NOLINT(*-nodiscard) const models::storage::StorageCreateBody& body) const; void delete_(UserId user, StorageId storage) const; + [[nodiscard]] std::vector getStorageMembers(UserId user, StorageId storage) const; void addMember(UserId user, StorageId storage, UserId member) const; void deleteMember(UserId user, StorageId storage, UserId member) const; + [[nodiscard]] InvitationId inviteMember(UserId user, StorageId storage) const; [[nodiscard]] std::optional activate(UserId user, InvitationId invitation) const; }; +using StorageApiRef = const api::StoragesApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/api/users.hpp b/src/backend/api/users.hpp index 21c22299..e353d6ec 100644 --- a/src/backend/api/users.hpp +++ b/src/backend/api/users.hpp @@ -17,4 +17,6 @@ class UsersApi : ApiBase { const models::user::UpdateUserInfoBody& body) const; }; +using UserApiRef = const api::UsersApi&; + } // namespace cookcookhnya::api diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index 1228ba30..259ccc1c 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace cookcookhnya::api::models::ingredient { namespace json = boost::json; diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index 76a59244..8d29e26c 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -16,7 +16,7 @@ namespace cookcookhnya::api::models::ingredient { struct Ingredient { IngredientId id; std::string name; - std::optional status = std::nullopt; + std::optional status = std::nullopt; friend Ingredient tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/backend/models/moderation.cpp b/src/backend/models/moderation.cpp new file mode 100644 index 00000000..8bfc366e --- /dev/null +++ b/src/backend/models/moderation.cpp @@ -0,0 +1,30 @@ +#include "moderation.hpp" + +#include "publication_request_status.hpp" +#include "utils/serialization.hpp" + +#include +#include +#include + +#include + +namespace cookcookhnya::api::models::moderation { + +namespace json = boost::json; + +PublicationRequest tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { + return { + .name = value_to(j.at("name")), + .status = j.as_object().if_contains("status") ? value_to(j.at("status")) + : PublicationRequestStatus::NO_REQUEST, + .created = utils::parseIsoTime(value_to(j.at("created"))), + .updated = j.as_object().if_contains("updated") + ? std::optional{utils::parseIsoTime(value_to(j.at("updated")))} + : std::nullopt, + .reason = j.as_object().if_contains("reason") ? value_to(j.at("reason")) + : std::nullopt, + }; +} + +} // namespace cookcookhnya::api::models::moderation diff --git a/src/backend/models/moderation.hpp b/src/backend/models/moderation.hpp new file mode 100644 index 00000000..1c8d6287 --- /dev/null +++ b/src/backend/models/moderation.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "publication_request_status.hpp" + +#include +#include + +#include +#include + +namespace cookcookhnya::api::models::moderation { + +struct PublicationRequest { + std::string name; + PublicationRequestStatus status = PublicationRequestStatus::NO_REQUEST; + 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); +}; + +} // namespace cookcookhnya::api::models::moderation diff --git a/src/backend/models/publication_request_status.cpp b/src/backend/models/publication_request_status.cpp index 7f85b07b..e21abc5f 100644 --- a/src/backend/models/publication_request_status.cpp +++ b/src/backend/models/publication_request_status.cpp @@ -1,6 +1,15 @@ -#include "backend/models/publication_request_status.hpp" +#include "publication_request_status.hpp" -namespace cookcookhnya::api::models::status { +#include "utils/utils.hpp" + +#include +#include + +#include +#include +#include + +namespace cookcookhnya::api::models::moderation { PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, const boost::json::value& j) { @@ -13,4 +22,14 @@ PublicationRequestStatus tag_invoke(boost::json::value_to_tag(status)]); +} + +} // namespace cookcookhnya::utils diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp index c3cf284a..875fe4bf 100644 --- a/src/backend/models/publication_request_status.hpp +++ b/src/backend/models/publication_request_status.hpp @@ -1,13 +1,21 @@ #pragma once #include +#include #include +#include -namespace cookcookhnya::api::models::status { +namespace cookcookhnya::api::models::moderation { -enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED, NO_REQUEST }; +enum class PublicationRequestStatus : std::uint8_t { NO_REQUEST, PENDING, ACCEPTED, REJECTED }; PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); -} // namespace cookcookhnya::api::models::status +} // namespace cookcookhnya::api::models::moderation + +namespace cookcookhnya::utils { + +std::string to_string(api::models::moderation::PublicationRequestStatus status); + +} // namespace cookcookhnya::utils diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 6a4ca359..e720d973 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -5,12 +5,13 @@ #include #include #include + #include -#include namespace cookcookhnya::api::models::recipe { namespace json = boost::json; +using moderation::PublicationRequestStatus; RecipeSummary tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { @@ -54,12 +55,12 @@ RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json:: .ingredients = value_to(j.at("ingredients")), .name = value_to(j.at("name")), .link = value_to(j.at("sourceLink")), - // Deal with optionals using ternary + // 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")) - : status::PublicationRequestStatus::NO_REQUEST, + ? value_to(j.at("moderationStatus")) + : PublicationRequestStatus::NO_REQUEST, }; } @@ -70,17 +71,17 @@ RecipeSearchResponse tag_invoke(json::value_to_tag /*tag*/ }; } -PublicationHistoryRecipe tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { +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")) + : PublicationRequestStatus::NO_REQUEST, .created = utils::parseIsoTime(value_to(j.at("createdAt"))), - .reason = j.as_object().if_contains("reason") - ? value_to(j.at("reason")) - : "", - .status = j.as_object().if_contains("status") ? value_to(j.at("status")) - : status::PublicationRequestStatus::NO_REQUEST, .updated = j.as_object().if_contains("updatedAt") - ? utils::parseIsoTime(value_to(j.at("updatedAt"))) - : std::chrono::time_point(), + ? 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 d32486a7..0d0d6891 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -1,9 +1,9 @@ #pragma once #include "backend/id_types.hpp" -#include "backend/models/storage.hpp" -#include "backend/models/user.hpp" #include "publication_request_status.hpp" +#include "storage.hpp" +#include "user.hpp" #include #include @@ -14,7 +14,6 @@ #include namespace cookcookhnya::api::models::recipe { -using namespace status; struct RecipeSummary { RecipeId id; @@ -46,7 +45,7 @@ struct RecipeDetails { std::string name; std::optional link; std::optional creator; - std::optional moderationStatus; + std::optional moderationStatus; friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; @@ -82,13 +81,13 @@ struct RecipeSearchResponse { const boost::json::value& j); }; -struct PublicationHistoryRecipe { +struct RecipePublicationRequest { + moderation::PublicationRequestStatus status = moderation::PublicationRequestStatus::NO_REQUEST; std::chrono::system_clock::time_point created; - std::optional reason; - PublicationRequestStatus status{}; std::optional updated; + std::optional reason; - friend PublicationHistoryRecipe tag_invoke(boost::json::value_to_tag, + friend RecipePublicationRequest tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/backend/models/request_history.cpp b/src/backend/models/request_history.cpp deleted file mode 100644 index db760fe7..00000000 --- a/src/backend/models/request_history.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "request_history.hpp" -#include "publication_request_status.hpp" -#include "utils/serialization.hpp" -#include -#include -#include - -namespace cookcookhnya::api::models::request_history { - -namespace json = boost::json; - -PublicationHistoryInstance tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - return { - .name = value_to(j.at("name")), - .created = utils::parseIsoTime(value_to(j.at("created"))), - .reason = j.as_object().if_contains("reason") - ? value_to(j.at("reason")) - : "", - .status = j.as_object().if_contains("status") ? value_to(j.at("status")) - : status::PublicationRequestStatus::NO_REQUEST, - .updated = j.as_object().if_contains("updated") ? utils::parseIsoTime(value_to(j.at("updated"))) - : std::chrono::time_point(), - }; -} - -} // namespace cookcookhnya::api::models::request_history diff --git a/src/backend/models/request_history.hpp b/src/backend/models/request_history.hpp deleted file mode 100644 index d7d8a139..00000000 --- a/src/backend/models/request_history.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "publication_request_status.hpp" - -#include -#include - -#include -#include - -namespace cookcookhnya::api::models::request_history { - -struct PublicationHistoryInstance { - std::string name; - std::chrono::system_clock::time_point created; - std::optional reason; - api::models::status::PublicationRequestStatus status{}; - std::optional updated; - - friend PublicationHistoryInstance tag_invoke(boost::json::value_to_tag, - const boost::json::value& j); -}; -} // namespace cookcookhnya::api::models::request_history diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 98f4126e..2d2d534d 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -16,10 +16,10 @@ target_sources(main PRIVATE src/handlers/personal_account/recipe/search_ingredients.cpp src/handlers/personal_account/recipe/view.cpp - src/handlers/personal_account/recipe/publication_history.cpp + src/handlers/personal_account/recipe/moderation_history.cpp src/handlers/personal_account/view.cpp - src/handlers/personal_account/request_history.cpp + src/handlers/personal_account/publication_history.cpp src/handlers/recipe/add_storage.cpp src/handlers/recipe/view.cpp @@ -27,6 +27,7 @@ target_sources(main PRIVATE src/handlers/recipes_suggestions/view.cpp src/handlers/shopping_list/create.cpp + src/handlers/shopping_list/search.cpp src/handlers/shopping_list/storage_selection_to_buy.cpp src/handlers/shopping_list/view.cpp diff --git a/src/handlers/commands/my_storages.cpp b/src/handlers/commands/my_storages.cpp index efd08470..58ffe49e 100644 --- a/src/handlers/commands/my_storages.cpp +++ b/src/handlers/commands/my_storages.cpp @@ -1,5 +1,6 @@ #include "my_storages.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "render/storages_list/view.hpp" #include "states.hpp" @@ -8,7 +9,7 @@ namespace cookcookhnya::handlers::commands { using namespace render::storages_list; -void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { renderStorageList(false, m.from->id, m.chat->id, bot, api); stateManager.put(StorageList{}); }; diff --git a/src/handlers/commands/my_storages.hpp b/src/handlers/commands/my_storages.hpp index 36402ad7..87719af2 100644 --- a/src/handlers/commands/my_storages.hpp +++ b/src/handlers/commands/my_storages.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::commands { -void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleMyStoragesCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/commands/start.cpp b/src/handlers/commands/start.cpp index 197a9472..bc86772f 100644 --- a/src/handlers/commands/start.cpp +++ b/src/handlers/commands/start.cpp @@ -1,11 +1,13 @@ #include "start.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/user.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" #include "states.hpp" +#include #include #include #include @@ -14,33 +16,38 @@ namespace cookcookhnya::handlers::commands { using namespace render::main_menu; +using namespace api::models::user; using namespace std::literals; -void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { auto userId = m.from->id; + auto chatId = m.chat->id; + std::string fullName = m.from->firstName; if (!m.from->lastName.empty()) { fullName += ' '; fullName += m.from->lastName; } + std::optional alias; if (!m.from->username.empty()) alias = m.from->username; - api.getUsersApi().updateInfo( - userId, - api::models::user::UpdateUserInfoBody{.alias = std::move(m.from->username), .fullName = std::move(fullName)}); + api.getUsersApi().updateInfo(userId, + UpdateUserInfoBody{.alias = std::move(alias), .fullName = std::move(fullName)}); auto startText = m.text; - const int hashPos = "/start "sv.size(); + const std::size_t hashPos = "/start "sv.size(); if (startText.size() > hashPos - 1) { auto hash = std::string(m.text).substr(hashPos); auto storage = api.getStoragesApi().activate(userId, hash); - renderMainMenu(false, storage->name, m.from->id, m.chat->id, bot, api); + if (!storage) + return; + renderMainMenu(false, storage->name, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } - renderMainMenu(false, std::nullopt, m.from->id, m.chat->id, bot, api); + renderMainMenu(false, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); }; diff --git a/src/handlers/commands/start.hpp b/src/handlers/commands/start.hpp index feb4539c..cccae238 100644 --- a/src/handlers/commands/start.hpp +++ b/src/handlers/commands/start.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::commands { -void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api); void handleNoState(MessageRef m, BotRef bot); diff --git a/src/handlers/commands/wanna_eat.cpp b/src/handlers/commands/wanna_eat.cpp index 8e246f98..a33c15ae 100644 --- a/src/handlers/commands/wanna_eat.cpp +++ b/src/handlers/commands/wanna_eat.cpp @@ -1,5 +1,6 @@ #include "wanna_eat.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" #include "render/main_menu/view.hpp" @@ -7,6 +8,7 @@ #include "render/storages_selection/view.hpp" #include "states.hpp" #include "utils/utils.hpp" + #include namespace cookcookhnya::handlers::commands { @@ -15,7 +17,7 @@ using namespace render::select_storages; using namespace render::main_menu; using namespace render::recipes_suggestions; -void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { auto storages = api.getStoragesApi().getStoragesList(m.from->id); if (storages.empty()) { bot.sendMessage(m.chat->id, utils::utf8str(u8"😔 К сожалению, у вас пока что нет хранилищ.")); @@ -25,8 +27,8 @@ void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRe message::deleteMessageId(m.from->id); renderRecipesSuggestion({storages}, 0, m.from->id, m.chat->id, bot, api); stateManager.put(SuggestedRecipesList{ - .pageNo = 0, .selectedStorages = storages, + .pageNo = 0, .fromStorage = false, }); } else { diff --git a/src/handlers/commands/wanna_eat.hpp b/src/handlers/commands/wanna_eat.hpp index 398424eb..ff67da45 100644 --- a/src/handlers/commands/wanna_eat.hpp +++ b/src/handlers/commands/wanna_eat.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::commands { -void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index b81195bd..ecd2cdba 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -1,10 +1,5 @@ #pragma once -#include "backend/api/api.hpp" -#include "backend/api/ingredients.hpp" -#include "backend/api/recipes.hpp" -#include "backend/api/storages.hpp" -#include "backend/api/users.hpp" #include "states.hpp" #include @@ -27,7 +22,7 @@ using states::CustomIngredientsList; using states::StorageList; -using states::AllPublicationHistory; +using states::TotalPublicationHistory; using states::RecipeStorageAddition; using states::RecipeView; @@ -46,6 +41,7 @@ using states::StoragesSelection; using states::SuggestedRecipesList; using states::ShoppingListCreation; +using states::ShoppingListIngredientSearch; using states::ShoppingListStorageSelectionToBuy; using states::ShoppingListView; @@ -56,20 +52,10 @@ using states::CustomRecipesList; using states::RecipeCustomView; // Type aliases -using ApiClientRef = const api::ApiClient&; -using UserApiRef = const api::UsersApi&; -using StorageApiRef = const api::StoragesApi&; -using IngredientsApiRef = const api::IngredientsApi&; -using RecipesApiRef = const api::RecipesApi&; -using ShoppingListApiRef = const api::ShoppingListApi&; - using BotRef = const TgBot::Api&; using SMRef = const states::StateManager&; using MessageRef = const TgBot::Message&; using CallbackQueryRef = const TgBot::CallbackQuery&; using InlineQueryRef = const TgBot::InlineQuery&; -using NoState = tg_stater::HandlerTypes::NoState; -using AnyState = tg_stater::HandlerTypes::AnyState; - } // namespace cookcookhnya::handlers diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 9d3e076c..9f075377 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -13,13 +13,14 @@ #include "personal_account/ingredients_list/publish.hpp" #include "personal_account/ingredients_list/view.hpp" -#include "personal_account/recipe/publication_history.hpp" +#include "personal_account/recipe/moderation_history.hpp" #include "personal_account/recipe/search_ingredients.hpp" #include "personal_account/recipe/view.hpp" + #include "personal_account/recipes_list/create.hpp" #include "personal_account/recipes_list/view.hpp" -#include "personal_account/request_history.hpp" +#include "personal_account/publication_history.hpp" #include "personal_account/view.hpp" #include "recipe/add_storage.hpp" @@ -28,6 +29,7 @@ #include "recipes_suggestions/view.hpp" #include "shopping_list/create.hpp" +#include "shopping_list/search.hpp" #include "shopping_list/storage_selection_to_buy.hpp" #include "shopping_list/view.hpp" @@ -45,46 +47,49 @@ #include "storages_selection/view.hpp" -#include "handlers/common.hpp" - #include #include -namespace cookcookhnya::handlers { +namespace cookcookhnya::handlers::bot_handlers { -using namespace commands; -using namespace main_menu; -using namespace personal_account; -using namespace personal_account::ingredients; -using namespace personal_account::recipes; +using namespace handlers::commands; +using namespace handlers::main_menu; +using namespace handlers::personal_account; +using namespace handlers::personal_account::ingredients; +using namespace handlers::personal_account::recipe; +using namespace handlers::personal_account::recipes_list; +using namespace handlers::shopping_list; using namespace handlers::recipe; -using namespace shopping_list; -using namespace storage; -using namespace storage::ingredients; -using namespace storage::members; -using namespace storages_list; -using namespace storages_selection; -using namespace recipes_suggestions; -using namespace personal_account::publication_history; -using namespace personal_account::recipe::publication_history; +using namespace handlers::storage; +using namespace handlers::storage::ingredients; +using namespace handlers::storage::members; +using namespace handlers::storages_list; +using namespace handlers::storages_selection; +using namespace handlers::recipes_suggestions; using namespace tg_stater; -namespace bot_handlers { +using NoState = tg_stater::HandlerTypes::NoState; +using AnyState = tg_stater::HandlerTypes::AnyState; -// Commands -constexpr char startCmd[] = "start"; // NOLINT(*c-arrays) -constexpr char myStoragesCmd[] = "my_storages"; // NOLINT(*c-arrays) -constexpr char shoppingListCmd[] = "shopping_list"; // NOLINT(*c-arrays) -constexpr char personalAccountCmd[] = "personal_account"; // NOLINT(*c-arrays) -constexpr char wannaEatCmd[] = "wanna_eat"; // NOLINT(*c-arrays) using noStateHandler = Handler; -using startCmdHandler = Handler; // NOLINT(*decay) + +// Commands +constexpr char startCmd[] = "start"; // NOLINT(*c-arrays) +using startCmdHandler = Handler; // NOLINT(*decay) + +constexpr char myStoragesCmd[] = "my_storages"; // NOLINT(*c-arrays) using myStoragesCmdHandler = Handler; // NOLINT(*decay) + +constexpr char shoppingListCmd[] = "shopping_list"; // NOLINT(*c-arrays) using shoppingListCmdHandler = Handler; // NOLINT(*decay) + +constexpr char personalAccountCmd[] = "personal_account"; // NOLINT(*c-arrays) using personalAccountCmdHandler = - Handler; // NOLINT(*decay) + Handler; // NOLINT(*decay) + +constexpr char wannaEatCmd[] = "wanna_eat"; // NOLINT(*c-arrays) using wannaEatCmdHandler = Handler; // NOLINT(*decay) // MainMenu @@ -133,10 +138,12 @@ using shoppingListCreationCQHandler = Handler; using shoppingListStorageSelectionToBuyCQHandler = Handler; +using shoppingListIngredientSearchCQHandler = Handler; +using shoppingListIngredientSearchIQHandler = Handler; // Personal account using personalAccountMenuCQHandler = Handler; -using allPublicationHistoryHandler = Handler; +using totalPublicationHistoryCQHandler = Handler; // Custom Recipes List using customRecipesListCQHandler = Handler; @@ -147,7 +154,7 @@ using createCustomRecipeCQHandler = Handler; using customRecipeIngredientsSearchCQHandler = Handler; using customRecipeIngredientsSearchIQHandler = Handler; -using customRecipePublicationHistoryHandler = Handler; -} // namespace bot_handlers +using customRecipePublicationHistoryCQHandler = + Handler; -} // namespace cookcookhnya::handlers +} // namespace cookcookhnya::handlers::bot_handlers diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index 7ba6330f..e5225668 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/api/storages.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" @@ -22,7 +23,8 @@ using namespace render::shopping_list; using namespace render::personal_account; using namespace std::views; -void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { +void handleMainMenuCQ( + MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -38,7 +40,7 @@ void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SM if (storages.size() == 1) { std::vector storage = {storages[0]}; renderRecipesSuggestion(storage, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storage, .fromStorage = false}); + stateManager.put(SuggestedRecipesList{.selectedStorages = storage, .pageNo = 0, .fromStorage = false}); return; } renderStorageSelection({}, userId, chatId, bot, api); diff --git a/src/handlers/main_menu/view.hpp b/src/handlers/main_menu/view.hpp index 9bc51877..56a9086f 100644 --- a/src/handlers/main_menu/view.hpp +++ b/src/handlers/main_menu/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::main_menu { -void handleMainMenuCQ(MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); +void handleMainMenuCQ(MainMenu&, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::main_menu diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index a677ca58..32a43d0d 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -1,27 +1,29 @@ #include "create.hpp" +#include "backend/api/ingredients.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" -#include "ranges" #include "render/personal_account/ingredients_list/create.hpp" #include "render/personal_account/ingredients_list/view.hpp" #include "render/personal_account/recipe/search_ingredients.hpp" #include "render/storage/ingredients/view.hpp" -#include "states.hpp" #include "utils/utils.hpp" +#include + namespace cookcookhnya::handlers::personal_account::ingredients { -using namespace render::recipe::ingredients; + +using namespace render::storage::ingredients; +using namespace render::personal_account::recipe; using namespace render::personal_account::ingredients; using namespace std::views; -using namespace render::storage::ingredients; + void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& /*unused*/, MessageRef m, BotRef& bot, SMRef stateManager, - IngredientsApiRef api) { - + api::IngredientsApiRef api) { auto name = m.text; auto userId = m.from->id; auto chatId = m.chat->id; @@ -39,7 +41,7 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName CallbackQueryRef cq, BotRef& bot, SMRef stateManager, - IngredientsApiRef api) { + api::IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -50,18 +52,20 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName } void handleCustomIngredientConfirmationCQ( - CustomIngredientConfirmation& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + CustomIngredientConfirmation& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; auto name = state.name; + if (cq.data == "confirm") { api.getIngredientsApi().createCustom(userId, api::models::ingredient::IngredientCreateBody{name}); renderCustomIngredientsList(true, state.pageNo, userId, chatId, bot, api); stateManager.put(CustomIngredientsList{}); } + if (cq.data == "back") { - size_t numOfIngredientsOnPage = 5; + const std::size_t numOfIngredientsOnPage = 5; if (state.recipeFrom.has_value()) { auto newState = diff --git a/src/handlers/personal_account/ingredients_list/create.hpp b/src/handlers/personal_account/ingredients_list/create.hpp index 64600c5d..9bd09f74 100644 --- a/src/handlers/personal_account/ingredients_list/create.hpp +++ b/src/handlers/personal_account/ingredients_list/create.hpp @@ -1,5 +1,7 @@ #pragma once +#include "backend/api/api.hpp" +#include "backend/api/ingredients.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::ingredients { @@ -8,15 +10,18 @@ void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterNam MessageRef m, BotRef& bot, SMRef stateManager, - IngredientsApiRef api); + api::IngredientsApiRef api); void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, - IngredientsApiRef api); + api::IngredientsApiRef api); -void handleCustomIngredientConfirmationCQ( - CustomIngredientConfirmation& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); +void handleCustomIngredientConfirmationCQ(CustomIngredientConfirmation& /*unused*/, + CallbackQueryRef cq, + BotRef& bot, + SMRef stateManager, + api::ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/publish.cpp b/src/handlers/personal_account/ingredients_list/publish.cpp index 8377193a..648ddb87 100644 --- a/src/handlers/personal_account/ingredients_list/publish.cpp +++ b/src/handlers/personal_account/ingredients_list/publish.cpp @@ -1,5 +1,6 @@ #include "publish.hpp" +#include "backend/api/ingredients.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/personal_account/ingredients_list/view.hpp" @@ -11,7 +12,7 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; void handleCustomIngredientPublishCQ( - CustomIngredientPublish& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { + CustomIngredientPublish& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/personal_account/ingredients_list/publish.hpp b/src/handlers/personal_account/ingredients_list/publish.hpp index aaf2b74b..2c8c9956 100644 --- a/src/handlers/personal_account/ingredients_list/publish.hpp +++ b/src/handlers/personal_account/ingredients_list/publish.hpp @@ -1,10 +1,14 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::ingredients { -void handleCustomIngredientPublishCQ( - CustomIngredientPublish& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api); +void handleCustomIngredientPublishCQ(CustomIngredientPublish& /*unused*/, + CallbackQueryRef cq, + BotRef& bot, + SMRef stateManager, + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/view.cpp b/src/handlers/personal_account/ingredients_list/view.cpp index 52c08711..74bb3181 100644 --- a/src/handlers/personal_account/ingredients_list/view.cpp +++ b/src/handlers/personal_account/ingredients_list/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "render/personal_account/ingredients_list/create.hpp" #include "render/personal_account/ingredients_list/publish.hpp" @@ -16,7 +17,7 @@ using namespace render::personal_account::ingredients; using namespace render::personal_account; void handleCustomIngredientsListCQ( - CustomIngredientsList& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + CustomIngredientsList& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/personal_account/ingredients_list/view.hpp b/src/handlers/personal_account/ingredients_list/view.hpp index c28eb38a..ef1fd4bf 100644 --- a/src/handlers/personal_account/ingredients_list/view.hpp +++ b/src/handlers/personal_account/ingredients_list/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account::ingredients { void handleCustomIngredientsListCQ( - CustomIngredientsList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + CustomIngredientsList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/request_history.cpp b/src/handlers/personal_account/publication_history.cpp similarity index 54% rename from src/handlers/personal_account/request_history.cpp rename to src/handlers/personal_account/publication_history.cpp index 2e9c3dbf..6fcf6979 100644 --- a/src/handlers/personal_account/request_history.cpp +++ b/src/handlers/personal_account/publication_history.cpp @@ -1,13 +1,13 @@ -#include "request_history.hpp" +#include "publication_history.hpp" #include "render/personal_account/view.hpp" -namespace cookcookhnya::handlers::personal_account::publication_history { +namespace cookcookhnya::handlers::personal_account { using namespace render::personal_account; -void handleAllPublicationHistoryCQ( - AllPublicationHistory& /**/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef /**/) { +void handleTotalPublicationHistoryCQ( + TotalPublicationHistory& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef /**/) { const std::string data = cq.data; bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; @@ -20,4 +20,4 @@ void handleAllPublicationHistoryCQ( } } -} // namespace cookcookhnya::handlers::personal_account::publication_history +} // namespace cookcookhnya::handlers::personal_account diff --git a/src/handlers/personal_account/publication_history.hpp b/src/handlers/personal_account/publication_history.hpp new file mode 100644 index 00000000..376f9619 --- /dev/null +++ b/src/handlers/personal_account/publication_history.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::personal_account { + +void handleTotalPublicationHistoryCQ( + TotalPublicationHistory& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); + +} // namespace cookcookhnya::handlers::personal_account diff --git a/src/handlers/personal_account/recipe/publication_history.cpp b/src/handlers/personal_account/recipe/moderation_history.cpp similarity index 62% rename from src/handlers/personal_account/recipe/publication_history.cpp rename to src/handlers/personal_account/recipe/moderation_history.cpp index f3a6ca01..63d9fc5b 100644 --- a/src/handlers/personal_account/recipe/publication_history.cpp +++ b/src/handlers/personal_account/recipe/moderation_history.cpp @@ -1,15 +1,15 @@ -#include "publication_history.hpp" +#include "moderation_history.hpp" #include "handlers/common.hpp" -#include "render/personal_account/recipe/publication_history.hpp" +#include "render/personal_account/recipe/moderation_history.hpp" #include "render/personal_account/recipe/view.hpp" -namespace cookcookhnya::handlers::personal_account::recipe::publication_history { -using namespace render::personal_account; -using namespace render::personal_account::recipes; -using namespace render::personal_account::recipe::publication_history; +namespace cookcookhnya::handlers::personal_account::recipe { + +using namespace render::personal_account::recipe; + void handleCustomRecipePublicationHistoryCQ( - CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; @@ -18,8 +18,8 @@ void handleCustomRecipePublicationHistoryCQ( auto ingredientsAndRecipeName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); stateManager.put(RecipeCustomView{.recipeId = state.recipeId, .pageNo = state.pageNo, - .ingredients = std::get<0>(ingredientsAndRecipeName), - .recipeName = std::get<1>(ingredientsAndRecipeName)}); + .ingredients = ingredientsAndRecipeName.first, + .recipeName = ingredientsAndRecipeName.second}); return; } @@ -35,4 +35,4 @@ void handleCustomRecipePublicationHistoryCQ( } } -} // namespace cookcookhnya::handlers::personal_account::recipe::publication_history +} // namespace cookcookhnya::handlers::personal_account::recipe diff --git a/src/handlers/personal_account/recipe/moderation_history.hpp b/src/handlers/personal_account/recipe/moderation_history.hpp new file mode 100644 index 00000000..285a4d22 --- /dev/null +++ b/src/handlers/personal_account/recipe/moderation_history.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::personal_account::recipe { + +void handleCustomRecipePublicationHistoryCQ( + CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); + +} // namespace cookcookhnya::handlers::personal_account::recipe diff --git a/src/handlers/personal_account/recipe/publication_history.hpp b/src/handlers/personal_account/recipe/publication_history.hpp deleted file mode 100644 index c12ea275..00000000 --- a/src/handlers/personal_account/recipe/publication_history.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "handlers/common.hpp" - -namespace cookcookhnya::handlers::personal_account::recipe::publication_history { - -void handleCustomRecipePublicationHistoryCQ( - CustomRecipePublicationHistory& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); - -} // namespace cookcookhnya::handlers::personal_account::recipe::publication_history diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 1e6658f5..e69f6ca6 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -1,5 +1,7 @@ #include "search_ingredients.hpp" +#include "backend/api/api.hpp" +#include "backend/api/ingredients.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" @@ -18,15 +20,14 @@ #include #include -namespace cookcookhnya::handlers::personal_account::recipes { +namespace cookcookhnya::handlers::personal_account::recipe { using namespace api::models::ingredient; -using namespace render::recipe::ingredients; -using namespace render::personal_account::recipes; +using namespace render::personal_account::ingredients; +using namespace render::personal_account::recipe; using namespace std::ranges; using namespace std::views; -using namespace render::personal_account::ingredients; -using namespace render::suggest_custom_ingredient; + namespace { const std::size_t numOfIngredientsOnPage = 5; @@ -36,7 +37,7 @@ void updateSearch(CustomRecipeIngredientsSearch& state, bool isQueryChanged, BotRef bot, tg_types::UserId userId, - IngredientsApiRef api) { + api::IngredientsApiRef api) { state.pageNo = isQueryChanged ? 0 : state.pageNo; auto response = api.searchForRecipe( userId, state.recipeId, state.query, threshhold, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage); @@ -61,7 +62,7 @@ void updateSearch(CustomRecipeIngredientsSearch& state, } // namespace void handleCustomRecipeIngredientsSearchCQ( - CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); const auto userId = cq.from->id; const auto chatId = cq.message->chat->id; @@ -72,7 +73,7 @@ void handleCustomRecipeIngredientsSearchCQ( stateManager.put(RecipeCustomView{.recipeId = state.recipeId, .pageNo = 0, .ingredients = std::move(ingredients), - .recipeName = std::get<1>(ingredientsAndName)}); + .recipeName = ingredientsAndName.second}); return; } @@ -118,7 +119,7 @@ void handleCustomRecipeIngredientsSearchCQ( void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api) { + api::IngredientsApiRef api) { state.query = iq.query; const auto userId = iq.from->id; if (iq.query.empty()) { @@ -130,7 +131,7 @@ void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, updateSearch(state, true, bot, userId, api); } // Cache is not disabled on Windows and Linux desktops. Works on Android and Web - bot.answerInlineQuery(iq.id, {}, 0); + // bot.answerInlineQuery(iq.id, {}, 0); } -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipe diff --git a/src/handlers/personal_account/recipe/search_ingredients.hpp b/src/handlers/personal_account/recipe/search_ingredients.hpp index 065e154b..a176c414 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.hpp +++ b/src/handlers/personal_account/recipe/search_ingredients.hpp @@ -1,15 +1,17 @@ #pragma once +#include "backend/api/api.hpp" +#include "backend/api/ingredients.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::personal_account::recipes { +namespace cookcookhnya::handlers::personal_account::recipe { void handleCustomRecipeIngredientsSearchCQ( - CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + CustomRecipeIngredientsSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); void handleCustomRecipeIngredientsSearchIQ(CustomRecipeIngredientsSearch& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api); + api::IngredientsApiRef api); -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipe diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index 21c299a0..bba8fc13 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -1,7 +1,8 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" -#include "render/personal_account/recipe/publication_history.hpp" +#include "render/personal_account/recipe/moderation_history.hpp" #include "render/personal_account/recipe/search_ingredients.hpp" #include "render/personal_account/recipes_list/view.hpp" #include "states.hpp" @@ -11,18 +12,16 @@ #include #include -namespace cookcookhnya::handlers::personal_account::recipes { - -using namespace render::personal_account::recipes; -using namespace render::recipe::ingredients; -using namespace render::personal_account::recipe::publication_history; +namespace cookcookhnya::handlers::personal_account::recipe { +using namespace render::personal_account::recipes_list; +using namespace render::personal_account::recipe; using namespace std::views; const std::size_t numOfIngredientsOnPage = 5; void handleRecipeCustomViewCQ( - RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; @@ -54,7 +53,7 @@ void handleRecipeCustomViewCQ( // Not peeking (if button with this data then idle or rejected) bool isPeeking = false; renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); - stateManager.put(states::CustomRecipePublicationHistory{ + stateManager.put(CustomRecipePublicationHistory{ .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); bot.answerCallbackQuery(cq.id); return; @@ -63,11 +62,11 @@ void handleRecipeCustomViewCQ( // Peeking (if button with this data then accepted or pending) bool isPeeking = true; renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); - stateManager.put(states::CustomRecipePublicationHistory{ + stateManager.put(CustomRecipePublicationHistory{ .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); bot.answerCallbackQuery(cq.id); return; } } -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipe diff --git a/src/handlers/personal_account/recipe/view.hpp b/src/handlers/personal_account/recipe/view.hpp index aac53df8..16753d5f 100644 --- a/src/handlers/personal_account/recipe/view.hpp +++ b/src/handlers/personal_account/recipe/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::personal_account::recipes { +namespace cookcookhnya::handlers::personal_account::recipe { void handleRecipeCustomViewCQ( - RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + RecipeCustomView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipe diff --git a/src/handlers/personal_account/recipes_list/create.cpp b/src/handlers/personal_account/recipes_list/create.cpp index d875a8a6..46626dbd 100644 --- a/src/handlers/personal_account/recipes_list/create.cpp +++ b/src/handlers/personal_account/recipes_list/create.cpp @@ -1,38 +1,38 @@ #include "create.hpp" +#include "backend/api/recipes.hpp" #include "backend/models/recipe.hpp" #include "handlers/common.hpp" #include "render/personal_account/recipe/view.hpp" #include "render/personal_account/recipes_list/view.hpp" -namespace cookcookhnya::handlers::personal_account::recipes { +namespace cookcookhnya::handlers::personal_account::recipes_list { -using namespace render::personal_account::recipes; +using namespace api::models::recipe; +using namespace render::personal_account::recipes_list; +using namespace render::personal_account::recipe; void handleCreateCustomRecipeMsg( - CreateCustomRecipe& state, MessageRef m, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi) { - + CreateCustomRecipe& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi) { // Init with no ingredients and link. My suggestion: to use link as author's alias - state.recipeId = recipeApi.create( - m.from->id, api::models::recipe::RecipeCreateBody{.name = m.text, .ingredients = {}, .link = ""}); // - - renderCustomRecipe(false, m.from->id, m.chat->id, state.recipeId, bot, recipeApi); - stateManager.put(RecipeCustomView{.recipeId = state.recipeId, - .pageNo = 0, - .ingredients = {}, - .recipeName = m.text}); // If it went from creation then as user will return - // from RecipeView to RecipesList on 1st page + auto recipeId = recipeApi.create(m.from->id, RecipeCreateBody{.name = m.text, .ingredients = {}, .link = ""}); + + renderCustomRecipe(false, m.from->id, m.chat->id, recipeId, bot, recipeApi); + // If it went from creation then as user will return + // from RecipeView to RecipesList on 1st page + stateManager.put(RecipeCustomView{.recipeId = recipeId, .pageNo = 0, .ingredients = {}, .recipeName = m.text}); }; void handleCreateCustomRecipeCQ( - CreateCustomRecipe& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi) { + CreateCustomRecipe& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi) { bot.answerCallbackQuery(cq.id); + if (cq.data == "cancel_recipe_creation") { renderCustomRecipesList(state.pageNo, cq.from->id, cq.message->chat->id, bot, recipeApi); - stateManager.put(CustomRecipesList{ - .pageNo = state.pageNo}); // If it went from creation then as user will return from - // RecipeView to RecipesList on page from which they entered in this state; + // If it went from creation then as user will return from + // RecipeView to RecipesList on page from which they entered in this state; + stateManager.put(CustomRecipesList{.pageNo = state.pageNo}); } }; -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipes_list diff --git a/src/handlers/personal_account/recipes_list/create.hpp b/src/handlers/personal_account/recipes_list/create.hpp index e96edc7b..81721fde 100644 --- a/src/handlers/personal_account/recipes_list/create.hpp +++ b/src/handlers/personal_account/recipes_list/create.hpp @@ -1,13 +1,14 @@ #pragma once +#include "backend/api/recipes.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::personal_account::recipes { +namespace cookcookhnya::handlers::personal_account::recipes_list { void handleCreateCustomRecipeMsg( - CreateCustomRecipe&, MessageRef m, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi); + CreateCustomRecipe&, MessageRef m, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi); void handleCreateCustomRecipeCQ( - CreateCustomRecipe&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, RecipesApiRef recipeApi); + CreateCustomRecipe&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::RecipesApiRef recipeApi); -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipes_list diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index aa09d210..11b9a1ff 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/personal_account/recipe/view.hpp" @@ -10,14 +11,17 @@ #include "utils/parsing.hpp" #include +#include -namespace cookcookhnya::handlers::personal_account::recipes { +namespace cookcookhnya::handlers::personal_account::recipes_list { -void handleCustomRecipesListCQ( - CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { - using namespace render::personal_account; - using namespace render::personal_account::recipes; +using namespace render::personal_account; +using namespace render::personal_account::recipe; +using namespace render::personal_account::recipes_list; +using namespace std::literals; +void handleCustomRecipesListCQ( + CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; @@ -30,34 +34,35 @@ void handleCustomRecipesListCQ( bot.answerCallbackQuery(cq.id); return; } + if (data == "custom_recipe_create") { renderRecipeCreation(chatId, userId, bot); - stateManager.put(CreateCustomRecipe{.recipeId = {}, .pageNo = state.pageNo}); + stateManager.put(CreateCustomRecipe{.pageNo = state.pageNo}); bot.answerCallbackQuery(cq.id); return; } - if (data[0] == 'r') { - auto recipeId = utils::parseSafe(data.substr(1, data.size())); + if (data.starts_with("recipe_")) { + auto recipeId = utils::parseSafe(std::string_view{data}.substr("recipe_"sv.size())); if (recipeId) { auto ingredientsAndName = renderCustomRecipe(true, userId, chatId, recipeId.value(), bot, api); stateManager.put(RecipeCustomView{.recipeId = recipeId.value(), .pageNo = state.pageNo, - .ingredients = std::get<0>(ingredientsAndName), - .recipeName = std::get<1>(ingredientsAndName)}); + .ingredients = ingredientsAndName.first, + .recipeName = ingredientsAndName.second}); } bot.answerCallbackQuery(cq.id); return; } if (data != "dont_handle") { - auto pageNo = utils::parseSafe(data); - if (pageNo) { - state.pageNo = *pageNo; - } - renderCustomRecipesList(*pageNo, userId, chatId, bot, api); + if (data == "page_left") + state.pageNo--; + else if (data == "page_right") + state.pageNo++; + renderCustomRecipesList(state.pageNo, userId, chatId, bot, api); return; } } -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipes_list diff --git a/src/handlers/personal_account/recipes_list/view.hpp b/src/handlers/personal_account/recipes_list/view.hpp index c9194ccb..9f1f9cac 100644 --- a/src/handlers/personal_account/recipes_list/view.hpp +++ b/src/handlers/personal_account/recipes_list/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::personal_account::recipes { +namespace cookcookhnya::handlers::personal_account::recipes_list { void handleCustomRecipesListCQ( - CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); -} // namespace cookcookhnya::handlers::personal_account::recipes +} // namespace cookcookhnya::handlers::personal_account::recipes_list diff --git a/src/handlers/personal_account/request_history.hpp b/src/handlers/personal_account/request_history.hpp deleted file mode 100644 index 072d03a5..00000000 --- a/src/handlers/personal_account/request_history.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "handlers/common.hpp" - -namespace cookcookhnya::handlers::personal_account::publication_history { - -void handleAllPublicationHistoryCQ( - AllPublicationHistory& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); - -} // namespace cookcookhnya::handlers::personal_account::publication_history diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 3a5d3c27..a950afa8 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -1,27 +1,31 @@ #include "handlers/personal_account/view.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" #include "render/personal_account/ingredients_list/view.hpp" +#include "render/personal_account/publication_history.hpp" #include "render/personal_account/recipes_list/view.hpp" -#include "render/personal_account/request_history.hpp" + #include +#include #include namespace cookcookhnya::handlers::personal_account { -using namespace render::personal_account::recipes; using namespace render::main_menu; +using namespace render::personal_account::recipes_list; using namespace render::personal_account::ingredients; -using namespace render::personal_account::publication_history; +using namespace render::personal_account; + +const std::size_t numOfHistoryInstances = 10; void handlePersonalAccountMenuCQ( - PersonalAccountMenu& /**/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { - const std::string data = cq.data; + PersonalAccountMenu& /**/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; - std::size_t numOfHistoryInstances = 10; + const std::string data = cq.data; if (data == "recipes") { renderCustomRecipesList(0, userId, chatId, bot, api); @@ -40,7 +44,7 @@ void handlePersonalAccountMenuCQ( } if (data == "history") { renderRequestHistory(userId, 0, numOfHistoryInstances, chatId, bot, api); - stateManager.put(AllPublicationHistory{.pageNo = 0}); + stateManager.put(TotalPublicationHistory{.pageNo = 0}); return; } } diff --git a/src/handlers/personal_account/view.hpp b/src/handlers/personal_account/view.hpp index 3d31fdcd..ce94f2a0 100644 --- a/src/handlers/personal_account/view.hpp +++ b/src/handlers/personal_account/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::personal_account { void handlePersonalAccountMenuCQ( - PersonalAccountMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api); + PersonalAccountMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::personal_account diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp index 299f3f43..29be33ed 100644 --- a/src/handlers/recipe/add_storage.cpp +++ b/src/handlers/recipe/add_storage.cpp @@ -1,5 +1,6 @@ #include "add_storage.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" @@ -9,67 +10,65 @@ #include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" +#include #include +#include +#include namespace cookcookhnya::handlers::recipe { using namespace render::recipe; +using namespace api::models::storage; void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); - std::string data = cq.data; + const std::string& data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; + if (data == "back") { + renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); + return; + } + if (data[0] == '-') { - auto newStorageIdStr = data.substr(1, data.size()); - auto newStorageId = utils::parseSafe(newStorageIdStr); + auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; - state.addedStorages.push_back(newStorage); - utils::addStorage(state.availability, newStorage); - renderStoragesSuggestion(state.availability, - state.selectedStorages, - state.addedStorages, - state.recipeId, + const StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; + state.prevState.addedStorages.push_back(newStorage); + utils::addStorage(state.prevState.availability, newStorage); + renderStoragesSuggestion(state.prevState.availability, + state.prevState.prevState.selectedStorages, + state.prevState.addedStorages, + state.prevState.recipeId, userId, chatId, bot, api); } } + if (data[0] == '+') { - auto newStorageIdStr = data.substr(1, data.size()); - auto newStorageId = utils::parseSafe(newStorageIdStr); + auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); if (newStorageId) { auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - api::models::storage::StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; - state.addedStorages.erase( - std::ranges::find(state.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); - utils::deleteStorage(state.availability, newStorage); - renderStoragesSuggestion(state.availability, - state.selectedStorages, - state.addedStorages, - state.recipeId, + const StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; + state.prevState.addedStorages.erase(std::ranges::find( + state.prevState.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); + utils::deleteStorage(state.prevState.availability, newStorage); + renderStoragesSuggestion(state.prevState.availability, + state.prevState.prevState.selectedStorages, + state.prevState.addedStorages, + state.prevState.recipeId, userId, chatId, bot, api); } } - - if (data == "back") { - renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); - return; - } } } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/add_storage.hpp b/src/handlers/recipe/add_storage.hpp index 3267f272..ac477b12 100644 --- a/src/handlers/recipe/add_storage.hpp +++ b/src/handlers/recipe/add_storage.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::recipe { void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index bd94a513..12f3de20 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -1,13 +1,16 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" #include "render/recipe/add_storage.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" +#include "states.hpp" #include +#include #include namespace cookcookhnya::handlers::recipe { @@ -15,53 +18,56 @@ namespace cookcookhnya::handlers::recipe { using namespace render::recipes_suggestions; using namespace render::shopping_list; using namespace render::recipe; +using namespace api::models::ingredient; +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { - std::string data = cq.data; +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; + if (data == "start_cooking") { // TODO: add state of begginig of cooking return; } + if (data == "shopping_list") { - std::vector selectedIngredients; - std::vector allIngredients; - for (const auto& infoPair : state.availability) { - if (infoPair.second.available == utils::AvailabiltiyType::not_available) { - selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); + std::vector selectedIngredients; + std::vector allIngredients; + for (const auto& av : state.availability) { + if (av.available == AvailabilityType::NOT_AVAILABLE) { + selectedIngredients.push_back({.id = av.ingredient.id, .name = av.ingredient.name}); } - allIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); + allIngredients.push_back({.id = av.ingredient.id, .name = av.ingredient.name}); } renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); - stateManager.put(ShoppingListCreation{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .selectedIngredients = selectedIngredients, - .allIngredients = allIngredients, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + stateManager.put(ShoppingListCreation{ + .prevState = std::move(state), + .selectedIngredients = selectedIngredients, + .allIngredients = allIngredients, + }); bot.answerCallbackQuery(cq.id); return; } + if (data == "back_from_recipe_view") { - renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{ - .pageNo = state.pageNo, .selectedStorages = state.selectedStorages, .fromStorage = state.fromStorage}); + renderRecipesSuggestion(state.prevState.selectedStorages, state.prevState.pageNo, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; } if (data == "add_storages") { - renderStoragesSuggestion( - state.availability, state.selectedStorages, state.addedStorages, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeStorageAddition{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + renderStoragesSuggestion(state.availability, + state.prevState.selectedStorages, + state.addedStorages, + state.recipeId, + userId, + chatId, + bot, + api); + stateManager.put(RecipeStorageAddition{.prevState = std::move(state)}); return; } } diff --git a/src/handlers/recipe/view.hpp b/src/handlers/recipe/view.hpp index a425acfc..6cefa542 100644 --- a/src/handlers/recipe/view.hpp +++ b/src/handlers/recipe/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::recipe { -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index ed72ad40..d8e96e97 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" @@ -23,7 +24,7 @@ using namespace render::recipe; using namespace render::main_menu; void handleSuggestedRecipesListCQ( - SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; @@ -54,22 +55,21 @@ void handleSuggestedRecipesListCQ( return; auto inStorage = utils::inStoragesAvailability(state.selectedStorages, *recipeId, userId, api); renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = {}, - .availability = inStorage, - .recipeId = *recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); - + stateManager.put(RecipeView{ + .prevState = std::move(state), + .addedStorages = {}, + .availability = inStorage, + .recipeId = *recipeId, + }); return; } if (data != "dont_handle") { - auto pageNo = utils::parseSafe(data); - if (pageNo) { - state.pageNo = *pageNo; - } - renderRecipesSuggestion(state.selectedStorages, *pageNo, userId, chatId, bot, api); + if (data == "page_left") + state.pageNo--; + else if (data == "page_right") + state.pageNo++; + renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); return; } } diff --git a/src/handlers/recipes_suggestions/view.hpp b/src/handlers/recipes_suggestions/view.hpp index b291db03..be415b79 100644 --- a/src/handlers/recipes_suggestions/view.hpp +++ b/src/handlers/recipes_suggestions/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::recipes_suggestions { void handleSuggestedRecipesListCQ( - SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::recipes_suggestions diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 692c10d7..6bdfc354 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" @@ -9,6 +10,8 @@ #include #include +#include +#include namespace cookcookhnya::handlers::shopping_list { @@ -16,22 +19,18 @@ using namespace render::shopping_list; using namespace render::recipe; void handleShoppingListCreationCQ( - ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { - std::string data = cq.data; + ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + const std::string& data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; if (data == "back") { - renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; } + if (data == "confirm") { auto shoppingApi = api.getShoppingListApi(); std::vector putIds; @@ -40,17 +39,13 @@ void handleShoppingListCreationCQ( putIds.push_back(ingredient.id); } shoppingApi.put(userId, putIds); - renderRecipeView(state.availability, state.recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{.selectedStorages = state.selectedStorages, - .addedStorages = state.addedStorages, - .availability = state.availability, - .recipeId = state.recipeId, - .fromStorage = state.fromStorage, - .pageNo = state.pageNo}); + renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; } - if (data[0] == '-') { + + if (data[0] == '+') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); if (newIngredientId) { @@ -58,8 +53,10 @@ void handleShoppingListCreationCQ( state.selectedIngredients.push_back(ingredient); } renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); + return; } - if (data[0] == '+') { + + if (data[0] == '-') { auto newIngredientIdStr = data.substr(1, data.size()); auto newIngredientId = utils::parseSafe(newIngredientIdStr); if (newIngredientId) { @@ -67,6 +64,7 @@ void handleShoppingListCreationCQ( state.selectedIngredients, *newIngredientId, &api::models::ingredient::Ingredient::id)); } renderShoppingListCreation(state.selectedIngredients, state.allIngredients, userId, chatId, bot); + return; } } } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/create.hpp b/src/handlers/shopping_list/create.hpp index 2feaac37..bf8d6b62 100644 --- a/src/handlers/shopping_list/create.hpp +++ b/src/handlers/shopping_list/create.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::shopping_list { void handleShoppingListCreationCQ( - ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/search.cpp b/src/handlers/shopping_list/search.cpp new file mode 100644 index 00000000..fcb6b55a --- /dev/null +++ b/src/handlers/shopping_list/search.cpp @@ -0,0 +1,89 @@ +#include "search.hpp" + +#include "backend/api/api.hpp" +#include "backend/api/publicity_filter.hpp" +#include "backend/id_types.hpp" +#include "backend/models/shopping_list.hpp" +#include "handlers/common.hpp" +#include "render/shopping_list/search.hpp" +#include "render/shopping_list/view.hpp" +#include "utils/parsing.hpp" + +#include +#include +#include + +namespace cookcookhnya::handlers::shopping_list { + +using namespace render::shopping_list; +using namespace api; +using namespace api::models::shopping_list; + +const std::size_t searchThreshold = 70; + +void handleShoppingListIngredientSearchCQ( + ShoppingListIngredientSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + + if (cq.data == "back") { + renderShoppingList(state.prevState, userId, chatId, bot); + stateManager.put(auto{std::move(state.prevState)}); + return; + } + + if (cq.data == "page_left" || cq.data == "page_right") { + if (cq.data == "page_left") { + if (state.pagination.pageNo == 0) + return; + state.pagination.pageNo--; + } else { + if (state.pagination.totalItems <= state.pagination.pageNo * searchPageSize) + return; + state.pagination.pageNo++; + } + auto result = api.getIngredientsApi().search(userId, + PublicityFilterType::All, + state.query, + searchThreshold, + searchPageSize, + searchPageSize * state.pagination.pageNo); + state.page = result.page; + renderShoppingListIngredientSearch(state, searchPageSize, userId, chatId, bot); + return; + } + + if (cq.data.starts_with("ingredient_")) { + auto mIngredient = utils::parseSafe(std::string_view{cq.data}.substr(sizeof("ingredient_") - 1)); + if (!mIngredient) + return; + api.getShoppingListApi().put(userId, {*mIngredient}); + auto ingredientInfo = api.getIngredientsApi().get(userId, *mIngredient); + state.prevState.items.put({ShoppingListItem{.ingredientId = ingredientInfo.id, .name = ingredientInfo.name}}); + return; + } +} + +void handleShoppingListIngredientSearchIQ(ShoppingListIngredientSearch& state, + InlineQueryRef iq, + BotRef bot, + api::IngredientsApiRef api) { + auto userId = iq.from->id; + if (iq.query.empty()) { + state.query = ""; + state.pagination = {}; + state.page = {}; + renderShoppingListIngredientSearch(state, searchPageSize, userId, userId, bot); + return; + } + + auto result = api.search(userId, PublicityFilterType::All, iq.query, searchThreshold, searchPageSize, 0); + state.query = iq.query; + state.pagination.pageNo = 0; + state.pagination.totalItems = result.found; + state.page = result.page; + renderShoppingListIngredientSearch(state, searchPageSize, userId, userId, bot); +} + +} // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/search.hpp b/src/handlers/shopping_list/search.hpp new file mode 100644 index 00000000..20c5f72e --- /dev/null +++ b/src/handlers/shopping_list/search.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "handlers/common.hpp" + +#include + +namespace cookcookhnya::handlers::shopping_list { + +const std::size_t searchPageSize = 10; + +void handleShoppingListIngredientSearchCQ( + ShoppingListIngredientSearch&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); + +void handleShoppingListIngredientSearchIQ(ShoppingListIngredientSearch& state, + InlineQueryRef iq, + BotRef bot, + api::IngredientsApiRef api); + +} // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/storage_selection_to_buy.cpp b/src/handlers/shopping_list/storage_selection_to_buy.cpp index 37acc821..76394b33 100644 --- a/src/handlers/shopping_list/storage_selection_to_buy.cpp +++ b/src/handlers/shopping_list/storage_selection_to_buy.cpp @@ -1,5 +1,6 @@ #include "storage_selection_to_buy.hpp" +#include "backend/api/shopping_lists.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/shopping_list/view.hpp" @@ -17,7 +18,7 @@ void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy CallbackQueryRef cq, BotRef bot, SMRef stateManager, - ShoppingListApiRef api) { + api::ShoppingListApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/shopping_list/storage_selection_to_buy.hpp b/src/handlers/shopping_list/storage_selection_to_buy.hpp index 7801f9c1..3bb7ea03 100644 --- a/src/handlers/shopping_list/storage_selection_to_buy.hpp +++ b/src/handlers/shopping_list/storage_selection_to_buy.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/shopping_lists.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::shopping_list { @@ -8,6 +9,6 @@ void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy CallbackQueryRef cq, BotRef bot, SMRef stateManager, - ShoppingListApiRef api); + api::ShoppingListApiRef api); } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index 36ff3d20..c7120375 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -1,13 +1,17 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" +#include "handlers/shopping_list/search.hpp" #include "render/main_menu/view.hpp" +#include "render/shopping_list/search.hpp" #include "render/shopping_list/storage_selection_to_buy.hpp" #include "render/shopping_list/view.hpp" #include "states.hpp" #include "utils/parsing.hpp" +#include #include #include #include @@ -16,12 +20,11 @@ namespace cookcookhnya::handlers::shopping_list { using namespace render::main_menu; using namespace render::shopping_list; - using namespace std::views; using namespace std::ranges; void handleShoppingListViewCQ( - ShoppingListView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + ShoppingListView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -34,8 +37,9 @@ void handleShoppingListViewCQ( } if (cq.data == "search") { - renderMainMenu(true, std::nullopt, userId, cq.message->chat->id, bot, api); - stateManager.put(MainMenu{}); + ShoppingListIngredientSearch newState{.prevState = std::move(state), .query = "", .pagination = {}, .page = {}}; + renderShoppingListIngredientSearch(newState, searchPageSize, userId, chatId, bot); + stateManager.put(std::move(newState)); return; } diff --git a/src/handlers/shopping_list/view.hpp b/src/handlers/shopping_list/view.hpp index cf9d5d60..095f139e 100644 --- a/src/handlers/shopping_list/view.hpp +++ b/src/handlers/shopping_list/view.hpp @@ -1,9 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::shopping_list { -void handleShoppingListViewCQ(ShoppingListView&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleShoppingListViewCQ( + ShoppingListView&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/handlers/storage/delete.cpp b/src/handlers/storage/delete.cpp index e82a149a..3440bc0e 100644 --- a/src/handlers/storage/delete.cpp +++ b/src/handlers/storage/delete.cpp @@ -1,18 +1,19 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/storage/view.hpp" #include "render/storages_list/view.hpp" #include "states.hpp" -namespace cookcookhnya::handlers::storages_list { +namespace cookcookhnya::handlers::storage { using namespace render::storages_list; using namespace render::storage; void handleStorageDeletionCQ( - StorageDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); if (cq.data == "confirm") { storageApi.delete_(cq.from->id, state.storageId); @@ -25,4 +26,4 @@ void handleStorageDeletionCQ( } }; -} // namespace cookcookhnya::handlers::storages_list +} // namespace cookcookhnya::handlers::storage diff --git a/src/handlers/storage/delete.hpp b/src/handlers/storage/delete.hpp index 28e25044..a3e26683 100644 --- a/src/handlers/storage/delete.hpp +++ b/src/handlers/storage/delete.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::storages_list { +namespace cookcookhnya::handlers::storage { void handleStorageDeletionCQ( - StorageDeletion&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageDeletion&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); -} // namespace cookcookhnya::handlers::storages_list +} // namespace cookcookhnya::handlers::storage diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 0b88215d..6b50f16e 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" @@ -22,16 +23,20 @@ namespace cookcookhnya::handlers::storage::ingredients { using namespace render::storage; using namespace render::storage::ingredients; -using namespace api::models::ingredient; using namespace render::suggest_custom_ingredient; using namespace render::personal_account::ingredients; -// Global vars +using namespace api::models::ingredient; + +namespace { + const size_t numOfIngredientsOnPage = 5; const size_t threshhold = 70; -namespace { -void updateSearch( - StorageIngredientsList& state, bool isQueryChanged, BotRef bot, tg_types::UserId userId, IngredientsApiRef api) { +void updateSearch(StorageIngredientsList& state, + bool isQueryChanged, + BotRef bot, + tg_types::UserId userId, + api::IngredientsApiRef api) { state.pageNo = isQueryChanged ? 0 : state.pageNo; auto response = api.searchForStorage(userId, state.storageId, @@ -59,7 +64,7 @@ void updateSearch( } // namespace void handleStorageIngredientsListCQ( - StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); const auto userId = cq.from->id; const auto chatId = cq.message->chat->id; @@ -69,13 +74,13 @@ void handleStorageIngredientsListCQ( stateManager.put(StorageView{state.storageId}); return; } - if (cq.data == "prev") { + if (cq.data == "page_left") { state.pageNo -= 1; updateSearch(state, false, bot, userId, api); return; } - if (cq.data == "next") { + if (cq.data == "page_right") { state.pageNo += 1; updateSearch(state, false, bot, userId, api); return; @@ -88,7 +93,6 @@ void handleStorageIngredientsListCQ( } if (cq.data != "dont_handle") { - auto mIngredient = utils::parseSafe(cq.data); if (!mIngredient) return; @@ -111,7 +115,7 @@ void handleStorageIngredientsListCQ( void handleStorageIngredientsListIQ(StorageIngredientsList& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api) { + api::IngredientsApiRef api) { const auto userId = iq.from->id; state.inlineQuery = iq.query; if (iq.query.empty()) { @@ -123,7 +127,7 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, updateSearch(state, true, bot, userId, api); } // Cache is not disabled on Windows and Linux desktops. Works on Android and Web - bot.answerInlineQuery(iq.id, {}, 0); + // bot.answerInlineQuery(iq.id, {}, 0); } } // namespace cookcookhnya::handlers::storage::ingredients diff --git a/src/handlers/storage/ingredients/view.hpp b/src/handlers/storage/ingredients/view.hpp index ca6ab044..bdcaa212 100644 --- a/src/handlers/storage/ingredients/view.hpp +++ b/src/handlers/storage/ingredients/view.hpp @@ -1,15 +1,16 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::ingredients { void handleStorageIngredientsListCQ( - StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + StorageIngredientsList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); void handleStorageIngredientsListIQ(StorageIngredientsList& state, InlineQueryRef iq, BotRef bot, - IngredientsApiRef api); + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::storage::ingredients diff --git a/src/handlers/storage/members/add.cpp b/src/handlers/storage/members/add.cpp index 317c4725..43a80c1d 100644 --- a/src/handlers/storage/members/add.cpp +++ b/src/handlers/storage/members/add.cpp @@ -1,5 +1,6 @@ #include "add.hpp" +#include "backend/api/storages.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" #include "render/storage/members/add.hpp" @@ -17,7 +18,7 @@ namespace cookcookhnya::handlers::storage::members { using namespace render::storage::members; void handleStorageMemberAdditionMsg( - StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { auto chatId = m.chat->id; auto userId = m.from->id; @@ -50,7 +51,7 @@ void handleStorageMemberAdditionMsg( }; void handleStorageMemberAdditionCQ( - StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/storage/members/add.hpp b/src/handlers/storage/members/add.hpp index a767890f..7aa24d70 100644 --- a/src/handlers/storage/members/add.hpp +++ b/src/handlers/storage/members/add.hpp @@ -1,13 +1,14 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::members { void handleStorageMemberAdditionMsg( - StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberAddition& state, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); void handleStorageMemberAdditionCQ( - StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storage::members diff --git a/src/handlers/storage/members/delete.cpp b/src/handlers/storage/members/delete.cpp index 5ffabb33..f5595225 100644 --- a/src/handlers/storage/members/delete.cpp +++ b/src/handlers/storage/members/delete.cpp @@ -1,5 +1,6 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "handlers/common.hpp" #include "render/storage/members/view.hpp" #include "tg_types.hpp" @@ -10,7 +11,7 @@ namespace cookcookhnya::handlers::storage::members { using namespace render::storage::members; void handleStorageMemberDeletionCQ( - StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; diff --git a/src/handlers/storage/members/delete.hpp b/src/handlers/storage/members/delete.hpp index 562321dc..de4464fd 100644 --- a/src/handlers/storage/members/delete.hpp +++ b/src/handlers/storage/members/delete.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::members { void handleStorageMemberDeletionCQ( - StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storage::members diff --git a/src/handlers/storage/members/view.cpp b/src/handlers/storage/members/view.cpp index 411100cb..ffa8156e 100644 --- a/src/handlers/storage/members/view.cpp +++ b/src/handlers/storage/members/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "handlers/common.hpp" #include "render/storage/members/add.hpp" #include "render/storage/members/delete.hpp" @@ -11,7 +12,7 @@ using namespace render::storage::members; using namespace render::storage; void handleStorageMemberViewCQ( - StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/storage/members/view.hpp b/src/handlers/storage/members/view.hpp index 4fbe3ec4..2ad670ef 100644 --- a/src/handlers/storage/members/view.hpp +++ b/src/handlers/storage/members/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage::members { void handleStorageMemberViewCQ( - StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageMemberView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storage::members diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index 4dc2b052..f7232ec7 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/recipes_suggestions/view.hpp" @@ -12,6 +13,7 @@ #include #include #include +#include namespace cookcookhnya::handlers::storage { @@ -20,11 +22,13 @@ using namespace render::storage::members; using namespace render::storages_list; using namespace render::recipes_suggestions; using namespace render::delete_storage; +using namespace api::models::storage; using namespace std::views; const std::size_t numOfIngredientsOnPage = 5; -void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { +void handleStorageViewCQ( + StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; @@ -34,20 +38,31 @@ void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SM auto newState = StorageIngredientsList{state.storageId, ingredients | as_rvalue, ""}; renderIngredientsListSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); stateManager.put(std::move(newState)); - } else if (cq.data == "members") { + return; + } + + if (cq.data == "members") { renderMemberList(true, state.storageId, userId, chatId, bot, api); stateManager.put(StorageMemberView{state.storageId}); - } else if (cq.data == "back") { + return; + } + + if (cq.data == "back") { renderStorageList(true, userId, chatId, bot, api); stateManager.put(StorageList{}); - } else if (cq.data == "wanna_eat") { + return; + } + + if (cq.data == "wanna_eat") { auto storageDetails = api.getStoragesApi().get(userId, state.storageId); - api::models::storage::StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; + const StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; std::vector storages = {storage}; renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{.pageNo = 0, .selectedStorages = storages, .fromStorage = true}); + stateManager.put(SuggestedRecipesList{.selectedStorages = storages, .pageNo = 0, .fromStorage = true}); return; - } else if (cq.data == "delete") { + } + + if (cq.data == "delete") { renderStorageDeletion(state.storageId, chatId, bot, cq.from->id, api); stateManager.put(StorageDeletion{state.storageId}); return; diff --git a/src/handlers/storage/view.hpp b/src/handlers/storage/view.hpp index 8d537a92..d1143a61 100644 --- a/src/handlers/storage/view.hpp +++ b/src/handlers/storage/view.hpp @@ -1,9 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storage { -void handleStorageViewCQ(StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleStorageViewCQ( + StorageView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::storage diff --git a/src/handlers/storages_list/create.cpp b/src/handlers/storages_list/create.cpp index 42188ec2..d2798bc7 100644 --- a/src/handlers/storages_list/create.cpp +++ b/src/handlers/storages_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/storages.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" @@ -13,7 +14,7 @@ namespace cookcookhnya::handlers::storages_list { using namespace render::storages_list; void handleStorageCreationEnterNameMsg( - StorageCreationEnterName& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi) { + StorageCreationEnterName& /*unused*/, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { storageApi.create(m.from->id, api::models::storage::StorageCreateBody{m.text}); // Create storage body with new name auto text = utils::utf8str(u8"🏷 Введите новое имя хранилища"); auto messageId = message::getMessageId(m.from->id); @@ -28,7 +29,7 @@ void handleStorageCreationEnterNameCQ(StorageCreationEnterName& /*unused*/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, - StorageApiRef storageApi) { + api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); if (cq.data == "back") { renderStorageList(true, cq.from->id, cq.message->chat->id, bot, storageApi); diff --git a/src/handlers/storages_list/create.hpp b/src/handlers/storages_list/create.hpp index 32b834a3..29b45b63 100644 --- a/src/handlers/storages_list/create.hpp +++ b/src/handlers/storages_list/create.hpp @@ -1,13 +1,14 @@ #pragma once +#include "backend/api/storages.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storages_list { void handleStorageCreationEnterNameMsg( - StorageCreationEnterName&, MessageRef m, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageCreationEnterName&, MessageRef m, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); void handleStorageCreationEnterNameCQ( - StorageCreationEnterName&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, StorageApiRef storageApi); + StorageCreationEnterName&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi); } // namespace cookcookhnya::handlers::storages_list diff --git a/src/handlers/storages_list/view.cpp b/src/handlers/storages_list/view.cpp index 83e30aa6..1abfc1fb 100644 --- a/src/handlers/storages_list/view.cpp +++ b/src/handlers/storages_list/view.cpp @@ -1,6 +1,6 @@ #include "view.hpp" -#include "backend/api/storages.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" @@ -13,25 +13,28 @@ namespace cookcookhnya::handlers::storages_list { using namespace render::main_menu; -using namespace render::create_storage; +using namespace render::storages_list; using namespace render::storage; void handleStorageListCQ( - StorageList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + StorageList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; auto storages = api.getStoragesApi().getStoragesList(userId); + if (cq.data == "create") { renderStorageCreation(chatId, userId, bot); stateManager.put(StorageCreationEnterName{}); return; } + if (cq.data == "back") { renderMainMenu(true, std::nullopt, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } + auto storageId = utils::parseSafe(cq.data); if (storageId) { renderStorageView(*storageId, cq.from->id, chatId, bot, api); diff --git a/src/handlers/storages_list/view.hpp b/src/handlers/storages_list/view.hpp index 73594b68..37fcfb38 100644 --- a/src/handlers/storages_list/view.hpp +++ b/src/handlers/storages_list/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storages_list { -void handleStorageListCQ(StorageList&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); +void handleStorageListCQ(StorageList&, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::storages_list diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 85744e53..271cbba6 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" @@ -9,6 +10,7 @@ #include "utils/parsing.hpp" #include +#include #include #include @@ -20,7 +22,7 @@ using namespace render::select_storages; using namespace render::main_menu; void handleStoragesSelectionCQ( - StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api) { + StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; @@ -28,7 +30,7 @@ void handleStoragesSelectionCQ( if (cq.data == "confirm") { renderRecipesSuggestion(state.selectedStorages, 0, userId, chatId, bot, api); stateManager.put(SuggestedRecipesList{ - .pageNo = 0, .selectedStorages = std::move(state.selectedStorages), .fromStorage = false}); + .selectedStorages = std::move(state.selectedStorages), .pageNo = 0, .fromStorage = false}); return; } diff --git a/src/handlers/storages_selection/view.hpp b/src/handlers/storages_selection/view.hpp index f8638f12..654cd486 100644 --- a/src/handlers/storages_selection/view.hpp +++ b/src/handlers/storages_selection/view.hpp @@ -1,10 +1,11 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::storages_selection { void handleStoragesSelectionCQ( - StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, ApiClientRef api); + StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::storages_selection diff --git a/src/main.cpp b/src/main.cpp index 275bf35b..f9e691c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -70,8 +70,10 @@ int main(int argc, char* argv[]) { customRecipeIngredientsSearchCQHandler, customRecipeIngredientsSearchIQHandler, shoppingListStorageSelectionToBuyCQHandler, - customRecipePublicationHistoryHandler, - allPublicationHistoryHandler> + customRecipePublicationHistoryCQHandler, + totalPublicationHistoryCQHandler, + shoppingListIngredientSearchCQHandler, + shoppingListIngredientSearchIQHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 462e17c5..c2788ab8 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -10,10 +10,10 @@ target_sources(main PRIVATE src/render/personal_account/recipe/search_ingredients.cpp src/render/personal_account/recipe/view.cpp - src/render/personal_account/recipe/publication_history.cpp + src/render/personal_account/recipe/moderation_history.cpp src/render/personal_account/view.cpp - src/render/personal_account/request_history.cpp + src/render/personal_account/publication_history.cpp src/render/recipe/add_storage.cpp src/render/recipe/view.cpp @@ -21,6 +21,7 @@ target_sources(main PRIVATE src/render/recipes_suggestions/view.cpp src/render/shopping_list/create.cpp + src/render/shopping_list/search.cpp src/render/shopping_list/storage_selection_to_buy.cpp src/render/shopping_list/view.cpp diff --git a/src/render/common.hpp b/src/render/common.hpp index d6481d56..4866cdba 100644 --- a/src/render/common.hpp +++ b/src/render/common.hpp @@ -1,12 +1,5 @@ #pragma once -#include "backend/api/api.hpp" -#include "backend/api/ingredients.hpp" -#include "backend/api/recipes.hpp" -#include "backend/api/request_history.hpp" -#include "backend/api/shopping_lists.hpp" -#include "backend/api/storages.hpp" -#include "backend/api/users.hpp" #include "patched_bot.hpp" #include "tg_types.hpp" #include "utils/utils.hpp" @@ -27,15 +20,6 @@ class InlineKeyboardMarkup; namespace cookcookhnya::render { -// API -using ApiClient = const api::ApiClient&; -using StorageApiRef = const api::StoragesApi&; -using IngredientsApiRef = const api::IngredientsApi&; -using UserApiRef = const api::UsersApi&; -using RecipesApiRef = const api::RecipesApi&; -using ShoppingListApiRef = const api::ShoppingListApi&; -using RequestHistoryApiRef = const api::RequestHistoryApi&; - using UserId = tg_types::UserId; using ChatId = tg_types::ChatId; using MessageId = tg_types::MessageId; diff --git a/src/render/main_menu/view.cpp b/src/render/main_menu/view.cpp index ea7e60f4..cc9e38ea 100644 --- a/src/render/main_menu/view.cpp +++ b/src/render/main_menu/view.cpp @@ -1,11 +1,12 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/utils.hpp" -#include #include +#include #include namespace cookcookhnya::render::main_menu { @@ -17,37 +18,30 @@ void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi) { + api::StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); - const std::size_t buttonRows = storages.empty() ? 3 : 4; - InlineKeyboard keyboard(buttonRows); - - if (!storages.empty()) { - keyboard[0].push_back(makeCallbackButton(u8"🍱 Хранилища", "storage_list")); - keyboard[1].push_back(makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat")); - keyboard[2].push_back(makeCallbackButton(u8"🗒 Список покупок", "shopping_list")); - keyboard[3].push_back(makeCallbackButton(u8"👤 Личный кабинет", "personal_account")); - } else { - keyboard[0].push_back(makeCallbackButton(u8"🍱 Хранилища", "storage_list")); - keyboard[1].push_back(makeCallbackButton(u8"🗒 Список покупок", "shopping_list")); - keyboard[2].push_back(makeCallbackButton(u8"👤 Личный кабинет", "personal_account")); - } - auto text = utils::utf8str( - u8"🍳 Добро пожаловать в CookCookNya — ваш личный бот для быстро подбора рецептов и многого другого!"); + u8"🍳 Добро пожаловать в CookCookNya — ваш личный бот для быстрого подбора рецептов и многого другого!"); if (inviteStorage) { - if (*inviteStorage) { + if (*inviteStorage) text += utils::utf8str(u8"\n\nВы были успешно добавлены в хранилище 🍱") + **inviteStorage; - } else { + else text += utils::utf8str(u8"\n\nК сожалению, данное приглашение уже было использовано 🥲"); - } } + + InlineKeyboardBuilder keyboard{4}; + keyboard << makeCallbackButton(u8"🍱 Хранилища", "storage_list") << NewRow{}; + if (!storages.empty()) + keyboard << makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat") << NewRow{}; + keyboard << makeCallbackButton(u8"🗒 Список покупок", "shopping_list") << NewRow{} + << makeCallbackButton(u8"👤 Личный кабинет", "personal_account"); + if (toBeEdited) { if (auto messageId = message::getMessageId(userId)) - bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } else { - auto message = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); + auto message = bot.sendMessage(chatId, text, std::move(keyboard)); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/main_menu/view.hpp b/src/render/main_menu/view.hpp index 5d5d885a..f2e4b772 100644 --- a/src/render/main_menu/view.hpp +++ b/src/render/main_menu/view.hpp @@ -1,8 +1,10 @@ #pragma once +#include "backend/api/storages.hpp" #include "render/common.hpp" #include +#include namespace cookcookhnya::render::main_menu { @@ -11,6 +13,6 @@ void renderMainMenu(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi); + api::StorageApiRef storageApi); } // namespace cookcookhnya::render::main_menu diff --git a/src/render/pagination.hpp b/src/render/pagination.hpp new file mode 100644 index 00000000..92725d5d --- /dev/null +++ b/src/render/pagination.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "render/common.hpp" +#include "utils/utils.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// forward declaration +namespace TgBot { +class InlineKeyboardButton; +} // namespace TgBot + +namespace cookcookhnya::render { + +template > ItemButtons> +struct PaginationMarkup { + ItemButtons itemButtons; + std::shared_ptr leftButton; + std::shared_ptr pageButton; + std::shared_ptr rightButton; + + friend InlineKeyboardBuilder& operator<<(InlineKeyboardBuilder& keyboard, PaginationMarkup&& markup) { + for (auto&& b : std::move(markup).itemButtons) + keyboard << std::move(b) << NewRow{}; + if (markup.leftButton) + keyboard << std::move(markup).leftButton << std::move(markup).pageButton << std::move(markup).rightButton + << NewRow{}; + return keyboard; + } +}; + +/** + * Single column list with an extra row of page switching if more than one page. + */ +template + requires std::is_invocable_r_v, + ItemButtonMaker, + std::ranges::range_reference_t> +auto constructPagination( + std::size_t pageNo, std::size_t pageSize, std::size_t totalItems, R&& page, ItemButtonMaker&& makeItemButton) { + using namespace std::views; + const std::size_t pagesCount = (totalItems + pageSize - 1) / pageSize; // ceiling + const bool lastPage = pageNo + 1 >= pagesCount; + + auto itemButtons = std::forward(page) | transform(std::forward(makeItemButton)); + std::shared_ptr leftButton = nullptr; + std::shared_ptr pageButton = nullptr; + std::shared_ptr rightButton = nullptr; + + if (pagesCount > 1) { + enum PageArrows : std::uint8_t { + NOTHING = 0b00U, + LEFT = 0b01U, + RIGHT = 0b10U, + BOTH = 0b11U, + }; + + const char8_t* const emptyText = u8"ㅤ"; // not empty! invisible symbol + PageArrows b = NOTHING; + if (pageNo != 0) + b = static_cast(b | LEFT); + if (!lastPage) + b = static_cast(b | RIGHT); + + leftButton = + (b & LEFT) != 0 ? makeCallbackButton(u8"◀️", "page_left") : makeCallbackButton(emptyText, "do_not_handle"); + rightButton = + (b & RIGHT) != 0 ? makeCallbackButton(u8"▶️", "page_right") : makeCallbackButton(emptyText, "do_not_handle"); + pageButton = + makeCallbackButton(std::format("{} {} {}", pageNo + 1, utils::utf8str(u8"из"), pagesCount), "dont_handle"); + } + + return PaginationMarkup{ + std::move(itemButtons), std::move(leftButton), std::move(pageButton), std::move(rightButton)}; +} + +} // namespace cookcookhnya::render diff --git a/src/render/personal_account/ingredients_list/create.cpp b/src/render/personal_account/ingredients_list/create.cpp index 1b391ccf..e3af6699 100644 --- a/src/render/personal_account/ingredients_list/create.cpp +++ b/src/render/personal_account/ingredients_list/create.cpp @@ -1,5 +1,6 @@ #include "create.hpp" +#include "backend/api/ingredients.hpp" #include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" @@ -24,7 +25,7 @@ void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot) { } void renderCustomIngredientConfirmation( - bool toBeEdited, std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + bool toBeEdited, std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api) { InlineKeyboard keyboard(2); keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); diff --git a/src/render/personal_account/ingredients_list/create.hpp b/src/render/personal_account/ingredients_list/create.hpp index ce3d2c89..1a709038 100644 --- a/src/render/personal_account/ingredients_list/create.hpp +++ b/src/render/personal_account/ingredients_list/create.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "render/common.hpp" #include @@ -9,6 +10,6 @@ namespace cookcookhnya::render::personal_account::ingredients { void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot); void renderCustomIngredientConfirmation( - bool toBeEdited, std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); + bool toBeEdited, std::string ingredientName, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/ingredients_list/publish.cpp b/src/render/personal_account/ingredients_list/publish.cpp index 3b65b9f7..9d2b5ee4 100644 --- a/src/render/personal_account/ingredients_list/publish.cpp +++ b/src/render/personal_account/ingredients_list/publish.cpp @@ -1,9 +1,9 @@ #include "publish.hpp" +#include "backend/api/ingredients.hpp" #include "backend/api/publicity_filter.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -14,7 +14,7 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; -void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { +void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api) { auto ingredientsResp = api.search(userId, PublicityFilterType::Custom); // TODO: make pagination for ingredients diff --git a/src/render/personal_account/ingredients_list/publish.hpp b/src/render/personal_account/ingredients_list/publish.hpp index e8f28a86..66e8b9c9 100644 --- a/src/render/personal_account/ingredients_list/publish.hpp +++ b/src/render/personal_account/ingredients_list/publish.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "render/common.hpp" namespace cookcookhnya::render::personal_account::ingredients { -void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); +void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index 1765863e..1965d9c7 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/ingredients.hpp" #include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" @@ -7,6 +8,9 @@ #include "utils/to_string.hpp" #include "utils/utils.hpp" +#include + +#include #include #include #include @@ -16,12 +20,13 @@ namespace cookcookhnya::render::personal_account::ingredients { +using namespace api::models::ingredient; using namespace tg_types; namespace { -std::pair> constructNavigationMessage( - std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::IngredientList& ingredientsList) { +std::pair> +constructNavigationMessage(std::size_t pageNo, std::size_t numOfRecipesOnPage, IngredientList& ingredientsList) { const size_t amountOfRecipes = ingredientsList.found; const std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); @@ -52,9 +57,8 @@ std::pair> constructN return std::make_pair(text, buttons); } -std::pair constructMessage(size_t pageNo, - size_t numOfIngredientsOnPage, - api::models::ingredient::IngredientList& ingredientsList) { +std::pair +constructMessage(size_t pageNo, size_t numOfIngredientsOnPage, IngredientList& ingredientsList) { std::size_t numOfRows = 0; if (ingredientsList.found == 0) numOfRows = 2; @@ -96,7 +100,7 @@ std::pair constructMessage(size_t pageNo, } // namespace void renderCustomIngredientsList( - bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api) { const std::size_t numOfIngredientsOnPage = 10; auto ingredientsList = diff --git a/src/render/personal_account/ingredients_list/view.hpp b/src/render/personal_account/ingredients_list/view.hpp index 6cb4a0a6..d535d955 100644 --- a/src/render/personal_account/ingredients_list/view.hpp +++ b/src/render/personal_account/ingredients_list/view.hpp @@ -1,10 +1,13 @@ #pragma once +#include "backend/api/ingredients.hpp" #include "render/common.hpp" +#include + namespace cookcookhnya::render::personal_account::ingredients { void renderCustomIngredientsList( - bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); + bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/publication_history.cpp b/src/render/personal_account/publication_history.cpp new file mode 100644 index 00000000..734ae693 --- /dev/null +++ b/src/render/personal_account/publication_history.cpp @@ -0,0 +1,41 @@ +#include "publication_history.hpp" + +#include "backend/models/moderation.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" + +#include +#include + +namespace cookcookhnya::render::personal_account { + +void renderRequestHistory(UserId userId, + size_t pageNo, + size_t numOfInstances, + ChatId chatId, + BotRef bot, + api::ModerationApiRef moderationApi) { + InlineKeyboardBuilder keyboard{1}; + + const std::size_t maxShownItems = 20; + auto history = moderationApi.getAllPublicationRequests(userId, maxShownItems, pageNo * numOfInstances); + + std::string toPrint = utils::utf8str(u8"ℹ️История запросов на публикацию ваших рецептов и ингредиентов\n\n\n"); + for (auto& req : history) { + toPrint += std::format("*{}* статус: {} ", req.name, utils::to_string(req.status)); + if (req.reason.has_value()) + toPrint += std::format("по причине: {} ", req.reason.value()); + toPrint += std::format("запрос создан: {} ", utils::to_string(req.created)); + if (req.updated.has_value()) { + toPrint += std::format("последенее обновление: {}", utils::to_string(req.updated.value())); + } + toPrint += "\n\n"; + } + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + auto messageId = message::getMessageId(userId); + if (messageId) { + bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); + } +} +} // namespace cookcookhnya::render::personal_account diff --git a/src/render/personal_account/publication_history.hpp b/src/render/personal_account/publication_history.hpp new file mode 100644 index 00000000..8e5ab749 --- /dev/null +++ b/src/render/personal_account/publication_history.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "backend/api/moderation.hpp" +#include "render/common.hpp" + +namespace cookcookhnya::render::personal_account { + +void renderRequestHistory(UserId userId, + std::size_t pageNo, + std::size_t numOfInstances, + ChatId chatId, + BotRef bot, + api::ModerationApiRef moderationApi); + +} // namespace cookcookhnya::render::personal_account diff --git a/src/render/personal_account/recipe/publication_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp similarity index 95% rename from src/render/personal_account/recipe/publication_history.cpp rename to src/render/personal_account/recipe/moderation_history.cpp index cbf87a65..e14cfa8f 100644 --- a/src/render/personal_account/recipe/publication_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -1,4 +1,4 @@ -#include "publication_history.hpp" +#include "moderation_history.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" @@ -12,7 +12,8 @@ #include #include -namespace cookcookhnya::render::personal_account::recipe::publication_history { +namespace cookcookhnya::render::personal_account::recipe { + using namespace std::views; void renderPublicationHistory(UserId userId, @@ -21,7 +22,7 @@ void renderPublicationHistory(UserId userId, std::string& recipeName, bool isPeek, BotRef bot, - RecipesApiRef recipesApi) { + api::RecipesApiRef recipesApi) { auto history = recipesApi.getRecipeRequestHistory(userId, recipeId); InlineKeyboardBuilder keyboard{2}; // confirm and back @@ -60,7 +61,7 @@ void renderPublicationHistory(UserId userId, bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); } } -} // namespace cookcookhnya::render::personal_account::recipe::publication_history +} // namespace cookcookhnya::render::personal_account::recipe // Uncomment in case of EMERGENCY (or if you know what for is) // use instead of history | reverse | ... diff --git a/src/render/personal_account/recipe/publication_history.hpp b/src/render/personal_account/recipe/moderation_history.hpp similarity index 62% rename from src/render/personal_account/recipe/publication_history.hpp rename to src/render/personal_account/recipe/moderation_history.hpp index c8253cd7..eb2177e7 100644 --- a/src/render/personal_account/recipe/publication_history.hpp +++ b/src/render/personal_account/recipe/moderation_history.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/recipes.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" -namespace cookcookhnya::render::personal_account::recipe::publication_history { +namespace cookcookhnya::render::personal_account::recipe { void renderPublicationHistory(UserId userId, ChatId chatId, @@ -11,6 +12,6 @@ void renderPublicationHistory(UserId userId, std::string& recipeName, bool isPeek, BotRef bot, - RecipesApiRef recipesApi); + api::RecipesApiRef recipesApi); -} // namespace cookcookhnya::render::personal_account::recipe::publication_history +} // namespace cookcookhnya::render::personal_account::recipe diff --git a/src/render/personal_account/recipe/search_ingredients.cpp b/src/render/personal_account/recipe/search_ingredients.cpp index b57a0fff..6304af19 100644 --- a/src/render/personal_account/recipe/search_ingredients.cpp +++ b/src/render/personal_account/recipe/search_ingredients.cpp @@ -4,118 +4,46 @@ #include "message_tracker.hpp" #include "patched_bot.hpp" #include "render/common.hpp" +#include "render/pagination.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include -#include -#include #include -#include #include #include #include #include #include -#include -namespace cookcookhnya::render::recipe::ingredients { +namespace TgBot { +class InlineKeyboardMarkup; +} // namespace TgBot + +namespace cookcookhnya::render::personal_account::recipe { using namespace api::models::ingredient; using namespace tg_types; +using namespace std::views; +using std::ranges::to; namespace { -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t numOfRecipesOnPage, - const states::CustomRecipeIngredientsSearch& state) { - using namespace std::views; - const size_t amountOfRecipes = state.totalFound; - int maxPageNum = - static_cast(std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage))); - - const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); - // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that - // will be actually shown - const bool ifMaxPage = static_cast(amountOfRecipes) - - static_cast(numOfRecipesOnPage) * (static_cast(state.pageNo) + 1) <= - 0; - - if (offset + recipesToShow >= fullKeyBoardSize) { - InlineKeyboard error(0); - return error; - } - const size_t arrowsRow = offset + recipesToShow; - // Don't reserve for arrows if it's first page is max(im) - InlineKeyboard keyboard(state.pageNo == 0 && ifMaxPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); +std::shared_ptr +constructKeyboard(std::size_t pageNo, std::size_t pageSize, const states::CustomRecipeIngredientsSearch& state) { + InlineKeyboardBuilder keyboard; auto searchButton = std::make_shared(); searchButton->text = utils::utf8str(u8"✏️ Редактировать"); searchButton->switchInlineQueryCurrentChat = ""; - keyboard[0].push_back(std::move(searchButton)); - - for (auto [row, ing] : zip(drop(keyboard, 1), state.searchItems)) - row.push_back(makeCallbackButton((ing.isInRecipe ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id))); - - if (state.pageNo == 0 && ifMaxPage) { - // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; - } - keyboard[arrowsRow].reserve(3); - - // Helps to reduce code. Power of C++ YEAH BABE! - uint8_t b = 0; - - // Simply enamurate every case - if (state.pageNo == 0) { - if (!ifMaxPage) { - b |= uint8_t{0b01}; - } - } else if (ifMaxPage) { - b |= uint8_t{0b10}; - } else { - b |= uint8_t{0b11}; - } - - // Check from left to right due to buttons being displayed like that - for (int i = 1; i >= 0; i--) { - // Compare two bits under b mask. If 1 was on b mask then we need to place arrow somewhere - if ((b & static_cast((uint8_t{0b1} << static_cast(i)))) == - (uint8_t{0b1} << static_cast(i))) { - // if we need to place arrow then check the i, which represents bit which we are checking right now - if (i == 1) { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", "prev")); // left - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", "next")); // right - } - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - } - } - // Put state.pageNo as button - keyboard[arrowsRow].insert( - keyboard[arrowsRow].begin() + 1, - makeCallbackButton(std::format("{} из {}", state.pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::CustomRecipeIngredientsSearch& state) { - // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 - // for editing - other buttons are ingredients - const size_t numOfRows = 3; - const size_t offset = 1; // Number of rows before list + keyboard << std::move(searchButton) << NewRow{}; - const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); - - InlineKeyboard keyboard = constructNavigationsMarkup(offset, numOfRows + recipesToShow, numOfRecipesOnPage, state); - if (keyboard.empty()) { // If error happened - return keyboard; - } + auto makeRecipeButton = [](const IngredientSearchForRecipeItem& ing) { + return makeCallbackButton((ing.isInRecipe ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id)); + }; + keyboard << constructPagination(pageNo, pageSize, state.totalFound, state.searchItems, makeRecipeButton) + << makeCallbackButton(u8"↩️ Назад", "back"); return keyboard; } @@ -123,31 +51,18 @@ InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::CustomRe } // namespace void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& state, - size_t numOfIngredientsOnPage, + std::size_t numOfIngredientsOnPage, UserId userId, ChatId chatId, BotRef bot) { - using namespace std::views; - using std::ranges::to; - std::string list = state.recipeIngredients.getValues() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); - auto text = utils::utf8str(u8"📝Нажмите на кнопку ✏️ Редактировать и начните вводить названия продуктов:\n\n") + std::move(list); - if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText(text, - chatId, - *messageId, - "", - "", - nullptr, - makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); - } -} -} // namespace cookcookhnya::render::recipe::ingredients -namespace cookcookhnya::render::suggest_custom_ingredient { + if (auto messageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *messageId, constructKeyboard(state.pageNo, numOfIngredientsOnPage, state)); +} void renderSuggestIngredientCustomisation(const states::CustomRecipeIngredientsSearch& state, UserId userId, @@ -165,8 +80,8 @@ void renderSuggestIngredientCustomisation(const states::CustomRecipeIngredientsS makeCallbackButton(std::format("Создать личный ингредиент: {}", state.query), "i" + state.query)); keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); - } + if (auto messageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); } -} // namespace cookcookhnya::render::suggest_custom_ingredient + +} // namespace cookcookhnya::render::personal_account::recipe diff --git a/src/render/personal_account/recipe/search_ingredients.hpp b/src/render/personal_account/recipe/search_ingredients.hpp index 7b7561f6..050d877d 100644 --- a/src/render/personal_account/recipe/search_ingredients.hpp +++ b/src/render/personal_account/recipe/search_ingredients.hpp @@ -2,20 +2,20 @@ #include "render/common.hpp" #include "states.hpp" + #include -namespace cookcookhnya::render::recipe::ingredients { + +namespace cookcookhnya::render::personal_account::recipe { void renderRecipeIngredientsSearch(const states::CustomRecipeIngredientsSearch& state, size_t numOfIngredientsOnPage, UserId userId, ChatId chatId, BotRef bot); -} // namespace cookcookhnya::render::recipe::ingredients - -namespace cookcookhnya::render::suggest_custom_ingredient { void renderSuggestIngredientCustomisation(const states::CustomRecipeIngredientsSearch& state, UserId userId, ChatId chatId, BotRef bot); -} // namespace cookcookhnya::render::suggest_custom_ingredient + +} // namespace cookcookhnya::render::personal_account::recipe diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 581b4859..ddb946bf 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -1,8 +1,9 @@ #include "view.hpp" +#include "backend/api/recipes.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" -#include "backend/models/recipe.hpp" +#include "backend/models/publication_request_status.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/utils.hpp" @@ -10,15 +11,16 @@ #include #include #include -#include #include #include -namespace cookcookhnya::render::personal_account::recipes { +namespace cookcookhnya::render::personal_account::recipe { -std::tuple, std::string> renderCustomRecipe( - bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi) { +using namespace api::models::ingredient; +using namespace api::models::moderation; +std::pair, std::string> renderCustomRecipe( + bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, api::RecipesApiRef recipesApi) { auto recipeDetails = recipesApi.get(userId, recipeId); std::vector ingredients; @@ -41,8 +43,8 @@ std::tuple, std::string> render 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.value() == api::models::recipe::PublicationRequestStatus::NO_REQUEST || - recipeDetails.moderationStatus.value() == api::models::recipe::PublicationRequestStatus::REJECTED) { + if (recipeDetails.moderationStatus.value() == PublicationRequestStatus::NO_REQUEST || + recipeDetails.moderationStatus.value() == PublicationRequestStatus::REJECTED) { keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; } else { keyboard << makeCallbackButton(u8"📢 История публикаций", "peekpublish") << NewRow{}; @@ -58,6 +60,7 @@ std::tuple, std::string> render auto message = bot.sendMessage(chatId, toPrint, std::move(keyboard), "Markdown"); message::addMessageId(userId, message->messageId); } - return {ingredients, recipeDetails.name}; + return {std::move(ingredients), std::move(recipeDetails.name)}; } -} // namespace cookcookhnya::render::personal_account::recipes + +} // namespace cookcookhnya::render::personal_account::recipe diff --git a/src/render/personal_account/recipe/view.hpp b/src/render/personal_account/recipe/view.hpp index aaaddcc6..73a66d7f 100644 --- a/src/render/personal_account/recipe/view.hpp +++ b/src/render/personal_account/recipe/view.hpp @@ -1,13 +1,17 @@ #pragma once +#include "backend/api/recipes.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "render/common.hpp" + +#include +#include #include -namespace cookcookhnya::render::personal_account::recipes { +namespace cookcookhnya::render::personal_account::recipe { -std::tuple, std::string> renderCustomRecipe( - bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, RecipesApiRef recipesApi); +std::pair, std::string> renderCustomRecipe( + bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, api::RecipesApiRef recipesApi); -} // namespace cookcookhnya::render::personal_account::recipes +} // namespace cookcookhnya::render::personal_account::recipe diff --git a/src/render/personal_account/recipes_list/create.cpp b/src/render/personal_account/recipes_list/create.cpp index 68b08dc0..cb5d2598 100644 --- a/src/render/personal_account/recipes_list/create.cpp +++ b/src/render/personal_account/recipes_list/create.cpp @@ -6,7 +6,7 @@ #include -namespace cookcookhnya::render::personal_account::recipes { +namespace cookcookhnya::render::personal_account::recipes_list { void renderRecipeCreation(ChatId chatId, UserId userId, BotRef bot) { // BackendProvider bkn InlineKeyboard keyboard(1); @@ -18,4 +18,4 @@ void renderRecipeCreation(ChatId chatId, UserId userId, BotRef bot) { // Backend } }; -} // namespace cookcookhnya::render::personal_account::recipes +} // namespace cookcookhnya::render::personal_account::recipes_list diff --git a/src/render/personal_account/recipes_list/create.hpp b/src/render/personal_account/recipes_list/create.hpp index d44f541a..e48ff0a4 100644 --- a/src/render/personal_account/recipes_list/create.hpp +++ b/src/render/personal_account/recipes_list/create.hpp @@ -2,8 +2,8 @@ #include "render/common.hpp" -namespace cookcookhnya::render::personal_account::recipes { +namespace cookcookhnya::render::personal_account::recipes_list { void renderRecipeCreation(ChatId chatId, UserId userId, BotRef bot); -} // namespace cookcookhnya::render::personal_account::recipes +} // namespace cookcookhnya::render::personal_account::recipes_list diff --git a/src/render/personal_account/recipes_list/view.cpp b/src/render/personal_account/recipes_list/view.cpp index 393520ce..59e19ffe 100644 --- a/src/render/personal_account/recipes_list/view.cpp +++ b/src/render/personal_account/recipes_list/view.cpp @@ -5,132 +5,51 @@ #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" +#include "render/pagination.hpp" #include "render/personal_account/recipes_list/view.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" -#include -#include #include -#include #include +#include #include -#include -namespace cookcookhnya::render::personal_account::recipes { +namespace TgBot { +class InlineKeyboardMarkup; +} // namespace TgBot -namespace { - -// offset is variable which defines amout of rows before beggining of paging -// fullKeyBoardSize is self explanatory -InlineKeyboard constructNavigationsMarkup(std::size_t offset, - std::size_t fullKeyBoardSize, - std::size_t pageNo, - std::size_t numOfRecipesOnPage, - api::models::recipe::RecipesList& recipesList) { - const size_t amountOfRecipes = recipesList.found; - std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); +namespace cookcookhnya::render::personal_account::recipes_list { - const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); +using namespace api::models::recipe; - const bool lastPage = - static_cast(amountOfRecipes) - static_cast(numOfRecipesOnPage) * (static_cast(pageNo) + 1) <= 0; - - if (offset + recipesToShow >= fullKeyBoardSize) { - InlineKeyboard error(0); - return error; - } - const size_t arrowsRow = offset + recipesToShow; - // Don't reserve for arrows if it's first page is max(im) - InlineKeyboard keyboard(pageNo == 0 && lastPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); - std::size_t counter = 0; - for (std::size_t i = 0; i < recipesToShow; i++) { - // Print on button in form "1. {Recipe}" - keyboard[i + offset].push_back(makeCallbackButton( - std::format("{}. {}", 1 + counter + ((pageNo)*numOfRecipesOnPage), recipesList.page[counter].name), - std::format("r{}", recipesList.page[counter].id))); // RECIPE ID - counter++; - } - if (pageNo == 0 && lastPage) { - // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; - } - keyboard[arrowsRow].reserve(3); +namespace { - enum PageArrows : std::uint8_t { - NOTHING = 0b00U, - LEFT = 0b01U, - RIGHT = 0b10U, +std::shared_ptr +constructKeyboard(std::size_t pageNo, std::size_t pageSize, RecipesList& recipesList) { + InlineKeyboardBuilder keyboard; + keyboard << makeCallbackButton(u8"🆕 Создать", "custom_recipe_create") << NewRow{}; + auto makeRecipeButton = [i = (pageNo * pageSize) + 1](RecipeSummary& r) mutable { + return makeCallbackButton(std::format("{}. {}", i++, r.name), "recipe_" + utils::to_string(r.id)); }; - - PageArrows b = NOTHING; - if (pageNo != 0) - b = static_cast(b | LEFT); - if (!lastPage) - b = static_cast(b | RIGHT); - - if ((b & LEFT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - if ((b & RIGHT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - // Put pageNo as button - keyboard[arrowsRow].insert(keyboard[arrowsRow].begin() + 1, - makeCallbackButton(std::format("{} из {}", pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard constructOnlyCreate() { - InlineKeyboard keyboard(2); - keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard -constructMarkup(size_t pageNo, size_t numOfRecipesOnPage, api::models::recipe::RecipesList& recipesList) { - // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 - // for adding new recipe - other buttons are recipes - const size_t numOfRows = 3; - const size_t offset = 1; // Number of rows before list - - const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - - InlineKeyboard keyboard = - recipesList.found == 0 - ? constructOnlyCreate() - : constructNavigationsMarkup(offset, numOfRows + recipesToShow, pageNo, numOfRecipesOnPage, recipesList); - if (keyboard.empty()) { // If error happened - return keyboard; - } - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "custom_recipe_create")); - + keyboard << constructPagination(pageNo, pageSize, recipesList.found, recipesList.page, makeRecipeButton) + << makeCallbackButton(u8"↩️ Назад", "back"); return keyboard; } } // namespace -void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi) { - std::string pageInfo = utils::utf8str(u8"🔪 Рецепты созданные вами:"); - - auto messageId = message::getMessageId(userId); - +void renderCustomRecipesList( + std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::RecipesApiRef recipesApi) { const std::size_t numOfRecipesOnPage = 5; auto recipesList = recipesApi.getList(userId, PublicityFilterType::Custom, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); - if (recipesList.found == 0) { - pageInfo = utils::utf8str(u8"🔪 Вы находитесь в Мои рецепты. Создавайте и делитесь новыми рецептами.\n\n"); - } - if (messageId) { - bot.editMessageText( - pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + const std::string pageInfo = utils::utf8str( + recipesList.found > 0 ? u8"🔪 Рецепты созданные вами:" + : u8"🔪 Вы находитесь в Мои рецепты. Создавайте и делитесь новыми рецептами.\n\n"); + + if (auto messageId = message::getMessageId(userId)) { + bot.editMessageText(pageInfo, chatId, *messageId, constructKeyboard(pageNo, numOfRecipesOnPage, recipesList)); } } -} // namespace cookcookhnya::render::personal_account::recipes +} // namespace cookcookhnya::render::personal_account::recipes_list diff --git a/src/render/personal_account/recipes_list/view.hpp b/src/render/personal_account/recipes_list/view.hpp index 7bc978fa..72ad983e 100644 --- a/src/render/personal_account/recipes_list/view.hpp +++ b/src/render/personal_account/recipes_list/view.hpp @@ -1,11 +1,13 @@ #pragma once +#include "backend/api/recipes.hpp" #include "render/common.hpp" #include -namespace cookcookhnya::render::personal_account::recipes { +namespace cookcookhnya::render::personal_account::recipes_list { -void renderCustomRecipesList(std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, RecipesApiRef recipesApi); +void renderCustomRecipesList( + std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, api::RecipesApiRef recipesApi); -} // namespace cookcookhnya::render::personal_account::recipes +} // namespace cookcookhnya::render::personal_account::recipes_list diff --git a/src/render/personal_account/request_history.cpp b/src/render/personal_account/request_history.cpp deleted file mode 100644 index 5f666a57..00000000 --- a/src/render/personal_account/request_history.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "request_history.hpp" - -#include "backend/models/request_history.hpp" -#include "message_tracker.hpp" -#include "render/common.hpp" - -#include -#include - -namespace cookcookhnya::render::personal_account::publication_history { -void renderRequestHistory(UserId userId, - size_t pageNo, - size_t numOfInstances, - ChatId chatId, - BotRef bot, - RequestHistoryApiRef reqHistoryApi) { - InlineKeyboardBuilder keyboard{1}; - - auto history = reqHistoryApi.getAllRequestHistory(userId, 10, pageNo * numOfInstances); - /*std::vector history = { - { - .name = "хуй слона", - .created = std::chrono::system_clock::now(), - .status = api::models::recipe::PublicationRequestStatus::PENDING, - }, - { - .name = "хуй asd", - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .name = "jtgg", - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - }, - { - .name = "jghggf", - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .name = "hjfhg", - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .name = "loijh", - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }, - { - .name = "ngsdf", - .created = std::chrono::system_clock::now(), - .reason = "ПИДАРАС", - .status = api::models::recipe::PublicationRequestStatus::REJECTED, - .updated = std::chrono::system_clock::now(), - }};*/ - std::string toPrint = utils::utf8str(u8"ℹ️История запросов на публикацию ваших рецептов и ингредиентов\n\n\n"); - for (auto& req : history) { - toPrint += std::format("*{}* статус: {} ", req.name, utils::to_string(req.status)); - if (req.reason.has_value()) - toPrint += std::format("по причине: {} ", req.reason.value()); - toPrint += std::format("запрос создан: {} ", utils::to_string(req.created)); - if (req.updated.has_value()) { - toPrint += std::format("последенее обновление: {}", utils::to_string(req.updated.value())); - } - toPrint += "\n\n"; - } - - keyboard << makeCallbackButton(u8"↩️ Назад", "back"); - auto messageId = message::getMessageId(userId); - if (messageId) { - bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); - } -} -} // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/personal_account/request_history.hpp b/src/render/personal_account/request_history.hpp deleted file mode 100644 index 2d5035a0..00000000 --- a/src/render/personal_account/request_history.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "render/common.hpp" - -namespace cookcookhnya::render::personal_account::publication_history { -void renderRequestHistory( - UserId userId, size_t pageNo, size_t numOfInstances, ChatId chatId, BotRef bot, RequestHistoryApiRef reqHistoryApi); -} // namespace cookcookhnya::render::personal_account::publication_history diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp index 5ed05461..70c9ef0e 100644 --- a/src/render/recipe/add_storage.cpp +++ b/src/render/recipe/add_storage.cpp @@ -1,14 +1,17 @@ #include "add_storage.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/ingredients_availability.hpp" +#include "states.hpp" #include "utils/utils.hpp" #include "view.hpp" +#include +#include #include #include #include @@ -17,13 +20,15 @@ namespace cookcookhnya::render::recipe { using namespace api::models::recipe; +using namespace api::models::storage; +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; -textGenInfo storageAdditionView( - const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ApiClient api) { +textGenInfo storageAdditionView(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + api::ApiClientRef api) { auto recipe = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; @@ -37,19 +42,19 @@ textGenInfo storageAdditionView( text += std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.second.available == utils::AvailabiltiyType::available) { - text += "`[+]` " + infoPair.first.name + "\n"; - } else if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { - text += "`[?]` " + infoPair.first.name + "\n"; + if (infoPair.available == AvailabilityType::AVAILABLE) { + text += "`[+]` " + infoPair.ingredient.name + "\n"; + } else if (infoPair.available == AvailabilityType::OTHER_STORAGES) { + text += "`[?]` " + infoPair.ingredient.name + "\n"; isIngredientIsOtherStorages = true; text += "Доступно в: "; - auto storages = infoPair.second.storages; + auto storages = infoPair.storages; for (std::size_t i = 0; i != storages.size(); ++i) { text += storages[i].name; text += i != storages.size() - 1 ? ", " : "\n"; } } else { - text += "`[ ]` " + infoPair.first.name + "\n"; + text += "`[ ]` " + infoPair.ingredient.name + "\n"; isIngredientNotAvailable = true; } } @@ -61,22 +66,20 @@ textGenInfo storageAdditionView( .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; } -void renderStoragesSuggestion( - const std::vector>& inStoragesAvailability, - const std::vector& selectedStorages, - const std::vector& addedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api) { +void renderStoragesSuggestion(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + const std::vector& addedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + api::ApiClientRef api) { auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); - std::vector storages; + std::vector storages; for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { - for (const auto& storage : infoPair.second.storages) { - if (std::ranges::find(storages, storage.id, &api::models::storage::StorageSummary::id) == - storages.end()) { + if (infoPair.available == AvailabilityType::OTHER_STORAGES) { + for (const auto& storage : infoPair.storages) { + if (std::ranges::find(storages, storage.id, &StorageSummary::id) == storages.end()) { storages.push_back(storage); } } @@ -90,8 +93,7 @@ void renderStoragesSuggestion( if (i % 2 == 0) keyboard[i / 2].reserve(2); const bool isSelected = - std::ranges::find(addedStorages, storages[i].id, &api::models::storage::StorageSummary::id) != - addedStorages.end(); + std::ranges::find(addedStorages, storages[i].id, &StorageSummary::id) != addedStorages.end(); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; @@ -105,7 +107,7 @@ void renderStoragesSuggestion( auto messageId = message::getMessageId(userId); if (messageId) { - bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "MarkdownV2"); + bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); } } } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp index d6fb071e..2f417f3a 100644 --- a/src/render/recipe/add_storage.hpp +++ b/src/render/recipe/add_storage.hpp @@ -1,32 +1,29 @@ #pragma once +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" #include "render/recipe/view.hpp" -#include "utils/ingredients_availability.hpp" +#include "states.hpp" #include namespace cookcookhnya::render::recipe { -textGenInfo storageAdditionView( - const std::vector>& - inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - ApiClient api); +textGenInfo storageAdditionView(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + api::ApiClientRef api); -void renderStoragesSuggestion( - const std::vector>& - inStoragesAvailability, - const std::vector& selectedStorages, - const std::vector& addedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - ApiClient api); +void renderStoragesSuggestion(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + const std::vector& addedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + api::ApiClientRef api); } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index 71df1ada..e5795b9f 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -1,40 +1,44 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/ingredients_availability.hpp" +#include "states.hpp" #include "utils/utils.hpp" +#include #include #include #include +#include namespace cookcookhnya::render::recipe { using namespace api::models::recipe; +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; -textGenInfo -recipeView(const std::vector>& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ApiClient api) { +textGenInfo recipeView(const std::vector& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + api::ApiClientRef api) { auto recipeIngredients = api.getRecipesApi().get(userId, recipeId); bool isIngredientNotAvailable = false; bool isIngredientIsOtherStorages = false; - const std::string recipeName = recipeIngredients.name; + std::string& recipeName = recipeIngredients.name; auto text = std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); - for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.second.available == utils::AvailabiltiyType::available) { - text += "`[+]` " + infoPair.first.name + "\n"; - } else if (infoPair.second.available == utils::AvailabiltiyType::other_storages) { - text += "`[?]` " + infoPair.first.name + "\n"; + for (const auto& availability : inStoragesAvailability) { + if (availability.available == AvailabilityType::AVAILABLE) { + text += "`[+]` " + availability.ingredient.name + "\n"; + } else if (availability.available == AvailabilityType::OTHER_STORAGES) { + text += "`[?]` " + availability.ingredient.name + "\n"; isIngredientIsOtherStorages = true; } else { - text += "`[ ]` " + infoPair.first.name + "\n"; + text += "`[ ]` " + availability.ingredient.name + "\n"; isIngredientNotAvailable = true; } } @@ -46,14 +50,14 @@ recipeView(const std::vector>& inStoragesAvailability, +void renderRecipeView(std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, - ApiClient api) { + api::ApiClientRef api) { auto textGen = recipeView(inStoragesAvailability, recipeId, userId, api); - const size_t buttonRows = textGen.isIngredientNotAvailable ? 3 : 2; + const std::size_t buttonRows = textGen.isIngredientNotAvailable ? 3 : 2; InlineKeyboard keyboard(buttonRows); keyboard[0].push_back(makeCallbackButton(u8"🧑‍🍳 Готовить", "start_cooking")); @@ -70,7 +74,7 @@ void renderRecipeView(std::vector #include namespace cookcookhnya::render::recipe { @@ -14,19 +16,16 @@ struct textGenInfo { bool isIngredientIsOtherStorages; }; -void renderRecipeView(std::vector>& - inStoragesAvailability, +void renderRecipeView(std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, - ApiClient api); + api::ApiClientRef api); -textGenInfo -recipeView(const std::vector>& - inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ApiClient api); +textGenInfo recipeView(const std::vector& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + api::ApiClientRef api); } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 73de6998..a1e6591e 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -1,119 +1,42 @@ #include "view.hpp" +#include "backend/api/recipes.hpp" #include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" +#include "render/pagination.hpp" #include "utils/utils.hpp" -#include -#include #include -#include #include +#include #include #include #include +namespace TgBot { +class InlineKeyboardMarkup; +} // namespace TgBot + namespace cookcookhnya::render::recipes_suggestions { using namespace api::models::storage; using namespace api::models::recipe; using namespace std::views; -using namespace std::ranges; +using std::ranges::to; namespace { -// offset is variable which defines amout of rows before beggining of paging -// fullKeyBoardSize is self explanatory -InlineKeyboard constructNavigationsMarkup(std::size_t offset, - std::size_t fullKeyBoardSize, - std::size_t pageNo, - std::size_t numOfRecipesOnPage, - RecipesListWithIngredientsCount recipesList) { - const std::size_t amountOfRecipes = recipesList.found; - std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); - - const size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that - // will be actually shown - const bool lastPage = - static_cast(amountOfRecipes) - static_cast(numOfRecipesOnPage) * (static_cast(pageNo) + 1) <= 0; - - if (offset + recipesToShow > fullKeyBoardSize) { // IN ERROR HANDLING MAY USE ASSERT - InlineKeyboard error(0); - return error; - } - const std::size_t arrowsRow = offset + recipesToShow; - - InlineKeyboard keyboard(pageNo == 0 && lastPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); - int counter = 0; - for (std::size_t i = 0; i < recipesToShow; i++) { - keyboard[i + offset].push_back(makeCallbackButton(std::format("{}. {} [{} из {}]", - 1 + counter + ((pageNo)*numOfRecipesOnPage), - recipesList.page[counter].name, - recipesList.page[counter].available, - recipesList.page[counter].total), - std::format("recipe_{}", recipesList.page[counter].id))); - counter++; - } - if (pageNo == 0 && lastPage) { - // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; - } - keyboard[arrowsRow].reserve(3); - - enum PageArrows : std::uint8_t { - NOTHING = 0b00U, - LEFT = 0b01U, - RIGHT = 0b10U, +std::shared_ptr +constructKeyboard(std::size_t pageNo, std::size_t pageSize, RecipesListWithIngredientsCount& recipesList) { + InlineKeyboardBuilder keyboard; + auto makeRecipeButton = [i = (pageNo * pageSize) + 1](RecipeSummaryWithIngredients& r) mutable { + return makeCallbackButton(std::format("{}. {} [{} из {}]", i++, r.name, r.available, r.total), + std::format("recipe_{}", r.id)); }; - - PageArrows b = NOTHING; - if (pageNo != 0) - b = static_cast(b | LEFT); - if (!lastPage) - b = static_cast(b | RIGHT); - - if ((b & LEFT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); // left - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - if ((b & RIGHT) != 0) - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); // right - else - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - - // Put pageNo as button - keyboard[arrowsRow].insert(keyboard[arrowsRow].begin() + 1, - makeCallbackButton(std::format("{} из {}", pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard constructOnlyBack() { - InlineKeyboard keyboard(1); - keyboard[0].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard -constructMarkup(std::size_t pageNo, std::size_t numOfRecipesOnPage, RecipesListWithIngredientsCount& recipesList) { - const std::size_t numOfRows = 2; // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS) - const std::size_t offset = 0; // Number of rows before list - const std::size_t recipesToShow = std::min(numOfRecipesOnPage, recipesList.page.size()); - - InlineKeyboard keyboard = - recipesList.found == 0 - ? constructOnlyBack() - : constructNavigationsMarkup(offset, numOfRows + recipesToShow, pageNo, numOfRecipesOnPage, recipesList); - if (keyboard.empty()) { // If error happened ADD PROPER ERROR HANDLING IF FUNCTION WILL BE REUSED - return keyboard; - } - + keyboard << constructPagination(pageNo, pageSize, recipesList.found, recipesList.page, makeRecipeButton) + << makeCallbackButton(u8"↩️ Назад", "back"); return keyboard; } @@ -124,23 +47,22 @@ void renderRecipesSuggestion(std::vector& storages, UserId userId, ChatId chatId, BotRef bot, - RecipesApiRef recipesApi) { - std::string pageInfo = utils::utf8str(u8"🔪 Рецепты подобранные специально для вас"); + api::RecipesApiRef recipesApi) { const std::size_t numOfRecipesOnPage = 5; const std::size_t numOfRecipes = 500; - auto storagesIds = storages | views::transform(&StorageSummary::id) | to(); + auto storagesIds = storages | transform(&StorageSummary::id) | to(); auto recipesList = recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipes, pageNo * numOfRecipesOnPage); - if (recipesList.found == 0) { - pageInfo = utils::utf8str(u8"😔 К сожалению, нам не удалось найти подходящие рецепты для вас..."); - } + const std::string text = + utils::utf8str(recipesList.found > 0 ? u8"🔪 Рецепты подобранные специально для вас" + : u8"😔 К сожалению, нам не удалось найти подходящие рецепты для вас..."); + auto keyboardMarkup = constructKeyboard(pageNo, numOfRecipesOnPage, recipesList); + if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText( - pageInfo, chatId, *messageId, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + bot.editMessageText(text, chatId, *messageId, keyboardMarkup); } else { - auto message = bot.sendMessage( - chatId, pageInfo, makeKeyboardMarkup(constructMarkup(pageNo, numOfRecipesOnPage, recipesList))); + auto message = bot.sendMessage(chatId, text, keyboardMarkup); message::addMessageId(userId, message->messageId); } } diff --git a/src/render/recipes_suggestions/view.hpp b/src/render/recipes_suggestions/view.hpp index 8ba90811..dd3720ea 100644 --- a/src/render/recipes_suggestions/view.hpp +++ b/src/render/recipes_suggestions/view.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/api.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" @@ -15,6 +16,6 @@ void renderRecipesSuggestion(std::vector& UserId userId, ChatId chatId, BotRef bot, - RecipesApiRef recipesApi); + api::RecipesApiRef recipesApi); } // namespace cookcookhnya::render::recipes_suggestions diff --git a/src/render/shopping_list/create.cpp b/src/render/shopping_list/create.cpp index 40adbb5d..a75bb221 100644 --- a/src/render/shopping_list/create.cpp +++ b/src/render/shopping_list/create.cpp @@ -6,6 +6,7 @@ #include "utils/to_string.hpp" #include "utils/utils.hpp" +#include #include #include #include @@ -23,7 +24,7 @@ void renderShoppingListCreation(const std::vector& selectedIngredien UserId userId, ChatId chatId, BotRef bot) { - std::string text = utils::utf8str(u8"📝 Выберите продукты, которые хотели бы добавить в список покупок\n\n"); + const std::string text = utils::utf8str(u8"📝 Выберите продукты, которые хотели бы добавить в список покупок\n\n"); const std::size_t buttonRows = ((selectedIngredients.size() + 1) / 2) + 1; // ceil(ingredientsCount / 2), back InlineKeyboardBuilder keyboard{buttonRows}; @@ -31,12 +32,12 @@ void renderShoppingListCreation(const std::vector& selectedIngredien for (auto chunk : allIngredients | chunk(2)) { keyboard.reserveInRow(2); for (const Ingredient& ing : chunk) { - const bool isSelected = - std::ranges::contains(selectedIngredients, ing.id, &api::models::ingredient::Ingredient::id); - std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); - const char* actionPrefix = isSelected ? "+" : "-"; - std::string text = std::format("{} {}", emoji, ing.name); - std::string data = actionPrefix + utils::to_string(ing.id); + const bool isSelected = std::ranges::contains(selectedIngredients, ing.id, &Ingredient::id); + std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); // second is not empty but invisible! + // button data is onclick action for bot: "+" is "add", "-" is "remove" + const char* actionPrefix = isSelected ? "-" : "+"; + const std::string text = std::format("{} {}", emoji, ing.name); + const std::string data = actionPrefix + utils::to_string(ing.id); keyboard << makeCallbackButton(text, data); } keyboard << NewRow{}; diff --git a/src/render/shopping_list/search.cpp b/src/render/shopping_list/search.cpp new file mode 100644 index 00000000..3e5d059f --- /dev/null +++ b/src/render/shopping_list/search.cpp @@ -0,0 +1,50 @@ +#include "search.hpp" + +#include "backend/models/ingredient.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "render/pagination.hpp" +#include "states.hpp" +#include "utils/to_string.hpp" +#include "utils/utils.hpp" + +#include +#include +#include +#include + +namespace TgBot { +class InlineKeyboardButton; +} // namespace TgBot + +namespace cookcookhnya::render::shopping_list { + +using namespace api::models::ingredient; +using namespace tg_types; +using namespace std::views; +using namespace std::ranges; + +void renderShoppingListIngredientSearch( + const states::ShoppingListIngredientSearch& state, std::size_t pageSize, UserId userId, ChatId chatId, BotRef bot) { + const std::string text = utils::utf8str(u8"🍗 Используйте кнопку ниже, чтобы найти ингредиенты для добавления"); + + InlineKeyboardBuilder keyboard{state.page.size() + 2}; // search, items (n), back + + auto searchButton = std::make_shared(); + searchButton->text = utils::utf8str(u8"🔍 Искать"); + searchButton->switchInlineQueryCurrentChat = ""; + keyboard << std::move(searchButton) << NewRow{}; + + auto makeItemButton = [](const Ingredient& ing) { + // const auto* emptyBrackets = "[ㅤ] "; + return makeCallbackButton(ing.name, "ingredient_" + utils::to_string(ing.id)); + }; + keyboard << constructPagination( + state.pagination.pageNo, pageSize, state.pagination.totalItems, state.page, makeItemButton) + << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto messageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); +} + +} // namespace cookcookhnya::render::shopping_list diff --git a/src/render/shopping_list/search.hpp b/src/render/shopping_list/search.hpp new file mode 100644 index 00000000..f6dfed70 --- /dev/null +++ b/src/render/shopping_list/search.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "render/common.hpp" +#include "states.hpp" + +#include + +namespace cookcookhnya::render::shopping_list { + +void renderShoppingListIngredientSearch( + const states::ShoppingListIngredientSearch& state, std::size_t pageSize, UserId userId, ChatId chatId, BotRef bot); + +} // namespace cookcookhnya::render::shopping_list diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 6713f363..649287d6 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -14,7 +14,7 @@ namespace cookcookhnya::render::shopping_list { void renderShoppingList(const states::ShoppingListView& state, UserId userId, ChatId chatId, BotRef bot) { auto items = state.items.getValues(); - bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); + const bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); InlineKeyboardBuilder keyboard{3 + items.size()}; // add, remove and/or buy, list (n), back diff --git a/src/render/storage/delete.cpp b/src/render/storage/delete.cpp index 6c5ec1d5..9c9379cb 100644 --- a/src/render/storage/delete.cpp +++ b/src/render/storage/delete.cpp @@ -1,5 +1,6 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -10,7 +11,7 @@ namespace cookcookhnya::render::delete_storage { void renderStorageDeletion( - api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi) { + api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); InlineKeyboard keyboard(2); keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); diff --git a/src/render/storage/delete.hpp b/src/render/storage/delete.hpp index 12a463b8..00d2f27c 100644 --- a/src/render/storage/delete.hpp +++ b/src/render/storage/delete.hpp @@ -1,11 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::delete_storage { void renderStorageDeletion( - api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, StorageApiRef storageApi); + api::StorageId storageId, ChatId chatId, BotRef bot, UserId userId, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::delete_storage diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index ee7ecd7c..5fd2fb94 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -2,144 +2,61 @@ #include "backend/models/ingredient.hpp" #include "message_tracker.hpp" -#include "patched_bot.hpp" #include "render/common.hpp" +#include "render/pagination.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" -#include - -#include -#include #include -#include #include #include #include #include #include -#include + +namespace TgBot { +class InlineKeyboardMarkup; +} // namespace TgBot namespace cookcookhnya::render::storage::ingredients { using namespace api::models::ingredient; using namespace tg_types; +using namespace std::views; +using std::ranges::to; namespace { -InlineKeyboard constructNavigationsMarkup(size_t offset, - size_t fullKeyBoardSize, - size_t numOfRecipesOnPage, - const states::StorageIngredientsList& state) { - using namespace std::views; - const size_t amountOfRecipes = state.totalFound; - int maxPageNum = - static_cast(std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage))); - - const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); - // + 1 because of the 0-indexing, as comparisson is between num of recipes gotten and that - // will be actually shown - const bool ifMaxPage = static_cast(amountOfRecipes) - - static_cast(numOfRecipesOnPage) * (static_cast(state.pageNo) + 1) <= - 0; - - if (offset + recipesToShow >= fullKeyBoardSize) { - InlineKeyboard error(0); - return error; - } - const size_t arrowsRow = offset + recipesToShow; - // Don't reserve for arrows if it's first page is max(im) - InlineKeyboard keyboard(state.pageNo == 0 && ifMaxPage ? fullKeyBoardSize - 1 : fullKeyBoardSize); +std::shared_ptr +constructKeyboard(std::size_t pageNo, std::size_t pageSize, const states::StorageIngredientsList& state) { + InlineKeyboardBuilder keyboard; auto searchButton = std::make_shared(); searchButton->text = utils::utf8str(u8"✏️ Редактировать"); searchButton->switchInlineQueryCurrentChat = ""; - keyboard[0].push_back(std::move(searchButton)); - for (auto [row, ing] : zip(drop(keyboard, 1), state.searchItems)) - row.push_back(makeCallbackButton((ing.isInStorage ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id))); - - if (state.pageNo == 0 && ifMaxPage) { - // instead of arrows row - keyboard[arrowsRow].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; - } - keyboard[arrowsRow].reserve(3); - - // Helps to reduce code. Power of C++ YEAH BABE! - uint8_t b = 0; - - // Simply enamurate every case - if (state.pageNo == 0) { - if (!ifMaxPage) { - b |= uint8_t{0b01}; - } - } else if (ifMaxPage) { - b |= uint8_t{0b10}; - } else { - b |= uint8_t{0b11}; - } - - // Check from left to right due to buttons being displayed like that - for (int i = 1; i >= 0; i--) { - // Compare two bits under b mask. If 1 was on b mask then we need to place arrow somewhere - if ((b & static_cast((uint8_t{0b1} << static_cast(i)))) == - (uint8_t{0b1} << static_cast(i))) { - // if we need to place arrow then check the i, which represents bit which we are checking right now - if (i == 1) { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"◀️", "prev")); // left - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"▶️", "next")); // right - } - } else { - keyboard[arrowsRow].push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - } - } - // Put state.pageNo as button - keyboard[arrowsRow].insert( - keyboard[arrowsRow].begin() + 1, - makeCallbackButton(std::format("{} из {}", state.pageNo + 1, maxPageNum), "dont_handle")); - keyboard[arrowsRow + 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - return keyboard; -} - -InlineKeyboard constructMarkup(size_t numOfRecipesOnPage, const states::StorageIngredientsList& state) { - // 1 for back button return, 1 for arrows (ALWAYS ACCOUNT ARROWS), 1 - // for editing - other buttons are ingredients - const size_t numOfRows = 3; - const size_t offset = 1; // Number of rows before list - - const size_t recipesToShow = std::min(numOfRecipesOnPage, state.searchItems.size()); - - InlineKeyboard keyboard = constructNavigationsMarkup(offset, numOfRows + recipesToShow, numOfRecipesOnPage, state); - if (keyboard.empty()) { // If error happened - return keyboard; - } + keyboard << std::move(searchButton) << NewRow{}; + auto makeIngredientButton = [](const IngredientSearchForStorageItem& ing) { + return makeCallbackButton((ing.isInStorage ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id)); + }; + keyboard << constructPagination(pageNo, pageSize, state.totalFound, state.searchItems, makeIngredientButton) + << makeCallbackButton(u8"↩️ Назад", "back"); return keyboard; } } // namespace void renderIngredientsListSearch(const states::StorageIngredientsList& state, - size_t numOfIngredientsOnPage, + std::size_t numOfIngredientsOnPage, UserId userId, ChatId chatId, BotRef bot) { - using namespace std::views; - using std::ranges::to; std::string list = state.storageIngredients.getValues() | transform([](auto& i) { return std::format("• {}\n", i.name); }) | join | to(); auto text = utils::utf8str(u8"🍗 Ваши ингредиенты:\n\n") + std::move(list); if (auto messageId = message::getMessageId(userId)) { - bot.editMessageText(text, - chatId, - *messageId, - "", - "", - nullptr, - makeKeyboardMarkup(constructMarkup(numOfIngredientsOnPage, state))); + bot.editMessageText(text, chatId, *messageId, constructKeyboard(state.pageNo, numOfIngredientsOnPage, state)); } } diff --git a/src/render/storage/ingredients/view.hpp b/src/render/storage/ingredients/view.hpp index 3019eab6..c07870ae 100644 --- a/src/render/storage/ingredients/view.hpp +++ b/src/render/storage/ingredients/view.hpp @@ -1,6 +1,8 @@ #pragma once + #include "render/common.hpp" #include "states.hpp" + #include namespace cookcookhnya::render::storage::ingredients { diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index 6378b21a..a32c8f94 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -1,5 +1,6 @@ #include "add.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -7,6 +8,7 @@ #include +#include #include #include #include @@ -14,7 +16,7 @@ namespace cookcookhnya::render::storage::members { void renderStorageMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const std::size_t buttonRows = 2; @@ -24,24 +26,23 @@ void renderStorageMemberAddition( auto text = utils::utf8str(u8"📩 Создайте ссылку или перешлите сообщение пользователя, чтобы добавить его в хранилище.\n"); auto messageId = message::getMessageId(userId); - if (messageId) { - bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); - } + if (messageId) + bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); }; void renderShareLinkMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const std::size_t buttonRows = 2; - InlineKeyboard keyboard(buttonRows); + InlineKeyboard keyboard{buttonRows}; auto inviteButton = std::make_shared(); inviteButton->text = utils::utf8str(u8"📤 Поделиться"); - auto hash = storageApi.inviteMember(userId, storageId); - const auto telegramBotAlias = bot.getUnderlying().getMe()->username; - auto inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + - "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=" + hash; + const api::InvitationId hash = storageApi.inviteMember(userId, storageId); + const std::string telegramBotAlias = bot.getUnderlying().getMe()->username; + const std::string inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + + "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=" + hash; inviteButton->url = "https://t.me/share/url?url=" + inviteText; keyboard[0].push_back(std::move(inviteButton)); diff --git a/src/render/storage/members/add.hpp b/src/render/storage/members/add.hpp index 18462217..6bcabd1f 100644 --- a/src/render/storage/members/add.hpp +++ b/src/render/storage/members/add.hpp @@ -1,14 +1,15 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storage::members { void renderStorageMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); void renderShareLinkMemberAddition( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage::members diff --git a/src/render/storage/members/delete.cpp b/src/render/storage/members/delete.cpp index a5584411..f557205f 100644 --- a/src/render/storage/members/delete.cpp +++ b/src/render/storage/members/delete.cpp @@ -1,5 +1,6 @@ #include "delete.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -14,7 +15,7 @@ namespace cookcookhnya::render::storage::members { void renderStorageMemberDeletion( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); auto members = storageApi.getStorageMembers(userId, storageId); diff --git a/src/render/storage/members/delete.hpp b/src/render/storage/members/delete.hpp index 71dae8c0..ef17bc5b 100644 --- a/src/render/storage/members/delete.hpp +++ b/src/render/storage/members/delete.hpp @@ -1,11 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storage::members { void renderStorageMemberDeletion( - const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const api::StorageId& storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage::members diff --git a/src/render/storage/members/view.cpp b/src/render/storage/members/view.cpp index 8009c32f..f1fd4a2a 100644 --- a/src/render/storage/members/view.cpp +++ b/src/render/storage/members/view.cpp @@ -1,10 +1,12 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/utils.hpp" +#include #include #include #include @@ -19,7 +21,7 @@ void renderMemberList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi) { + api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const bool isOwner = storage.ownerId == userId; const std::size_t buttonRows = isOwner ? 2 : 1; @@ -28,9 +30,8 @@ void renderMemberList(bool toBeEdited, if (isOwner) { keyboard[0].push_back(makeCallbackButton(u8"🔐 Добавить", "add")); - if (storageApi.getStorageMembers(userId, storageId).size() > 1) { + if (storageApi.getStorageMembers(userId, storageId).size() > 1) keyboard[0].push_back(makeCallbackButton(u8"🔒 Удалить", "delete")); - } keyboard[1].push_back(makeCallbackButton(u8"↩️Назад", "back")); } else { keyboard[0].push_back(makeCallbackButton(u8"↩️Назад", "back")); @@ -44,9 +45,9 @@ void renderMemberList(bool toBeEdited, for (auto [i, name] : std::views::enumerate(memberNames)) std::format_to(std::back_inserter(list), " {}. {}\n", i + 1, name); auto text = utils::utf8str(u8"👥 Список участников\n") + list; + if (toBeEdited) { - auto messageId = message::getMessageId(userId); - if (messageId) + if (auto messageId = message::getMessageId(userId)) bot.editMessageText(text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard))); } else { auto messageId = bot.sendMessage(chatId, text, makeKeyboardMarkup(std::move(keyboard))); diff --git a/src/render/storage/members/view.hpp b/src/render/storage/members/view.hpp index 8ab807a8..fd16b370 100644 --- a/src/render/storage/members/view.hpp +++ b/src/render/storage/members/view.hpp @@ -1,5 +1,6 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" @@ -10,6 +11,6 @@ void renderMemberList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, - StorageApiRef storageApi); + api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage::members diff --git a/src/render/storage/view.cpp b/src/render/storage/view.cpp index a9c7244e..e5b4b60f 100644 --- a/src/render/storage/view.cpp +++ b/src/render/storage/view.cpp @@ -1,5 +1,6 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -10,7 +11,8 @@ namespace cookcookhnya::render::storage { -void renderStorageView(api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { +void renderStorageView( + api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storage = storageApi.get(userId, storageId); const std::size_t buttonRows = storage.ownerId == userId ? 3 : 2; InlineKeyboard keyboard(buttonRows); diff --git a/src/render/storage/view.hpp b/src/render/storage/view.hpp index 15240b5e..b4d1245b 100644 --- a/src/render/storage/view.hpp +++ b/src/render/storage/view.hpp @@ -1,10 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "backend/id_types.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storage { -void renderStorageView(api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); +void renderStorageView( + api::StorageId storageId, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storage diff --git a/src/render/storages_list/create.cpp b/src/render/storages_list/create.cpp index 0d99ae7f..6617ea55 100644 --- a/src/render/storages_list/create.cpp +++ b/src/render/storages_list/create.cpp @@ -6,7 +6,7 @@ #include -namespace cookcookhnya::render::create_storage { +namespace cookcookhnya::render::storages_list { void renderStorageCreation(ChatId chatId, UserId userId, BotRef bot) { // BackendProvider bkn InlineKeyboard keyboard(1); @@ -18,4 +18,4 @@ void renderStorageCreation(ChatId chatId, UserId userId, BotRef bot) { // Backen } }; -} // namespace cookcookhnya::render::create_storage +} // namespace cookcookhnya::render::storages_list diff --git a/src/render/storages_list/create.hpp b/src/render/storages_list/create.hpp index c2e9671e..c7c39418 100644 --- a/src/render/storages_list/create.hpp +++ b/src/render/storages_list/create.hpp @@ -2,8 +2,8 @@ #include "render/common.hpp" -namespace cookcookhnya::render::create_storage { +namespace cookcookhnya::render::storages_list { void renderStorageCreation(ChatId chatId, UserId userId, BotRef bot); -} // namespace cookcookhnya::render::create_storage +} // namespace cookcookhnya::render::storages_list diff --git a/src/render/storages_list/view.cpp b/src/render/storages_list/view.cpp index 28409e88..ffdbae21 100644 --- a/src/render/storages_list/view.cpp +++ b/src/render/storages_list/view.cpp @@ -1,8 +1,8 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -14,7 +14,7 @@ namespace cookcookhnya::render::storages_list { using namespace tg_types; using namespace std::views; -void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { +void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { auto storages = storageApi.getStoragesList(userId); const std::size_t buttonRows = ((storages.size() + 1) / 2) + 1; // ceil(storagesCount / 2) and back diff --git a/src/render/storages_list/view.hpp b/src/render/storages_list/view.hpp index 2e5ea72b..30204116 100644 --- a/src/render/storages_list/view.hpp +++ b/src/render/storages_list/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/storages.hpp" #include "render/common.hpp" namespace cookcookhnya::render::storages_list { -void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); +void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::storages_list diff --git a/src/render/storages_selection/view.cpp b/src/render/storages_selection/view.cpp index 9cd31b6e..48205181 100644 --- a/src/render/storages_selection/view.cpp +++ b/src/render/storages_selection/view.cpp @@ -1,10 +1,10 @@ #include "view.hpp" +#include "backend/api/storages.hpp" #include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -23,7 +23,7 @@ using namespace tg_types; using namespace std::views; void renderStorageSelection( - const StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi) { + const StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { const auto& selectedStorages = state.selectedStorages; auto allStorages = storageApi.getStoragesList(userId); @@ -36,8 +36,8 @@ void renderStorageSelection( const bool isSelected = std::ranges::contains(selectedStorages, storage.id, &StorageSummary::id); std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); const char* actionPrefix = isSelected ? "+" : "-"; - std::string text = std::format("{} {}", emoji, storage.name); - std::string data = actionPrefix + utils::to_string(storage.id); + const std::string text = std::format("{} {}", emoji, storage.name); + const std::string data = actionPrefix + utils::to_string(storage.id); keyboard << makeCallbackButton(text, data); } keyboard << NewRow{}; diff --git a/src/render/storages_selection/view.hpp b/src/render/storages_selection/view.hpp index 465b86a9..4fe026f2 100644 --- a/src/render/storages_selection/view.hpp +++ b/src/render/storages_selection/view.hpp @@ -1,11 +1,12 @@ #pragma once +#include "backend/api/storages.hpp" #include "render/common.hpp" #include "states.hpp" namespace cookcookhnya::render::select_storages { void renderStorageSelection( - const states::StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, StorageApiRef storageApi); + const states::StoragesSelection& state, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi); } // namespace cookcookhnya::render::select_storages diff --git a/src/states.hpp b/src/states.hpp index 1deacf17..24572626 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -2,18 +2,17 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" +#include "backend/models/recipe.hpp" #include "backend/models/shopping_list.hpp" #include "backend/models/storage.hpp" #include "utils/fast_sorted_db.hpp" -#include "utils/ingredients_availability.hpp" +#include "utils/utils.hpp" -#include #include #include -#include #include -#include +#include #include #include #include @@ -28,6 +27,11 @@ struct StorageIdMixin { StorageIdMixin(api::StorageId storageId) : storageId{storageId} {} // NOLINT(*-explicit-*) }; +struct Pagination { + std::size_t pageNo; + std::size_t totalItems; +}; + } // namespace detail struct MainMenu {}; @@ -79,8 +83,7 @@ struct StorageIngredientsList : detail::StorageIdMixin { std::vector searchItems; std::string inlineQuery; - template - requires std::convertible_to, IngredientsDb::mapped_type> + template R> StorageIngredientsList(api::StorageId storageId, R&& ingredients, std::string iq) : StorageIdMixin{storageId}, storageIngredients{std::forward(ingredients)}, inlineQuery(std::move(iq)) {} }; @@ -89,28 +92,33 @@ struct StoragesSelection { std::vector selectedStorages; }; struct SuggestedRecipesList { - std::size_t pageNo; std::vector selectedStorages; + std::size_t pageNo; bool fromStorage; }; struct RecipeView { - std::vector selectedStorages; + enum struct AvailabilityType : std::uint8_t { NOT_AVAILABLE, AVAILABLE, OTHER_STORAGES }; + + struct IngredientAvailability { + cookcookhnya::api::models::recipe::IngredientInRecipe ingredient; + AvailabilityType available = AvailabilityType::NOT_AVAILABLE; + std::vector storages; + }; + + SuggestedRecipesList prevState; std::vector addedStorages; - std::vector> - availability; + std::vector availability; api::RecipeId recipeId; - bool fromStorage; - std::size_t pageNo; }; struct RecipeStorageAddition { - std::vector selectedStorages; - std::vector addedStorages; - std::vector> - availability; - api::RecipeId recipeId; - bool fromStorage; - std::size_t pageNo; + RecipeView prevState; +}; + +struct ShoppingListCreation { + RecipeView prevState; + std::vector selectedIngredients; + std::vector allIngredients; }; struct CustomRecipesList { @@ -127,8 +135,7 @@ struct CustomRecipeIngredientsSearch { std::size_t pageNo = 0; std::vector searchItems; - template - requires std::convertible_to, IngredientsDb::mapped_type> + template R> CustomRecipeIngredientsSearch(api::RecipeId recipeId, R&& ingredients, std::string inlineQuery) : recipeId(recipeId), recipeIngredients{std::forward(ingredients)}, query(std::move(inlineQuery)) {} }; @@ -141,19 +148,6 @@ struct RecipeCustomView { }; struct CreateCustomRecipe { - api::RecipeId recipeId; - std::size_t pageNo; -}; - -struct ShoppingListCreation { - std::vector selectedStorages; - std::vector addedStorages; - std::vector> - availability; - api::RecipeId recipeId; - std::vector selectedIngredients; - std::vector allIngredients; - bool fromStorage; std::size_t pageNo; }; @@ -176,6 +170,12 @@ struct ShoppingListStorageSelectionToBuy { std::vector selectedIngredients; std::vector storages; }; +struct ShoppingListIngredientSearch { + ShoppingListView prevState; + std::string query; + detail::Pagination pagination; + std::vector page; +}; struct CustomRecipePublicationHistory { api::RecipeId recipeId; @@ -183,7 +183,7 @@ struct CustomRecipePublicationHistory { std::string recipeName; }; -struct AllPublicationHistory { +struct TotalPublicationHistory { std::size_t pageNo; }; @@ -214,7 +214,8 @@ using State = std::variant; + TotalPublicationHistory, + ShoppingListIngredientSearch>; using StateManager = tg_stater::StateProxy>; diff --git a/src/utils/fast_sorted_db.hpp b/src/utils/fast_sorted_db.hpp index ee7cfddf..0ec3cb26 100644 --- a/src/utils/fast_sorted_db.hpp +++ b/src/utils/fast_sorted_db.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include "utils/utils.hpp" + #include #include #include @@ -26,8 +27,7 @@ class FastSortedDb { public: FastSortedDb() = default; - template - requires std::convertible_to, T> + template R> FastSortedDb(R&& items) { // NOLINT(*explicit*) for (auto&& item : std::ranges::views::all(std::forward(items))) put(std::forward(item)); @@ -80,11 +80,11 @@ class FastSortedDb { } [[nodiscard]] auto getValues() { - return items | std::views::transform([](auto& p) -> T& { return p.second; }); + return items | std::views::values; } [[nodiscard]] auto getValues() const { - return items | std::views::transform([](const auto& p) -> const T& { return p.second; }); + return items | std::views::values; } }; diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index 7e7f5edb..449b400c 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -1,42 +1,52 @@ #include "ingredients_availability.hpp" +#include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/storage.hpp" +#include "states.hpp" +#include "tg_types.hpp" +#include #include #include +#include +#include +#include namespace cookcookhnya::utils { using namespace api; +using namespace api::models::storage; using namespace tg_types; - -std::vector> -inStoragesAvailability(std::vector& selectedStorages, - RecipeId recipeId, - UserId userId, - const api::ApiClient& api) { +using IngredientAvailability = states::RecipeView::IngredientAvailability; +using AvailabilityType = states::RecipeView::AvailabilityType; +using namespace std::views; +using namespace std::ranges; + +std::vector inStoragesAvailability(std::vector& selectedStorages, + RecipeId recipeId, + UserId userId, + const api::ApiClient& api) { auto allStorages = api.getStoragesApi().getStoragesList(userId); auto recipe = api.getRecipesApi().get(userId, recipeId); - auto selectedStoragesSet = selectedStorages | std::views::transform(&api::models::storage::StorageSummary::id) | - std::ranges::to(); + auto selectedStoragesSet = selectedStorages | views::transform(&StorageSummary::id) | to(); - std::unordered_map allStoragesMap; + std::unordered_map allStoragesMap; for (const auto& storage : allStorages) { allStoragesMap.emplace(storage.id, storage); } - std::vector> result; + std::vector result; - for (const auto& ingredient : recipe.ingredients) { + for (auto& ingredient : recipe.ingredients) { IngredientAvailability availability; - std::vector storages; + std::vector storages; bool hasInSelected = false; bool hasInOther = false; - for (const auto& storage : ingredient.inStorages) { + for (auto& storage : ingredient.inStorages) { auto it = allStoragesMap.find(storage.id); if (it == allStoragesMap.end()) continue; @@ -49,41 +59,39 @@ inStoragesAvailability(std::vector& selectedSto } } + availability.ingredient = std::move(ingredient); if (hasInSelected) { - availability.available = AvailabiltiyType::available; + availability.available = AvailabilityType::AVAILABLE; availability.storages = std::move(storages); } else if (hasInOther) { - availability.available = AvailabiltiyType::other_storages; + availability.available = AvailabilityType::OTHER_STORAGES; availability.storages = std::move(storages); } else { - availability.available = AvailabiltiyType::not_available; + availability.available = AvailabilityType::NOT_AVAILABLE; } - result.emplace_back(ingredient, std::move(availability)); + result.push_back(std::move(availability)); } return result; } -void addStorage(std::vector>& availability, - const api::models::storage::StorageSummary& storage) { - for (auto& infoPair : availability) { - auto it = std::ranges::find(infoPair.second.storages, storage.id, &models::storage::StorageSummary::id); - if (it != infoPair.second.storages.end()) { - infoPair.second.storages.erase(it); - infoPair.second.available = AvailabiltiyType::available; +void addStorage(std::vector& availability, const StorageSummary& storage) { + for (auto& info : availability) { + auto it = std::ranges::find(info.storages, storage.id, &StorageSummary::id); + if (it != info.storages.end()) { + info.storages.erase(it); + info.available = AvailabilityType::AVAILABLE; } } } -void deleteStorage( - std::vector>& availability, - const api::models::storage::StorageSummary& storage) { +void deleteStorage(std::vector& availability, const StorageSummary& storage) { for (auto& infoPair : availability) { - for (auto& storage_ : infoPair.first.inStorages) { + for (auto& storage_ : infoPair.ingredient.inStorages) { if (storage.id == storage_.id) { - infoPair.second.storages.push_back(storage); - infoPair.second.available = AvailabiltiyType::other_storages; + infoPair.storages.push_back(storage); + infoPair.available = AvailabilityType::OTHER_STORAGES; } } } diff --git a/src/utils/ingredients_availability.hpp b/src/utils/ingredients_availability.hpp index 646cbb08..56acee0b 100644 --- a/src/utils/ingredients_availability.hpp +++ b/src/utils/ingredients_availability.hpp @@ -2,32 +2,24 @@ #include "backend/api/api.hpp" #include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" +#include "states.hpp" +#include "tg_types.hpp" -#include #include namespace cookcookhnya::utils { -enum struct AvailabiltiyType : std::uint8_t { available, not_available, other_storages }; - -struct IngredientAvailability { - AvailabiltiyType available = AvailabiltiyType::not_available; - std::vector storages; -}; - -std::vector> +std::vector inStoragesAvailability(std::vector& selectedStorages, api::RecipeId recipeId, tg_types::UserId userId, const api::ApiClient& api); -void addStorage(std::vector>& availability, +void addStorage(std::vector& availability, const api::models::storage::StorageSummary& storage); -void deleteStorage( - std::vector>& availability, - const api::models::storage::StorageSummary& storage); +void deleteStorage(std::vector& availability, + const api::models::storage::StorageSummary& storage); } // namespace cookcookhnya::utils diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 86927817..f3a20107 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -1,29 +1,11 @@ #include "to_string.hpp" -#include "backend/models/publication_request_status.hpp" - -#include "utils/utils.hpp" -#include "uuid.hpp" - -#include - #include +#include #include namespace cookcookhnya::utils { -std::string to_string(const Uuid& u) { - return boost::lexical_cast(u); -} - -std::string to_string(const cookcookhnya::api::models::status::PublicationRequestStatus status) { - const std::vector statusStr = {utf8str(u8"🟡 На рассмотрении"), - utf8str(u8"🟢 Принят"), - utf8str(u8"🔴 Отклонен"), - utf8str(u8"⚪️ Вы еще не отправили запрос")}; - return statusStr[static_cast(status)]; -} - std::string to_string(std::chrono::system_clock::time_point tp) { std::time_t time = std::chrono::system_clock::to_time_t(tp); std::tm tm = *std::gmtime(&time); diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index 469d288d..2e8532aa 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -1,9 +1,6 @@ #pragma once -#include "backend/models/publication_request_status.hpp" - -#include "uuid.hpp" - +#include #include namespace cookcookhnya::utils { @@ -16,10 +13,6 @@ std::string to_string(const T& t) { return std::to_string(t); } -std::string to_string(const Uuid& u); - -std::string to_string(cookcookhnya::api::models::status::PublicationRequestStatus status); - std::string to_string(std::chrono::system_clock::time_point tp); } // namespace cookcookhnya::utils diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index 25157add..15233d3d 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include #include @@ -17,4 +19,7 @@ std::shared_ptr make_shared(T&& t) { return std::make_shared>(std::forward(t)); } +template +concept range_of = std::ranges::range && std::convertible_to, ValueType>; + } // namespace cookcookhnya::utils diff --git a/src/utils/uuid.cpp b/src/utils/uuid.cpp index 94fa5072..daeae6d5 100644 --- a/src/utils/uuid.cpp +++ b/src/utils/uuid.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace boost::uuids { uuid tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { @@ -12,3 +14,11 @@ uuid tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { } } // namespace boost::uuids + +namespace cookcookhnya::utils { + +std::string to_string(const Uuid& u) { + return boost::lexical_cast(u); +} + +} // namespace cookcookhnya::utils diff --git a/src/utils/uuid.hpp b/src/utils/uuid.hpp index 1343dd0f..eec3d1b8 100644 --- a/src/utils/uuid.hpp +++ b/src/utils/uuid.hpp @@ -22,6 +22,12 @@ using Uuid = boost::uuids::uuid; } // namespace cookcookhnya +namespace cookcookhnya::utils { + +std::string to_string(const Uuid& u); + +} // namespace cookcookhnya::utils + template <> struct std::formatter : formatter { template From 5d2e290dab7ce2849af51bcf4ce233a4716737b7 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Mon, 21 Jul 2025 09:15:11 +0300 Subject: [PATCH 058/106] refactor: fix linter errors --- src/render/personal_account/recipe/search_ingredients.cpp | 2 +- src/render/shopping_list/search.cpp | 1 - src/render/shopping_list/storage_selection_to_buy.cpp | 1 - src/render/shopping_list/view.cpp | 1 - src/render/storage/ingredients/view.cpp | 2 +- src/utils/to_string.cpp | 7 ++++--- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/render/personal_account/recipe/search_ingredients.cpp b/src/render/personal_account/recipe/search_ingredients.cpp index 7d205c2c..23cb4dee 100644 --- a/src/render/personal_account/recipe/search_ingredients.cpp +++ b/src/render/personal_account/recipe/search_ingredients.cpp @@ -69,7 +69,7 @@ void renderSuggestIngredientCustomisation(const states::CustomRecipeIngredientsS ChatId chatId, BotRef bot) { InlineKeyboard keyboard(3); - std::string text = utils::utf8str(u8"📝 Продолжите редактирование запроса или объявите личный ингредиент"); + const std::string text = utils::utf8str(u8"📝 Продолжите редактирование запроса или объявите личный ингредиент"); auto searchButton = std::make_shared(); searchButton->text = utils::utf8str(u8"✏️ Редактировать"); diff --git a/src/render/shopping_list/search.cpp b/src/render/shopping_list/search.cpp index 3e5d059f..a73831a6 100644 --- a/src/render/shopping_list/search.cpp +++ b/src/render/shopping_list/search.cpp @@ -5,7 +5,6 @@ #include "render/common.hpp" #include "render/pagination.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include diff --git a/src/render/shopping_list/storage_selection_to_buy.cpp b/src/render/shopping_list/storage_selection_to_buy.cpp index 6c4583e0..2059fa55 100644 --- a/src/render/shopping_list/storage_selection_to_buy.cpp +++ b/src/render/shopping_list/storage_selection_to_buy.cpp @@ -3,7 +3,6 @@ #include "message_tracker.hpp" #include "render/common.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 0979aca2..7ab4efb6 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -3,7 +3,6 @@ #include "message_tracker.hpp" #include "render/common.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 12ee16a0..fa034e0f 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -80,7 +80,7 @@ void renderSuggestIngredientCustomisation(const states::StorageIngredientsList& ChatId chatId, BotRef bot) { InlineKeyboard keyboard(3); - std::string text = utils::utf8str(u8"📝 Продолжите редактирование запроса или объявите личный ингредиент"); + const std::string text = utils::utf8str(u8"📝 Продолжите редактирование запроса или объявите личный ингредиент"); auto searchButton = std::make_shared(); searchButton->text = utils::utf8str(u8"✏️ Редактировать"); diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 48559758..a699ddae 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -3,15 +3,16 @@ #include #include #include +#include namespace cookcookhnya::utils { std::string to_string(std::chrono::system_clock::time_point tp) { - std::time_t time = std::chrono::system_clock::to_time_t(tp); - std::tm tm = *std::gmtime(&time); + const std::time_t time = std::chrono::system_clock::to_time_t(tp); + const std::tm tm = *std::gmtime(&time); std::ostringstream oss; oss << std::put_time(&tm, "%Y-%m-%d %H:%M"); - return oss.str(); + return std::move(oss).str(); } } // namespace cookcookhnya::utils From 98c86dd01c58c88f86d150f9be7a08a7b1d153a8 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 14:20:54 +0300 Subject: [PATCH 059/106] refactor: fix linting v1 --- .clang-tidy | 4 +++- src/backend/models/recipe.hpp | 2 +- .../personal_account/ingredients_list/create.cpp | 2 +- .../personal_account/recipe/moderation_history.cpp | 2 +- .../personal_account/recipe/search_ingredients.cpp | 2 +- src/handlers/personal_account/recipe/view.cpp | 4 ++-- src/handlers/storage/ingredients/view.cpp | 2 +- .../personal_account/recipe/moderation_history.cpp | 11 ++++++----- src/render/personal_account/recipe/view.cpp | 6 +++--- 9 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 58e27008..f8817f1d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -30,7 +30,9 @@ Checks: "*, -cppcoreguidelines-avoid-do-while, -bugprone-easily-swappable-parameters, -misc-non-private-member-variables-in-classes, - -llvm-header-guard," + -llvm-header-guard, + + -misc-include-cleaner" CheckOptions: cppcoreguidelines-pro-type-member-init.IgnoreArrays: true readability-implicit-bool-conversion.AllowPointerConditions: true diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 0d0d6891..03d78cee 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; - std::optional moderationStatus; + moderation::PublicationRequestStatus moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST; friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index c6feeed1..1128d7c2 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -67,7 +67,7 @@ void handleCustomIngredientConfirmationCQ( if (cq.data == "back") { const std::size_t numOfIngredientsOnPage = 5; - if (state.recipeFrom.has_value()) { + if (state.recipeFrom.has_value() && state.ingredients.has_value()) { auto newState = CustomRecipeIngredientsSearch{state.recipeFrom.value(), state.ingredients.value() | as_rvalue, ""}; renderRecipeIngredientsSearch(newState, numOfIngredientsOnPage, userId, chatId, bot); diff --git a/src/handlers/personal_account/recipe/moderation_history.cpp b/src/handlers/personal_account/recipe/moderation_history.cpp index 84e59d55..bf4b29b6 100644 --- a/src/handlers/personal_account/recipe/moderation_history.cpp +++ b/src/handlers/personal_account/recipe/moderation_history.cpp @@ -26,7 +26,7 @@ void handleCustomRecipePublicationHistoryCQ( if (data == "confirm") { // Peeking (if button with this data then accepted or pending) api.getRecipesApi().publishCustom(userId, state.recipeId); - bool isPeeking = true; + const bool isPeeking = true; renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); stateManager.put(states::CustomRecipePublicationHistory{ .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 27e5070c..edc881a3 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -93,7 +93,7 @@ void handleCustomRecipeIngredientsSearchCQ( } if (cq.data.starts_with("ingredient_")) { - std::string ingredientName{std::string_view{cq.data}.substr("ingredient_"sv.size())}; + const std::string ingredientName{std::string_view{cq.data}.substr("ingredient_"sv.size())}; renderCustomIngredientConfirmation(true, ingredientName, userId, chatId, bot, api); auto ingredients = state.recipeIngredients.getValues() | as_rvalue | to(); stateManager.put(CustomIngredientConfirmation{ingredientName, state.recipeId, ingredients, std::nullopt}); diff --git a/src/handlers/personal_account/recipe/view.cpp b/src/handlers/personal_account/recipe/view.cpp index c06d6903..87a8d670 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -51,7 +51,7 @@ void handleRecipeCustomViewCQ( if (data == "publish") { // Not peeking (if button with this data then idle or rejected) - bool isPeeking = false; + const bool isPeeking = false; renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); stateManager.put(CustomRecipePublicationHistory{ .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); @@ -60,7 +60,7 @@ void handleRecipeCustomViewCQ( } if (data == "peekpublish") { // Peeking (if button with this data then accepted or pending) - bool isPeeking = true; + const bool isPeeking = true; renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); stateManager.put(CustomRecipePublicationHistory{ .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 717eb4db..66e1ddb7 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -101,7 +101,7 @@ void handleStorageIngredientsListCQ( } if (cq.data.starts_with("ingredient_")) { - std::string ingredientName{std::string_view{cq.data}.substr("ingredient_"sv.size())}; + const std::string ingredientName{std::string_view{cq.data}.substr("ingredient_"sv.size())}; renderCustomIngredientConfirmation(true, ingredientName, userId, chatId, bot, api); stateManager.put(CustomIngredientConfirmation{ingredientName, std::nullopt, std::nullopt, state.storageId}); } diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index e14cfa8f..16bf9828 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -8,6 +8,7 @@ #include "utils/utils.hpp" #include +#include #include #include #include @@ -30,11 +31,11 @@ void renderPublicationHistory(UserId userId, std::string toPrint; toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n"); if (!history.empty()) { - toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[history.size() - 1].status) + - (history[history.size() - 1].reason.has_value() - ? std::format(" по причине {} ", history[history.size() - 1].reason.value()) - : " ") + - utils::to_string(history[history.size() - 1].created) + "\n\n"; + size_t lastUpdate = history.size() - 1; + toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdate].status) + + std::format(" по причине {} ", + history[lastUpdate].reason.has_value() ? history[lastUpdate].reason.value() : " ") + + utils::to_string(history[lastUpdate].created) + "\n\n"; // Remove the lastest history instance as it's showed differently history.erase(history.end()); diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index ddb946bf..704a0ca5 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -38,13 +38,13 @@ std::pair, std::string> renderCustomRecipe( }); } - toPrint += "\n🌐 [Статус проверки] " + utils::to_string(recipeDetails.moderationStatus.value()); + toPrint += "\n🌐 [Статус проверки] " + utils::to_string(recipeDetails.moderationStatus); 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.value() == PublicationRequestStatus::NO_REQUEST || - recipeDetails.moderationStatus.value() == PublicationRequestStatus::REJECTED) { + if (recipeDetails.moderationStatus == PublicationRequestStatus::NO_REQUEST || + recipeDetails.moderationStatus == PublicationRequestStatus::REJECTED) { keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; } else { keyboard << makeCallbackButton(u8"📢 История публикаций", "peekpublish") << NewRow{}; From 626c00e8e9b1eb8cd6f6d8fb1e59244637ba7c2e Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 14:53:23 +0300 Subject: [PATCH 060/106] fix: bug with state to string and linting v2 --- .../models/publication_request_status.hpp | 2 +- .../recipe/moderation_history.cpp | 57 +++---------------- 2 files changed, 8 insertions(+), 51 deletions(-) diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp index 875fe4bf..49c46e72 100644 --- a/src/backend/models/publication_request_status.hpp +++ b/src/backend/models/publication_request_status.hpp @@ -8,7 +8,7 @@ namespace cookcookhnya::api::models::moderation { -enum class PublicationRequestStatus : std::uint8_t { NO_REQUEST, PENDING, ACCEPTED, REJECTED }; +enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED, NO_REQUEST }; PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index 16bf9828..0560f259 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -31,11 +31,13 @@ void renderPublicationHistory(UserId userId, std::string toPrint; toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n"); if (!history.empty()) { - size_t lastUpdate = history.size() - 1; - toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdate].status) + - std::format(" по причине {} ", - history[lastUpdate].reason.has_value() ? history[lastUpdate].reason.value() : " ") + - utils::to_string(history[lastUpdate].created) + "\n\n"; + const size_t lastUpdate = history.size() - 1; + // Construct current status string + toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdate].status); + if (history[lastUpdate].reason.has_value()) + toPrint += std::format(" по причине {}", history[lastUpdate].reason.value()); + toPrint += " " + utils::to_string(history[lastUpdate].created) + "\n\n"; + // Remove the lastest history instance as it's showed differently history.erase(history.end()); @@ -63,48 +65,3 @@ void renderPublicationHistory(UserId userId, } } } // namespace cookcookhnya::render::personal_account::recipe - -// Uncomment in case of EMERGENCY (or if you know what for is) -// use instead of history | reverse | ... -/* -const std::vector prefixes = {utils::utf8str(u8"Статус"), - utils::utf8str(u8"по причине"), - utils::utf8str(u8"запрос создан"), - utils::utf8str(u8"последенее обновление")}; - - // What if you forgot what for loop is? Хы-хы (Yes it's probably possible to zip "fields" and "prefixes" and - // interate through it but it would require "for" and i don't know what is it) reverse to print lastest request - the - // last in list - auto res = - history | reverse | transform([&prefixes](const api::models::recipe::PublicationHistoryRecipe& req) { - //!!!!IMPORTANT See that tie to match prefixes!!!! - auto fields = std::tie(req.status, req.reason, req.created, req.updated); - return iota(0U, prefixes.size()) | transform([fields, &prefixes](size_t idx) -> std::string { - switch (idx) { - case 0: - // statusStr to convert enum to string - return prefixes[0] + ": " + utils::to_string(std::get<0>(fields)) + " "; - case 1: - if (std::get<1>(fields).has_value()) { - return prefixes[1] + ": " + std::get<1>(fields).value() + " "; - } - return ", "; - // Need to work with chrono - case 2: - return prefixes[2] + ": " + utils::to_string(std::get<2>(fields)) + ", "; - case 3: - if (std::get<3>(fields).has_value()) { - return prefixes[3] + ": " + utils::to_string(std::get<3>(fields).value()) + "\n\n"; - } - return "\n\n"; - default: - return ""; - } - }); - }) | - join; // Join here instead of in the loop - - std::ranges::for_each(res, [&toPrint](const std::string& s) { toPrint += s; }); - } -*/ From 891777197e176d81be50a1089266b3043a83cfa8 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 15:19:45 +0300 Subject: [PATCH 061/106] refactor: linting v3 --- .../personal_account/recipe/moderation_history.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index 0560f259..02a4b954 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -31,12 +31,14 @@ void renderPublicationHistory(UserId userId, std::string toPrint; toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n"); if (!history.empty()) { - const size_t lastUpdate = history.size() - 1; + const size_t lastUpdatedInstance = history.size() - 1; // Construct current status string - toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdate].status); - if (history[lastUpdate].reason.has_value()) - toPrint += std::format(" по причине {}", history[lastUpdate].reason.value()); - toPrint += " " + utils::to_string(history[lastUpdate].created) + "\n\n"; + toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdatedInstance].status); + if (history[lastUpdatedInstance].reason) { + auto reason = history[lastUpdatedInstance].reason.value(); + toPrint += std::format(" по причине {}", reason); + } + toPrint += " " + utils::to_string(history[lastUpdatedInstance].created) + "\n\n"; // Remove the lastest history instance as it's showed differently history.erase(history.end()); From 5ceae8023111e4a2c2b0f99569c4ee268a8a85e7 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 15:45:02 +0300 Subject: [PATCH 062/106] fix: time synch with backend, lint v3 --- .../personal_account/recipe/moderation_history.cpp | 5 +++-- src/utils/serialization.cpp | 9 ++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index 02a4b954..cfec6d3e 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -35,8 +35,9 @@ void renderPublicationHistory(UserId userId, // Construct current status string toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdatedInstance].status); if (history[lastUpdatedInstance].reason) { - auto reason = history[lastUpdatedInstance].reason.value(); - toPrint += std::format(" по причине {}", reason); + toPrint += + std::format(" по причине {}", + history[lastUpdatedInstance].reason.value()); // NOLINT(bugprone-unchecked-optional-access) } toPrint += " " + utils::to_string(history[lastUpdatedInstance].created) + "\n\n"; diff --git a/src/utils/serialization.cpp b/src/utils/serialization.cpp index 18d0038d..b916fe1e 100644 --- a/src/utils/serialization.cpp +++ b/src/utils/serialization.cpp @@ -5,10 +5,13 @@ namespace cookcookhnya::utils { std::chrono::system_clock::time_point parseIsoTime(std::string s) { - std::istringstream ss{std::move(s)}; std::chrono::system_clock::time_point tp; - ss >> std::chrono::parse("%FT%T%z", tp); - return ss.fail() ? throw std::runtime_error("Could not parse datetime") : tp; + std::istringstream ss(std::move(s)); + ss >> std::chrono::parse("%FT%TZ", tp); // Parse as UTC + if (ss.fail()) { + throw std::runtime_error("Could not parse datetime"); + } + return tp; // Still UTC, but debugger may show local time } } // namespace cookcookhnya::utils From 1ed0c20b2696454e431f43964b687ca3c185c81a Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 15:52:10 +0300 Subject: [PATCH 063/106] fix: now gets timezone of user, lint experiment --- src/render/personal_account/recipe/moderation_history.cpp | 6 ++---- src/utils/to_string.cpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index cfec6d3e..4779be73 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -34,10 +34,8 @@ void renderPublicationHistory(UserId userId, const size_t lastUpdatedInstance = history.size() - 1; // Construct current status string toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdatedInstance].status); - if (history[lastUpdatedInstance].reason) { - toPrint += - std::format(" по причине {}", - history[lastUpdatedInstance].reason.value()); // NOLINT(bugprone-unchecked-optional-access) + if (auto reason = history[lastUpdatedInstance].reason) { + toPrint += std::format(" по причине {}", reason.value()); } toPrint += " " + utils::to_string(history[lastUpdatedInstance].created) + "\n\n"; diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index a699ddae..c8de6895 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -9,7 +9,7 @@ namespace cookcookhnya::utils { std::string to_string(std::chrono::system_clock::time_point tp) { const std::time_t time = std::chrono::system_clock::to_time_t(tp); - const std::tm tm = *std::gmtime(&time); + const std::tm tm = *std::localtime(&time); std::ostringstream oss; oss << std::put_time(&tm, "%Y-%m-%d %H:%M"); return std::move(oss).str(); From b7432e06547442dbf6ed71c00d662bb6eeb958c9 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 19:18:51 +0300 Subject: [PATCH 064/106] refactor: moderation history render --- src/backend/models/recipe.cpp | 4 +- .../recipe/moderation_history.cpp | 29 ++++++++++--- src/handlers/personal_account/recipe/view.cpp | 14 +------ .../recipe/moderation_history.cpp | 41 ++++++++++++++----- .../recipe/moderation_history.hpp | 11 ++--- src/render/personal_account/recipe/view.cpp | 2 +- 6 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index e720d973..1b6666a9 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -73,8 +73,8 @@ 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")) - : PublicationRequestStatus::NO_REQUEST, + .status = j.as_object().if_contains("status") ? value_to(j.at("status")) + : moderation::PublicationRequestStatus::NO_REQUEST, .created = utils::parseIsoTime(value_to(j.at("createdAt"))), .updated = j.as_object().if_contains("updatedAt") ? std::optional{utils::parseIsoTime(value_to(j.at("updatedAt")))} diff --git a/src/handlers/personal_account/recipe/moderation_history.cpp b/src/handlers/personal_account/recipe/moderation_history.cpp index bf4b29b6..0031b92f 100644 --- a/src/handlers/personal_account/recipe/moderation_history.cpp +++ b/src/handlers/personal_account/recipe/moderation_history.cpp @@ -1,5 +1,6 @@ #include "moderation_history.hpp" +#include "backend/models/publication_request_status.hpp" #include "handlers/common.hpp" #include "render/personal_account/recipe/moderation_history.hpp" #include "render/personal_account/recipe/view.hpp" @@ -14,6 +15,17 @@ void handleCustomRecipePublicationHistoryCQ( auto chatId = cq.message->chat->id; auto userId = cq.from->id; + if (data == "backFromRules") { + auto history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); + renderPublicationHistory(userId, chatId, state.recipeName, history, bot); + bot.answerCallbackQuery(cq.id); + return; + } + if (data == "rules") { + renderPublicationRules(userId, chatId, bot); + bot.answerCallbackQuery(cq.id); + return; + } if (data == "back") { auto ingredientsAndRecipeName = renderCustomRecipe(true, userId, chatId, state.recipeId, bot, api); stateManager.put(CustomRecipeView{.recipeId = state.recipeId, @@ -25,11 +37,18 @@ void handleCustomRecipePublicationHistoryCQ( if (data == "confirm") { // Peeking (if button with this data then accepted or pending) - api.getRecipesApi().publishCustom(userId, state.recipeId); - const bool isPeeking = true; - renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); - stateManager.put(states::CustomRecipePublicationHistory{ - .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); + 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); + + if (shouldPublish) { + api.getRecipesApi().publishCustom(userId, state.recipeId); + // Get updated history + history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); + } + renderPublicationHistory(userId, chatId, state.recipeName, 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 87a8d670..409c4314 100644 --- a/src/handlers/personal_account/recipe/view.cpp +++ b/src/handlers/personal_account/recipe/view.cpp @@ -50,18 +50,8 @@ void handleRecipeCustomViewCQ( } if (data == "publish") { - // Not peeking (if button with this data then idle or rejected) - const bool isPeeking = false; - renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); - stateManager.put(CustomRecipePublicationHistory{ - .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); - bot.answerCallbackQuery(cq.id); - return; - } - if (data == "peekpublish") { - // Peeking (if button with this data then accepted or pending) - const bool isPeeking = true; - renderPublicationHistory(userId, chatId, state.recipeId, state.recipeName, isPeeking, bot, api); + auto history = api.getRecipesApi().getRecipeRequestHistory(userId, state.recipeId); + renderPublicationHistory(userId, chatId, state.recipeName, history, bot); stateManager.put(CustomRecipePublicationHistory{ .recipeId = state.recipeId, .pageNo = state.pageNo, .recipeName = state.recipeName}); bot.answerCallbackQuery(cq.id); diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index 4779be73..690f21ac 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -1,6 +1,5 @@ #include "moderation_history.hpp" -#include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" @@ -19,18 +18,18 @@ using namespace std::views; void renderPublicationHistory(UserId userId, ChatId chatId, - api::RecipeId recipeId, std::string& recipeName, - bool isPeek, - BotRef bot, - api::RecipesApiRef recipesApi) { - auto history = recipesApi.getRecipeRequestHistory(userId, recipeId); + std::vector history, + BotRef bot) { - InlineKeyboardBuilder keyboard{2}; // confirm and back + bool isConfirm = true; + InlineKeyboardBuilder keyboard{3}; // confirm, back and rules std::string toPrint; - toPrint = (utils::utf8str(u8"История запросов на публикацию *") + recipeName + "*\n"); + 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; const size_t lastUpdatedInstance = history.size() - 1; // Construct current status string toPrint += utils::utf8str(u8"ℹ️ Текущий статус: ") + utils::to_string(history[lastUpdatedInstance].status); @@ -54,15 +53,35 @@ void renderPublicationHistory(UserId userId, } } - // Text is created moving to the markup - // If not peeking then show accept button - if (!isPeek) { + if (isConfirm) { keyboard << makeCallbackButton(u8"▶️Подтвердить", "confirm") << NewRow{}; } + keyboard << makeCallbackButton(u8"❗️Правила", "rules") << NewRow{}; keyboard << makeCallbackButton(u8"↩️ Назад", "back"); if (auto messageId = message::getMessageId(userId)) { bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); } } + +void renderPublicationRules(UserId userId, ChatId chatId, BotRef bot) { + // Rules + std::string toPrint = + utils::utf8str(u8"❗️ *Правила публикации рецептов:*") + + "\n1. *Статус рецепта*\nНельзя отправить запрос на публикацию, если рецепт:\n - уже принят " + "(опубликован);\n - находится на рассмотрении модерации. \n2. *Ингредиенты* \n Запрещено публиковать " + "рецепт, если в нём есть:\n - ингредиенты, не прошедшие модерацию (_ожидают проверки_);\n - " + "ингредиенты, отклонённые администрацией.\n3. *Название рецепта*\n - Не должно содержать " + "нецензурную лексику, оскорбления или спам;\n - Должно точно отражать содержание рецепта (например: " + "\"Спагетти карбонара\", а не \"Вкуснятина\"). \n4. *Дополнительно*\n - Запрещено размещать контактные " + "данные или рекламу." + + utils::utf8str(u8"\n⚠️ Нарушение правил приведёт к отклонению рецепта "); + + InlineKeyboardBuilder keyboard{1}; // back + keyboard << makeCallbackButton(u8"↩️ Назад", "backFromRules"); + + if (auto messageId = message::getMessageId(userId)) { + bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); + } +} } // namespace cookcookhnya::render::personal_account::recipe diff --git a/src/render/personal_account/recipe/moderation_history.hpp b/src/render/personal_account/recipe/moderation_history.hpp index eb2177e7..6965fe34 100644 --- a/src/render/personal_account/recipe/moderation_history.hpp +++ b/src/render/personal_account/recipe/moderation_history.hpp @@ -1,17 +1,14 @@ #pragma once -#include "backend/api/recipes.hpp" -#include "backend/id_types.hpp" +#include "backend/models/recipe.hpp" #include "render/common.hpp" namespace cookcookhnya::render::personal_account::recipe { void renderPublicationHistory(UserId userId, ChatId chatId, - api::RecipeId recipeId, std::string& recipeName, - bool isPeek, - BotRef bot, - api::RecipesApiRef recipesApi); - + std::vector history, + BotRef bot); +void renderPublicationRules(UserId userId, ChatId chatId, BotRef bot); } // namespace cookcookhnya::render::personal_account::recipe diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 704a0ca5..077615d2 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -47,7 +47,7 @@ std::pair, std::string> renderCustomRecipe( recipeDetails.moderationStatus == PublicationRequestStatus::REJECTED) { keyboard << makeCallbackButton(u8"📢 Опубликовать", "publish") << NewRow{}; } else { - keyboard << makeCallbackButton(u8"📢 История публикаций", "peekpublish") << NewRow{}; + keyboard << makeCallbackButton(u8"📢 История публикаций", "publish") << NewRow{}; } keyboard << makeCallbackButton(u8"↩️ Назад", "back"); From ca7e0ce6eef7f5ae38f2ecc44a6484d8208152d9 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Mon, 21 Jul 2025 19:46:10 +0300 Subject: [PATCH 065/106] linting v4 --- src/render/personal_account/recipe/moderation_history.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/personal_account/recipe/moderation_history.cpp b/src/render/personal_account/recipe/moderation_history.cpp index 690f21ac..23190baa 100644 --- a/src/render/personal_account/recipe/moderation_history.cpp +++ b/src/render/personal_account/recipe/moderation_history.cpp @@ -66,7 +66,7 @@ void renderPublicationHistory(UserId userId, void renderPublicationRules(UserId userId, ChatId chatId, BotRef bot) { // Rules - std::string toPrint = + const std::string toPrint = utils::utf8str(u8"❗️ *Правила публикации рецептов:*") + "\n1. *Статус рецепта*\nНельзя отправить запрос на публикацию, если рецепт:\n - уже принят " "(опубликован);\n - находится на рассмотрении модерации. \n2. *Ингредиенты* \n Запрещено публиковать " From 87a4cf94f76296ab26db2956361a06518cf8ca80 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Mon, 21 Jul 2025 22:22:39 +0300 Subject: [PATCH 066/106] fix: normal view of pending and no_req statuses --- src/backend/api/ingredients.cpp | 2 +- src/backend/models/ingredient.cpp | 7 ++-- src/backend/models/ingredient.hpp | 3 +- .../models/publication_request_status.cpp | 10 +++--- .../models/publication_request_status.hpp | 4 +-- .../recipe/search_ingredients.cpp | 6 +++- src/handlers/recipe/view.cpp | 11 ++++-- src/handlers/storage/ingredients/view.cpp | 5 ++- .../ingredients_list/publish.cpp | 36 ++++++++++++------- .../ingredients_list/view.cpp | 8 ++--- src/render/personal_account/recipe/view.cpp | 7 ++-- src/utils/to_string.cpp | 2 +- src/utils/to_string.hpp | 2 +- 13 files changed, 61 insertions(+), 42 deletions(-) diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index c1235035..1b22d4db 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -82,7 +82,7 @@ IngredientSearchResponse IngredientsApi::search(UserId user, {"filter", utils::to_string(filter)}}); } -// GET /recipes +// GET /ingredients IngredientList IngredientsApi::getList(UserId user, PublicityFilterType filter, std::size_t count, std::size_t offset) const { auto result = search(user, filter, "", 0, count, offset); diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index 1228ba30..2a0cc39e 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -1,4 +1,5 @@ #include "backend/models/ingredient.hpp" +#include "backend/models/publication_request_status.hpp" #include #include @@ -12,9 +13,9 @@ Ingredient tag_invoke(json::value_to_tag /*tag*/, const json::value& return { .id = value_to(j.at("id")), .name = value_to(j.at("name")), - .status = j.as_object().if_contains("moderation_status") - ? value_to(j.at("moderation_status")) - : std::nullopt, + .moderationStatus = j.as_object().if_contains("moderationStatus") + ? value_to(j.at("moderationStatus")) + : moderation::PublicationRequestStatus::NO_REQUEST, }; } diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index 76a59244..719bd0ed 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -16,7 +15,7 @@ namespace cookcookhnya::api::models::ingredient { struct Ingredient { IngredientId id; std::string name; - std::optional status = std::nullopt; + moderation::PublicationRequestStatus moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST; friend Ingredient 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 a6d00a90..d5117335 100644 --- a/src/backend/models/publication_request_status.cpp +++ b/src/backend/models/publication_request_status.cpp @@ -1,16 +1,16 @@ #include "backend/models/publication_request_status.hpp" -namespace cookcookhnya::api::models::status { +namespace cookcookhnya::api::models::moderation { PublicationRequestStatus tag_invoke(boost::json::value_to_tag /*tag*/, const boost::json::value& j) { - if (j.at("status") == "Pending") + if (j == "pending") return PublicationRequestStatus::PENDING; - if (j.at("status") == "Accepted") + if (j == "accepted") return PublicationRequestStatus::ACCEPTED; - if (j.at("status") == "Rejected") + if (j == "rejected") return PublicationRequestStatus::REJECTED; return PublicationRequestStatus::NO_REQUEST; } -} // namespace cookcookhnya::api::models::status +} // namespace cookcookhnya::api::models::moderation diff --git a/src/backend/models/publication_request_status.hpp b/src/backend/models/publication_request_status.hpp index c3cf284a..56727588 100644 --- a/src/backend/models/publication_request_status.hpp +++ b/src/backend/models/publication_request_status.hpp @@ -4,10 +4,10 @@ #include -namespace cookcookhnya::api::models::status { +namespace cookcookhnya::api::models::moderation { enum class PublicationRequestStatus : std::uint8_t { PENDING, ACCEPTED, REJECTED, NO_REQUEST }; PublicationRequestStatus tag_invoke(boost::json::value_to_tag, const boost::json::value& j); -} // namespace cookcookhnya::api::models::status +} // namespace cookcookhnya::api::models::moderation diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 233faace..74f4552a 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -90,7 +91,10 @@ void handleCustomRecipeIngredientsSearchCQ( state.recipeIngredients.remove(*mIngredient); } else { api.getIngredientsApi().putToRecipe(userId, state.recipeId, *mIngredient); - state.recipeIngredients.put({.id = it->id, .name = it->name}); + state.recipeIngredients.put( + {.id = it->id, + .name = it->name, + .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); } it->isInRecipe = !it->isInRecipe; renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, chatId, bot); diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index bd94a513..dfde952b 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -7,6 +7,7 @@ #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" +#include #include #include @@ -29,9 +30,15 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe std::vector allIngredients; for (const auto& infoPair : state.availability) { if (infoPair.second.available == utils::AvailabiltiyType::not_available) { - selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); + selectedIngredients.push_back( + {.id = infoPair.first.id, + .name = infoPair.first.name, + .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); } - allIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); + allIngredients.push_back( + {.id = infoPair.first.id, + .name = infoPair.first.name, + .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); } renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); stateManager.put(ShoppingListCreation{.selectedStorages = state.selectedStorages, diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 7f979f8e..cbbe04a2 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -84,7 +84,10 @@ void handleStorageIngredientsListCQ( state.storageIngredients.remove(*mIngredient); } else { api.getIngredientsApi().putToStorage(userId, state.storageId, *mIngredient); - state.storageIngredients.put({.id = it->id, .name = it->name}); + state.storageIngredients.put( + {.id = it->id, + .name = it->name, + .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); } it->isInStorage = !it->isInStorage; renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, chatId, bot); diff --git a/src/render/personal_account/ingredients_list/publish.cpp b/src/render/personal_account/ingredients_list/publish.cpp index 3b65b9f7..d6788c5c 100644 --- a/src/render/personal_account/ingredients_list/publish.cpp +++ b/src/render/personal_account/ingredients_list/publish.cpp @@ -1,41 +1,51 @@ #include "publish.hpp" #include "backend/api/publicity_filter.hpp" +#include "backend/models/ingredient.hpp" +#include "backend/models/publication_request_status.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/to_string.hpp" #include "utils/utils.hpp" -#include #include +#include #include +#include + namespace cookcookhnya::render::personal_account::ingredients { -using namespace tg_types; +using namespace std::views; void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { auto ingredientsResp = api.search(userId, PublicityFilterType::Custom); // TODO: make pagination for ingredients - auto ingredients = ingredientsResp.page; - const std::size_t buttonRows = ((ingredients.size() + 1) / 2) + 1; - InlineKeyboard keyboard(buttonRows); - for (std::size_t i = 0; i < ingredients.size(); i++) { - if (i % 2 == 0) - keyboard[(i / 2)].reserve(2); - keyboard[(i / 2)].push_back( - makeCallbackButton("• " + ingredients[i].name, utils::to_string(ingredients[i].id))); + std::vector ingredients; + for (const auto& ing : ingredientsResp.page){ + if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { + ingredients.push_back(ing); + } + } + + InlineKeyboardBuilder keyboard{ingredients.size() + 1}; + for (auto chunk : ingredients | chunk(2)) { + keyboard.reserveInRow(2); + for (const auto& i : chunk){ + keyboard << makeCallbackButton("• " + i.name, utils::to_string(i.id)); + } + keyboard << NewRow{}; } - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); auto text = std::format( - "{} Какой ингредиент вы хотите предложить для добавления в CookCookNya? (Все предложения проходят проверку)\n ", + "{} Какой ингредиент вы хотите предложить для добавления в CookCookhNya? (Все предложения проходят проверку)\n ", utils::utf8str(u8"📥")); auto messageId = message::getMessageId(userId); if (messageId) { - bot.editMessageText(text, chatId, *messageId, "", "", nullptr, makeKeyboardMarkup(std::move(keyboard))); + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); } } diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index a5bc7ebd..e9f8f03e 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -30,9 +30,7 @@ std::pair> constructN text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - if (ing.status) { - text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); - } + text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); } std::vector buttons; @@ -72,9 +70,7 @@ std::pair constructMessage(size_t pageNo, } else if (ingredientsList.found <= numOfIngredientsOnPage) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - if (ing.status) { - text += std::format("• {}, Статус: {}\n", utils::to_string(*ing.status), ing.name); - } + text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); } keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index e3dfe969..582e8c14 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -26,10 +26,9 @@ std::vector renderCustomRecipe( toPrint += (utils::utf8str(u8"Рецепт: ") + recipeDetails.name + "\n"); for (auto& it : recipeDetails.ingredients) { toPrint += std::format("• {}\n", it.name); - ingredients.push_back({ - .id = it.id, - .name = it.name, - }); + ingredients.push_back({.id = it.id, + .name = it.name, + .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); } if (recipeDetails.link) diff --git a/src/utils/to_string.cpp b/src/utils/to_string.cpp index 2ea41a16..01f97cc4 100644 --- a/src/utils/to_string.cpp +++ b/src/utils/to_string.cpp @@ -15,7 +15,7 @@ std::string to_string(const Uuid& u) { return boost::lexical_cast(u); } -std::string to_string(const cookcookhnya::api::models::status::PublicationRequestStatus status) { +std::string to_string(const cookcookhnya::api::models::moderation::PublicationRequestStatus status) { const std::array statusStr = {utf8str(u8"🟡 На рассмотрении"), utf8str(u8"🟢 Принят"), utf8str(u8"🔴 Отклонен"), diff --git a/src/utils/to_string.hpp b/src/utils/to_string.hpp index 815ef722..5bf47492 100644 --- a/src/utils/to_string.hpp +++ b/src/utils/to_string.hpp @@ -17,6 +17,6 @@ std::string to_string(const T& t) { std::string to_string(const Uuid& u); -std::string to_string(api::models::status::PublicationRequestStatus status); +std::string to_string(api::models::moderation::PublicationRequestStatus status); } // namespace cookcookhnya::utils From cf27905c4a00b5cbc25e7ae268d1dafeabff34b2 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 03:42:12 +0300 Subject: [PATCH 067/106] fix: page size for suggested recipes --- src/render/recipes_suggestions/view.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index a05b6507..b2d7091b 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -49,10 +49,10 @@ void renderRecipesSuggestion(std::vector& storages, BotRef bot, api::RecipesApiRef recipesApi) { const std::size_t numOfRecipesOnPage = 5; - const std::size_t numOfRecipes = 500; auto storagesIds = storages | views::transform(&StorageSummary::id) | to(); - auto recipesList = recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipes, pageNo * numOfRecipesOnPage); + auto recipesList = + recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); const std::string text = utils::utf8str(recipesList.found > 0 ? u8"🔪 Рецепты подобранные специально для вас" From fa46f38826da429ab96708f8e68e5923579873ea Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 03:47:41 +0300 Subject: [PATCH 068/106] fix: shopping list command --- src/handlers/commands/shopping_list.cpp | 16 ++++++++++++---- src/handlers/commands/shopping_list.hpp | 3 ++- src/handlers/main_menu/view.cpp | 4 +--- src/states.hpp | 6 ++++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/handlers/commands/shopping_list.cpp b/src/handlers/commands/shopping_list.cpp index cf435cae..2a3f19fc 100644 --- a/src/handlers/commands/shopping_list.cpp +++ b/src/handlers/commands/shopping_list.cpp @@ -1,18 +1,26 @@ #include "shopping_list.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" #include "message_tracker.hpp" #include "render/shopping_list/view.hpp" #include "states.hpp" +#include + namespace cookcookhnya::handlers::commands { using namespace render::shopping_list; -void handleShoppingListCmd(MessageRef m, BotRef bot, SMRef stateManager) { - auto newState = ShoppingListView{}; - message::deleteMessageId(m.from->id); - renderShoppingList(newState, m.from->id, m.chat->id, bot); +void handleShoppingListCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + const auto userId = m.from->id; + + const bool hasStorages = !api.getStoragesApi().getStoragesList(userId).empty(); + auto items = api.getShoppingListApi().get(userId); + + auto newState = ShoppingListView{.items = std::move(items), .canBuy = hasStorages}; + message::deleteMessageId(userId); + renderShoppingList(newState, userId, m.chat->id, bot); stateManager.put(newState); }; diff --git a/src/handlers/commands/shopping_list.hpp b/src/handlers/commands/shopping_list.hpp index cc5aae0a..dcb89c7e 100644 --- a/src/handlers/commands/shopping_list.hpp +++ b/src/handlers/commands/shopping_list.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::commands { -void handleShoppingListCmd(MessageRef m, BotRef bot, SMRef stateManager); +void handleShoppingListCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::commands diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index e5225668..bef43ad5 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -51,10 +51,8 @@ void handleMainMenuCQ( if (cq.data == "shopping_list") { const bool canBuy = !storages.empty(); auto items = api.getShoppingListApi().get(userId); - ShoppingListView::ItemsDb itemsDb{ - items | transform([](auto& i) { return ShoppingListView::SelectableItem{std::move(i)}; })}; - auto newState = ShoppingListView{.items = std::move(itemsDb), .canBuy = canBuy}; + auto newState = ShoppingListView{.items = std::move(items), .canBuy = canBuy}; renderShoppingList(newState, userId, chatId, bot); stateManager.put(std::move(newState)); return; diff --git a/src/states.hpp b/src/states.hpp index a1a205ae..0552e990 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -23,7 +23,7 @@ namespace detail { struct StorageIdMixin { api::StorageId storageId; - StorageIdMixin(api::StorageId storageId) : storageId{storageId} {} // NOLINT(*-explicit-*) + StorageIdMixin(api::StorageId storageId) : storageId{storageId} {} // NOLINT(*explicit*) }; struct Pagination { @@ -151,8 +151,10 @@ struct RecipeIngredientsSearch { struct ShoppingListView { // NOLINT(*member-init) // Strange. Flags only this struct due to ItemsDb struct SelectableItem : api::models::shopping_list::ShoppingListItem { bool selected = false; + SelectableItem(api::models::shopping_list::ShoppingListItem item) // NOLINT(*explicit*) + : ShoppingListItem{std::move(item)} {} }; - using ItemsDb = utils::FastSortedDb; + using ItemsDb = utils::FastSortedDb; ItemsDb items; bool canBuy; From 7912d46085071e11b3e234dd900cf7a99f08cd51 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 04:00:55 +0300 Subject: [PATCH 069/106] feat: make shopping list of two columns --- src/render/recipes_suggestions/view.cpp | 1 - src/render/shopping_list/view.cpp | 14 +++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index b2d7091b..3f407c93 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -1,6 +1,5 @@ #include "view.hpp" -#include "backend/api/api.hpp" #include "backend/models/recipe.hpp" #include "backend/models/storage.hpp" #include "message_tracker.hpp" diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 0979aca2..95f344f0 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -3,7 +3,6 @@ #include "message_tracker.hpp" #include "render/common.hpp" #include "states.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -12,11 +11,13 @@ namespace cookcookhnya::render::shopping_list { +using namespace std::views; + void renderShoppingList(const states::ShoppingListView& state, UserId userId, ChatId chatId, BotRef bot) { auto items = state.items.getValues(); const bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); - InlineKeyboardBuilder keyboard{3 + items.size()}; // add, remove and/or buy, list (n), back + InlineKeyboardBuilder keyboard{3 + ((items.size() / 2) + 1)}; // add, remove and/or buy, list (n/2), back keyboard << makeCallbackButton(u8"🔍 Поиск", "search") << NewRow{}; @@ -27,9 +28,12 @@ void renderShoppingList(const states::ShoppingListView& state, UserId userId, Ch keyboard << NewRow{}; } - for (const auto& item : items) { - const char* const selectedMark = item.selected ? "[ + ] " : "[ᅠ] "; - keyboard << makeCallbackButton(selectedMark + item.name, utils::to_string(item.ingredientId)) << NewRow{}; + for (auto row : items | chunk(2)) { + for (const auto& item : row) { + const char* const selectedMark = item.selected ? "[ + ] " : "[ᅠ] "; // not empty! + keyboard << makeCallbackButton(selectedMark + item.name, utils::to_string(item.ingredientId)); + } + keyboard << NewRow{}; } keyboard << makeCallbackButton(u8"↩️ Назад", "back"); From 6e218d6b7c4bff1b45a7cf035dbc3b0fc28ceca4 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 04:35:16 +0300 Subject: [PATCH 070/106] refactor: move /recipe to /suggested_recipe --- src/handlers/CMakeLists.txt | 4 +- src/handlers/handlers_list.hpp | 6 +- src/handlers/recipe/add_storage.cpp | 74 ------------ src/handlers/recipe/add_storage.hpp | 11 -- src/handlers/recipe/view.cpp | 75 ------------ src/handlers/recipe/view.hpp | 10 -- src/handlers/recipes_suggestions/view.cpp | 4 +- src/handlers/shopping_list/create.cpp | 4 +- src/handlers/suggested_recipe/add_storage.cpp | 6 +- src/handlers/suggested_recipe/add_storage.hpp | 4 +- src/handlers/suggested_recipe/view.cpp | 6 +- src/handlers/suggested_recipe/view.hpp | 4 +- src/render/CMakeLists.txt | 4 +- src/render/recipe/add_storage.cpp | 113 ------------------ src/render/recipe/add_storage.hpp | 29 ----- src/render/recipe/view.cpp | 81 ------------- src/render/recipe/view.hpp | 31 ----- src/render/suggested_recipe/add_storage.cpp | 4 +- src/render/suggested_recipe/add_storage.hpp | 4 +- src/render/suggested_recipe/view.cpp | 4 +- src/render/suggested_recipe/view.hpp | 4 +- 21 files changed, 29 insertions(+), 453 deletions(-) delete mode 100644 src/handlers/recipe/add_storage.cpp delete mode 100644 src/handlers/recipe/add_storage.hpp delete mode 100644 src/handlers/recipe/view.cpp delete mode 100644 src/handlers/recipe/view.hpp delete mode 100644 src/render/recipe/add_storage.cpp delete mode 100644 src/render/recipe/add_storage.hpp delete mode 100644 src/render/recipe/view.cpp delete mode 100644 src/render/recipe/view.hpp diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 70018ba9..73fab435 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -21,8 +21,8 @@ target_sources(main PRIVATE src/handlers/personal_account/view.cpp src/handlers/personal_account/publication_history.cpp - src/handlers/recipe/add_storage.cpp - src/handlers/recipe/view.cpp + src/handlers/suggested_recipe/add_storage.cpp + src/handlers/suggested_recipe/view.cpp src/handlers/recipes_suggestions/view.cpp diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 907d08d0..3644ffe2 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -23,8 +23,8 @@ #include "personal_account/publication_history.hpp" #include "personal_account/view.hpp" -#include "recipe/add_storage.hpp" -#include "recipe/view.hpp" +#include "suggested_recipe/add_storage.hpp" +#include "suggested_recipe/view.hpp" #include "recipes_suggestions/view.hpp" @@ -60,7 +60,7 @@ using namespace handlers::personal_account::ingredients; using namespace handlers::personal_account::recipe; using namespace handlers::personal_account::recipes_list; using namespace handlers::shopping_list; -using namespace handlers::recipe; +using namespace handlers::suggested_recipe; using namespace handlers::storage; using namespace handlers::storage::ingredients; using namespace handlers::storage::members; diff --git a/src/handlers/recipe/add_storage.cpp b/src/handlers/recipe/add_storage.cpp deleted file mode 100644 index 29be33ed..00000000 --- a/src/handlers/recipe/add_storage.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "add_storage.hpp" - -#include "backend/api/api.hpp" -#include "backend/id_types.hpp" -#include "backend/models/storage.hpp" -#include "handlers/common.hpp" -#include "render/recipe/add_storage.hpp" -#include "render/recipe/view.hpp" -#include "states.hpp" -#include "utils/ingredients_availability.hpp" -#include "utils/parsing.hpp" - -#include -#include -#include -#include - -namespace cookcookhnya::handlers::recipe { - -using namespace render::recipe; -using namespace api::models::storage; - -void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { - bot.answerCallbackQuery(cq.id); - const std::string& data = cq.data; - auto chatId = cq.message->chat->id; - auto userId = cq.from->id; - - if (data == "back") { - renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); - stateManager.put(auto{std::move(state.prevState)}); - return; - } - - if (data[0] == '-') { - auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); - if (newStorageId) { - auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - const StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; - state.prevState.addedStorages.push_back(newStorage); - utils::addStorage(state.prevState.availability, newStorage); - renderStoragesSuggestion(state.prevState.availability, - state.prevState.prevState.selectedStorages, - state.prevState.addedStorages, - state.prevState.recipeId, - userId, - chatId, - bot, - api); - } - } - - if (data[0] == '+') { - auto newStorageId = utils::parseSafe(std::string_view{data}.substr(1)); - if (newStorageId) { - auto newStorageDetails = api.getStoragesApi().get(userId, *newStorageId); - const StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; - state.prevState.addedStorages.erase(std::ranges::find( - state.prevState.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); - utils::deleteStorage(state.prevState.availability, newStorage); - renderStoragesSuggestion(state.prevState.availability, - state.prevState.prevState.selectedStorages, - state.prevState.addedStorages, - state.prevState.recipeId, - userId, - chatId, - bot, - api); - } - } -} - -} // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/add_storage.hpp b/src/handlers/recipe/add_storage.hpp deleted file mode 100644 index ac477b12..00000000 --- a/src/handlers/recipe/add_storage.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "backend/api/api.hpp" -#include "handlers/common.hpp" - -namespace cookcookhnya::handlers::recipe { - -void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); - -} // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp deleted file mode 100644 index 12f3de20..00000000 --- a/src/handlers/recipe/view.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "view.hpp" - -#include "backend/api/api.hpp" -#include "backend/id_types.hpp" -#include "backend/models/ingredient.hpp" -#include "handlers/common.hpp" -#include "render/recipe/add_storage.hpp" -#include "render/recipes_suggestions/view.hpp" -#include "render/shopping_list/create.hpp" -#include "states.hpp" - -#include -#include -#include - -namespace cookcookhnya::handlers::recipe { - -using namespace render::recipes_suggestions; -using namespace render::shopping_list; -using namespace render::recipe; -using namespace api::models::ingredient; -using IngredientAvailability = states::RecipeView::IngredientAvailability; -using AvailabilityType = states::RecipeView::AvailabilityType; - -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { - const std::string data = cq.data; - auto chatId = cq.message->chat->id; - auto userId = cq.from->id; - - if (data == "start_cooking") { - // TODO: add state of begginig of cooking - return; - } - - if (data == "shopping_list") { - std::vector selectedIngredients; - std::vector allIngredients; - for (const auto& av : state.availability) { - if (av.available == AvailabilityType::NOT_AVAILABLE) { - selectedIngredients.push_back({.id = av.ingredient.id, .name = av.ingredient.name}); - } - allIngredients.push_back({.id = av.ingredient.id, .name = av.ingredient.name}); - } - renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); - stateManager.put(ShoppingListCreation{ - .prevState = std::move(state), - .selectedIngredients = selectedIngredients, - .allIngredients = allIngredients, - }); - bot.answerCallbackQuery(cq.id); - return; - } - - if (data == "back_from_recipe_view") { - renderRecipesSuggestion(state.prevState.selectedStorages, state.prevState.pageNo, userId, chatId, bot, api); - stateManager.put(auto{std::move(state.prevState)}); - bot.answerCallbackQuery(cq.id); - return; - } - - if (data == "add_storages") { - renderStoragesSuggestion(state.availability, - state.prevState.selectedStorages, - state.addedStorages, - state.recipeId, - userId, - chatId, - bot, - api); - stateManager.put(RecipeStorageAddition{.prevState = std::move(state)}); - return; - } -} - -} // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/view.hpp b/src/handlers/recipe/view.hpp deleted file mode 100644 index 6cefa542..00000000 --- a/src/handlers/recipe/view.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "backend/api/api.hpp" -#include "handlers/common.hpp" - -namespace cookcookhnya::handlers::recipe { - -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); - -} // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index d8e96e97..8f276471 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -4,10 +4,10 @@ #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" -#include "render/recipe/view.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/storage/view.hpp" #include "render/storages_selection/view.hpp" +#include "render/suggested_recipe/view.hpp" #include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" @@ -20,7 +20,7 @@ namespace cookcookhnya::handlers::recipes_suggestions { using namespace render::recipes_suggestions; using namespace render::select_storages; using namespace render::storage; -using namespace render::recipe; +using namespace render::suggested_recipe; using namespace render::main_menu; void handleSuggestedRecipesListCQ( diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 6bdfc354..0c2e65a5 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -4,8 +4,8 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" -#include "render/recipe/view.hpp" #include "render/shopping_list/create.hpp" +#include "render/suggested_recipe/view.hpp" #include "utils/parsing.hpp" #include @@ -16,7 +16,7 @@ namespace cookcookhnya::handlers::shopping_list { using namespace render::shopping_list; -using namespace render::recipe; +using namespace render::suggested_recipe; void handleShoppingListCreationCQ( ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { diff --git a/src/handlers/suggested_recipe/add_storage.cpp b/src/handlers/suggested_recipe/add_storage.cpp index 1d4b530d..699ed372 100644 --- a/src/handlers/suggested_recipe/add_storage.cpp +++ b/src/handlers/suggested_recipe/add_storage.cpp @@ -15,9 +15,9 @@ #include #include -namespace cookcookhnya::handlers::recipe { +namespace cookcookhnya::handlers::suggested_recipe { -using namespace render::recipe; +using namespace render::suggested_recipe; using namespace api::models::storage; void handleRecipeStorageAdditionCQ( @@ -71,4 +71,4 @@ void handleRecipeStorageAdditionCQ( } } -} // namespace cookcookhnya::handlers::recipe +} // namespace cookcookhnya::handlers::suggested_recipe diff --git a/src/handlers/suggested_recipe/add_storage.hpp b/src/handlers/suggested_recipe/add_storage.hpp index ac477b12..9e08e3b9 100644 --- a/src/handlers/suggested_recipe/add_storage.hpp +++ b/src/handlers/suggested_recipe/add_storage.hpp @@ -3,9 +3,9 @@ #include "backend/api/api.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::recipe { +namespace cookcookhnya::handlers::suggested_recipe { void handleRecipeStorageAdditionCQ( RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); -} // namespace cookcookhnya::handlers::recipe +} // namespace cookcookhnya::handlers::suggested_recipe diff --git a/src/handlers/suggested_recipe/view.cpp b/src/handlers/suggested_recipe/view.cpp index 4a8af62e..5af4b5fe 100644 --- a/src/handlers/suggested_recipe/view.cpp +++ b/src/handlers/suggested_recipe/view.cpp @@ -13,11 +13,11 @@ #include #include -namespace cookcookhnya::handlers::recipe { +namespace cookcookhnya::handlers::suggested_recipe { using namespace render::recipes_suggestions; using namespace render::shopping_list; -using namespace render::recipe; +using namespace render::suggested_recipe; using namespace api::models::ingredient; using IngredientAvailability = states::RecipeView::IngredientAvailability; using AvailabilityType = states::RecipeView::AvailabilityType; @@ -72,4 +72,4 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe } } -} // namespace cookcookhnya::handlers::recipe +} // namespace cookcookhnya::handlers::suggested_recipe diff --git a/src/handlers/suggested_recipe/view.hpp b/src/handlers/suggested_recipe/view.hpp index 6cefa542..512c6af6 100644 --- a/src/handlers/suggested_recipe/view.hpp +++ b/src/handlers/suggested_recipe/view.hpp @@ -3,8 +3,8 @@ #include "backend/api/api.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::recipe { +namespace cookcookhnya::handlers::suggested_recipe { void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); -} // namespace cookcookhnya::handlers::recipe +} // namespace cookcookhnya::handlers::suggested_recipe diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index d397f9e7..bdbde47a 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -15,8 +15,8 @@ target_sources(main PRIVATE src/render/personal_account/view.cpp src/render/personal_account/publication_history.cpp - src/render/recipe/add_storage.cpp - src/render/recipe/view.cpp + src/render/suggested_recipe/add_storage.cpp + src/render/suggested_recipe/view.cpp src/render/recipes_suggestions/view.cpp diff --git a/src/render/recipe/add_storage.cpp b/src/render/recipe/add_storage.cpp deleted file mode 100644 index 70c9ef0e..00000000 --- a/src/render/recipe/add_storage.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "add_storage.hpp" - -#include "backend/api/api.hpp" -#include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" -#include "backend/models/storage.hpp" -#include "message_tracker.hpp" -#include "render/common.hpp" -#include "states.hpp" -#include "utils/utils.hpp" -#include "view.hpp" - -#include -#include -#include -#include -#include -#include - -namespace cookcookhnya::render::recipe { - -using namespace api::models::recipe; -using namespace api::models::storage; -using IngredientAvailability = states::RecipeView::IngredientAvailability; -using AvailabilityType = states::RecipeView::AvailabilityType; - -textGenInfo storageAdditionView(const std::vector& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api) { - auto recipe = api.getRecipesApi().get(userId, recipeId); - - bool isIngredientNotAvailable = false; - bool isIngredientIsOtherStorages = false; - const std::string recipeName = recipe.name; - auto text = std::format("{} Выбранные хранилища: ", utils::utf8str(u8"🍱")); - for (std::size_t i = 0; i != selectedStorages.size(); ++i) { - text += selectedStorages[i].name; - text += i != selectedStorages.size() - 1 ? ", " : "\n"; - } - text += std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); - - for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.available == AvailabilityType::AVAILABLE) { - text += "`[+]` " + infoPair.ingredient.name + "\n"; - } else if (infoPair.available == AvailabilityType::OTHER_STORAGES) { - text += "`[?]` " + infoPair.ingredient.name + "\n"; - isIngredientIsOtherStorages = true; - text += "Доступно в: "; - auto storages = infoPair.storages; - for (std::size_t i = 0; i != storages.size(); ++i) { - text += storages[i].name; - text += i != storages.size() - 1 ? ", " : "\n"; - } - } else { - text += "`[ ]` " + infoPair.ingredient.name + "\n"; - isIngredientNotAvailable = true; - } - } - if (recipe.link) - text += utils::utf8str(u8"\n🌐 Источник: ") + *recipe.link; - - return {.text = text, - .isIngredientNotAvailable = isIngredientNotAvailable, - .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; -} - -void renderStoragesSuggestion(const std::vector& inStoragesAvailability, - const std::vector& selectedStorages, - const std::vector& addedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - api::ApiClientRef api) { - auto textGen = storageAdditionView(inStoragesAvailability, selectedStorages, recipeId, userId, api); - std::vector storages; - for (const auto& infoPair : inStoragesAvailability) { - if (infoPair.available == AvailabilityType::OTHER_STORAGES) { - for (const auto& storage : infoPair.storages) { - if (std::ranges::find(storages, storage.id, &StorageSummary::id) == storages.end()) { - storages.push_back(storage); - } - } - } - } - storages.insert(storages.end(), addedStorages.begin(), addedStorages.end()); - const size_t buttonRows = ((storages.size() + 1) / 2) + 1; - InlineKeyboard keyboard(buttonRows); - - for (std::size_t i = 0; i < storages.size(); ++i) { - if (i % 2 == 0) - keyboard[i / 2].reserve(2); - const bool isSelected = - std::ranges::find(addedStorages, storages[i].id, &StorageSummary::id) != addedStorages.end(); - - std::string emoji = utils::utf8str(isSelected ? u8"[ + ]" : u8"[ᅠ]"); - const char* actionPrefix = isSelected ? "+" : "-"; - const std::string text = std::format("{} {}", emoji, storages[i].name); - const std::string data = actionPrefix + utils::to_string(storages[i].id); - keyboard[i / 2].push_back(makeCallbackButton(text, data)); - } - if (!storages.empty()) { - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - } - - auto messageId = message::getMessageId(userId); - if (messageId) { - bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); - } -} -} // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/add_storage.hpp b/src/render/recipe/add_storage.hpp deleted file mode 100644 index 2f417f3a..00000000 --- a/src/render/recipe/add_storage.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "backend/api/api.hpp" -#include "backend/id_types.hpp" -#include "backend/models/storage.hpp" -#include "render/common.hpp" -#include "render/recipe/view.hpp" -#include "states.hpp" - -#include - -namespace cookcookhnya::render::recipe { - -textGenInfo storageAdditionView(const std::vector& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api); - -void renderStoragesSuggestion(const std::vector& inStoragesAvailability, - const std::vector& selectedStorages, - const std::vector& addedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - api::ApiClientRef api); - -} // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp deleted file mode 100644 index e5795b9f..00000000 --- a/src/render/recipe/view.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "view.hpp" - -#include "backend/api/api.hpp" -#include "backend/id_types.hpp" -#include "backend/models/recipe.hpp" -#include "message_tracker.hpp" -#include "render/common.hpp" -#include "states.hpp" -#include "utils/utils.hpp" - -#include -#include -#include -#include -#include - -namespace cookcookhnya::render::recipe { - -using namespace api::models::recipe; -using IngredientAvailability = states::RecipeView::IngredientAvailability; -using AvailabilityType = states::RecipeView::AvailabilityType; - -textGenInfo recipeView(const std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api) { - auto recipeIngredients = api.getRecipesApi().get(userId, recipeId); - - bool isIngredientNotAvailable = false; - bool isIngredientIsOtherStorages = false; - std::string& recipeName = recipeIngredients.name; - auto text = std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); - - for (const auto& availability : inStoragesAvailability) { - if (availability.available == AvailabilityType::AVAILABLE) { - text += "`[+]` " + availability.ingredient.name + "\n"; - } else if (availability.available == AvailabilityType::OTHER_STORAGES) { - text += "`[?]` " + availability.ingredient.name + "\n"; - isIngredientIsOtherStorages = true; - } else { - text += "`[ ]` " + availability.ingredient.name + "\n"; - isIngredientNotAvailable = true; - } - } - if (recipeIngredients.link) - text += utils::utf8str(u8"\n🌐 Источник: ") + *recipeIngredients.link; - - return {.text = text, - .isIngredientNotAvailable = isIngredientNotAvailable, - .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; -} - -void renderRecipeView(std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - api::ApiClientRef api) { - auto textGen = recipeView(inStoragesAvailability, recipeId, userId, api); - const std::size_t buttonRows = textGen.isIngredientNotAvailable ? 3 : 2; - InlineKeyboard keyboard(buttonRows); - - keyboard[0].push_back(makeCallbackButton(u8"🧑‍🍳 Готовить", "start_cooking")); - - if (textGen.isIngredientIsOtherStorages) { - keyboard[0].push_back(makeCallbackButton(u8"?", "add_storages")); - } - if (textGen.isIngredientNotAvailable) { - keyboard[1].push_back(makeCallbackButton(u8"📝 Составить список продуктов", "shopping_list")); - } - - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back_from_recipe_view")); - - auto messageId = message::getMessageId(userId); - if (messageId) { - // Only on difference between function above - bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); - } -} - -} // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.hpp b/src/render/recipe/view.hpp deleted file mode 100644 index 56a89917..00000000 --- a/src/render/recipe/view.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "backend/api/api.hpp" -#include "backend/id_types.hpp" -#include "render/common.hpp" -#include "states.hpp" - -#include -#include - -namespace cookcookhnya::render::recipe { - -struct textGenInfo { - std::string text; - bool isIngredientNotAvailable; - bool isIngredientIsOtherStorages; -}; - -void renderRecipeView(std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - api::ApiClientRef api); - -textGenInfo recipeView(const std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api); - -} // namespace cookcookhnya::render::recipe diff --git a/src/render/suggested_recipe/add_storage.cpp b/src/render/suggested_recipe/add_storage.cpp index fbee48f2..35a5ccf5 100644 --- a/src/render/suggested_recipe/add_storage.cpp +++ b/src/render/suggested_recipe/add_storage.cpp @@ -17,7 +17,7 @@ #include #include -namespace cookcookhnya::render::recipe { +namespace cookcookhnya::render::suggested_recipe { using namespace api::models::recipe; using namespace api::models::storage; @@ -110,4 +110,4 @@ void renderStoragesSuggestion(const std::vector& inStora bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); } } -} // namespace cookcookhnya::render::recipe +} // namespace cookcookhnya::render::suggested_recipe diff --git a/src/render/suggested_recipe/add_storage.hpp b/src/render/suggested_recipe/add_storage.hpp index d44bd4f4..b4dcb25f 100644 --- a/src/render/suggested_recipe/add_storage.hpp +++ b/src/render/suggested_recipe/add_storage.hpp @@ -9,7 +9,7 @@ #include -namespace cookcookhnya::render::recipe { +namespace cookcookhnya::render::suggested_recipe { TextGenInfo storageAdditionView(const std::vector& inStoragesAvailability, const std::vector& selectedStorages, @@ -26,4 +26,4 @@ void renderStoragesSuggestion(const std::vector #include -namespace cookcookhnya::render::recipe { +namespace cookcookhnya::render::suggested_recipe { using namespace api::models::recipe; using IngredientAvailability = states::RecipeView::IngredientAvailability; @@ -78,4 +78,4 @@ void renderRecipeView(std::vector& inStoragesAvailabilit } } -} // namespace cookcookhnya::render::recipe +} // namespace cookcookhnya::render::suggested_recipe diff --git a/src/render/suggested_recipe/view.hpp b/src/render/suggested_recipe/view.hpp index 0caf3cf4..67795acf 100644 --- a/src/render/suggested_recipe/view.hpp +++ b/src/render/suggested_recipe/view.hpp @@ -8,7 +8,7 @@ #include #include -namespace cookcookhnya::render::recipe { +namespace cookcookhnya::render::suggested_recipe { struct TextGenInfo { std::string text; @@ -28,4 +28,4 @@ TextGenInfo recipeView(const std::vector Date: Tue, 22 Jul 2025 14:15:50 +0300 Subject: [PATCH 071/106] fix: custom recipe view didn't open --- src/handlers/personal_account/recipes_list/view.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index 94585ad8..8dd838da 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -11,6 +11,7 @@ #include "utils/parsing.hpp" #include +#include namespace cookcookhnya::handlers::personal_account::recipes { @@ -18,6 +19,7 @@ void handleCustomRecipesListCQ( CustomRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { using namespace render::personal_account; using namespace render::personal_account::recipes; + using namespace std::literals; bot.answerCallbackQuery(cq.id); @@ -39,8 +41,8 @@ void handleCustomRecipesListCQ( return; } - if (data[0] == 'r') { - auto recipeId = utils::parseSafe(data.substr(1, data.size())); + if (data.starts_with("recipe_") { + auto recipeId = utils::parseSafe(std::string_view{data}.substr("recipe_"sv.size())); if (recipeId) { auto ingredients = renderCustomRecipe(true, userId, chatId, recipeId.value(), bot, api); stateManager.put( From d51c33912994acbdee0ea89450d7bd1f5c4d08e0 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:21:46 +0300 Subject: [PATCH 072/106] fix: max down syntax error --- src/handlers/personal_account/recipes_list/view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/personal_account/recipes_list/view.cpp b/src/handlers/personal_account/recipes_list/view.cpp index 8dd838da..90f21ad0 100644 --- a/src/handlers/personal_account/recipes_list/view.cpp +++ b/src/handlers/personal_account/recipes_list/view.cpp @@ -41,7 +41,7 @@ void handleCustomRecipesListCQ( return; } - if (data.starts_with("recipe_") { + if (data.starts_with("recipe_")) { auto recipeId = utils::parseSafe(std::string_view{data}.substr("recipe_"sv.size())); if (recipeId) { auto ingredients = renderCustomRecipe(true, userId, chatId, recipeId.value(), bot, api); From 3066a3e0baf64b4a59d93957423f83538c41c5f0 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Tue, 22 Jul 2025 15:47:44 +0300 Subject: [PATCH 073/106] feat: whole history of requests connected to backend --- src/backend/api/recipes.cpp | 10 +++++----- src/backend/models/moderation.cpp | 7 ++++--- src/backend/models/moderation.hpp | 1 + src/main.cpp | 2 +- .../personal_account/publication_history.cpp | 16 +++++++++++----- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 13f79185..3f16fb5c 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -66,15 +66,15 @@ void RecipesApi::delete_(UserId user, RecipeId recipeId) const { jsonDeleteAuthed(user, std::format("/recipes/{}", recipeId)); } -// POST /recipes/{recipeId}/request-publication +// POST /recipes/{recipeId}/publication-requests void RecipesApi::publishCustom(UserId user, RecipeId recipe) const { - jsonPostAuthed(user, std::format("recipes/{}/request-publication", recipe)); + jsonPostAuthed(user, std::format("recipes/{}/publication-requests", recipe)); } -// GET /recipe/{id}/moderation-history +// GET /recipes/{recipeId}/publication-requests std::vector RecipesApi::getRecipeRequestHistory(UserId user, RecipeId recipe) const { - return jsonGetAuthed>(user, - std::format("/recipes/{}/moderation-history", recipe)); + return jsonGetAuthed>( + user, std::format("/recipes/{}/publication-requests", recipe)); } } // namespace cookcookhnya::api diff --git a/src/backend/models/moderation.cpp b/src/backend/models/moderation.cpp index 8bfc366e..5432f70e 100644 --- a/src/backend/models/moderation.cpp +++ b/src/backend/models/moderation.cpp @@ -16,11 +16,12 @@ namespace json = boost::json; PublicationRequest tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { 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, - .created = utils::parseIsoTime(value_to(j.at("created"))), - .updated = j.as_object().if_contains("updated") - ? std::optional{utils::parseIsoTime(value_to(j.at("updated")))} + .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 1c8d6287..c39e7308 100644 --- a/src/backend/models/moderation.hpp +++ b/src/backend/models/moderation.hpp @@ -12,6 +12,7 @@ namespace cookcookhnya::api::models::moderation { struct PublicationRequest { std::string name; + std::string requestType; PublicationRequestStatus status = PublicationRequestStatus::NO_REQUEST; std::chrono::system_clock::time_point created; std::optional updated; diff --git a/src/main.cpp b/src/main.cpp index 7a17cad6..91b270a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -77,7 +77,7 @@ int main(int argc, char* argv[]) { shoppingListIngredientSearchIQHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; - TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; + TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; // sdf if (useWebhook) { const std::string path = "/"s + utils::getenvWithError("WEBHOOK_SECRET"); // NOLINT(*include*) bot.startWebhook(std::move(tgBot), diff --git a/src/render/personal_account/publication_history.cpp b/src/render/personal_account/publication_history.cpp index 734ae693..5e18ff69 100644 --- a/src/render/personal_account/publication_history.cpp +++ b/src/render/personal_account/publication_history.cpp @@ -6,9 +6,9 @@ #include #include - +#include namespace cookcookhnya::render::personal_account { - +using namespace std::views; void renderRequestHistory(UserId userId, size_t pageNo, size_t numOfInstances, @@ -20,9 +20,15 @@ void renderRequestHistory(UserId userId, const std::size_t maxShownItems = 20; auto history = moderationApi.getAllPublicationRequests(userId, maxShownItems, pageNo * numOfInstances); - std::string toPrint = utils::utf8str(u8"ℹ️История запросов на публикацию ваших рецептов и ингредиентов\n\n\n"); - for (auto& req : history) { - toPrint += std::format("*{}* статус: {} ", req.name, utils::to_string(req.status)); + std::string toPrint = utils::utf8str(u8"ℹ️История запросов на публикацию ваших рецептов и ингредиентов\n\n"); + for (auto& req : history | reverse) { + std::string rcpIngRender; + if (req.requestType == "recipe") + rcpIngRender = utils::utf8str(u8"📖"); + 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()); toPrint += std::format("запрос создан: {} ", utils::to_string(req.created)); From 86a7f60410e34e82a0529e4f121c764eb0b81695 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Tue, 22 Jul 2025 16:22:34 +0300 Subject: [PATCH 074/106] fix: new structure of customIngredient --- src/backend/api/ingredients.cpp | 12 ++++++++++ src/backend/api/ingredients.hpp | 7 ++++++ src/backend/models/ingredient.cpp | 24 +++++++++++++++---- src/backend/models/ingredient.hpp | 18 +++++++++++++- .../recipe/search_ingredients.cpp | 3 +-- src/handlers/recipe/view.cpp | 7 ++---- src/handlers/storage/ingredients/view.cpp | 3 +-- .../ingredients_list/publish.cpp | 5 ++-- .../ingredients_list/view.cpp | 11 +++++---- src/render/personal_account/recipe/view.cpp | 3 +-- 10 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index 1b22d4db..89897886 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -89,6 +89,18 @@ IngredientsApi::getList(UserId user, PublicityFilterType filter, std::size_t cou return {.page = std::move(result.page), .found = result.found}; } +// GET /ingredients +CustomIngredientList +IngredientsApi::customIngredientsSearch(UserId user, std::string query, std::size_t threshold, std::size_t count, std::size_t offset) const { + return jsonGetAuthed(user, + "/ingredients", + {{"query", std::move(query)}, + {"size", utils::to_string(count)}, + {"offset", utils::to_string(offset)}, + {"threshold", utils::to_string(threshold)}, + {"filter", utils::to_string(PublicityFilterType::Custom)}}); +} + // GET /public/ingredients/{ingredientId} Ingredient IngredientsApi::getPublicIngredient(IngredientId ingredient) const { return jsonGet(std::format("/public/ingredients/{}", ingredient)); diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index bf3101ef..1fa0194b 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -51,6 +51,13 @@ class IngredientsApi : ApiBase { PublicityFilterType filter = PublicityFilterType::All, std::size_t count = 50, // NOLINT(*magic-number*) std::size_t offset = 0) const; + + [[nodiscard]] models::ingredient::CustomIngredientList customIngredientsSearch(UserId user, + std::string query, + std::size_t threshold, + std::size_t count = 50, // NOLINT(*magic-number*) + std::size_t offset = 0) const; + [[nodiscard]] models::ingredient::Ingredient getPublicIngredient(IngredientId ingredient) const; diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index 2a0cc39e..dd3b4d39 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -1,9 +1,9 @@ #include "backend/models/ingredient.hpp" -#include "backend/models/publication_request_status.hpp" #include #include #include +#include namespace cookcookhnya::api::models::ingredient { @@ -13,9 +13,18 @@ Ingredient tag_invoke(json::value_to_tag /*tag*/, const json::value& return { .id = value_to(j.at("id")), .name = value_to(j.at("name")), - .moderationStatus = j.as_object().if_contains("moderationStatus") - ? value_to(j.at("moderationStatus")) - : moderation::PublicationRequestStatus::NO_REQUEST, + }; +} + +CustomIngredient tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { + const auto& status = j.at("status"); + return { + .id = value_to(j.at("id")), + .name = value_to(j.at("name")), + .moderationStatus = value_to(status.at("type")), + .reason = status.as_object().if_contains("reason") + ? value_to(j.at("reason")) + : std::nullopt, }; } @@ -71,4 +80,11 @@ IngredientList tag_invoke(json::value_to_tag /*tag*/, const json }; } +CustomIngredientList tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { + return { + .page = value_to(j.at("results")), + .found = value_to(j.at("found")), + }; +} + } // namespace cookcookhnya::api::models::ingredient diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index 719bd0ed..c38d44e5 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -15,11 +16,19 @@ namespace cookcookhnya::api::models::ingredient { struct Ingredient { IngredientId id; std::string name; - moderation::PublicationRequestStatus moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST; friend Ingredient tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; +struct CustomIngredient { + IngredientId id; + std::string name; + moderation::PublicationRequestStatus moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST; + std::optional reason = std::nullopt; + + friend CustomIngredient tag_invoke(boost::json::value_to_tag, const boost::json::value& j); +}; + struct IngredientCreateBody { std::string name; @@ -75,4 +84,11 @@ struct IngredientList { friend IngredientList tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; +struct CustomIngredientList { + std::vector page; + std::size_t found; + + friend CustomIngredientList tag_invoke(boost::json::value_to_tag, const boost::json::value& j); +}; + } // namespace cookcookhnya::api::models::ingredient diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 74f4552a..5907d94c 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -93,8 +93,7 @@ void handleCustomRecipeIngredientsSearchCQ( api.getIngredientsApi().putToRecipe(userId, state.recipeId, *mIngredient); state.recipeIngredients.put( {.id = it->id, - .name = it->name, - .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); + .name = it->name}); } it->isInRecipe = !it->isInRecipe; renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, chatId, bot); diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index dfde952b..118c6ce1 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -7,7 +7,6 @@ #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" -#include #include #include @@ -32,13 +31,11 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe if (infoPair.second.available == utils::AvailabiltiyType::not_available) { selectedIngredients.push_back( {.id = infoPair.first.id, - .name = infoPair.first.name, - .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); + .name = infoPair.first.name}); } allIngredients.push_back( {.id = infoPair.first.id, - .name = infoPair.first.name, - .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); + .name = infoPair.first.name}); } renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); stateManager.put(ShoppingListCreation{.selectedStorages = state.selectedStorages, diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index cbbe04a2..cf11e889 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -86,8 +86,7 @@ void handleStorageIngredientsListCQ( api.getIngredientsApi().putToStorage(userId, state.storageId, *mIngredient); state.storageIngredients.put( {.id = it->id, - .name = it->name, - .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); + .name = it->name}); } it->isInStorage = !it->isInStorage; renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, chatId, bot); diff --git a/src/render/personal_account/ingredients_list/publish.cpp b/src/render/personal_account/ingredients_list/publish.cpp index d6788c5c..cfd883dd 100644 --- a/src/render/personal_account/ingredients_list/publish.cpp +++ b/src/render/personal_account/ingredients_list/publish.cpp @@ -1,6 +1,5 @@ #include "publish.hpp" -#include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" #include "backend/models/publication_request_status.hpp" #include "message_tracker.hpp" @@ -19,10 +18,10 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace std::views; void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - auto ingredientsResp = api.search(userId, PublicityFilterType::Custom); + auto ingredientsResp = api.customIngredientsSearch(userId, "", 0); // TODO: make pagination for ingredients - std::vector ingredients; + std::vector ingredients; for (const auto& ing : ingredientsResp.page){ if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { ingredients.push_back(ing); diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index e9f8f03e..5080aa14 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -1,7 +1,7 @@ #include "view.hpp" -#include "backend/api/publicity_filter.hpp" #include "backend/models/ingredient.hpp" +#include "backend/models/publication_request_status.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/to_string.hpp" @@ -21,7 +21,7 @@ using namespace tg_types; namespace { std::pair> constructNavigationMessage( - std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::IngredientList& ingredientsList) { + std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::CustomIngredientList& ingredientsList) { const size_t amountOfRecipes = ingredientsList.found; const std::size_t maxPageNum = std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); @@ -31,6 +31,9 @@ std::pair> constructN text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); + if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::REJECTED){ + + } } std::vector buttons; @@ -52,7 +55,7 @@ std::pair> constructN std::pair constructMessage(size_t pageNo, size_t numOfIngredientsOnPage, - api::models::ingredient::IngredientList& ingredientsList) { + api::models::ingredient::CustomIngredientList& ingredientsList) { std::size_t numOfRows = 0; if (ingredientsList.found == 0) numOfRows = 2; @@ -96,7 +99,7 @@ void renderCustomIngredientsList( const std::size_t numOfIngredientsOnPage = 10; auto ingredientsList = - api.getList(userId, PublicityFilterType::Custom, numOfIngredientsOnPage, pageNo * numOfIngredientsOnPage); + api.customIngredientsSearch(userId, "", 0, numOfIngredientsOnPage, pageNo * numOfIngredientsOnPage); auto res = constructMessage(pageNo, numOfIngredientsOnPage, ingredientsList); auto text = res.first; diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 582e8c14..a3f0294f 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -27,8 +27,7 @@ std::vector renderCustomRecipe( for (auto& it : recipeDetails.ingredients) { toPrint += std::format("• {}\n", it.name); ingredients.push_back({.id = it.id, - .name = it.name, - .moderationStatus = api::models::moderation::PublicationRequestStatus::NO_REQUEST}); + .name = it.name}); } if (recipeDetails.link) From 9c9c4073d6a1652b22369ac13a77174f451901fc Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 18:16:41 +0300 Subject: [PATCH 075/106] refactor: button data in storage ingredients --- src/handlers/storage/ingredients/view.cpp | 11 +++++++---- src/render/storage/ingredients/view.cpp | 7 ++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 66e1ddb7..23ed874e 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -63,6 +63,7 @@ void updateSearch(StorageIngredientsList& state, if (state.totalFound == 0) renderSuggestIngredientCustomisation(state, userId, userId, bot); } + } // namespace void handleStorageIngredientsListCQ( @@ -100,14 +101,15 @@ void handleStorageIngredientsListCQ( return; } - if (cq.data.starts_with("ingredient_")) { - const std::string ingredientName{std::string_view{cq.data}.substr("ingredient_"sv.size())}; + if (cq.data.starts_with("create_ingredient")) { + const std::string ingredientName = state.inlineQuery; renderCustomIngredientConfirmation(true, ingredientName, userId, chatId, bot, api); stateManager.put(CustomIngredientConfirmation{ingredientName, std::nullopt, std::nullopt, state.storageId}); } - if (cq.data != "dont_handle") { - auto mIngredient = utils::parseSafe(cq.data); + if (cq.data.starts_with("ingredient_")) { + auto mIngredient = + utils::parseSafe(std::string_view{cq.data}.substr("ingredient_"sv.size())); if (!mIngredient) return; auto it = std::ranges::find(state.searchItems, *mIngredient, &IngredientSearchForStorageItem::id); @@ -141,6 +143,7 @@ void handleStorageIngredientsListIQ(StorageIngredientsList& state, updateSearch(state, true, bot, userId, api); } // Cache is not disabled on Windows and Linux desktops. Works on Android and Web + // Not answer to disable cache // bot.answerInlineQuery(iq.id, {}, 0); } diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index fa034e0f..0af7fed0 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -37,7 +37,8 @@ constructKeyboard(std::size_t pageNo, std::size_t pageSize, const states::Storag keyboard << std::move(searchButton) << NewRow{}; auto makeIngredientButton = [](const IngredientSearchForStorageItem& ing) { - return makeCallbackButton((ing.isInStorage ? "[ + ] " : "[ㅤ] ") + ing.name, utils::to_string(ing.id)); + return makeCallbackButton((ing.isInStorage ? "[ + ] " : "[ㅤ] ") + ing.name, + "ingredient_" + utils::to_string(ing.id)); }; keyboard << constructPagination(pageNo, pageSize, state.totalFound, state.searchItems, makeIngredientButton); @@ -87,8 +88,8 @@ void renderSuggestIngredientCustomisation(const states::StorageIngredientsList& searchButton->switchInlineQueryCurrentChat = ""; keyboard[0].push_back(std::move(searchButton)); // Mark as ingredient - keyboard[1].push_back(makeCallbackButton(std::format("Создать личный ингредиент: {}", state.inlineQuery), - "ingredient_" + state.inlineQuery)); + keyboard[1].push_back( + makeCallbackButton(std::format("Создать личный ингредиент: {}", state.inlineQuery), "create_ingredient")); keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); if (auto messageId = message::getMessageId(userId)) { From 74cdacb0ecabde9604c020e5275675cad0d4d2d3 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Tue, 22 Jul 2025 18:33:14 +0300 Subject: [PATCH 076/106] fix: done with list of ingredients --- src/backend/api/ingredients.cpp | 16 ++-- src/backend/api/ingredients.hpp | 14 ++-- src/backend/models/ingredient.cpp | 19 +++-- src/backend/models/ingredient.hpp | 3 +- .../ingredients_list/view.cpp | 16 ++-- .../recipe/search_ingredients.cpp | 4 +- src/handlers/recipe/view.cpp | 8 +- src/handlers/storage/ingredients/view.cpp | 4 +- .../ingredients_list/publish.cpp | 13 ++- .../ingredients_list/view.cpp | 84 +++++++++++-------- src/render/personal_account/recipe/view.cpp | 3 +- 11 files changed, 101 insertions(+), 83 deletions(-) diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index 89897886..1aca1378 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -90,15 +90,15 @@ IngredientsApi::getList(UserId user, PublicityFilterType filter, std::size_t cou } // GET /ingredients -CustomIngredientList -IngredientsApi::customIngredientsSearch(UserId user, std::string query, std::size_t threshold, std::size_t count, std::size_t offset) const { +CustomIngredientList IngredientsApi::customIngredientsSearch( + UserId user, std::string query, std::size_t threshold, std::size_t count, std::size_t offset) const { return jsonGetAuthed(user, - "/ingredients", - {{"query", std::move(query)}, - {"size", utils::to_string(count)}, - {"offset", utils::to_string(offset)}, - {"threshold", utils::to_string(threshold)}, - {"filter", utils::to_string(PublicityFilterType::Custom)}}); + "/ingredients", + {{"query", std::move(query)}, + {"size", utils::to_string(count)}, + {"offset", utils::to_string(offset)}, + {"threshold", utils::to_string(threshold)}, + {"filter", utils::to_string(PublicityFilterType::Custom)}}); } // GET /public/ingredients/{ingredientId} diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index 1fa0194b..d359b085 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -51,13 +51,13 @@ class IngredientsApi : ApiBase { PublicityFilterType filter = PublicityFilterType::All, std::size_t count = 50, // NOLINT(*magic-number*) std::size_t offset = 0) const; - - [[nodiscard]] models::ingredient::CustomIngredientList customIngredientsSearch(UserId user, - std::string query, - std::size_t threshold, - std::size_t count = 50, // NOLINT(*magic-number*) - std::size_t offset = 0) const; - + + [[nodiscard]] models::ingredient::CustomIngredientList + customIngredientsSearch(UserId user, + std::string query, + std::size_t threshold, + std::size_t count = 50, // NOLINT(*magic-number*) + std::size_t offset = 0) const; [[nodiscard]] models::ingredient::Ingredient getPublicIngredient(IngredientId ingredient) const; diff --git a/src/backend/models/ingredient.cpp b/src/backend/models/ingredient.cpp index dd3b4d39..7d658981 100644 --- a/src/backend/models/ingredient.cpp +++ b/src/backend/models/ingredient.cpp @@ -1,4 +1,5 @@ #include "backend/models/ingredient.hpp" +#include "backend/models/publication_request_status.hpp" #include #include @@ -17,14 +18,22 @@ Ingredient tag_invoke(json::value_to_tag /*tag*/, const json::value& } CustomIngredient tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { - const auto& status = j.at("status"); + const auto& status = j.at("moderationStatus"); + if (status.is_object()) { + return { + .id = value_to(j.at("id")), + .name = value_to(j.at("name")), + .moderationStatus = value_to(status.at("type")), + .reason = status.as_object().if_contains("reason") + ? value_to(status.at("reason")) + : std::nullopt, + }; + } return { .id = value_to(j.at("id")), .name = value_to(j.at("name")), - .moderationStatus = value_to(status.at("type")), - .reason = status.as_object().if_contains("reason") - ? value_to(j.at("reason")) - : std::nullopt, + .moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST, + .reason = std::nullopt, }; } diff --git a/src/backend/models/ingredient.hpp b/src/backend/models/ingredient.hpp index c38d44e5..483263f6 100644 --- a/src/backend/models/ingredient.hpp +++ b/src/backend/models/ingredient.hpp @@ -88,7 +88,8 @@ struct CustomIngredientList { std::vector page; std::size_t found; - friend CustomIngredientList tag_invoke(boost::json::value_to_tag, const boost::json::value& j); + friend CustomIngredientList tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); }; } // namespace cookcookhnya::api::models::ingredient diff --git a/src/handlers/personal_account/ingredients_list/view.cpp b/src/handlers/personal_account/ingredients_list/view.cpp index 52c08711..ce3d71bc 100644 --- a/src/handlers/personal_account/ingredients_list/view.cpp +++ b/src/handlers/personal_account/ingredients_list/view.cpp @@ -7,8 +7,6 @@ #include "render/personal_account/view.hpp" #include "states.hpp" -#include "utils/parsing.hpp" -#include namespace cookcookhnya::handlers::personal_account::ingredients { @@ -35,10 +33,16 @@ void handleCustomIngredientsListCQ( stateManager.put(PersonalAccountMenu{}); return; } - auto pageNo = utils::parseSafe(cq.data); - if (pageNo) { - renderCustomIngredientsList(true, *pageNo, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = *pageNo}); + if (cq.data == "prev") { + state.pageNo -= 1; + renderCustomIngredientsList(true, state.pageNo, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); + return; + } + if (cq.data == "next") { + state.pageNo += 1; + renderCustomIngredientsList(true, state.pageNo, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); return; } } diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 5907d94c..96bc6e00 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -91,9 +91,7 @@ void handleCustomRecipeIngredientsSearchCQ( state.recipeIngredients.remove(*mIngredient); } else { api.getIngredientsApi().putToRecipe(userId, state.recipeId, *mIngredient); - state.recipeIngredients.put( - {.id = it->id, - .name = it->name}); + state.recipeIngredients.put({.id = it->id, .name = it->name}); } it->isInRecipe = !it->isInRecipe; renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, chatId, bot); diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 118c6ce1..bd94a513 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -29,13 +29,9 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe std::vector allIngredients; for (const auto& infoPair : state.availability) { if (infoPair.second.available == utils::AvailabiltiyType::not_available) { - selectedIngredients.push_back( - {.id = infoPair.first.id, - .name = infoPair.first.name}); + selectedIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); } - allIngredients.push_back( - {.id = infoPair.first.id, - .name = infoPair.first.name}); + allIngredients.push_back({.id = infoPair.first.id, .name = infoPair.first.name}); } renderShoppingListCreation(selectedIngredients, allIngredients, userId, chatId, bot); stateManager.put(ShoppingListCreation{.selectedStorages = state.selectedStorages, diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index cf11e889..7f979f8e 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -84,9 +84,7 @@ void handleStorageIngredientsListCQ( state.storageIngredients.remove(*mIngredient); } else { api.getIngredientsApi().putToStorage(userId, state.storageId, *mIngredient); - state.storageIngredients.put( - {.id = it->id, - .name = it->name}); + state.storageIngredients.put({.id = it->id, .name = it->name}); } it->isInStorage = !it->isInStorage; renderIngredientsListSearch(state, numOfIngredientsOnPage, userId, chatId, bot); diff --git a/src/render/personal_account/ingredients_list/publish.cpp b/src/render/personal_account/ingredients_list/publish.cpp index cfd883dd..3b2483be 100644 --- a/src/render/personal_account/ingredients_list/publish.cpp +++ b/src/render/personal_account/ingredients_list/publish.cpp @@ -12,7 +12,6 @@ #include #include - namespace cookcookhnya::render::personal_account::ingredients { using namespace std::views; @@ -22,16 +21,16 @@ void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, // TODO: make pagination for ingredients std::vector ingredients; - for (const auto& ing : ingredientsResp.page){ + for (const auto& ing : ingredientsResp.page) { if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { ingredients.push_back(ing); } } - + InlineKeyboardBuilder keyboard{ingredients.size() + 1}; for (auto chunk : ingredients | chunk(2)) { keyboard.reserveInRow(2); - for (const auto& i : chunk){ + for (const auto& i : chunk) { keyboard << makeCallbackButton("• " + i.name, utils::to_string(i.id)); } keyboard << NewRow{}; @@ -39,9 +38,9 @@ void renderCustomIngredientPublication(UserId userId, ChatId chatId, BotRef bot, keyboard << makeCallbackButton(u8"↩️ Назад", "back"); - auto text = std::format( - "{} Какой ингредиент вы хотите предложить для добавления в CookCookhNya? (Все предложения проходят проверку)\n ", - utils::utf8str(u8"📥")); + auto text = std::format("{} Какой ингредиент вы хотите предложить для добавления в CookCookhNya? (Все предложения " + "проходят проверку)\n ", + utils::utf8str(u8"📥")); auto messageId = message::getMessageId(userId); if (messageId) { bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index 5080aa14..0b272245 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -20,42 +20,32 @@ using namespace tg_types; namespace { -std::pair> constructNavigationMessage( - std::size_t pageNo, std::size_t numOfRecipesOnPage, api::models::ingredient::CustomIngredientList& ingredientsList) { - const size_t amountOfRecipes = ingredientsList.found; - const std::size_t maxPageNum = - std::ceil(static_cast(amountOfRecipes) / static_cast(numOfRecipesOnPage)); - - std::string text; - - text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); - for (const auto& ing : ingredientsList.page) { - text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); - if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::REJECTED){ - - } - } - +std::vector constructNavigationButtons(std::size_t pageNo, std::size_t maxPageNum) { std::vector buttons; + auto forward = makeCallbackButton(u8"▶️", "next"); + auto backward = makeCallbackButton(u8"◀️", "prev"); + auto dont_handle = makeCallbackButton(u8"ㅤ", "dont_handle"); + auto page = makeCallbackButton(std::format("{} из {}", (pageNo + 1), maxPageNum), "dont_handle"); if (pageNo == maxPageNum) { - buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); - buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); - buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); + buttons.push_back(backward); + buttons.push_back(page); + buttons.push_back(dont_handle); } else if (pageNo == 0) { - buttons.push_back(makeCallbackButton(u8"ㅤ", "dont_handle")); - buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); - buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); + buttons.push_back(dont_handle); + buttons.push_back(page); + buttons.push_back(forward); } else { - buttons.push_back(makeCallbackButton(u8"◀️", utils::to_string(pageNo - 1))); - buttons.push_back(makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle")); - buttons.push_back(makeCallbackButton(u8"▶️", utils::to_string(pageNo + 1))); + buttons.push_back(backward); + buttons.push_back(page); + buttons.push_back(forward); } - return std::make_pair(text, buttons); + return buttons; } -std::pair constructMessage(size_t pageNo, - size_t numOfIngredientsOnPage, - api::models::ingredient::CustomIngredientList& ingredientsList) { +std::pair constructMessage( // NOLINT(*complexity*) + size_t pageNo, + size_t numOfIngredientsOnPage, + api::models::ingredient::CustomIngredientList& ingredientsList) { std::size_t numOfRows = 0; if (ingredientsList.found == 0) numOfRows = 2; @@ -65,26 +55,50 @@ std::pair constructMessage(size_t pageNo, numOfRows = 4; std::string text; InlineKeyboard keyboard(numOfRows); + if (ingredientsList.found == 0) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. Создавайте и делитесь новыми ингредиентами\\.\n\n"); keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - } else if (ingredientsList.found <= numOfIngredientsOnPage) { + } else if (ingredientsList.found <= numOfIngredientsOnPage && pageNo == 0) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); for (const auto& ing : ingredientsList.page) { - text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); + if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { + text += std::format("• {}\n", ing.name); + } else { + if (ing.reason) { + text += std::format( + "• {}, Статус: {}, Причина: {}\n", ing.name, utils::to_string(ing.moderationStatus), *ing.reason); + } else { + text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); + } + } } keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); } else { - auto message = constructNavigationMessage(pageNo, numOfIngredientsOnPage, ingredientsList); - text = message.first; + text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); + for (const auto& ing : ingredientsList.page) { + if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { + text += std::format("• {}\n", ing.name); + } else { + if (ing.reason) { + text += std::format( + "• {}, Статус: {}, Причина: {}\n", ing.name, utils::to_string(ing.moderationStatus), *ing.reason); + } else { + text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); + } + } + + } keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); keyboard[2].reserve(3); - for (const auto& navigButton : message.second) { + const std::size_t maxPageNum = + std::ceil(static_cast(ingredientsList.found) / static_cast(numOfIngredientsOnPage)); + for (const auto& navigButton : constructNavigationButtons(pageNo, maxPageNum)) { keyboard[2].push_back(navigButton); } keyboard[3].push_back(makeCallbackButton(u8"↩️ Назад", "back")); @@ -96,7 +110,7 @@ std::pair constructMessage(size_t pageNo, void renderCustomIngredientsList( bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - const std::size_t numOfIngredientsOnPage = 10; + const std::size_t numOfIngredientsOnPage = 5; auto ingredientsList = api.customIngredientsSearch(userId, "", 0, numOfIngredientsOnPage, pageNo * numOfIngredientsOnPage); diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index a3f0294f..c60b75d7 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -26,8 +26,7 @@ std::vector renderCustomRecipe( toPrint += (utils::utf8str(u8"Рецепт: ") + recipeDetails.name + "\n"); for (auto& it : recipeDetails.ingredients) { toPrint += std::format("• {}\n", it.name); - ingredients.push_back({.id = it.id, - .name = it.name}); + ingredients.push_back({.id = it.id, .name = it.name}); } if (recipeDetails.link) From 3d3d8b0909da0a36e2d72b86d51dfcf6fa775d08 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 19:23:50 +0300 Subject: [PATCH 077/106] refactor: move parseIsoTime to parsing.hpp --- src/backend/models/moderation.cpp | 2 +- src/backend/models/recipe.cpp | 5 +++-- src/utils/CMakeLists.txt | 1 - src/utils/parsing.cpp | 12 ++++++++++++ src/utils/parsing.hpp | 4 ++++ src/utils/serialization.cpp | 17 ----------------- src/utils/serialization.hpp | 10 ---------- 7 files changed, 20 insertions(+), 31 deletions(-) delete mode 100644 src/utils/serialization.cpp delete mode 100644 src/utils/serialization.hpp diff --git a/src/backend/models/moderation.cpp b/src/backend/models/moderation.cpp index 8bfc366e..5fd3c83d 100644 --- a/src/backend/models/moderation.cpp +++ b/src/backend/models/moderation.cpp @@ -1,7 +1,7 @@ #include "moderation.hpp" #include "publication_request_status.hpp" -#include "utils/serialization.hpp" +#include "utils/parsing.hpp" #include #include diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 1b6666a9..081dfdf5 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -1,5 +1,6 @@ -#include "backend/models/recipe.hpp" -#include "utils/serialization.hpp" +#include "recipe.hpp" + +#include "utils/parsing.hpp" #include #include diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 669e2f22..3c20a6f7 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -4,5 +4,4 @@ target_sources(main PRIVATE src/utils/utils.cpp src/utils/uuid.cpp src/utils/ingredients_availability.cpp - src/utils/serialization.cpp ) diff --git a/src/utils/parsing.cpp b/src/utils/parsing.cpp index 508aa4a4..2018f09a 100644 --- a/src/utils/parsing.cpp +++ b/src/utils/parsing.cpp @@ -5,7 +5,9 @@ #include #include +#include #include +#include #include namespace cookcookhnya::utils { @@ -23,4 +25,14 @@ Uuid parse(std::string_view s) noexcept(false) { return boost::lexical_cast(s); } +std::chrono::system_clock::time_point parseIsoTime(std::string s) { + std::chrono::system_clock::time_point tp; + std::istringstream ss(std::move(s)); + ss >> std::chrono::parse("%FT%TZ", tp); // Parse as UTC + if (ss.fail()) { + throw std::runtime_error("Could not parse datetime"); + } + return tp; // Still UTC, but debugger may show local time +} + } // namespace cookcookhnya::utils diff --git a/src/utils/parsing.hpp b/src/utils/parsing.hpp index 50f82e87..0885071b 100644 --- a/src/utils/parsing.hpp +++ b/src/utils/parsing.hpp @@ -3,8 +3,10 @@ #include "uuid.hpp" #include +#include #include #include +#include #include #include @@ -34,4 +36,6 @@ T parse(std::string_view s) noexcept(false) { template <> Uuid parse(std::string_view s) noexcept(false); +std::chrono::system_clock::time_point parseIsoTime(std::string s); + } // namespace cookcookhnya::utils diff --git a/src/utils/serialization.cpp b/src/utils/serialization.cpp deleted file mode 100644 index b916fe1e..00000000 --- a/src/utils/serialization.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "serialization.hpp" -#include -#include - -namespace cookcookhnya::utils { - -std::chrono::system_clock::time_point parseIsoTime(std::string s) { - std::chrono::system_clock::time_point tp; - std::istringstream ss(std::move(s)); - ss >> std::chrono::parse("%FT%TZ", tp); // Parse as UTC - if (ss.fail()) { - throw std::runtime_error("Could not parse datetime"); - } - return tp; // Still UTC, but debugger may show local time -} - -} // namespace cookcookhnya::utils diff --git a/src/utils/serialization.hpp b/src/utils/serialization.hpp deleted file mode 100644 index 72bdf417..00000000 --- a/src/utils/serialization.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include - -namespace cookcookhnya::utils { - -std::chrono::system_clock::time_point parseIsoTime(std::string s); - -} // namespace cookcookhnya::utils From 60ce6bd71faacc022f876479158007ff0b4a54ef Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Tue, 22 Jul 2025 19:37:19 +0300 Subject: [PATCH 078/106] feat: done with status of custom ingredients --- src/backend/api/ingredients.cpp | 5 + src/backend/api/ingredients.hpp | 2 + src/handlers/CMakeLists.txt | 1 + src/handlers/common.hpp | 1 + src/handlers/handlers_list.hpp | 4 +- .../ingredients_list/create.cpp | 23 ++-- .../ingredients_list/delete.cpp | 26 ++++ .../ingredients_list/delete.hpp | 10 ++ .../ingredients_list/publish.cpp | 6 +- .../ingredients_list/view.cpp | 25 ++-- src/handlers/personal_account/view.cpp | 4 +- src/main.cpp | 1 + src/render/CMakeLists.txt | 1 + .../ingredients_list/delete.cpp | 48 ++++++++ .../ingredients_list/delete.hpp | 9 ++ .../ingredients_list/view.cpp | 116 ++++++++---------- .../ingredients_list/view.hpp | 3 +- src/states.hpp | 15 +-- 18 files changed, 189 insertions(+), 111 deletions(-) create mode 100644 src/handlers/personal_account/ingredients_list/delete.cpp create mode 100644 src/handlers/personal_account/ingredients_list/delete.hpp create mode 100644 src/render/personal_account/ingredients_list/delete.cpp create mode 100644 src/render/personal_account/ingredients_list/delete.hpp diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index 1aca1378..1d8c2b85 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -137,6 +137,11 @@ IngredientId IngredientsApi::createCustom(UserId user, const IngredientCreateBod return utils::parse(postWithJsonAuthed(user, "/ingredients", body)); } +// DELETE /ingredients/{ingredientId} +void IngredientsApi::deleteCustom(UserId user, IngredientId ingredient) const { + jsonDeleteAuthed(user, std::format("/ingredients/{}", ingredient)); +} + // POST /recipes/{ingredientId}/request-publication void IngredientsApi::publishCustom(UserId user, IngredientId ingredient) const { jsonPostAuthed(user, std::format("/ingredients/{}/request-publication", ingredient)); diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index d359b085..d8f2bf32 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -76,6 +76,8 @@ class IngredientsApi : ApiBase { IngredientId createCustom(UserId user, // NOLINT(*-nodiscard) const models::ingredient::IngredientCreateBody& body) const; + void deleteCustom(UserId user, IngredientId ingredient) const; // NOLINT(*-nodiscard) + void publishCustom(UserId user, IngredientId ingredient) const; }; diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 713d7b88..95759a8d 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(main PRIVATE src/handlers/personal_account/ingredients_list/create.cpp src/handlers/personal_account/ingredients_list/publish.cpp src/handlers/personal_account/ingredients_list/view.cpp + src/handlers/personal_account/ingredients_list/delete.cpp src/handlers/personal_account/recipes_list/create.cpp src/handlers/personal_account/recipes_list/view.cpp diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 855c2ea4..8988edf2 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -22,6 +22,7 @@ using states::PersonalAccountMenu; using states::CustomIngredientConfirmation; using states::CustomIngredientCreationEnterName; +using states::CustomIngredientDeletion; using states::CustomIngredientPublish; using states::CustomIngredientsList; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index f2cee479..a8c6bfbb 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -5,6 +5,7 @@ #include "main_menu/view.hpp" +#include "personal_account/ingredients_list//delete.hpp" #include "personal_account/ingredients_list/create.hpp" #include "personal_account/ingredients_list/publish.hpp" #include "personal_account/ingredients_list/view.hpp" @@ -79,9 +80,10 @@ using customIngredientCreationEnterNameMsgHandler = using customIngredientCreationEnterNameCQHandler = Handler; using customIngredientConfirmationCQHandler = Handler; +using handleCustomIngredientDeletionCQHandler = Handler; using customIngredientPublishCQHandler = Handler; -// StorageListCreate +// StorageList using storageListCQHandler = Handler; using storageCreationEnterNameMsgHandler = Handler; using storageCreationEnterNameCQHandler = Handler; diff --git a/src/handlers/personal_account/ingredients_list/create.cpp b/src/handlers/personal_account/ingredients_list/create.cpp index 1f748adc..e0ea126d 100644 --- a/src/handlers/personal_account/ingredients_list/create.cpp +++ b/src/handlers/personal_account/ingredients_list/create.cpp @@ -12,8 +12,11 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; -void handleCustomIngredientCreationEnterNameMsg( - CustomIngredientCreationEnterName& state, MessageRef m, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { +void handleCustomIngredientCreationEnterNameMsg(CustomIngredientCreationEnterName& /*unused*/, + MessageRef m, + BotRef& bot, + SMRef stateManager, + IngredientsApiRef api) { auto name = m.text; auto userId = m.from->id; auto chatId = m.chat->id; @@ -24,10 +27,10 @@ void handleCustomIngredientCreationEnterNameMsg( bot.editMessageText(text, chatId, *messageId); } renderCustomIngredientConfirmation(name, userId, chatId, bot, api); - stateManager.put(CustomIngredientConfirmation{.pageNo = state.pageNo, .name = name}); + stateManager.put(CustomIngredientConfirmation{.name = name}); } -void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName& state, +void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, @@ -36,8 +39,8 @@ void handleCustomIngredientCreationEnterNameCQ(CustomIngredientCreationEnterName auto userId = cq.from->id; auto chatId = cq.message->chat->id; if (cq.data == "back") { - renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); + renderCustomIngredientsList(true, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{}); } } @@ -49,12 +52,12 @@ void handleCustomIngredientConfirmationCQ( auto name = state.name; if (cq.data == "confirm") { api.createCustom(userId, api::models::ingredient::IngredientCreateBody{name}); - renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); + renderCustomIngredientsList(true, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{}); } if (cq.data == "back") { - renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); + renderCustomIngredientsList(true, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{}); } } diff --git a/src/handlers/personal_account/ingredients_list/delete.cpp b/src/handlers/personal_account/ingredients_list/delete.cpp new file mode 100644 index 00000000..42a5c798 --- /dev/null +++ b/src/handlers/personal_account/ingredients_list/delete.cpp @@ -0,0 +1,26 @@ +#include "delete.hpp" + +#include "backend/id_types.hpp" +#include "handlers/common.hpp" +#include "render/personal_account/ingredients_list/view.hpp" +#include "states.hpp" +#include "utils/parsing.hpp" + +namespace cookcookhnya::handlers::personal_account::ingredients { + +using namespace render::personal_account::ingredients; + +void handleCustomIngredientDeletionCQ( + CustomIngredientDeletion& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { + bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + + auto ingredientId = utils::parseSafe(cq.data); + if (ingredientId) { + api.deleteCustom(userId, *ingredientId); + } + renderCustomIngredientsList(true, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{}); +} +} // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/delete.hpp b/src/handlers/personal_account/ingredients_list/delete.hpp new file mode 100644 index 00000000..cc5b4401 --- /dev/null +++ b/src/handlers/personal_account/ingredients_list/delete.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::personal_account::ingredients { + +void handleCustomIngredientDeletionCQ( + CustomIngredientDeletion& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api); + +} // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/publish.cpp b/src/handlers/personal_account/ingredients_list/publish.cpp index 8377193a..05aa2f98 100644 --- a/src/handlers/personal_account/ingredients_list/publish.cpp +++ b/src/handlers/personal_account/ingredients_list/publish.cpp @@ -11,7 +11,7 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; void handleCustomIngredientPublishCQ( - CustomIngredientPublish& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { + CustomIngredientPublish& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -20,7 +20,7 @@ void handleCustomIngredientPublishCQ( if (ingredientId) { api.publishCustom(userId, *ingredientId); } - renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); + renderCustomIngredientsList(true, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{}); } } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/ingredients_list/view.cpp b/src/handlers/personal_account/ingredients_list/view.cpp index ce3d71bc..c9bab5d5 100644 --- a/src/handlers/personal_account/ingredients_list/view.cpp +++ b/src/handlers/personal_account/ingredients_list/view.cpp @@ -2,8 +2,8 @@ #include "handlers/common.hpp" #include "render/personal_account/ingredients_list/create.hpp" +#include "render/personal_account/ingredients_list/delete.hpp" #include "render/personal_account/ingredients_list/publish.hpp" -#include "render/personal_account/ingredients_list/view.hpp" #include "render/personal_account/view.hpp" #include "states.hpp" @@ -14,18 +14,23 @@ using namespace render::personal_account::ingredients; using namespace render::personal_account; void handleCustomIngredientsListCQ( - CustomIngredientsList& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { + CustomIngredientsList& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; if (cq.data == "create") { renderCustomIngredientCreation(userId, chatId, bot); - stateManager.put(CustomIngredientCreationEnterName{.pageNo = state.pageNo}); + stateManager.put(CustomIngredientCreationEnterName{}); + return; + } + if (cq.data == "delete") { + renderCustomIngredientDeletion(userId, chatId, bot, api); + stateManager.put(CustomIngredientDeletion{}); return; } if (cq.data == "publish") { renderCustomIngredientPublication(userId, chatId, bot, api); - stateManager.put(CustomIngredientPublish{.pageNo = state.pageNo}); + stateManager.put(CustomIngredientPublish{}); return; } if (cq.data == "back") { @@ -33,17 +38,5 @@ void handleCustomIngredientsListCQ( stateManager.put(PersonalAccountMenu{}); return; } - if (cq.data == "prev") { - state.pageNo -= 1; - renderCustomIngredientsList(true, state.pageNo, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); - return; - } - if (cq.data == "next") { - state.pageNo += 1; - renderCustomIngredientsList(true, state.pageNo, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = state.pageNo}); - return; - } } } // namespace cookcookhnya::handlers::personal_account::ingredients diff --git a/src/handlers/personal_account/view.cpp b/src/handlers/personal_account/view.cpp index 82e7d664..36f2af94 100644 --- a/src/handlers/personal_account/view.cpp +++ b/src/handlers/personal_account/view.cpp @@ -30,8 +30,8 @@ void handlePersonalAccountMenuCQ( return; } if (data == "ingredients") { - renderCustomIngredientsList(true, 0, userId, chatId, bot, api); - stateManager.put(CustomIngredientsList{.pageNo = 0}); + renderCustomIngredientsList(true, userId, chatId, bot, api); + stateManager.put(CustomIngredientsList{}); return; } } diff --git a/src/main.cpp b/src/main.cpp index 26b71e0c..68eb53cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,6 +41,7 @@ int main(int argc, char* argv[]) { customIngredientCreationEnterNameCQHandler, customIngredientConfirmationCQHandler, customIngredientPublishCQHandler, + handleCustomIngredientDeletionCQHandler, storageListCQHandler, storageCreationEnterNameMsgHandler, storageCreationEnterNameCQHandler, diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 3b0625c8..d0bfffd0 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources(main PRIVATE src/render/personal_account/ingredients_list/create.cpp src/render/personal_account/ingredients_list/publish.cpp src/render/personal_account/ingredients_list/view.cpp + src/render/personal_account/ingredients_list/delete.cpp src/render/personal_account/recipes_list/create.cpp src/render/personal_account/recipes_list/view.cpp diff --git a/src/render/personal_account/ingredients_list/delete.cpp b/src/render/personal_account/ingredients_list/delete.cpp new file mode 100644 index 00000000..b183df75 --- /dev/null +++ b/src/render/personal_account/ingredients_list/delete.cpp @@ -0,0 +1,48 @@ +#include "delete.hpp" + +#include "backend/models/ingredient.hpp" +#include "backend/models/publication_request_status.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "utils/to_string.hpp" +#include "utils/utils.hpp" + +#include +#include +#include +#include + +namespace cookcookhnya::render::personal_account::ingredients { + +using namespace std::views; + +void renderCustomIngredientDeletion(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + auto ingredientsResp = api.customIngredientsSearch(userId, "", 0); + + // TODO: make pagination for ingredients + std::vector ingredients; + for (const auto& ing : ingredientsResp.page) { + if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { + ingredients.push_back(ing); + } + } + + InlineKeyboardBuilder keyboard{ingredients.size() + 1}; + for (auto chunk : ingredients | chunk(2)) { + keyboard.reserveInRow(2); + for (const auto& i : chunk) { + keyboard << makeCallbackButton("• " + i.name, utils::to_string(i.id)); + } + keyboard << NewRow{}; + } + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + + auto text = std::format("{} Какой ингредиент вы хотите удалить?\n", utils::utf8str(u8"🚮")); + auto messageId = message::getMessageId(userId); + if (messageId) { + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); + } +} + +} // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/ingredients_list/delete.hpp b/src/render/personal_account/ingredients_list/delete.hpp new file mode 100644 index 00000000..64624ddb --- /dev/null +++ b/src/render/personal_account/ingredients_list/delete.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "render/common.hpp" + +namespace cookcookhnya::render::personal_account::ingredients { + +void renderCustomIngredientDeletion(UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); + +} // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/render/personal_account/ingredients_list/view.cpp b/src/render/personal_account/ingredients_list/view.cpp index 0b272245..f6130a0a 100644 --- a/src/render/personal_account/ingredients_list/view.cpp +++ b/src/render/personal_account/ingredients_list/view.cpp @@ -4,7 +4,6 @@ #include "backend/models/publication_request_status.hpp" #include "message_tracker.hpp" #include "render/common.hpp" -#include "utils/to_string.hpp" #include "utils/utils.hpp" #include @@ -17,90 +16,75 @@ namespace cookcookhnya::render::personal_account::ingredients { using namespace tg_types; +using namespace cookcookhnya::api::models::moderation; namespace { -std::vector constructNavigationButtons(std::size_t pageNo, std::size_t maxPageNum) { - std::vector buttons; - auto forward = makeCallbackButton(u8"▶️", "next"); - auto backward = makeCallbackButton(u8"◀️", "prev"); - auto dont_handle = makeCallbackButton(u8"ㅤ", "dont_handle"); - auto page = makeCallbackButton(std::format("{} из {}", (pageNo + 1), maxPageNum), "dont_handle"); - if (pageNo == maxPageNum) { - buttons.push_back(backward); - buttons.push_back(page); - buttons.push_back(dont_handle); - } else if (pageNo == 0) { - buttons.push_back(dont_handle); - buttons.push_back(page); - buttons.push_back(forward); - } else { - buttons.push_back(backward); - buttons.push_back(page); - buttons.push_back(forward); - } - return buttons; -} - -std::pair constructMessage( // NOLINT(*complexity*) - size_t pageNo, - size_t numOfIngredientsOnPage, - api::models::ingredient::CustomIngredientList& ingredientsList) { +std::pair +constructMessage(api::models::ingredient::CustomIngredientList& ingredientsList) { // NOLINT(*complexity*) std::size_t numOfRows = 0; if (ingredientsList.found == 0) numOfRows = 2; - else if (ingredientsList.found <= numOfIngredientsOnPage) - numOfRows = 3; else numOfRows = 4; std::string text; InlineKeyboard keyboard(numOfRows); + std::vector noReq; + std::vector pending; + std::vector accepted; + std::vector rejected; + for (const auto& ing : ingredientsList.page) { + switch (ing.moderationStatus) { + case PublicationRequestStatus::NO_REQUEST: + noReq.push_back(ing); + break; + case PublicationRequestStatus::PENDING: + pending.push_back(ing); + break; + case PublicationRequestStatus::ACCEPTED: + accepted.push_back(ing); + break; + case PublicationRequestStatus::REJECTED: + rejected.push_back(ing); + break; + } + } + if (ingredientsList.found == 0) { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. Создавайте и делитесь новыми ингредиентами\\.\n\n"); keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - } else if (ingredientsList.found <= numOfIngredientsOnPage && pageNo == 0) { + } else { text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); - for (const auto& ing : ingredientsList.page) { - if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { + if (!noReq.empty()) { + text += "⚪️ Без запроса на публикацию:\n"; + for (const auto& ing : noReq) { text += std::format("• {}\n", ing.name); - } else { - if (ing.reason) { - text += std::format( - "• {}, Статус: {}, Причина: {}\n", ing.name, utils::to_string(ing.moderationStatus), *ing.reason); - } else { - text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); - } } } - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); - keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); - keyboard[2].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - } else { - text = utils::utf8str(u8"📋 Вы находитесь в Мои ингредиенты\\. \nВами созданные ингредиенты:\n\n"); - for (const auto& ing : ingredientsList.page) { - if (ing.moderationStatus == api::models::moderation::PublicationRequestStatus::NO_REQUEST) { + if (!pending.empty()) { + text += "\n🟡 На рассмотрении:\n"; + for (const auto& ing : pending) { text += std::format("• {}\n", ing.name); - } else { - if (ing.reason) { - text += std::format( - "• {}, Статус: {}, Причина: {}\n", ing.name, utils::to_string(ing.moderationStatus), *ing.reason); - } else { - text += std::format("• {}, Статус: {}\n", ing.name, utils::to_string(ing.moderationStatus)); - } } - } - keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); - keyboard[1].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); - keyboard[2].reserve(3); - const std::size_t maxPageNum = - std::ceil(static_cast(ingredientsList.found) / static_cast(numOfIngredientsOnPage)); - for (const auto& navigButton : constructNavigationButtons(pageNo, maxPageNum)) { - keyboard[2].push_back(navigButton); + if (!rejected.empty()) { + text += "\n🔴 Отклонены:\n"; + for (const auto& ing : rejected) { + text += std::format("• {}\n", ing.name); + } } + if (!accepted.empty()) { + text += "\n🟢 Приняты:\n"; + for (const auto& ing : accepted) { + text += std::format("• {}\n", ing.name); + } + } + keyboard[0].push_back(makeCallbackButton(u8"🆕 Создать", "create")); + keyboard[1].push_back(makeCallbackButton(u8"🚮 Удалить", "delete")); + keyboard[2].push_back(makeCallbackButton(u8"📢 Опубликовать", "publish")); keyboard[3].push_back(makeCallbackButton(u8"↩️ Назад", "back")); } return std::make_pair(text, keyboard); @@ -108,14 +92,12 @@ std::pair constructMessage( // NOLINT(*complexity*) } // namespace -void renderCustomIngredientsList( - bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { - const std::size_t numOfIngredientsOnPage = 5; +void renderCustomIngredientsList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api) { + const std::size_t numOfIngredientsOnPage = 500; - auto ingredientsList = - api.customIngredientsSearch(userId, "", 0, numOfIngredientsOnPage, pageNo * numOfIngredientsOnPage); + auto ingredientsList = api.customIngredientsSearch(userId, "", 0, numOfIngredientsOnPage); - auto res = constructMessage(pageNo, numOfIngredientsOnPage, ingredientsList); + auto res = constructMessage(ingredientsList); auto text = res.first; auto keyboard = res.second; if (toBeEdited) { diff --git a/src/render/personal_account/ingredients_list/view.hpp b/src/render/personal_account/ingredients_list/view.hpp index 6cb4a0a6..a277d5cd 100644 --- a/src/render/personal_account/ingredients_list/view.hpp +++ b/src/render/personal_account/ingredients_list/view.hpp @@ -4,7 +4,6 @@ namespace cookcookhnya::render::personal_account::ingredients { -void renderCustomIngredientsList( - bool toBeEdited, std::size_t pageNo, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); +void renderCustomIngredientsList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, IngredientsApiRef api); } // namespace cookcookhnya::render::personal_account::ingredients diff --git a/src/states.hpp b/src/states.hpp index 7df1f603..a3b94850 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -33,19 +33,13 @@ struct MainMenu {}; struct PersonalAccountMenu {}; -struct CustomIngredientsList { - std::size_t pageNo; -}; -struct CustomIngredientCreationEnterName { - std::size_t pageNo; -}; +struct CustomIngredientsList {}; +struct CustomIngredientCreationEnterName {}; struct CustomIngredientConfirmation { - std::size_t pageNo; std::string name; }; -struct CustomIngredientPublish { - std::size_t pageNo; -}; +struct CustomIngredientDeletion {}; +struct CustomIngredientPublish {}; struct StorageList {}; struct StorageCreationEnterName {}; @@ -167,6 +161,7 @@ using State = std::variant Date: Tue, 22 Jul 2025 19:40:21 +0300 Subject: [PATCH 079/106] 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 080/106] 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 0db3105c742ce1c448fecbc73aeb872448745ea5 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 20:10:33 +0300 Subject: [PATCH 081/106] chore: update tgbotstater --- .github/actions/build_deps/action.yml | 6 +++--- CMakeLists.txt | 4 ++++ Dockerfile | 4 ++-- conanfile.txt | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/actions/build_deps/action.yml b/.github/actions/build_deps/action.yml index dc59d791..9c92417c 100644 --- a/.github/actions/build_deps/action.yml +++ b/.github/actions/build_deps/action.yml @@ -17,7 +17,7 @@ runs: path: | ~/.conan2 /tmp/deps - key: ${{ runner.os }}-conan-cpp23-${{ hashFiles('Frontend/conanfile.txt') }}-tgbotstater-0.4.1 + key: ${{ runner.os }}-conan-cpp23-${{ hashFiles('Frontend/conanfile.txt') }}-tgbotstater-0.4.2 restore-keys: | ${{ runner.os }}-conan-cpp23- @@ -54,9 +54,9 @@ runs: run: | mkdir -p /tmp/deps cd /tmp/deps - wget https://github.com/Makcal/TgBotStater/archive/refs/tags/v0.4.1.tar.gz -O tgbotstater.tar.gz + wget https://github.com/Makcal/TgBotStater/archive/refs/tags/v0.4.2.tar.gz -O tgbotstater.tar.gz tar -xf tgbotstater.tar.gz - cd TgBotStater-0.4.1 + cd TgBotStater-0.4.2 conan create . --build=missing - name: Install project dependencies diff --git a/CMakeLists.txt b/CMakeLists.txt index 3774c9ee..71798432 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,10 @@ include(src/handlers/CMakeLists.txt) include(src/render/CMakeLists.txt) include(src/utils/CMakeLists.txt) +# settings +target_compile_definitions(main PRIVATE + TGBOTSTATER_NOT_LOG_HANDLERS_CALLS) + # setup target_include_directories(main PRIVATE ${CMAKE_SOURCE_DIR}/src diff --git a/Dockerfile b/Dockerfile index 64d456b5..c801bb53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,10 +17,10 @@ RUN conan profile detect \ RUN conan install --requires=boost/1.83.0 --build=missing WORKDIR /deps -RUN wget https://github.com/Makcal/TgBotStater/archive/refs/tags/v0.4.1.tar.gz -O tgbotstater.tar.gz \ +RUN wget https://github.com/Makcal/TgBotStater/archive/refs/tags/v0.4.2.tar.gz -O tgbotstater.tar.gz \ && tar -xf tgbotstater.tar.gz \ && rm tgbotstater.tar.gz \ - && cd TgBotStater-0.4.1 \ + && cd TgBotStater-0.4.2 \ && conan create . --build=missing WORKDIR /app diff --git a/conanfile.txt b/conanfile.txt index 6627a1fe..e93360e5 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,5 +1,5 @@ [requires] -tgbotstater/0.4.1 +tgbotstater/0.4.2 cpp-httplib/0.19.0 boost/1.83.0 From 8a866d3d07eab44c801930c4ff175a093ee26721 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Tue, 22 Jul 2025 20:19:03 +0300 Subject: [PATCH 082/106] 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 a9dc17aff663880b23eddd45d1c1198011a02a0e Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 20:42:52 +0300 Subject: [PATCH 083/106] feat: add recipes search --- src/backend/api/recipes.cpp | 6 +- src/backend/api/recipes.hpp | 13 ++-- src/handlers/CMakeLists.txt | 2 + src/handlers/common.hpp | 2 + src/handlers/handlers_list.hpp | 7 ++ src/handlers/main_menu/view.cpp | 10 ++- .../recipe/search_ingredients.cpp | 24 +++---- src/handlers/recipes_search/view.cpp | 64 +++++++++++++++++++ src/handlers/recipes_search/view.hpp | 14 ++++ src/handlers/storage/ingredients/view.cpp | 18 +++--- src/main.cpp | 4 +- src/render/CMakeLists.txt | 2 + src/render/main_menu/view.cpp | 4 +- src/render/pagination.hpp | 2 +- src/render/recipes_search/view.cpp | 45 +++++++++++++ src/render/recipes_search/view.hpp | 14 ++++ src/render/recipes_suggestions/view.hpp | 2 +- src/render/storages_list/view.cpp | 3 +- src/states.hpp | 32 ++++++---- 19 files changed, 218 insertions(+), 50 deletions(-) create mode 100644 src/handlers/recipes_search/view.cpp create mode 100644 src/handlers/recipes_search/view.hpp create mode 100644 src/render/recipes_search/view.cpp create mode 100644 src/render/recipes_search/view.hpp diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 13f79185..31da6823 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -33,9 +33,9 @@ RecipesListWithIngredientsCount RecipesApi::getSuggestedRecipes(UserId user, RecipeSearchResponse RecipesApi::search(UserId user, PublicityFilterType filter, std::string query, - std::size_t threshold, std::size_t size, - std::size_t offset) const { + std::size_t offset, + std::size_t threshold) const { return jsonGetAuthed(user, "/recipes", {{"query", std::move(query)}, @@ -47,7 +47,7 @@ RecipeSearchResponse RecipesApi::search(UserId user, // GET /recipes RecipesList RecipesApi::getList(UserId user, PublicityFilterType filter, std::size_t size, std::size_t offset) const { - auto result = search(user, filter, "", 0, size, offset); + auto result = search(user, filter, "", size, offset, 0); return {.page = std::move(result.page), .found = result.found}; } diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 187c2de0..d7be544e 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -25,12 +25,13 @@ class RecipesApi : ApiBase { size_t size = 500, // NOLINT(*magic-number*) size_t offset = 0) const; - [[nodiscard]] models::recipe::RecipeSearchResponse search(UserId user, - PublicityFilterType filter = PublicityFilterType::All, - std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic-number*) - std::size_t size = 100, // NOLINT(*magic-number*) - std::size_t offset = 0) const; + [[nodiscard]] models::recipe::RecipeSearchResponse + search(UserId user, + PublicityFilterType filter = PublicityFilterType::All, + std::string query = "", + std::size_t size = 100, // NOLINT(*magic-number*) + std::size_t offset = 0, + std::size_t threshold = 50) const; // NOLINT(*magic-number*) [[nodiscard]] models::recipe::RecipesList getList(UserId user, PublicityFilterType filter = PublicityFilterType::All, diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 73fab435..3d5166b7 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -45,4 +45,6 @@ target_sources(main PRIVATE src/handlers/storages_list/view.cpp src/handlers/storages_selection/view.cpp + + src/handlers/recipes_search/view.cpp ) diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 3a3cc280..d7d5d942 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -52,6 +52,8 @@ using states::CustomRecipePublicationHistory; using states::CustomRecipesList; using states::CustomRecipeView; +using states::RecipesSearch; + // Type aliases using BotRef = const TgBot::Api&; using SMRef = const states::StateManager&; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 3644ffe2..5ff0fd3d 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -48,6 +48,8 @@ #include "storages_selection/view.hpp" +#include "recipes_search/view.hpp" + #include #include @@ -67,6 +69,7 @@ using namespace handlers::storage::members; using namespace handlers::storages_list; using namespace handlers::storages_selection; using namespace handlers::recipes_suggestions; +using namespace handlers::recipes_search; using namespace tg_stater; @@ -161,4 +164,8 @@ using customRecipeIngredientsSearchIQHandler = Handler; +// Recipes search +using recipesSearchCQHandler = Handler; +using recipesSearchIQHandler = Handler; + } // namespace cookcookhnya::handlers::bot_handlers diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index bef43ad5..392d2993 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -5,12 +5,12 @@ #include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/personal_account/view.hpp" +#include "render/recipes_search/view.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/view.hpp" #include "render/storages_list/view.hpp" #include "render/storages_selection/view.hpp" -#include #include #include @@ -21,6 +21,7 @@ using namespace render::recipes_suggestions; using namespace render::select_storages; using namespace render::shopping_list; using namespace render::personal_account; +using namespace render::recipes_search; using namespace std::views; void handleMainMenuCQ( @@ -58,6 +59,13 @@ void handleMainMenuCQ( return; } + if (cq.data == "recipes_search") { + auto newState = RecipesSearch{}; + renderRecipesSearch(newState.pagination, newState.page, userId, chatId, bot); + stateManager.put(std::move(newState)); + return; + } + if (cq.data == "personal_account") { renderPersonalAccountMenu(userId, chatId, bot); stateManager.put(PersonalAccountMenu{}); diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index edc881a3..00fdb823 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -42,22 +42,22 @@ void updateSearch(CustomRecipeIngredientsSearch& state, tg_types::UserId userId, api::IngredientsApiRef api) { state.pageNo = isQueryChanged ? 0 : state.pageNo; + auto response = api.searchForRecipe( userId, state.recipeId, state.query, threshhold, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage); - if (response.found != state.totalFound || !std::ranges::equal(response.page, - state.searchItems, - std::ranges::equal_to{}, - &IngredientSearchForRecipeItem::id, - &IngredientSearchForRecipeItem::id)) { - state.searchItems = std::move(response.page); - state.totalFound = response.found; - if (auto mMessageId = message::getMessageId(userId)) { - if (state.totalFound != 0) { - renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); - return; - } + const auto idGetter = &IngredientSearchForRecipeItem::id; + if (std::ranges::equal(response.page, state.searchItems, {}, idGetter, idGetter)) + return; + + state.searchItems = std::move(response.page); + state.totalFound = response.found; + if (auto mMessageId = message::getMessageId(userId)) { + if (state.totalFound != 0) { + renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); + return; } } + if (state.totalFound == 0) renderSuggestIngredientCustomisation(state, userId, userId, bot); } diff --git a/src/handlers/recipes_search/view.cpp b/src/handlers/recipes_search/view.cpp new file mode 100644 index 00000000..29dde165 --- /dev/null +++ b/src/handlers/recipes_search/view.cpp @@ -0,0 +1,64 @@ +#include "view.hpp" + +#include "backend/api/api.hpp" +#include "backend/api/publicity_filter.hpp" +#include "backend/id_types.hpp" +#include "handlers/common.hpp" +#include "render/main_menu/view.hpp" +#include "render/recipes_search/view.hpp" +#include "utils/parsing.hpp" + +#include +#include +#include + +namespace cookcookhnya::handlers::recipes_search { + +using namespace render::main_menu; +using namespace render::recipes_search; +// using namespace render::recipe; +using namespace std::literals; + +void handleRecipesSearchCQ( + RecipesSearch& /*unused*/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + + if (cq.data == "back") { + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); + stateManager.put(MainMenu{}); + return; + } + + if (cq.data.starts_with("recipe_")) { + auto recipeId = utils::parseSafe(std::string_view{cq.data}.substr("recipe_"sv.size())); + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); + stateManager.put(MainMenu{}); + return; + } +} + +void handleRecipesSearchIQ(RecipesSearch& state, InlineQueryRef iq, BotRef bot, api::RecipesApiRef api) { + auto userId = iq.from->id; + if (iq.query.empty()) { + if (state.query.empty()) + return; + state.query = ""; + state.pagination = {}; + state.page = {}; + } else { + const std::size_t pageSize = 5; + auto result = api.search(userId, PublicityFilterType::All, state.query, pageSize); + const auto idGetter = &api::models::recipe::RecipeSummary::id; + if (std::ranges::equal(result.page, state.page, {}, idGetter, idGetter)) + return; + + state.query = iq.query; + state.pagination = {.pageNo = 0, .totalItems = result.found}; + state.page = result.page; + } + renderRecipesSearch(state.pagination, state.page, userId, userId, bot); +} + +} // namespace cookcookhnya::handlers::recipes_search diff --git a/src/handlers/recipes_search/view.hpp b/src/handlers/recipes_search/view.hpp new file mode 100644 index 00000000..c7660036 --- /dev/null +++ b/src/handlers/recipes_search/view.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "backend/api/recipes.hpp" +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::recipes_search { + +void handleRecipesSearchCQ( + RecipesSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); + +void handleRecipesSearchIQ(RecipesSearch& state, InlineQueryRef iq, BotRef bot, api::RecipesApiRef api); + +} // namespace cookcookhnya::handlers::recipes_search diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 23ed874e..7b19a925 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -50,16 +50,14 @@ void updateSearch(StorageIngredientsList& state, threshhold, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage); - if (response.found != state.totalFound || !std::ranges::equal(response.page, - state.searchItems, - std::ranges::equal_to{}, - &IngredientSearchForStorageItem::id, - &IngredientSearchForStorageItem::id)) { - state.searchItems = std::move(response.page); - state.totalFound = response.found; - if (auto mMessageId = message::getMessageId(userId)) - renderIngredientsListSearch(state, userId, userId, bot); - } + const auto idGetter = &IngredientSearchForStorageItem::id; + if (std::ranges::equal(response.page, state.searchItems, {}, idGetter, idGetter)) + return; + + state.searchItems = std::move(response.page); + state.totalFound = response.found; + if (auto mMessageId = message::getMessageId(userId)) + renderIngredientsListSearch(state, userId, userId, bot); if (state.totalFound == 0) renderSuggestIngredientCustomisation(state, userId, userId, bot); } diff --git a/src/main.cpp b/src/main.cpp index 7a17cad6..11799cbb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,7 +74,9 @@ int main(int argc, char* argv[]) { customRecipePublicationHistoryCQHandler, totalPublicationHistoryCQHandler, shoppingListIngredientSearchCQHandler, - shoppingListIngredientSearchIQHandler> + shoppingListIngredientSearchIQHandler, + recipesSearchCQHandler, + recipesSearchIQHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index bdbde47a..5ea4c4f1 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -39,4 +39,6 @@ target_sources(main PRIVATE src/render/storages_list/view.cpp src/render/storages_selection/view.cpp + + src/render/recipes_search/view.cpp ) diff --git a/src/render/main_menu/view.cpp b/src/render/main_menu/view.cpp index 80c191ab..f7bc8a64 100644 --- a/src/render/main_menu/view.cpp +++ b/src/render/main_menu/view.cpp @@ -30,11 +30,13 @@ void renderMainMenu(bool toBeEdited, text += utils::utf8str(u8"\n\nК сожалению, данное приглашение уже было использовано 🥲"); } - InlineKeyboardBuilder keyboard{4}; + const std::size_t rowsCount = 5; + InlineKeyboardBuilder keyboard{rowsCount}; keyboard << makeCallbackButton(u8"🍱 Хранилища", "storage_list") << NewRow{}; if (!storages.empty()) keyboard << makeCallbackButton(u8"😋 Хочу кушать!", "wanna_eat") << NewRow{}; keyboard << makeCallbackButton(u8"🧾 Список покупок", "shopping_list") << NewRow{} + << makeCallbackButton(u8"👨‍🍳 Рецепты", "recipes_search") << NewRow{} << makeCallbackButton(u8"👤 Личный кабинет", "personal_account"); if (toBeEdited) { diff --git a/src/render/pagination.hpp b/src/render/pagination.hpp index 92725d5d..7f287ecd 100644 --- a/src/render/pagination.hpp +++ b/src/render/pagination.hpp @@ -45,7 +45,7 @@ template auto constructPagination( std::size_t pageNo, std::size_t pageSize, std::size_t totalItems, R&& page, ItemButtonMaker&& makeItemButton) { using namespace std::views; - const std::size_t pagesCount = (totalItems + pageSize - 1) / pageSize; // ceiling + const std::size_t pagesCount = pageSize != 0 ? (totalItems + pageSize - 1) / pageSize : 0; // ceiling const bool lastPage = pageNo + 1 >= pagesCount; auto itemButtons = std::forward(page) | transform(std::forward(makeItemButton)); diff --git a/src/render/recipes_search/view.cpp b/src/render/recipes_search/view.cpp new file mode 100644 index 00000000..b1cef711 --- /dev/null +++ b/src/render/recipes_search/view.cpp @@ -0,0 +1,45 @@ +#include "view.hpp" + +#include "backend/models/recipe.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "render/pagination.hpp" +#include "states.hpp" +#include "utils/utils.hpp" + +#include + +namespace cookcookhnya::render::recipes_search { + +using namespace api::models::recipe; + +void renderRecipesSearch(const states::helpers::Pagination& pagination, + const decltype(states::RecipesSearch::page)& page, + UserId userId, + ChatId chatId, + BotRef bot) { + const std::string text = utils::utf8str(u8"Используйте кнопку ниже, чтобы искать рецепты"); + + InlineKeyboardBuilder keyboard{2 + page.size()}; + + auto searchButton = std::make_shared(); + searchButton->text = utils::utf8str(u8"🔎 Поиск"); + searchButton->switchInlineQueryCurrentChat = ""; + keyboard << std::move(searchButton) << NewRow{}; + + auto makeRecipeButton = [](const RecipeSummary& r) { + return makeCallbackButton(r.name, "recipe_" + utils::to_string(r.id)); + }; + keyboard << constructPagination(pagination.pageNo, page.size(), pagination.totalItems, page, makeRecipeButton); + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto mMessageId = message::getMessageId(userId)) { + bot.editMessageText(text, chatId, *mMessageId, std::move(keyboard)); + } else { + auto message = bot.sendMessage(chatId, text, std::move(keyboard)); + message::addMessageId(userId, message->messageId); + } +} + +} // namespace cookcookhnya::render::recipes_search diff --git a/src/render/recipes_search/view.hpp b/src/render/recipes_search/view.hpp new file mode 100644 index 00000000..9d109014 --- /dev/null +++ b/src/render/recipes_search/view.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "render/common.hpp" +#include "states.hpp" + +namespace cookcookhnya::render::recipes_search { + +void renderRecipesSearch(const states::helpers::Pagination& pagination, + const decltype(states::RecipesSearch::page)& page, + UserId userId, + ChatId chatId, + BotRef bot); + +} // namespace cookcookhnya::render::recipes_search diff --git a/src/render/recipes_suggestions/view.hpp b/src/render/recipes_suggestions/view.hpp index dd3720ea..34491c7d 100644 --- a/src/render/recipes_suggestions/view.hpp +++ b/src/render/recipes_suggestions/view.hpp @@ -1,6 +1,6 @@ #pragma once -#include "backend/api/api.hpp" +#include "backend/api/recipes.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" diff --git a/src/render/storages_list/view.cpp b/src/render/storages_list/view.cpp index 244bdb7e..02fe41ea 100644 --- a/src/render/storages_list/view.cpp +++ b/src/render/storages_list/view.cpp @@ -15,6 +15,8 @@ using namespace tg_types; using namespace std::views; void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot, api::StorageApiRef storageApi) { + const auto text = utils::utf8str(u8"🍱 Ваши хранилища"); + auto storages = storageApi.getStoragesList(userId); const std::size_t buttonRows = ((storages.size() + 1) / 2) + 1; // ceil(storagesCount / 2) and back @@ -29,7 +31,6 @@ void renderStorageList(bool toBeEdited, UserId userId, ChatId chatId, BotRef bot keyboard << makeCallbackButton(u8"↩️ Назад", "back") << makeCallbackButton(u8"🆕 Создать", "create"); - auto text = utils::utf8str(u8"🍱 Ваши хранилища"); if (toBeEdited) { auto messageId = message::getMessageId(userId); if (messageId) { diff --git a/src/states.hpp b/src/states.hpp index cbbeec0a..73b4436c 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -20,7 +20,7 @@ namespace cookcookhnya::states { -namespace detail { +namespace helpers { struct StorageIdMixin { api::StorageId storageId; @@ -32,11 +32,14 @@ struct Pagination { std::size_t totalItems; }; -} // namespace detail +} // namespace helpers struct MainMenu {}; struct PersonalAccountMenu {}; +struct TotalPublicationHistory { + std::size_t pageNo; +}; struct CustomIngredientsList { std::size_t pageNo; @@ -68,13 +71,13 @@ struct CustomIngredientPublish { struct StorageList {}; struct StorageCreationEnterName {}; -struct StorageView : detail::StorageIdMixin {}; -struct StorageDeletion : detail::StorageIdMixin {}; -struct StorageMemberView : detail::StorageIdMixin {}; -struct StorageMemberAddition : detail::StorageIdMixin {}; -struct StorageMemberDeletion : detail::StorageIdMixin {}; +struct StorageView : helpers::StorageIdMixin {}; +struct StorageDeletion : helpers::StorageIdMixin {}; +struct StorageMemberView : helpers::StorageIdMixin {}; +struct StorageMemberAddition : helpers::StorageIdMixin {}; +struct StorageMemberDeletion : helpers::StorageIdMixin {}; -struct StorageIngredientsList : detail::StorageIdMixin { +struct StorageIngredientsList : helpers::StorageIdMixin { using IngredientsDb = utils::FastSortedDb; IngredientsDb storageIngredients; @@ -88,7 +91,7 @@ struct StorageIngredientsList : detail::StorageIdMixin { : StorageIdMixin{storageId}, storageIngredients{std::forward(ingredients)}, inlineQuery(std::move(iq)) {} }; -struct StorageIngredientsDeletion : detail::StorageIdMixin { +struct StorageIngredientsDeletion : helpers::StorageIdMixin { std::vector selectedIngredients; std::vector storageIngredients; bool addedToShopList; @@ -182,7 +185,7 @@ struct ShoppingListStorageSelectionToBuy { struct ShoppingListIngredientSearch { ShoppingListView prevState; std::string query; - detail::Pagination pagination; + helpers::Pagination pagination; std::vector page; }; @@ -192,8 +195,10 @@ struct CustomRecipePublicationHistory { std::string recipeName; }; -struct TotalPublicationHistory { - std::size_t pageNo; +struct RecipesSearch { + std::string query; + helpers::Pagination pagination; + std::vector page; }; using State = std::variant; + ShoppingListIngredientSearch, + RecipesSearch>; using StateManager = tg_stater::StateProxy>; From 57674e640ad76c627b270fe0b3d25d05a191db96 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:56:09 +0300 Subject: [PATCH 084/106] ci: add dev branch as a fallback to linter cache --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc202ab9..e0807e7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,7 @@ jobs: key: ${{ runner.os }}-clang-tidy-${{ github.ref }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-clang-tidy-${{ github.ref }}- + ${{ runner.os }}-clang-tidy-refs/heads/dev ${{ runner.os }}-clang-tidy-refs/heads/main - name: Copy original cache file if exists From 3ea6a7a74f66ecdf5c089d014e56ad8662035916 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Tue, 22 Jul 2025 21:12:36 +0300 Subject: [PATCH 085/106] format --- src/handlers/personal_account/ingredients_list/delete.cpp | 7 +++++-- src/handlers/personal_account/ingredients_list/delete.hpp | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/handlers/personal_account/ingredients_list/delete.cpp b/src/handlers/personal_account/ingredients_list/delete.cpp index 62040615..0b5e0a60 100644 --- a/src/handlers/personal_account/ingredients_list/delete.cpp +++ b/src/handlers/personal_account/ingredients_list/delete.cpp @@ -10,8 +10,11 @@ namespace cookcookhnya::handlers::personal_account::ingredients { using namespace render::personal_account::ingredients; -void handleCustomIngredientDeletionCQ( - CustomIngredientDeletion& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::IngredientsApiRef api) { +void handleCustomIngredientDeletionCQ(CustomIngredientDeletion& /*unused*/, + CallbackQueryRef cq, + BotRef& bot, + SMRef stateManager, + api::IngredientsApiRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; diff --git a/src/handlers/personal_account/ingredients_list/delete.hpp b/src/handlers/personal_account/ingredients_list/delete.hpp index 064ba5b1..f2338f26 100644 --- a/src/handlers/personal_account/ingredients_list/delete.hpp +++ b/src/handlers/personal_account/ingredients_list/delete.hpp @@ -5,7 +5,10 @@ namespace cookcookhnya::handlers::personal_account::ingredients { -void handleCustomIngredientDeletionCQ( - CustomIngredientDeletion& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::IngredientsApiRef api); +void handleCustomIngredientDeletionCQ(CustomIngredientDeletion& /*unused*/, + CallbackQueryRef cq, + BotRef& bot, + SMRef stateManager, + api::IngredientsApiRef api); } // namespace cookcookhnya::handlers::personal_account::ingredients From 5c7963aa3481c0b27603dfba703687127d97b9c8 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Tue, 22 Jul 2025 23:41:08 +0300 Subject: [PATCH 086/106] refactor: rename Recipe to SuggestedRecipe --- src/backend/api/recipes.cpp | 4 +-- src/backend/api/recipes.hpp | 2 +- src/backend/models/recipe.cpp | 13 +++++----- src/backend/models/recipe.hpp | 5 ++-- src/handlers/common.hpp | 2 +- src/handlers/recipes_suggestions/view.cpp | 2 +- src/handlers/suggested_recipe/view.cpp | 7 +++--- src/handlers/suggested_recipe/view.hpp | 3 ++- src/render/personal_account/recipe/view.cpp | 2 +- src/render/recipes_search/view.cpp | 2 +- src/render/suggested_recipe/add_storage.cpp | 6 ++--- src/render/suggested_recipe/add_storage.hpp | 28 +++++++++++---------- src/render/suggested_recipe/view.cpp | 8 +++--- src/render/suggested_recipe/view.hpp | 4 +-- src/states.hpp | 8 +++--- src/utils/ingredients_availability.cpp | 6 ++--- src/utils/ingredients_availability.hpp | 6 ++--- 17 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 31da6823..047ab3de 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -52,8 +52,8 @@ RecipesList RecipesApi::getList(UserId user, PublicityFilterType filter, std::si } // GET /recipes/{recipeId} -RecipeDetails RecipesApi::get(UserId user, RecipeId recipe) const { - return jsonGetAuthed(user, std::format("/recipes/{}", recipe)); +SuggestedRecipeDetails RecipesApi::getSuggested(UserId user, RecipeId recipe) const { + return jsonGetAuthed(user, std::format("/recipes/{}", recipe)); } // POST /recipes diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index d7be544e..96487f1b 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -43,7 +43,7 @@ class RecipesApi : ApiBase { [[nodiscard]] std::vector getRecipeRequestHistory(UserId user, RecipeId recipe) const; - [[nodiscard]] models::recipe::RecipeDetails get(UserId user, RecipeId recipeId) const; + [[nodiscard]] models::recipe::SuggestedRecipeDetails getSuggested(UserId user, RecipeId recipeId) const; void delete_(UserId user, RecipeId recipe) const; diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 081dfdf5..43b5edc8 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -51,14 +51,15 @@ IngredientInRecipe tag_invoke(json::value_to_tag /*tag*/, co }; } -RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { +SuggestedRecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { - .ingredients = value_to(j.at("ingredients")), - .name = value_to(j.at("name")), - .link = value_to(j.at("sourceLink")), + .ingredients = value_to(j.at("ingredients")), + .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, + .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, diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index 03d78cee..c48c2257 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -40,14 +40,15 @@ struct IngredientInRecipe { friend IngredientInRecipe tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; -struct RecipeDetails { +struct SuggestedRecipeDetails { std::vector ingredients; std::string name; std::optional link; std::optional creator; moderation::PublicationRequestStatus moderationStatus = moderation::PublicationRequestStatus::NO_REQUEST; - friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); + friend SuggestedRecipeDetails tag_invoke(boost::json::value_to_tag, + const boost::json::value& j); }; struct RecipesList { diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index d7d5d942..677f7459 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -25,7 +25,7 @@ using states::StorageList; using states::TotalPublicationHistory; using states::RecipeStorageAddition; -using states::RecipeView; +using states::SuggestedRecipeView; using states::StorageCreationEnterName; using states::StorageDeletion; diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index 8f276471..4698cd4c 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -55,7 +55,7 @@ void handleSuggestedRecipesListCQ( return; auto inStorage = utils::inStoragesAvailability(state.selectedStorages, *recipeId, userId, api); renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); - stateManager.put(RecipeView{ + stateManager.put(SuggestedRecipeView{ .prevState = std::move(state), .addedStorages = {}, .availability = inStorage, diff --git a/src/handlers/suggested_recipe/view.cpp b/src/handlers/suggested_recipe/view.cpp index 5af4b5fe..7a2d7fa2 100644 --- a/src/handlers/suggested_recipe/view.cpp +++ b/src/handlers/suggested_recipe/view.cpp @@ -19,10 +19,11 @@ using namespace render::recipes_suggestions; using namespace render::shopping_list; using namespace render::suggested_recipe; using namespace api::models::ingredient; -using IngredientAvailability = states::RecipeView::IngredientAvailability; -using AvailabilityType = states::RecipeView::AvailabilityType; +using IngredientAvailability = states::SuggestedRecipeView::IngredientAvailability; +using AvailabilityType = states::SuggestedRecipeView::AvailabilityType; -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { +void handleRecipeViewCQ( + SuggestedRecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; diff --git a/src/handlers/suggested_recipe/view.hpp b/src/handlers/suggested_recipe/view.hpp index 512c6af6..f38a8de9 100644 --- a/src/handlers/suggested_recipe/view.hpp +++ b/src/handlers/suggested_recipe/view.hpp @@ -5,6 +5,7 @@ namespace cookcookhnya::handlers::suggested_recipe { -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); +void handleRecipeViewCQ( + SuggestedRecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::suggested_recipe diff --git a/src/render/personal_account/recipe/view.cpp b/src/render/personal_account/recipe/view.cpp index 077615d2..df141e85 100644 --- a/src/render/personal_account/recipe/view.cpp +++ b/src/render/personal_account/recipe/view.cpp @@ -21,7 +21,7 @@ using namespace api::models::moderation; std::pair, std::string> renderCustomRecipe( bool toBeEdited, UserId userId, ChatId chatId, api::RecipeId recipeId, BotRef bot, api::RecipesApiRef recipesApi) { - auto recipeDetails = recipesApi.get(userId, recipeId); + auto recipeDetails = recipesApi.getSuggested(userId, recipeId); std::vector ingredients; diff --git a/src/render/recipes_search/view.cpp b/src/render/recipes_search/view.cpp index b1cef711..7dfce0da 100644 --- a/src/render/recipes_search/view.cpp +++ b/src/render/recipes_search/view.cpp @@ -28,7 +28,7 @@ void renderRecipesSearch(const states::helpers::Pagination& pagination, keyboard << std::move(searchButton) << NewRow{}; auto makeRecipeButton = [](const RecipeSummary& r) { - return makeCallbackButton(r.name, "recipe_" + utils::to_string(r.id)); + return makeCallbackButton(utils::utf8str(u8"🔖 ") + r.name, "recipe_" + utils::to_string(r.id)); }; keyboard << constructPagination(pagination.pageNo, page.size(), pagination.totalItems, page, makeRecipeButton); diff --git a/src/render/suggested_recipe/add_storage.cpp b/src/render/suggested_recipe/add_storage.cpp index 35a5ccf5..2b526aa9 100644 --- a/src/render/suggested_recipe/add_storage.cpp +++ b/src/render/suggested_recipe/add_storage.cpp @@ -21,15 +21,15 @@ namespace cookcookhnya::render::suggested_recipe { using namespace api::models::recipe; using namespace api::models::storage; -using IngredientAvailability = states::RecipeView::IngredientAvailability; -using AvailabilityType = states::RecipeView::AvailabilityType; +using IngredientAvailability = states::SuggestedRecipeView::IngredientAvailability; +using AvailabilityType = states::SuggestedRecipeView::AvailabilityType; TextGenInfo storageAdditionView(const std::vector& inStoragesAvailability, const std::vector& selectedStorages, api::RecipeId recipeId, UserId userId, api::ApiClientRef api) { - auto recipe = api.getRecipesApi().get(userId, recipeId); + auto recipe = api.getRecipesApi().getSuggested(userId, recipeId); bool isIngredientNotAvailable = false; bool isIngredientIsOtherStorages = false; diff --git a/src/render/suggested_recipe/add_storage.hpp b/src/render/suggested_recipe/add_storage.hpp index b4dcb25f..b29e43ae 100644 --- a/src/render/suggested_recipe/add_storage.hpp +++ b/src/render/suggested_recipe/add_storage.hpp @@ -11,19 +11,21 @@ namespace cookcookhnya::render::suggested_recipe { -TextGenInfo storageAdditionView(const std::vector& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api); +TextGenInfo +storageAdditionView(const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + api::RecipeId recipeId, + UserId userId, + api::ApiClientRef api); -void renderStoragesSuggestion(const std::vector& inStoragesAvailability, - const std::vector& selectedStorages, - const std::vector& addedStorages, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - api::ApiClientRef api); +void renderStoragesSuggestion( + const std::vector& inStoragesAvailability, + const std::vector& selectedStorages, + const std::vector& addedStorages, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + api::ApiClientRef api); } // namespace cookcookhnya::render::suggested_recipe diff --git a/src/render/suggested_recipe/view.cpp b/src/render/suggested_recipe/view.cpp index 88f8e4b9..2933c9b3 100644 --- a/src/render/suggested_recipe/view.cpp +++ b/src/render/suggested_recipe/view.cpp @@ -17,19 +17,19 @@ namespace cookcookhnya::render::suggested_recipe { using namespace api::models::recipe; -using IngredientAvailability = states::RecipeView::IngredientAvailability; -using AvailabilityType = states::RecipeView::AvailabilityType; +using IngredientAvailability = states::SuggestedRecipeView::IngredientAvailability; +using AvailabilityType = states::SuggestedRecipeView::AvailabilityType; TextGenInfo recipeView(const std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, api::ApiClientRef api) { - auto recipeIngredients = api.getRecipesApi().get(userId, recipeId); + auto recipeIngredients = api.getRecipesApi().getSuggested(userId, recipeId); bool isIngredientNotAvailable = false; bool isIngredientIsOtherStorages = false; std::string& recipeName = recipeIngredients.name; - auto text = std::format("{} Ингредиенты для *{}* \n\n", utils::utf8str(u8"📖"), recipeName); + auto text = std::format("{} *{}* \n\n", utils::utf8str(u8"📖 Ингредиенты для"), recipeName); for (const auto& availability : inStoragesAvailability) { if (availability.available == AvailabilityType::AVAILABLE) { diff --git a/src/render/suggested_recipe/view.hpp b/src/render/suggested_recipe/view.hpp index 67795acf..57b40f3b 100644 --- a/src/render/suggested_recipe/view.hpp +++ b/src/render/suggested_recipe/view.hpp @@ -16,14 +16,14 @@ struct TextGenInfo { bool isIngredientIsOtherStorages; }; -void renderRecipeView(std::vector& inStoragesAvailability, +void renderRecipeView(std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, api::ApiClientRef api); -TextGenInfo recipeView(const std::vector& inStoragesAvailability, +TextGenInfo recipeView(const std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, api::ApiClientRef api); diff --git a/src/states.hpp b/src/states.hpp index 73b4436c..6a4093db 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -106,7 +106,7 @@ struct SuggestedRecipesList { std::size_t pageNo; bool fromStorage; }; -struct RecipeView { +struct SuggestedRecipeView { enum struct AvailabilityType : std::uint8_t { NOT_AVAILABLE, AVAILABLE, OTHER_STORAGES }; struct IngredientAvailability { @@ -122,11 +122,11 @@ struct RecipeView { }; struct RecipeStorageAddition { - RecipeView prevState; + SuggestedRecipeView prevState; }; struct ShoppingListCreation { - RecipeView prevState; + SuggestedRecipeView prevState; std::vector selectedIngredients; std::vector allIngredients; }; @@ -218,7 +218,7 @@ using State = std::variant inStoragesAvailability(std::vector(); diff --git a/src/utils/ingredients_availability.hpp b/src/utils/ingredients_availability.hpp index 56acee0b..0c68d8d6 100644 --- a/src/utils/ingredients_availability.hpp +++ b/src/utils/ingredients_availability.hpp @@ -10,16 +10,16 @@ namespace cookcookhnya::utils { -std::vector +std::vector inStoragesAvailability(std::vector& selectedStorages, api::RecipeId recipeId, tg_types::UserId userId, const api::ApiClient& api); -void addStorage(std::vector& availability, +void addStorage(std::vector& availability, const api::models::storage::StorageSummary& storage); -void deleteStorage(std::vector& availability, +void deleteStorage(std::vector& availability, const api::models::storage::StorageSummary& storage); } // namespace cookcookhnya::utils From 427362d4dcdfe4b98329257cdfcfbaad069487d0 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Wed, 23 Jul 2025 01:39:43 +0300 Subject: [PATCH 087/106] feat: add simple recipe view --- src/backend/api/ingredients.cpp | 18 ++++---- src/backend/api/ingredients.hpp | 21 ++++------ src/backend/api/recipes.cpp | 7 +++- src/backend/api/recipes.hpp | 28 ++++++------- src/backend/models/recipe.cpp | 11 +++++ src/backend/models/recipe.hpp | 10 +++++ src/handlers/CMakeLists.txt | 2 + src/handlers/common.hpp | 2 + src/handlers/handlers_list.hpp | 8 +++- .../recipe/search_ingredients.cpp | 3 +- src/handlers/recipe/view.cpp | 24 +++++++++++ src/handlers/recipe/view.hpp | 9 ++++ src/handlers/recipes_search/view.cpp | 38 +++++++++++++---- src/handlers/shopping_list/search.cpp | 6 +-- src/handlers/storage/ingredients/view.cpp | 5 +-- src/main.cpp | 5 ++- src/render/CMakeLists.txt | 2 + .../ingredients_list/create.cpp | 7 +++- src/render/recipe/view.cpp | 42 +++++++++++++++++++ src/render/recipe/view.hpp | 10 +++++ src/states.hpp | 8 +++- src/utils/u8format.hpp | 36 ++++++++++++++++ 22 files changed, 243 insertions(+), 59 deletions(-) create mode 100644 src/handlers/recipe/view.cpp create mode 100644 src/handlers/recipe/view.hpp create mode 100644 src/render/recipe/view.cpp create mode 100644 src/render/recipe/view.hpp create mode 100644 src/utils/u8format.hpp diff --git a/src/backend/api/ingredients.cpp b/src/backend/api/ingredients.cpp index c1235035..66c95a4e 100644 --- a/src/backend/api/ingredients.cpp +++ b/src/backend/api/ingredients.cpp @@ -54,9 +54,9 @@ void IngredientsApi::deleteMultipleFromStorage(UserId user, IngredientSearchForStorageResponse IngredientsApi::searchForStorage(UserId user, StorageId storage, std::string query, - std::size_t threshold, std::size_t count, - std::size_t offset) const { + std::size_t offset, + unsigned threshold) const { return jsonGetAuthed(user, "/ingredients-for-storage", {{"query", std::move(query)}, @@ -70,9 +70,9 @@ IngredientSearchForStorageResponse IngredientsApi::searchForStorage(UserId user, IngredientSearchResponse IngredientsApi::search(UserId user, PublicityFilterType filter, std::string query, - std::size_t threshold, std::size_t count, - std::size_t offset) const { + std::size_t offset, + unsigned threshold) const { return jsonGetAuthed(user, "/ingredients", {{"query", std::move(query)}, @@ -85,7 +85,7 @@ IngredientSearchResponse IngredientsApi::search(UserId user, // GET /recipes IngredientList IngredientsApi::getList(UserId user, PublicityFilterType filter, std::size_t count, std::size_t offset) const { - auto result = search(user, filter, "", 0, count, offset); + auto result = search(user, filter, "", count, offset, 0); return {.page = std::move(result.page), .found = result.found}; } @@ -105,12 +105,8 @@ void IngredientsApi::deleteFromRecipe(UserId user, RecipeId recipe, IngredientId } // GET /ingredients-for-recipe -IngredientSearchForRecipeResponse IngredientsApi::searchForRecipe(UserId user, - RecipeId recipe, - std::string query, - std::size_t threshold, - std::size_t count, - std::size_t offset) const { +IngredientSearchForRecipeResponse IngredientsApi::searchForRecipe( + UserId user, RecipeId recipe, std::string query, std::size_t count, std::size_t offset, unsigned threshold) const { return jsonGetAuthed(user, "/ingredients-for-recipe", {{"query", std::move(query)}, diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index 2297cc6c..54e19927 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -28,7 +28,6 @@ class IngredientsApi : ApiBase { std::size_t offset = 0) const; void putToStorage(UserId user, StorageId storage, IngredientId ingredient) const; - void deleteFromStorage(UserId user, StorageId storage, IngredientId ingredient) const; void @@ -38,17 +37,17 @@ class IngredientsApi : ApiBase { searchForStorage(UserId user, StorageId storage, std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic-number*) - std::size_t count = 50, // NOLINT(*magic-number*) - std::size_t offset = 0) const; + std::size_t count = 50, // NOLINT(*magic-number*) + std::size_t offset = 0, + unsigned threshold = 50) const; // NOLINT(*magic-number*) [[nodiscard]] models::ingredient::IngredientSearchResponse search(UserId user, PublicityFilterType filter = PublicityFilterType::All, std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic-number*) - std::size_t count = 50, // NOLINT(*magic-number*) - std::size_t offset = 0) const; + std::size_t count = 50, // NOLINT(*magic-number*) + std::size_t offset = 0, + unsigned threshold = 50) const; // NOLINT(*magic-number*) [[nodiscard]] models::ingredient::IngredientList getList(UserId user, PublicityFilterType filter = PublicityFilterType::All, @@ -58,20 +57,18 @@ class IngredientsApi : ApiBase { [[nodiscard]] models::ingredient::Ingredient getPublicIngredient(IngredientId ingredient) const; void putToRecipe(UserId user, RecipeId recipeId, IngredientId ingredient) const; - void deleteFromRecipe(UserId user, RecipeId recipeId, IngredientId ingredient) const; [[nodiscard]] models::ingredient::IngredientSearchForRecipeResponse searchForRecipe(UserId user, RecipeId recipe, std::string query = "", - std::size_t threshold = 50, // NOLINT(*magic-number*) - std::size_t count = 50, // NOLINT(*magic-number*) - std::size_t offset = 0) const; + std::size_t count = 50, // NOLINT(*magic-number*) + std::size_t offset = 0, + unsigned threshold = 50) const; // NOLINT(*magic-number*) IngredientId createCustom(UserId user, // NOLINT(*-nodiscard) const models::ingredient::IngredientCreateBody& body) const; - void publishCustom(UserId user, IngredientId ingredient) const; }; diff --git a/src/backend/api/recipes.cpp b/src/backend/api/recipes.cpp index 047ab3de..59d46a02 100644 --- a/src/backend/api/recipes.cpp +++ b/src/backend/api/recipes.cpp @@ -35,7 +35,7 @@ RecipeSearchResponse RecipesApi::search(UserId user, std::string query, std::size_t size, std::size_t offset, - std::size_t threshold) const { + unsigned threshold) const { return jsonGetAuthed(user, "/recipes", {{"query", std::move(query)}, @@ -51,6 +51,11 @@ RecipesList RecipesApi::getList(UserId user, PublicityFilterType filter, std::si return {.page = std::move(result.page), .found = result.found}; } +// GET /recipes/{recipeId} +RecipeDetails RecipesApi::get(UserId user, RecipeId recipe) const { + return jsonGetAuthed(user, std::format("/recipes/{}", recipe)); +} + // GET /recipes/{recipeId} SuggestedRecipeDetails RecipesApi::getSuggested(UserId user, RecipeId recipe) const { return jsonGetAuthed(user, std::format("/recipes/{}", recipe)); diff --git a/src/backend/api/recipes.hpp b/src/backend/api/recipes.hpp index 96487f1b..63750150 100644 --- a/src/backend/api/recipes.hpp +++ b/src/backend/api/recipes.hpp @@ -22,32 +22,32 @@ class RecipesApi : ApiBase { [[nodiscard]] models::recipe::RecipesListWithIngredientsCount getSuggestedRecipes(UserId user, const std::vector& storages, - size_t size = 500, // NOLINT(*magic-number*) - size_t offset = 0) const; + std::size_t size = 500, // NOLINT(*magic-number*) + std::size_t offset = 0) const; - [[nodiscard]] models::recipe::RecipeSearchResponse - search(UserId user, - PublicityFilterType filter = PublicityFilterType::All, - std::string query = "", - std::size_t size = 100, // NOLINT(*magic-number*) - std::size_t offset = 0, - std::size_t threshold = 50) const; // NOLINT(*magic-number*) + [[nodiscard]] models::recipe::SuggestedRecipeDetails getSuggested(UserId user, RecipeId recipeId) const; + + [[nodiscard]] models::recipe::RecipeSearchResponse search(UserId user, + PublicityFilterType filter = PublicityFilterType::All, + std::string query = "", + std::size_t size = 100, // NOLINT(*magic-number*) + std::size_t offset = 0, + unsigned threshold = 50) const; // NOLINT(*magic-number*) [[nodiscard]] models::recipe::RecipesList getList(UserId user, PublicityFilterType filter = PublicityFilterType::All, std::size_t size = 100, // NOLINT(*magic-number*) std::size_t offset = 0) const; + [[nodiscard]] models::recipe::RecipeDetails get(UserId user, RecipeId recipeId) const; + RecipeId create(UserId user, // NOLINT(*-nodiscard) const models::recipe::RecipeCreateBody& body) const; - [[nodiscard]] std::vector getRecipeRequestHistory(UserId user, - RecipeId recipe) const; - - [[nodiscard]] models::recipe::SuggestedRecipeDetails getSuggested(UserId user, RecipeId recipeId) const; - void delete_(UserId user, RecipeId recipe) const; void publishCustom(UserId user, RecipeId recipe) const; + [[nodiscard]] std::vector getRecipeRequestHistory(UserId user, + RecipeId recipe) const; }; using RecipesApiRef = const api::RecipesApi&; diff --git a/src/backend/models/recipe.cpp b/src/backend/models/recipe.cpp index 43b5edc8..1131fa3f 100644 --- a/src/backend/models/recipe.cpp +++ b/src/backend/models/recipe.cpp @@ -21,6 +21,17 @@ RecipeSummary tag_invoke(json::value_to_tag /*tag*/, const json:: }; } +RecipeDetails tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { + return { + .ingredients = value_to(j.at("ingredients")), + .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, + }; +} + RecipeSummaryWithIngredients tag_invoke(json::value_to_tag /*tag*/, const json::value& j) { return { diff --git a/src/backend/models/recipe.hpp b/src/backend/models/recipe.hpp index c48c2257..32c4c4ec 100644 --- a/src/backend/models/recipe.hpp +++ b/src/backend/models/recipe.hpp @@ -1,6 +1,7 @@ #pragma once #include "backend/id_types.hpp" +#include "backend/models/ingredient.hpp" #include "publication_request_status.hpp" #include "storage.hpp" #include "user.hpp" @@ -22,6 +23,15 @@ struct RecipeSummary { friend RecipeSummary tag_invoke(boost::json::value_to_tag, const boost::json::value& j); }; +struct RecipeDetails { + std::vector ingredients; + std::string name; + std::optional link; + std::optional creator; + + friend RecipeDetails tag_invoke(boost::json::value_to_tag, const boost::json::value& j); +}; + struct RecipeSummaryWithIngredients { RecipeId id; std::string name; diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 3d5166b7..1d943506 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -47,4 +47,6 @@ target_sources(main PRIVATE src/handlers/storages_selection/view.cpp src/handlers/recipes_search/view.cpp + + src/handlers/recipe/view.cpp ) diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 677f7459..22fc5cf7 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -54,6 +54,8 @@ using states::CustomRecipeView; using states::RecipesSearch; +using states::RecipeView; + // Type aliases using BotRef = const TgBot::Api&; using SMRef = const states::StateManager&; diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 5ff0fd3d..f9621826 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -50,6 +50,8 @@ #include "recipes_search/view.hpp" +#include "recipe/view.hpp" + #include #include @@ -70,6 +72,7 @@ using namespace handlers::storages_list; using namespace handlers::storages_selection; using namespace handlers::recipes_suggestions; using namespace handlers::recipes_search; +using namespace handlers::recipe; using namespace tg_stater; @@ -137,7 +140,7 @@ using storageIngredientsListIQHandler = Handler; // RecipeView -using recipeViewCQHandler = Handler; +using suggestedRecipeViewCQHandler = Handler; using recipeStorageAdditionCQHandler = Handler; using shoppingListCreationCQHandler = Handler; @@ -168,4 +171,7 @@ using customRecipePublicationHistoryCQHandler = using recipesSearchCQHandler = Handler; using recipesSearchIQHandler = Handler; +// Recipe +using recipeViewCQHandler = Handler; + } // namespace cookcookhnya::handlers::bot_handlers diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index 00fdb823..ddad97e1 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -44,7 +43,7 @@ void updateSearch(CustomRecipeIngredientsSearch& state, state.pageNo = isQueryChanged ? 0 : state.pageNo; auto response = api.searchForRecipe( - userId, state.recipeId, state.query, threshhold, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage); + userId, state.recipeId, state.query, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage, threshhold); const auto idGetter = &IngredientSearchForRecipeItem::id; if (std::ranges::equal(response.page, state.searchItems, {}, idGetter, idGetter)) return; diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp new file mode 100644 index 00000000..4ff979d1 --- /dev/null +++ b/src/handlers/recipe/view.cpp @@ -0,0 +1,24 @@ +#include "view.hpp" + +#include "handlers/common.hpp" +#include "render/recipes_search/view.hpp" + +#include + +namespace cookcookhnya::handlers::recipe { + +using namespace render::recipes_search; + +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager) { + bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + + if (cq.data == "back") { + renderRecipesSearch(state.prevState.pagination, state.prevState.page, userId, chatId, bot); + stateManager.put(auto{std::move(state.prevState)}); + return; + } +} + +} // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipe/view.hpp b/src/handlers/recipe/view.hpp new file mode 100644 index 00000000..a290306e --- /dev/null +++ b/src/handlers/recipe/view.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::recipe { + +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager); + +} // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipes_search/view.cpp b/src/handlers/recipes_search/view.cpp index 29dde165..14566e08 100644 --- a/src/handlers/recipes_search/view.cpp +++ b/src/handlers/recipes_search/view.cpp @@ -5,22 +5,27 @@ #include "backend/id_types.hpp" #include "handlers/common.hpp" #include "render/main_menu/view.hpp" +#include "render/recipe/view.hpp" #include "render/recipes_search/view.hpp" #include "utils/parsing.hpp" #include #include #include +#include namespace cookcookhnya::handlers::recipes_search { using namespace render::main_menu; using namespace render::recipes_search; -// using namespace render::recipe; +using namespace render::recipe; using namespace std::literals; +const std::size_t pageSize = 5; +const unsigned threshold = 70; + void handleRecipesSearchCQ( - RecipesSearch& /*unused*/, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + RecipesSearch& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -31,10 +36,30 @@ void handleRecipesSearchCQ( return; } + if (cq.data == "page_left" || cq.data == "page_right") { + if (cq.data == "page_left") { + if (state.pagination.pageNo == 0) + return; + state.pagination.pageNo--; + } else { + if (state.pagination.totalItems <= state.pagination.pageNo * pageSize) + return; + state.pagination.pageNo++; + } + auto result = api.getRecipesApi().search( + userId, PublicityFilterType::All, state.query, pageSize, pageSize * state.pagination.pageNo, threshold); + state.page = result.page; + renderRecipesSearch(state.pagination, state.page, userId, userId, bot); + return; + } + if (cq.data.starts_with("recipe_")) { - auto recipeId = utils::parseSafe(std::string_view{cq.data}.substr("recipe_"sv.size())); - renderMainMenu(true, std::nullopt, userId, chatId, bot, api); - stateManager.put(MainMenu{}); + auto mRecipeId = utils::parseSafe(std::string_view{cq.data}.substr("recipe_"sv.size())); + if (!mRecipeId) + return; + auto recipe = api.getRecipesApi().get(userId, *mRecipeId); + renderRecipeView(recipe, userId, chatId, bot); + stateManager.put(RecipeView{.prevState = std::move(state), .recipe = std::move(recipe)}); return; } } @@ -48,8 +73,7 @@ void handleRecipesSearchIQ(RecipesSearch& state, InlineQueryRef iq, BotRef bot, state.pagination = {}; state.page = {}; } else { - const std::size_t pageSize = 5; - auto result = api.search(userId, PublicityFilterType::All, state.query, pageSize); + auto result = api.search(userId, PublicityFilterType::All, iq.query, pageSize, 0, threshold); const auto idGetter = &api::models::recipe::RecipeSummary::id; if (std::ranges::equal(result.page, state.page, {}, idGetter, idGetter)) return; diff --git a/src/handlers/shopping_list/search.cpp b/src/handlers/shopping_list/search.cpp index fcb6b55a..e2740fec 100644 --- a/src/handlers/shopping_list/search.cpp +++ b/src/handlers/shopping_list/search.cpp @@ -46,9 +46,9 @@ void handleShoppingListIngredientSearchCQ( auto result = api.getIngredientsApi().search(userId, PublicityFilterType::All, state.query, - searchThreshold, searchPageSize, - searchPageSize * state.pagination.pageNo); + searchPageSize * state.pagination.pageNo, + searchThreshold); state.page = result.page; renderShoppingListIngredientSearch(state, searchPageSize, userId, chatId, bot); return; @@ -78,7 +78,7 @@ void handleShoppingListIngredientSearchIQ(ShoppingListIngredientSearch& state, return; } - auto result = api.search(userId, PublicityFilterType::All, iq.query, searchThreshold, searchPageSize, 0); + auto result = api.search(userId, PublicityFilterType::All, iq.query, searchPageSize, 0, searchThreshold); state.query = iq.query; state.pagination.pageNo = 0; state.pagination.totalItems = result.found; diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 7b19a925..595a73c3 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include @@ -47,9 +46,9 @@ void updateSearch(StorageIngredientsList& state, auto response = api.searchForStorage(userId, state.storageId, state.inlineQuery, - threshhold, numOfIngredientsOnPage, - state.pageNo * numOfIngredientsOnPage); + state.pageNo * numOfIngredientsOnPage, + threshhold); const auto idGetter = &IngredientSearchForStorageItem::id; if (std::ranges::equal(response.page, state.searchItems, {}, idGetter, idGetter)) return; diff --git a/src/main.cpp b/src/main.cpp index 11799cbb..d514d6ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) { storageIngredientsListIQHandler, storageIngredientsDeletionCQHandler, suggestedRecipeListCQHandler, - recipeViewCQHandler, + suggestedRecipeViewCQHandler, recipeStorageAdditionCQHandler, shoppingListCreationCQHandler, shoppingListViewCQHandler, @@ -76,7 +76,8 @@ int main(int argc, char* argv[]) { shoppingListIngredientSearchCQHandler, shoppingListIngredientSearchIQHandler, recipesSearchCQHandler, - recipesSearchIQHandler> + recipesSearchIQHandler, + recipeViewCQHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 5ea4c4f1..a27dfd39 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -41,4 +41,6 @@ target_sources(main PRIVATE src/render/storages_selection/view.cpp src/render/recipes_search/view.cpp + + src/render/recipe/view.cpp ) diff --git a/src/render/personal_account/ingredients_list/create.cpp b/src/render/personal_account/ingredients_list/create.cpp index fd2c6535..e847ee3c 100644 --- a/src/render/personal_account/ingredients_list/create.cpp +++ b/src/render/personal_account/ingredients_list/create.cpp @@ -14,6 +14,9 @@ namespace cookcookhnya::render::personal_account::ingredients { +const std::size_t pageSize = 5; +const unsigned threshold = 70; + void renderCustomIngredientCreation(UserId userId, ChatId chatId, BotRef bot) { InlineKeyboard keyboard(1); keyboard[0].push_back(makeCallbackButton(u8"↩️ Назад", "back")); @@ -30,8 +33,8 @@ void renderCustomIngredientConfirmation( keyboard[0].push_back(makeCallbackButton(u8"▶️ Подтвердить", "confirm")); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); - // NOLINTNEXTLINE(*magic-numbers*) - auto similarIngredients = api.search(userId, PublicityFilterType::All, std::move(ingredientName), 70, 5, 0).page; + auto similarIngredients = + api.search(userId, PublicityFilterType::All, std::move(ingredientName), pageSize, 0, threshold).page; std::string text; if (!similarIngredients.empty()) { diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp new file mode 100644 index 00000000..c7800015 --- /dev/null +++ b/src/render/recipe/view.cpp @@ -0,0 +1,42 @@ +#include "view.hpp" + +#include "backend/models/recipe.hpp" +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "utils/u8format.hpp" + +#include +#include +#include + +namespace cookcookhnya::render::recipe { + +void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, UserId userId, ChatId chatId, BotRef bot) { + std::string text = utils::u8format("{} *{}*\n\n{}", u8"📖 Рецепт", recipe.name, u8"Ингредиенты:\n"); + for (const auto& ing : recipe.ingredients) + text += utils::u8format("{} {}\n", u8"•", ing.name); + if (recipe.link) + text += utils::u8format("\n{}: {}\n", u8"🌐 Источник", *recipe.link); + if (recipe.creator) + text += utils::u8format("\n{}: {}\n", u8"👤 Автор", recipe.creator->fullName); + + InlineKeyboardBuilder keyboard{2}; // share, back + + auto shareButton = std::make_shared(); + shareButton->text = utils::utf8str(u8"📤 Поделиться"); + const std::string telegramBotAlias = bot.getUnderlying().getMe()->username; + const std::string inviteText = utils::u8format( + "{} **{}**\nhttps://t.me/{}?start={}", u8"Хочу поделиться с тобой рецептом", recipe.name, telegramBotAlias, ""); + shareButton->url = "https://t.me/share/url?url=" + "%D0%9D%D0%B0%D0%B6%D0%BC%D0%B8%20%D0%BD%D0%B0%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D1%83%2C%20%D1%" + "87%D1%82%D0%BE%D0%B1%D1%8B%20%D1%81%D1%82%D0%B0%D1%82%D1%8C%20%D1%83%D1%87%D0%B0%D1%81%D1%82%" + "D0%BD%D0%B8%D0%BA%D0%BE%D0%BC%20%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D0%BB%D0%B8%D1%89%D0%B0%20%F0%" + "9F%8D%B1%2A%2Aasd%2A%2A%20%D0%B2%20CookCookhNya%21%0Ahttps%3A%2F%2Ft.me%2Fstage_stand_bot%" + "3Fstart%3Dabf48eb2a98a1798e113b896b39b506c8c06bc8406449e3356aa0199f6099f23"; + keyboard << std::move(shareButton) << NewRow{} << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto mMessageId = message::getMessageId(userId)) + bot.editMessageText(text, chatId, *mMessageId, std::move(keyboard), "Markdown"); +} + +} // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.hpp b/src/render/recipe/view.hpp new file mode 100644 index 00000000..92e9ac9a --- /dev/null +++ b/src/render/recipe/view.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "backend/models/recipe.hpp" +#include "render/common.hpp" + +namespace cookcookhnya::render::recipe { + +void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, UserId userId, ChatId chatId, BotRef bot); + +} // namespace cookcookhnya::render::recipe diff --git a/src/states.hpp b/src/states.hpp index 6a4093db..dacc9190 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -201,6 +201,11 @@ struct RecipesSearch { std::vector page; }; +struct RecipeView { + RecipesSearch prevState; + api::models::recipe::RecipeDetails recipe; +}; + using State = std::variant; + RecipesSearch, + RecipeView>; using StateManager = tg_stater::StateProxy>; diff --git a/src/utils/u8format.hpp b/src/utils/u8format.hpp new file mode 100644 index 00000000..e171e8f0 --- /dev/null +++ b/src/utils/u8format.hpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +namespace cookcookhnya::utils { + +namespace detail { + +template +struct TransformUtf8 { + using type = T; + static T&& transform(T&& t) { // NOLINT(*not-moved) + return std::forward(t); + } +}; + +template +struct TransformUtf8 { // NOLINT(*c-arrays) + using type = std::string; + static std::string transform(const char8_t (&literal)[N]) { // NOLINT(*not-moved,*c-arrays) + return {literal, literal + N - 1}; // NOLINT(*decay) + } +}; + +template +using TransformUtf8_t = TransformUtf8::type; + +} // namespace detail + +template +std::string u8format(std::format_string...> format, Args&&... args) { + return std::format(format, detail::TransformUtf8::transform(std::forward(args))...); +} + +} // namespace cookcookhnya::utils From 00c7d4918d3cea5d6c6f3f732987b0a9659eca1b Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Wed, 23 Jul 2025 02:38:06 +0300 Subject: [PATCH 088/106] feat: add recipe sharing --- src/handlers/commands/start.cpp | 44 +++++++++++++++++++--------- src/handlers/recipe/view.cpp | 15 ++++++++-- src/handlers/recipe/view.hpp | 3 +- src/handlers/recipes_search/view.cpp | 4 +-- src/render/recipe/view.cpp | 31 +++++++++++++------- src/render/recipe/view.hpp | 6 +++- src/render/storage/members/add.cpp | 2 +- src/states.hpp | 2 +- 8 files changed, 74 insertions(+), 33 deletions(-) diff --git a/src/handlers/commands/start.cpp b/src/handlers/commands/start.cpp index bc86772f..c7bbf926 100644 --- a/src/handlers/commands/start.cpp +++ b/src/handlers/commands/start.cpp @@ -2,12 +2,13 @@ #include "backend/api/api.hpp" #include "backend/id_types.hpp" -#include "backend/models/user.hpp" #include "handlers/common.hpp" +#include "message_tracker.hpp" #include "render/main_menu/view.hpp" -#include "states.hpp" +#include "render/recipe/view.hpp" +#include "utils/parsing.hpp" +#include "utils/uuid.hpp" -#include #include #include #include @@ -16,12 +17,13 @@ namespace cookcookhnya::handlers::commands { using namespace render::main_menu; +using namespace render::recipe; using namespace api::models::user; using namespace std::literals; void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { - auto userId = m.from->id; - auto chatId = m.chat->id; + const auto userId = m.from->id; + const auto chatId = m.chat->id; std::string fullName = m.from->firstName; if (!m.from->lastName.empty()) { @@ -33,22 +35,36 @@ void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClient if (!m.from->username.empty()) alias = m.from->username; - api.getUsersApi().updateInfo(userId, - UpdateUserInfoBody{.alias = std::move(alias), .fullName = std::move(fullName)}); + api.getUsersApi().updateInfo(userId, {.alias = std::move(alias), .fullName = std::move(fullName)}); - auto startText = m.text; - const std::size_t hashPos = "/start "sv.size(); - if (startText.size() > hashPos - 1) { - auto hash = std::string(m.text).substr(hashPos); - auto storage = api.getStoragesApi().activate(userId, hash); + if (!m.text.starts_with("/start ")) { + // default case + renderMainMenu(false, std::nullopt, userId, chatId, bot, api); + stateManager.put(MainMenu{}); + return; + } + const std::string_view payload = std::string_view{m.text}.substr("/start "sv.size()); + + if (payload.starts_with("invite_")) { + const std::string_view hash = payload.substr("invite_"sv.size()); + auto storage = api.getStoragesApi().activate(userId, api::InvitationId{hash}); if (!storage) return; renderMainMenu(false, storage->name, userId, chatId, bot, api); stateManager.put(MainMenu{}); return; } - renderMainMenu(false, std::nullopt, userId, chatId, bot, api); - stateManager.put(MainMenu{}); + + if (payload.starts_with("recipe_")) { + const auto mRecipeId = utils::parseSafe(payload.substr("recipe_"sv.size())); + if (!mRecipeId) + return; + auto recipe = api.getRecipesApi().get(userId, *mRecipeId); + message::deleteMessageId(userId); + renderRecipeView(recipe, *mRecipeId, userId, chatId, bot); + stateManager.put(RecipeView{.prevState = std::nullopt, .recipe = std::move(recipe)}); + return; + } }; void handleNoState(MessageRef m, BotRef bot) { diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 4ff979d1..45cf7fcf 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -1,22 +1,31 @@ #include "view.hpp" +#include "backend/api/api.hpp" #include "handlers/common.hpp" +#include "render/main_menu/view.hpp" #include "render/recipes_search/view.hpp" +#include #include namespace cookcookhnya::handlers::recipe { using namespace render::recipes_search; +using namespace render::main_menu; -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager) { +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; if (cq.data == "back") { - renderRecipesSearch(state.prevState.pagination, state.prevState.page, userId, chatId, bot); - stateManager.put(auto{std::move(state.prevState)}); + if (auto& prevState = state.prevState) { + renderRecipesSearch(prevState->pagination, prevState->page, userId, chatId, bot); + stateManager.put(auto{std::move(*prevState)}); + } else { + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); + stateManager.put(MainMenu{}); + } return; } } diff --git a/src/handlers/recipe/view.hpp b/src/handlers/recipe/view.hpp index a290306e..6cefa542 100644 --- a/src/handlers/recipe/view.hpp +++ b/src/handlers/recipe/view.hpp @@ -1,9 +1,10 @@ #pragma once +#include "backend/api/api.hpp" #include "handlers/common.hpp" namespace cookcookhnya::handlers::recipe { -void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager); +void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipes_search/view.cpp b/src/handlers/recipes_search/view.cpp index 14566e08..0317e1f1 100644 --- a/src/handlers/recipes_search/view.cpp +++ b/src/handlers/recipes_search/view.cpp @@ -58,8 +58,8 @@ void handleRecipesSearchCQ( if (!mRecipeId) return; auto recipe = api.getRecipesApi().get(userId, *mRecipeId); - renderRecipeView(recipe, userId, chatId, bot); - stateManager.put(RecipeView{.prevState = std::move(state), .recipe = std::move(recipe)}); + renderRecipeView(recipe, *mRecipeId, userId, chatId, bot); + stateManager.put(RecipeView{.prevState = {std::move(state)}, .recipe = std::move(recipe)}); return; } } diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index c7800015..fc5992ff 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -1,17 +1,24 @@ #include "view.hpp" +#include "backend/id_types.hpp" #include "backend/models/recipe.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "utils/u8format.hpp" +#include + #include #include #include namespace cookcookhnya::render::recipe { -void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, UserId userId, ChatId chatId, BotRef bot) { +void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, + const api::RecipeId& recipeId, + UserId userId, + ChatId chatId, + BotRef bot) { std::string text = utils::u8format("{} *{}*\n\n{}", u8"📖 Рецепт", recipe.name, u8"Ингредиенты:\n"); for (const auto& ing : recipe.ingredients) text += utils::u8format("{} {}\n", u8"•", ing.name); @@ -24,19 +31,23 @@ void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, UserId u auto shareButton = std::make_shared(); shareButton->text = utils::utf8str(u8"📤 Поделиться"); - const std::string telegramBotAlias = bot.getUnderlying().getMe()->username; - const std::string inviteText = utils::u8format( - "{} **{}**\nhttps://t.me/{}?start={}", u8"Хочу поделиться с тобой рецептом", recipe.name, telegramBotAlias, ""); - shareButton->url = "https://t.me/share/url?url=" - "%D0%9D%D0%B0%D0%B6%D0%BC%D0%B8%20%D0%BD%D0%B0%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D1%83%2C%20%D1%" - "87%D1%82%D0%BE%D0%B1%D1%8B%20%D1%81%D1%82%D0%B0%D1%82%D1%8C%20%D1%83%D1%87%D0%B0%D1%81%D1%82%" - "D0%BD%D0%B8%D0%BA%D0%BE%D0%BC%20%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D0%BB%D0%B8%D1%89%D0%B0%20%F0%" - "9F%8D%B1%2A%2Aasd%2A%2A%20%D0%B2%20CookCookhNya%21%0Ahttps%3A%2F%2Ft.me%2Fstage_stand_bot%" - "3Fstart%3Dabf48eb2a98a1798e113b896b39b506c8c06bc8406449e3356aa0199f6099f23"; + const std::string botAlias = bot.getUnderlying().getMe()->username; + const std::string recipeUrl = std::format("https://t.me/{}?start=recipe_{}", botAlias, recipeId); + const std::string shareText = utils::u8format("{} **{}**", u8"Хочу поделиться с тобой рецептом", recipe.name); + + boost::urls::url url{"https://t.me/share/url"}; + url.params().append({"url", recipeUrl}); + url.params().append({"text", shareText}); + shareButton->url = url.buffer(); + keyboard << std::move(shareButton) << NewRow{} << makeCallbackButton(u8"↩️ Назад", "back"); if (auto mMessageId = message::getMessageId(userId)) bot.editMessageText(text, chatId, *mMessageId, std::move(keyboard), "Markdown"); + else { + auto messageId = bot.sendMessage(chatId, text, std::move(keyboard), "Markdown")->messageId; + message::addMessageId(userId, messageId); + } } } // namespace cookcookhnya::render::recipe diff --git a/src/render/recipe/view.hpp b/src/render/recipe/view.hpp index 92e9ac9a..6f308838 100644 --- a/src/render/recipe/view.hpp +++ b/src/render/recipe/view.hpp @@ -5,6 +5,10 @@ namespace cookcookhnya::render::recipe { -void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, UserId userId, ChatId chatId, BotRef bot); +void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, + const api::RecipeId& recipeId, + UserId userId, + ChatId chatId, + BotRef bot); } // namespace cookcookhnya::render::recipe diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index a32c8f94..8bb83a83 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -42,7 +42,7 @@ void renderShareLinkMemberAddition( const api::InvitationId hash = storageApi.inviteMember(userId, storageId); const std::string telegramBotAlias = bot.getUnderlying().getMe()->username; const std::string inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + - "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=" + hash; + "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=invite_" + hash; inviteButton->url = "https://t.me/share/url?url=" + inviteText; keyboard[0].push_back(std::move(inviteButton)); diff --git a/src/states.hpp b/src/states.hpp index dacc9190..c72d5063 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -202,7 +202,7 @@ struct RecipesSearch { }; struct RecipeView { - RecipesSearch prevState; + std::optional prevState; api::models::recipe::RecipeDetails recipe; }; From 874d14fab497871b40a896584885971655cf42c9 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Wed, 23 Jul 2025 04:33:25 +0300 Subject: [PATCH 089/106] ci: try to fix cache restor --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0807e7f..8d7427a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,8 @@ jobs: uses: actions/cache/restore@v4 with: path: .clang-tidy-cache.json - key: ${{ runner.os }}-clang-tidy-${{ github.ref }}-${{ github.run_id }} + key: + ${{ runner.os }}-clang-tidy-${{ github.ref }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-clang-tidy-${{ github.ref }}- ${{ runner.os }}-clang-tidy-refs/heads/dev From 3882b914fc9858d48c940ec36dbf174713706543 Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Wed, 23 Jul 2025 04:47:39 +0300 Subject: [PATCH 090/106] ci: try to fix cache restore --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d7427a9..e9a357ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,12 +62,11 @@ jobs: uses: actions/cache/restore@v4 with: path: .clang-tidy-cache.json - key: - ${{ runner.os }}-clang-tidy-${{ github.ref }}-${{ github.run_id }} + key: ${{ runner.os }}-clang-tidy-${{ github.ref }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-clang-tidy-${{ github.ref }}- - ${{ runner.os }}-clang-tidy-refs/heads/dev - ${{ runner.os }}-clang-tidy-refs/heads/main + # ${{ runner.os }}-clang-tidy-refs/heads/dev + # ${{ runner.os }}-clang-tidy-refs/heads/main - name: Copy original cache file if exists run: | From 24b911bde3502d25b50b01b35f7e06029f325b9b Mon Sep 17 00:00:00 2001 From: Maxim Fomin <62051211+Makcal@users.noreply.github.com> Date: Wed, 23 Jul 2025 05:06:53 +0300 Subject: [PATCH 091/106] ci: give up on fixing cache restore --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9a357ad..e0807e7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,8 +65,8 @@ jobs: key: ${{ runner.os }}-clang-tidy-${{ github.ref }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-clang-tidy-${{ github.ref }}- - # ${{ runner.os }}-clang-tidy-refs/heads/dev - # ${{ runner.os }}-clang-tidy-refs/heads/main + ${{ runner.os }}-clang-tidy-refs/heads/dev + ${{ runner.os }}-clang-tidy-refs/heads/main - name: Copy original cache file if exists run: | From 84a63c23a9af1ecac9f54799dcb588fce08380e2 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Wed, 23 Jul 2025 14:49:04 +0300 Subject: [PATCH 092/106] 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 b1cf22f9e1373dce330c3253a0d3982f13596211 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Wed, 23 Jul 2025 16:09:49 +0300 Subject: [PATCH 093/106] refactor: rename SuggestedRecipe to CookingPlanning --- src/handlers/CMakeLists.txt | 4 +-- src/handlers/common.hpp | 2 +- .../add_storage.cpp | 12 +++---- .../add_storage.hpp | 4 +-- .../view.cpp | 16 +++++----- src/handlers/cooking_planning/view.hpp | 11 +++++++ src/handlers/handlers_list.hpp | 8 ++--- src/handlers/recipes_suggestions/view.cpp | 8 ++--- src/handlers/shopping_list/create.cpp | 8 ++--- src/handlers/suggested_recipe/view.hpp | 11 ------- src/main.cpp | 2 +- src/render/CMakeLists.txt | 4 +-- .../add_storage.cpp | 8 ++--- .../add_storage.hpp | 10 +++--- .../view.cpp | 20 ++++++------ src/render/cooking_planning/view.hpp | 31 +++++++++++++++++++ src/render/suggested_recipe/view.hpp | 31 ------------------- src/states.hpp | 8 ++--- src/utils/ingredients_availability.cpp | 4 +-- src/utils/ingredients_availability.hpp | 6 ++-- 20 files changed, 104 insertions(+), 104 deletions(-) rename src/handlers/{suggested_recipe => cooking_planning}/add_storage.cpp (88%) rename src/handlers/{suggested_recipe => cooking_planning}/add_storage.hpp (67%) rename src/handlers/{suggested_recipe => cooking_planning}/view.cpp (82%) create mode 100644 src/handlers/cooking_planning/view.hpp delete mode 100644 src/handlers/suggested_recipe/view.hpp rename src/render/{suggested_recipe => cooking_planning}/add_storage.cpp (94%) rename src/render/{suggested_recipe => cooking_planning}/add_storage.hpp (66%) rename src/render/{suggested_recipe => cooking_planning}/view.cpp (82%) create mode 100644 src/render/cooking_planning/view.hpp delete mode 100644 src/render/suggested_recipe/view.hpp diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 8b88884b..351637b2 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -22,8 +22,8 @@ target_sources(main PRIVATE src/handlers/personal_account/view.cpp src/handlers/personal_account/publication_history.cpp - src/handlers/suggested_recipe/add_storage.cpp - src/handlers/suggested_recipe/view.cpp + src/handlers/cooking_planning/add_storage.cpp + src/handlers/cooking_planning/view.cpp src/handlers/recipes_suggestions/view.cpp diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 1bdce456..f65072b7 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -25,8 +25,8 @@ using states::StorageList; using states::TotalPublicationHistory; +using states::CookingPlanning; using states::RecipeStorageAddition; -using states::SuggestedRecipeView; using states::StorageCreationEnterName; using states::StorageDeletion; diff --git a/src/handlers/suggested_recipe/add_storage.cpp b/src/handlers/cooking_planning/add_storage.cpp similarity index 88% rename from src/handlers/suggested_recipe/add_storage.cpp rename to src/handlers/cooking_planning/add_storage.cpp index 699ed372..cb3377c0 100644 --- a/src/handlers/suggested_recipe/add_storage.cpp +++ b/src/handlers/cooking_planning/add_storage.cpp @@ -4,8 +4,8 @@ #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" -#include "render/suggested_recipe/add_storage.hpp" -#include "render/suggested_recipe/view.hpp" +#include "render/cooking_planning/add_storage.hpp" +#include "render/cooking_planning/view.hpp" #include "states.hpp" #include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" @@ -15,9 +15,9 @@ #include #include -namespace cookcookhnya::handlers::suggested_recipe { +namespace cookcookhnya::handlers::cooking_planning { -using namespace render::suggested_recipe; +using namespace render::cooking_planning; using namespace api::models::storage; void handleRecipeStorageAdditionCQ( @@ -28,7 +28,7 @@ void handleRecipeStorageAdditionCQ( auto userId = cq.from->id; if (data == "back") { - renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + renderCookingPlanning(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); stateManager.put(auto{std::move(state.prevState)}); return; } @@ -71,4 +71,4 @@ void handleRecipeStorageAdditionCQ( } } -} // namespace cookcookhnya::handlers::suggested_recipe +} // namespace cookcookhnya::handlers::cooking_planning diff --git a/src/handlers/suggested_recipe/add_storage.hpp b/src/handlers/cooking_planning/add_storage.hpp similarity index 67% rename from src/handlers/suggested_recipe/add_storage.hpp rename to src/handlers/cooking_planning/add_storage.hpp index 9e08e3b9..fbd0079d 100644 --- a/src/handlers/suggested_recipe/add_storage.hpp +++ b/src/handlers/cooking_planning/add_storage.hpp @@ -3,9 +3,9 @@ #include "backend/api/api.hpp" #include "handlers/common.hpp" -namespace cookcookhnya::handlers::suggested_recipe { +namespace cookcookhnya::handlers::cooking_planning { void handleRecipeStorageAdditionCQ( RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); -} // namespace cookcookhnya::handlers::suggested_recipe +} // namespace cookcookhnya::handlers::cooking_planning diff --git a/src/handlers/suggested_recipe/view.cpp b/src/handlers/cooking_planning/view.cpp similarity index 82% rename from src/handlers/suggested_recipe/view.cpp rename to src/handlers/cooking_planning/view.cpp index 7a2d7fa2..882c87b2 100644 --- a/src/handlers/suggested_recipe/view.cpp +++ b/src/handlers/cooking_planning/view.cpp @@ -4,26 +4,26 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" +#include "render/cooking_planning/add_storage.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" -#include "render/suggested_recipe/add_storage.hpp" #include "states.hpp" #include #include #include -namespace cookcookhnya::handlers::suggested_recipe { +namespace cookcookhnya::handlers::cooking_planning { using namespace render::recipes_suggestions; using namespace render::shopping_list; -using namespace render::suggested_recipe; +using namespace render::cooking_planning; using namespace api::models::ingredient; -using IngredientAvailability = states::SuggestedRecipeView::IngredientAvailability; -using AvailabilityType = states::SuggestedRecipeView::AvailabilityType; +using IngredientAvailability = states::CookingPlanning::IngredientAvailability; +using AvailabilityType = states::CookingPlanning::AvailabilityType; -void handleRecipeViewCQ( - SuggestedRecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { +void handleCookingPlanningCQ( + CookingPlanning& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; @@ -73,4 +73,4 @@ void handleRecipeViewCQ( } } -} // namespace cookcookhnya::handlers::suggested_recipe +} // namespace cookcookhnya::handlers::cooking_planning diff --git a/src/handlers/cooking_planning/view.hpp b/src/handlers/cooking_planning/view.hpp new file mode 100644 index 00000000..387a8ab4 --- /dev/null +++ b/src/handlers/cooking_planning/view.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::cooking_planning { + +void handleCookingPlanningCQ( + CookingPlanning& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); + +} // namespace cookcookhnya::handlers::cooking_planning diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 69619593..9dfbb0a1 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -24,8 +24,8 @@ #include "personal_account/publication_history.hpp" #include "personal_account/view.hpp" -#include "suggested_recipe/add_storage.hpp" -#include "suggested_recipe/view.hpp" +#include "cooking_planning/add_storage.hpp" +#include "cooking_planning/view.hpp" #include "recipes_suggestions/view.hpp" @@ -65,7 +65,7 @@ using namespace handlers::personal_account::ingredients; using namespace handlers::personal_account::recipe; using namespace handlers::personal_account::recipes_list; using namespace handlers::shopping_list; -using namespace handlers::suggested_recipe; +using namespace handlers::cooking_planning; using namespace handlers::storage; using namespace handlers::storage::ingredients; using namespace handlers::storage::members; @@ -142,7 +142,7 @@ using storageIngredientsListIQHandler = Handler; // RecipeView -using suggestedRecipeViewCQHandler = Handler; +using cookingPlanningCQHandler = Handler; using recipeStorageAdditionCQHandler = Handler; using shoppingListCreationCQHandler = Handler; diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index 4698cd4c..d88a7bdb 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -3,11 +3,11 @@ #include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "handlers/common.hpp" +#include "render/cooking_planning/view.hpp" #include "render/main_menu/view.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/storage/view.hpp" #include "render/storages_selection/view.hpp" -#include "render/suggested_recipe/view.hpp" #include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" @@ -20,7 +20,7 @@ namespace cookcookhnya::handlers::recipes_suggestions { using namespace render::recipes_suggestions; using namespace render::select_storages; using namespace render::storage; -using namespace render::suggested_recipe; +using namespace render::cooking_planning; using namespace render::main_menu; void handleSuggestedRecipesListCQ( @@ -54,8 +54,8 @@ void handleSuggestedRecipesListCQ( if (!recipeId) return; auto inStorage = utils::inStoragesAvailability(state.selectedStorages, *recipeId, userId, api); - renderRecipeView(inStorage, *recipeId, userId, chatId, bot, api); - stateManager.put(SuggestedRecipeView{ + renderCookingPlanning(inStorage, *recipeId, userId, chatId, bot, api); + stateManager.put(CookingPlanning{ .prevState = std::move(state), .addedStorages = {}, .availability = inStorage, diff --git a/src/handlers/shopping_list/create.cpp b/src/handlers/shopping_list/create.cpp index 0c2e65a5..6f18d3da 100644 --- a/src/handlers/shopping_list/create.cpp +++ b/src/handlers/shopping_list/create.cpp @@ -4,8 +4,8 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" +#include "render/cooking_planning/view.hpp" #include "render/shopping_list/create.hpp" -#include "render/suggested_recipe/view.hpp" #include "utils/parsing.hpp" #include @@ -16,7 +16,7 @@ namespace cookcookhnya::handlers::shopping_list { using namespace render::shopping_list; -using namespace render::suggested_recipe; +using namespace render::cooking_planning; void handleShoppingListCreationCQ( ShoppingListCreation& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { @@ -25,7 +25,7 @@ void handleShoppingListCreationCQ( auto userId = cq.from->id; if (data == "back") { - renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + renderCookingPlanning(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; @@ -39,7 +39,7 @@ void handleShoppingListCreationCQ( putIds.push_back(ingredient.id); } shoppingApi.put(userId, putIds); - renderRecipeView(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + renderCookingPlanning(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); stateManager.put(auto{std::move(state.prevState)}); bot.answerCallbackQuery(cq.id); return; diff --git a/src/handlers/suggested_recipe/view.hpp b/src/handlers/suggested_recipe/view.hpp deleted file mode 100644 index f38a8de9..00000000 --- a/src/handlers/suggested_recipe/view.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "backend/api/api.hpp" -#include "handlers/common.hpp" - -namespace cookcookhnya::handlers::suggested_recipe { - -void handleRecipeViewCQ( - SuggestedRecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); - -} // namespace cookcookhnya::handlers::suggested_recipe diff --git a/src/main.cpp b/src/main.cpp index 11d773f7..e383c07d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -60,7 +60,7 @@ int main(int argc, char* argv[]) { storageIngredientsListIQHandler, storageIngredientsDeletionCQHandler, suggestedRecipeListCQHandler, - suggestedRecipeViewCQHandler, + cookingPlanningCQHandler, recipeStorageAdditionCQHandler, shoppingListCreationCQHandler, shoppingListViewCQHandler, diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index b49d15ef..faaea959 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -16,8 +16,8 @@ target_sources(main PRIVATE src/render/personal_account/view.cpp src/render/personal_account/publication_history.cpp - src/render/suggested_recipe/add_storage.cpp - src/render/suggested_recipe/view.cpp + src/render/cooking_planning/add_storage.cpp + src/render/cooking_planning/view.cpp src/render/recipes_suggestions/view.cpp diff --git a/src/render/suggested_recipe/add_storage.cpp b/src/render/cooking_planning/add_storage.cpp similarity index 94% rename from src/render/suggested_recipe/add_storage.cpp rename to src/render/cooking_planning/add_storage.cpp index 2b526aa9..0f01f071 100644 --- a/src/render/suggested_recipe/add_storage.cpp +++ b/src/render/cooking_planning/add_storage.cpp @@ -17,12 +17,12 @@ #include #include -namespace cookcookhnya::render::suggested_recipe { +namespace cookcookhnya::render::cooking_planning { using namespace api::models::recipe; using namespace api::models::storage; -using IngredientAvailability = states::SuggestedRecipeView::IngredientAvailability; -using AvailabilityType = states::SuggestedRecipeView::AvailabilityType; +using IngredientAvailability = states::CookingPlanning::IngredientAvailability; +using AvailabilityType = states::CookingPlanning::AvailabilityType; TextGenInfo storageAdditionView(const std::vector& inStoragesAvailability, const std::vector& selectedStorages, @@ -110,4 +110,4 @@ void renderStoragesSuggestion(const std::vector& inStora bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); } } -} // namespace cookcookhnya::render::suggested_recipe +} // namespace cookcookhnya::render::cooking_planning diff --git a/src/render/suggested_recipe/add_storage.hpp b/src/render/cooking_planning/add_storage.hpp similarity index 66% rename from src/render/suggested_recipe/add_storage.hpp rename to src/render/cooking_planning/add_storage.hpp index b29e43ae..00539a63 100644 --- a/src/render/suggested_recipe/add_storage.hpp +++ b/src/render/cooking_planning/add_storage.hpp @@ -4,22 +4,22 @@ #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" -#include "render/suggested_recipe/view.hpp" +#include "render/cooking_planning/view.hpp" #include "states.hpp" #include -namespace cookcookhnya::render::suggested_recipe { +namespace cookcookhnya::render::cooking_planning { TextGenInfo -storageAdditionView(const std::vector& inStoragesAvailability, +storageAdditionView(const std::vector& inStoragesAvailability, const std::vector& selectedStorages, api::RecipeId recipeId, UserId userId, api::ApiClientRef api); void renderStoragesSuggestion( - const std::vector& inStoragesAvailability, + const std::vector& inStoragesAvailability, const std::vector& selectedStorages, const std::vector& addedStorages, api::RecipeId recipeId, @@ -28,4 +28,4 @@ void renderStoragesSuggestion( BotRef bot, api::ApiClientRef api); -} // namespace cookcookhnya::render::suggested_recipe +} // namespace cookcookhnya::render::cooking_planning diff --git a/src/render/suggested_recipe/view.cpp b/src/render/cooking_planning/view.cpp similarity index 82% rename from src/render/suggested_recipe/view.cpp rename to src/render/cooking_planning/view.cpp index 2933c9b3..8791c00d 100644 --- a/src/render/suggested_recipe/view.cpp +++ b/src/render/cooking_planning/view.cpp @@ -14,11 +14,11 @@ #include #include -namespace cookcookhnya::render::suggested_recipe { +namespace cookcookhnya::render::cooking_planning { using namespace api::models::recipe; -using IngredientAvailability = states::SuggestedRecipeView::IngredientAvailability; -using AvailabilityType = states::SuggestedRecipeView::AvailabilityType; +using IngredientAvailability = states::CookingPlanning::IngredientAvailability; +using AvailabilityType = states::CookingPlanning::AvailabilityType; TextGenInfo recipeView(const std::vector& inStoragesAvailability, api::RecipeId recipeId, @@ -50,12 +50,12 @@ TextGenInfo recipeView(const std::vector& inStoragesAvai .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; } -void renderRecipeView(std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - api::ApiClientRef api) { +void renderCookingPlanning(std::vector& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + api::ApiClientRef api) { auto textGen = recipeView(inStoragesAvailability, recipeId, userId, api); const std::size_t buttonRows = textGen.isIngredientNotAvailable ? 3 : 2; InlineKeyboard keyboard(buttonRows); @@ -78,4 +78,4 @@ void renderRecipeView(std::vector& inStoragesAvailabilit } } -} // namespace cookcookhnya::render::suggested_recipe +} // namespace cookcookhnya::render::cooking_planning diff --git a/src/render/cooking_planning/view.hpp b/src/render/cooking_planning/view.hpp new file mode 100644 index 00000000..d39f9179 --- /dev/null +++ b/src/render/cooking_planning/view.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "backend/id_types.hpp" +#include "render/common.hpp" +#include "states.hpp" + +#include +#include + +namespace cookcookhnya::render::cooking_planning { + +struct TextGenInfo { + std::string text; + bool isIngredientNotAvailable; + bool isIngredientIsOtherStorages; +}; + +void renderCookingPlanning(std::vector& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + ChatId chatId, + BotRef bot, + api::ApiClientRef api); + +TextGenInfo recipeView(const std::vector& inStoragesAvailability, + api::RecipeId recipeId, + UserId userId, + api::ApiClientRef api); + +} // namespace cookcookhnya::render::cooking_planning diff --git a/src/render/suggested_recipe/view.hpp b/src/render/suggested_recipe/view.hpp deleted file mode 100644 index 57b40f3b..00000000 --- a/src/render/suggested_recipe/view.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "backend/api/api.hpp" -#include "backend/id_types.hpp" -#include "render/common.hpp" -#include "states.hpp" - -#include -#include - -namespace cookcookhnya::render::suggested_recipe { - -struct TextGenInfo { - std::string text; - bool isIngredientNotAvailable; - bool isIngredientIsOtherStorages; -}; - -void renderRecipeView(std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - ChatId chatId, - BotRef bot, - api::ApiClientRef api); - -TextGenInfo recipeView(const std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api); - -} // namespace cookcookhnya::render::suggested_recipe diff --git a/src/states.hpp b/src/states.hpp index 71dbc1ed..66d1a10b 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -100,7 +100,7 @@ struct SuggestedRecipesList { std::size_t pageNo; bool fromStorage; }; -struct SuggestedRecipeView { +struct CookingPlanning { enum struct AvailabilityType : std::uint8_t { NOT_AVAILABLE, AVAILABLE, OTHER_STORAGES }; struct IngredientAvailability { @@ -116,11 +116,11 @@ struct SuggestedRecipeView { }; struct RecipeStorageAddition { - SuggestedRecipeView prevState; + CookingPlanning prevState; }; struct ShoppingListCreation { - SuggestedRecipeView prevState; + CookingPlanning prevState; std::vector selectedIngredients; std::vector allIngredients; }; @@ -218,7 +218,7 @@ using State = std::variant +std::vector inStoragesAvailability(std::vector& selectedStorages, api::RecipeId recipeId, tg_types::UserId userId, const api::ApiClient& api); -void addStorage(std::vector& availability, +void addStorage(std::vector& availability, const api::models::storage::StorageSummary& storage); -void deleteStorage(std::vector& availability, +void deleteStorage(std::vector& availability, const api::models::storage::StorageSummary& storage); } // namespace cookcookhnya::utils From fa36963a0d3bb7991bca636a70465ae4f8533d2b Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Wed, 23 Jul 2025 16:21:10 +0300 Subject: [PATCH 094/106] 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; From 273ba975e44b3e0a8c85f8f81372afa76d7bcf83 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Wed, 23 Jul 2025 17:10:40 +0300 Subject: [PATCH 095/106] refactor: move SelectableShoppingItem to helpers namespace --- src/handlers/shopping_list/view.cpp | 4 ++-- src/render/shopping_list/view.cpp | 2 +- src/states.hpp | 14 ++++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/handlers/shopping_list/view.cpp b/src/handlers/shopping_list/view.cpp index c7120375..051243f4 100644 --- a/src/handlers/shopping_list/view.cpp +++ b/src/handlers/shopping_list/view.cpp @@ -44,7 +44,7 @@ void handleShoppingListViewCQ( } if (cq.data == "remove") { - using SelectableItem = ShoppingListView::SelectableItem; + using SelectableItem = states::helpers::SelectableShoppingListItem; auto toDelete = state.items.getValues() | filter(&SelectableItem::selected) | views::transform(&SelectableItem::ingredientId) | to(); @@ -57,7 +57,7 @@ void handleShoppingListViewCQ( } if (cq.data == "buy") { - using SelectableItem = ShoppingListView::SelectableItem; + using SelectableItem = states::helpers::SelectableShoppingListItem; auto toBuy = state.items.getValues() | filter(&SelectableItem::selected) | views::transform(&SelectableItem::ingredientId) | to(); if (storages.size() == 1) { diff --git a/src/render/shopping_list/view.cpp b/src/render/shopping_list/view.cpp index 95f344f0..49da6e35 100644 --- a/src/render/shopping_list/view.cpp +++ b/src/render/shopping_list/view.cpp @@ -15,7 +15,7 @@ using namespace std::views; void renderShoppingList(const states::ShoppingListView& state, UserId userId, ChatId chatId, BotRef bot) { auto items = state.items.getValues(); - const bool anySelected = std::ranges::any_of(items, &states::ShoppingListView::SelectableItem::selected); + const bool anySelected = std::ranges::any_of(items, &states::helpers::SelectableShoppingListItem::selected); InlineKeyboardBuilder keyboard{3 + ((items.size() / 2) + 1)}; // add, remove and/or buy, list (n/2), back diff --git a/src/states.hpp b/src/states.hpp index 66d1a10b..b438c2b5 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -32,6 +32,12 @@ struct Pagination { std::size_t totalItems; }; +struct SelectableShoppingListItem : api::models::shopping_list::ShoppingListItem { + bool selected = false; + SelectableShoppingListItem(api::models::shopping_list::ShoppingListItem item) // NOLINT(*explicit*) + : ShoppingListItem{std::move(item)} {} +}; + } // namespace helpers struct MainMenu {}; @@ -161,12 +167,8 @@ struct RecipeIngredientsSearch { }; struct ShoppingListView { // NOLINT(*member-init) // Strange. Flags only this struct due to ItemsDb - struct SelectableItem : api::models::shopping_list::ShoppingListItem { - bool selected = false; - SelectableItem(api::models::shopping_list::ShoppingListItem item) // NOLINT(*explicit*) - : ShoppingListItem{std::move(item)} {} - }; - using ItemsDb = utils::FastSortedDb; + using ItemsDb = + utils::FastSortedDb; ItemsDb items; bool canBuy; From 5ebffe05b564a060b69fdf9b546486c9308b10f1 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Wed, 23 Jul 2025 19:16:49 +0300 Subject: [PATCH 096/106] fix: paging in storage ingredients deletion and new inviteButton in members addition --- src/render/storage/ingredients/delete.cpp | 2 +- src/render/storage/members/add.cpp | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/render/storage/ingredients/delete.cpp b/src/render/storage/ingredients/delete.cpp index 7f2fa25a..a53ac661 100644 --- a/src/render/storage/ingredients/delete.cpp +++ b/src/render/storage/ingredients/delete.cpp @@ -19,7 +19,7 @@ std::vector constructNavigationButtons(std::si auto forward = makeCallbackButton(u8"▶️", "next"); auto backward = makeCallbackButton(u8"◀️", "prev"); auto dont_handle = makeCallbackButton(u8"ㅤ", "dont_handle"); - auto page = makeCallbackButton(std::format("{} из {}", (pageNo + 1), (maxPageNum + 1)), "dont_handle"); + auto page = makeCallbackButton(std::format("{} из {}", (pageNo + 1), maxPageNum), "dont_handle"); if (pageNo == maxPageNum) { buttons.push_back(backward); buttons.push_back(page); diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index 8bb83a83..28e4e498 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -4,8 +4,11 @@ #include "backend/id_types.hpp" #include "message_tracker.hpp" #include "render/common.hpp" +#include "utils/u8format.hpp" #include "utils/utils.hpp" + +#include #include #include @@ -39,11 +42,15 @@ void renderShareLinkMemberAddition( auto inviteButton = std::make_shared(); inviteButton->text = utils::utf8str(u8"📤 Поделиться"); + const std::string botAlias = bot.getUnderlying().getMe()->username; const api::InvitationId hash = storageApi.inviteMember(userId, storageId); - const std::string telegramBotAlias = bot.getUnderlying().getMe()->username; - const std::string inviteText = "Нажми на ссылку, чтобы стать участником хранилища 🍱**" + storage.name + - "** в CookCookhNya!\nhttps://t.me/" + telegramBotAlias + "?start=invite_" + hash; - inviteButton->url = "https://t.me/share/url?url=" + inviteText; + const std::string storageUrl = std::format("https://t.me/{}?start=invite_{}", botAlias, hash); + const std::string shareText = utils::u8format("{} **{}** {}", u8"Нажми на ссылку, чтобы стать участником хранилища 🍱", storage.name, "в CookCookhNya!"); + + boost::urls::url url{"https://t.me/share/url"}; + url.params().append({"url", storageUrl}); + url.params().append({"text", shareText}); + inviteButton->url = url.buffer(); keyboard[0].push_back(std::move(inviteButton)); keyboard[1].push_back(makeCallbackButton(u8"↩️ Назад", "back")); From 2d138004cbac7831d42a3a563674b44d4b395480 Mon Sep 17 00:00:00 2001 From: s3rap1s Date: Wed, 23 Jul 2025 19:51:17 +0300 Subject: [PATCH 097/106] fix: invite button --- src/handlers/commands/start.cpp | 1 - src/render/storage/ingredients/delete.cpp | 2 +- src/render/storage/members/add.cpp | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/handlers/commands/start.cpp b/src/handlers/commands/start.cpp index c7bbf926..8749ae54 100644 --- a/src/handlers/commands/start.cpp +++ b/src/handlers/commands/start.cpp @@ -44,7 +44,6 @@ void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClient return; } const std::string_view payload = std::string_view{m.text}.substr("/start "sv.size()); - if (payload.starts_with("invite_")) { const std::string_view hash = payload.substr("invite_"sv.size()); auto storage = api.getStoragesApi().activate(userId, api::InvitationId{hash}); diff --git a/src/render/storage/ingredients/delete.cpp b/src/render/storage/ingredients/delete.cpp index a53ac661..e467ee72 100644 --- a/src/render/storage/ingredients/delete.cpp +++ b/src/render/storage/ingredients/delete.cpp @@ -71,7 +71,7 @@ constructMessage(std::vector& selectedIngre else buttonRows += 2; // + back + navig } else { - if (ingSize <= numOfIngredientsOnPage) { + if (ingSize <= numOfIngredientsOnPage && pageNo == 0) { if (withoutPutToShoppingListButton) buttonRows += 2; // + back + delete else diff --git a/src/render/storage/members/add.cpp b/src/render/storage/members/add.cpp index 28e4e498..662a7895 100644 --- a/src/render/storage/members/add.cpp +++ b/src/render/storage/members/add.cpp @@ -7,7 +7,6 @@ #include "utils/u8format.hpp" #include "utils/utils.hpp" - #include #include @@ -45,7 +44,8 @@ void renderShareLinkMemberAddition( const std::string botAlias = bot.getUnderlying().getMe()->username; const api::InvitationId hash = storageApi.inviteMember(userId, storageId); const std::string storageUrl = std::format("https://t.me/{}?start=invite_{}", botAlias, hash); - const std::string shareText = utils::u8format("{} **{}** {}", u8"Нажми на ссылку, чтобы стать участником хранилища 🍱", storage.name, "в CookCookhNya!"); + const std::string shareText = utils::u8format( + "{} **{}** {}", u8"Нажми на ссылку, чтобы стать участником хранилища 🍱", storage.name, "в CookCookhNya!"); boost::urls::url url{"https://t.me/share/url"}; url.params().append({"url", storageUrl}); From 9f903b42794cdec7a66a6d267135fa9a0d57c10c Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Wed, 23 Jul 2025 19:58:10 +0300 Subject: [PATCH 098/106] fix: new ingredient suggestion --- .../personal_account/recipe/search_ingredients.cpp | 12 ++++++++---- src/handlers/storage/ingredients/view.cpp | 7 +++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/handlers/personal_account/recipe/search_ingredients.cpp b/src/handlers/personal_account/recipe/search_ingredients.cpp index ddad97e1..6422962a 100644 --- a/src/handlers/personal_account/recipe/search_ingredients.cpp +++ b/src/handlers/personal_account/recipe/search_ingredients.cpp @@ -44,21 +44,25 @@ void updateSearch(CustomRecipeIngredientsSearch& state, auto response = api.searchForRecipe( userId, state.recipeId, state.query, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage, threshhold); + + state.totalFound = response.found; + if (state.totalFound == 0) { + renderSuggestIngredientCustomisation(state, userId, userId, bot); + return; + } + const auto idGetter = &IngredientSearchForRecipeItem::id; if (std::ranges::equal(response.page, state.searchItems, {}, idGetter, idGetter)) return; state.searchItems = std::move(response.page); - state.totalFound = response.found; + if (auto mMessageId = message::getMessageId(userId)) { if (state.totalFound != 0) { renderRecipeIngredientsSearch(state, numOfIngredientsOnPage, userId, userId, bot); return; } } - - if (state.totalFound == 0) - renderSuggestIngredientCustomisation(state, userId, userId, bot); } } // namespace diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index 595a73c3..e72e3575 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -49,6 +49,11 @@ void updateSearch(StorageIngredientsList& state, numOfIngredientsOnPage, state.pageNo * numOfIngredientsOnPage, threshhold); + state.totalFound = response.found; + if (state.totalFound == 0) { + renderSuggestIngredientCustomisation(state, userId, userId, bot); + return; + } const auto idGetter = &IngredientSearchForStorageItem::id; if (std::ranges::equal(response.page, state.searchItems, {}, idGetter, idGetter)) return; @@ -57,8 +62,6 @@ void updateSearch(StorageIngredientsList& state, state.totalFound = response.found; if (auto mMessageId = message::getMessageId(userId)) renderIngredientsListSearch(state, userId, userId, bot); - if (state.totalFound == 0) - renderSuggestIngredientCustomisation(state, userId, userId, bot); } } // namespace From 26a501f4aaa3b43831773a0b3c2ba7b9f2384570 Mon Sep 17 00:00:00 2001 From: Ilia Kliantsevich Date: Wed, 23 Jul 2025 20:49:30 +0300 Subject: [PATCH 099/106] fix: shopping list --- src/handlers/shopping_list/storage_selection_to_buy.cpp | 4 ++-- src/render/storage/ingredients/view.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handlers/shopping_list/storage_selection_to_buy.cpp b/src/handlers/shopping_list/storage_selection_to_buy.cpp index 76394b33..41454864 100644 --- a/src/handlers/shopping_list/storage_selection_to_buy.cpp +++ b/src/handlers/shopping_list/storage_selection_to_buy.cpp @@ -25,7 +25,7 @@ void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy if (cq.data == "back") { renderShoppingList(state.prevState, userId, chatId, bot); - stateManager.put(std::move(state.prevState)); + stateManager.put(auto{std::move(state.prevState)}); return; } @@ -40,7 +40,7 @@ void handleShoppingListStorageSelectionToBuyCQ(ShoppingListStorageSelectionToBuy for (auto& id : state.selectedIngredients) state.prevState.items.remove(id); renderShoppingList(state.prevState, userId, chatId, bot); - stateManager.put(std::move(state.prevState)); + stateManager.put(auto{std::move(state.prevState)}); } } // namespace cookcookhnya::handlers::shopping_list diff --git a/src/render/storage/ingredients/view.cpp b/src/render/storage/ingredients/view.cpp index 0af7fed0..53a18e60 100644 --- a/src/render/storage/ingredients/view.cpp +++ b/src/render/storage/ingredients/view.cpp @@ -84,7 +84,7 @@ void renderSuggestIngredientCustomisation(const states::StorageIngredientsList& const std::string text = utils::utf8str(u8"📝 Продолжите редактирование запроса или объявите личный ингредиент"); auto searchButton = std::make_shared(); - searchButton->text = utils::utf8str(u8"✏️ Редактировать"); + searchButton->text = utils::utf8str(u8"🛒 Редактировать"); searchButton->switchInlineQueryCurrentChat = ""; keyboard[0].push_back(std::move(searchButton)); // Mark as ingredient From 6d038629e745b892ee65fe54c7c1ccb186f1031b Mon Sep 17 00:00:00 2001 From: Badamshin Rashid <102172606+Rash1d1@users.noreply.github.com> Date: Wed, 23 Jul 2025 22:20:02 +0300 Subject: [PATCH 100/106] Update ci.yml --- .github/workflows/ci.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0807e7f..1cc11bdf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,4 @@ + name: ci pipeline for frontend on: @@ -23,7 +24,6 @@ jobs: working-directory: . run: | make build-release - clang-format-check: runs-on: ubuntu-latest needs: build @@ -40,7 +40,6 @@ jobs: run: | find src -type f \( -name '*.hpp' -or -name '*.cpp' \) \ -exec clang-format-19 --dry-run --Werror {} \+ - clang-tidy-check: runs-on: ubuntu-latest needs: build @@ -65,15 +64,14 @@ jobs: key: ${{ runner.os }}-clang-tidy-${{ github.ref }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-clang-tidy-${{ github.ref }}- - ${{ runner.os }}-clang-tidy-refs/heads/dev - ${{ runner.os }}-clang-tidy-refs/heads/main + ${{ runner.os }}-clang-tidy-refs/heads/dev- + ${{ runner.os }}-clang-tidy-refs/heads/main- - name: Copy original cache file if exists run: | if [ -f .clang-tidy-cache.json ]; then cp .clang-tidy-cache.json .clang-tidy-cache.json.orig fi - - name: Create clang-tidy cache script run: | cat > run-clang-tidy-cached.sh << 'EOF' @@ -151,7 +149,6 @@ jobs: - name: Run clang-tidy with caching run: | bash ./run-clang-tidy-cached.sh - - name: Check if cache file changed id: cache-changed run: | @@ -167,7 +164,6 @@ jobs: echo "No original cache file, assuming changed" echo "changed=true" >> $GITHUB_OUTPUT fi - - name: Debug cache state run: | echo "Cache hit: ${{ steps.cache-restore.outputs.cache-hit }}" @@ -178,7 +174,6 @@ jobs: else echo "No cache file found" fi - - name: Save clang-tidy cache if: always() && steps.cache-changed.outputs.changed == 'true' uses: actions/cache/save@v4 From 6916843d03af72274b249a55664ba6312e5709fd Mon Sep 17 00:00:00 2001 From: Badamshin Rashid <102172606+Rash1d1@users.noreply.github.com> Date: Wed, 23 Jul 2025 23:12:10 +0300 Subject: [PATCH 101/106] Update main.cpp --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 11d773f7..a3894820 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char* argv[]) { path); } else { bot.start(std::move(tgBot)); - } + } // pipidastr } catch (std::exception& e) { std::cout << e.what() << '\n'; return 1; From f5a3769f8721dace3dd193ec0479ee71e54e16d2 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Thu, 24 Jul 2025 00:20:09 +0300 Subject: [PATCH 102/106] refactor: rename RecipeStorageAddition to CookingPlanningStorageAddition --- src/handlers/common.hpp | 2 +- src/handlers/cooking_planning/add_storage.cpp | 4 ++-- src/handlers/cooking_planning/add_storage.hpp | 4 ++-- src/handlers/cooking_planning/view.cpp | 2 +- src/handlers/handlers_list.hpp | 3 ++- src/main.cpp | 2 +- src/states.hpp | 4 ++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index f65072b7..4f0e183c 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -26,7 +26,7 @@ using states::StorageList; using states::TotalPublicationHistory; using states::CookingPlanning; -using states::RecipeStorageAddition; +using states::CookingPlanningStorageAddition; using states::StorageCreationEnterName; using states::StorageDeletion; diff --git a/src/handlers/cooking_planning/add_storage.cpp b/src/handlers/cooking_planning/add_storage.cpp index cb3377c0..9db0d5be 100644 --- a/src/handlers/cooking_planning/add_storage.cpp +++ b/src/handlers/cooking_planning/add_storage.cpp @@ -20,8 +20,8 @@ namespace cookcookhnya::handlers::cooking_planning { using namespace render::cooking_planning; using namespace api::models::storage; -void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { +void handleCookingPlanningStorageAdditionCQ( + CookingPlanningStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); const std::string& data = cq.data; auto chatId = cq.message->chat->id; diff --git a/src/handlers/cooking_planning/add_storage.hpp b/src/handlers/cooking_planning/add_storage.hpp index fbd0079d..d5e0207c 100644 --- a/src/handlers/cooking_planning/add_storage.hpp +++ b/src/handlers/cooking_planning/add_storage.hpp @@ -5,7 +5,7 @@ namespace cookcookhnya::handlers::cooking_planning { -void handleRecipeStorageAdditionCQ( - RecipeStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); +void handleCookingPlanningStorageAdditionCQ( + CookingPlanningStorageAddition& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); } // namespace cookcookhnya::handlers::cooking_planning diff --git a/src/handlers/cooking_planning/view.cpp b/src/handlers/cooking_planning/view.cpp index 882c87b2..15c58002 100644 --- a/src/handlers/cooking_planning/view.cpp +++ b/src/handlers/cooking_planning/view.cpp @@ -68,7 +68,7 @@ void handleCookingPlanningCQ( chatId, bot, api); - stateManager.put(RecipeStorageAddition{.prevState = std::move(state)}); + stateManager.put(CookingPlanningStorageAddition{.prevState = std::move(state)}); return; } } diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 9dfbb0a1..1e6e6384 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -143,7 +143,8 @@ using storageIngredientsDeletionCQHandler = Handler; -using recipeStorageAdditionCQHandler = Handler; +using cookingPlanningStorageAdditionCQHandler = + Handler; using shoppingListCreationCQHandler = Handler; // Shopping list diff --git a/src/main.cpp b/src/main.cpp index e383c07d..8ae0c4ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,7 +61,7 @@ int main(int argc, char* argv[]) { storageIngredientsDeletionCQHandler, suggestedRecipeListCQHandler, cookingPlanningCQHandler, - recipeStorageAdditionCQHandler, + cookingPlanningStorageAdditionCQHandler, shoppingListCreationCQHandler, shoppingListViewCQHandler, personalAccountMenuCQHandler, diff --git a/src/states.hpp b/src/states.hpp index de1bbfa1..6149bebb 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -121,7 +121,7 @@ struct CookingPlanning { api::RecipeId recipeId; }; -struct RecipeStorageAddition { +struct CookingPlanningStorageAddition { CookingPlanning prevState; }; @@ -229,7 +229,7 @@ using State = std::variant Date: Thu, 24 Jul 2025 04:51:22 +0300 Subject: [PATCH 103/106] fix: improve render of publication history --- .../personal_account/publication_history.cpp | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/render/personal_account/publication_history.cpp b/src/render/personal_account/publication_history.cpp index 2d41bb9a..16c578f9 100644 --- a/src/render/personal_account/publication_history.cpp +++ b/src/render/personal_account/publication_history.cpp @@ -7,8 +7,11 @@ #include #include #include + namespace cookcookhnya::render::personal_account { + using namespace std::views; + void renderRequestHistory(UserId userId, size_t pageNo, size_t numOfInstances, @@ -22,26 +25,20 @@ void renderRequestHistory(UserId userId, std::string toPrint = utils::utf8str(u8"ℹ️История запросов на публикацию ваших рецептов и ингредиентов\n\n"); for (auto& req : history | reverse) { - std::string rcpIngRender; - if (req.requestType == "recipe") - rcpIngRender = utils::utf8str(u8"📖"); - else - rcpIngRender = utils::utf8str(u8"🥬"); - toPrint += std::format( - "{} {}: *{}* статус: {} ", rcpIngRender, req.requestType, req.name, utils::to_string(req.status.status)); + std::string emoji = utils::utf8str(req.requestType == "recipe" ? u8"📖" : u8"🥬"); + toPrint += std::format("{} *{}*\nСтатус: {}\n", emoji, 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)); + toPrint += std::format("По причине: {}\n", req.status.reason.value()); + toPrint += std::format("Запрос создан: {}\n", utils::to_string(req.created)); if (req.updated.has_value()) { - toPrint += std::format("последенее обновление: {}", utils::to_string(req.updated.value())); + toPrint += std::format("Последенее обновление: {}\n", utils::to_string(req.updated.value())); } toPrint += "\n\n"; } keyboard << makeCallbackButton(u8"↩️ Назад", "back"); - auto messageId = message::getMessageId(userId); - if (messageId) { + if (auto messageId = message::getMessageId(userId)) bot.editMessageText(toPrint, chatId, *messageId, std::move(keyboard), "Markdown"); - } } + } // namespace cookcookhnya::render::personal_account From b40e71719e55015f98f9329e8a5b0ef1331a20bb Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Thu, 24 Jul 2025 05:06:39 +0300 Subject: [PATCH 104/106] feat: after cooking ingredients spending --- src/backend/api/ingredients.hpp | 3 +- src/handlers/CMakeLists.txt | 2 + src/handlers/common.hpp | 2 + src/handlers/cooking/ingredients_spending.cpp | 70 +++++++++++++++++++ src/handlers/cooking/ingredients_spending.hpp | 11 +++ src/handlers/cooking_planning/view.cpp | 24 ++++++- src/handlers/handlers_list.hpp | 8 ++- src/main.cpp | 3 +- src/render/CMakeLists.txt | 2 + src/render/cooking/ingredients_spending.cpp | 55 +++++++++++++++ src/render/cooking/ingredients_spending.hpp | 16 +++++ src/states.hpp | 15 +++- 12 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 src/handlers/cooking/ingredients_spending.cpp create mode 100644 src/handlers/cooking/ingredients_spending.hpp create mode 100644 src/render/cooking/ingredients_spending.cpp create mode 100644 src/render/cooking/ingredients_spending.hpp diff --git a/src/backend/api/ingredients.hpp b/src/backend/api/ingredients.hpp index 7288e408..4ec684e4 100644 --- a/src/backend/api/ingredients.hpp +++ b/src/backend/api/ingredients.hpp @@ -30,8 +30,7 @@ class IngredientsApi : ApiBase { void putToStorage(UserId user, StorageId storage, IngredientId ingredient) const; void deleteFromStorage(UserId user, StorageId storage, IngredientId ingredient) const; - void - deleteMultipleFromStorage(UserId user, StorageId storage, const std::vector& ingredients = {}) const; + void deleteMultipleFromStorage(UserId user, StorageId storage, const std::vector& ingredients) const; [[nodiscard]] models::ingredient::IngredientSearchForStorageResponse searchForStorage(UserId user, diff --git a/src/handlers/CMakeLists.txt b/src/handlers/CMakeLists.txt index 351637b2..54dfd8d6 100644 --- a/src/handlers/CMakeLists.txt +++ b/src/handlers/CMakeLists.txt @@ -50,4 +50,6 @@ target_sources(main PRIVATE src/handlers/recipes_search/view.cpp src/handlers/recipe/view.cpp + + src/handlers/cooking/ingredients_spending.cpp ) diff --git a/src/handlers/common.hpp b/src/handlers/common.hpp index 4f0e183c..dd9c61fd 100644 --- a/src/handlers/common.hpp +++ b/src/handlers/common.hpp @@ -57,6 +57,8 @@ using states::RecipesSearch; using states::RecipeView; +using states::CookingIngredientsSpending; + // Type aliases using BotRef = const TgBot::Api&; using SMRef = const states::StateManager&; diff --git a/src/handlers/cooking/ingredients_spending.cpp b/src/handlers/cooking/ingredients_spending.cpp new file mode 100644 index 00000000..25818c59 --- /dev/null +++ b/src/handlers/cooking/ingredients_spending.cpp @@ -0,0 +1,70 @@ +#include "ingredients_spending.hpp" + +#include "backend/api/api.hpp" +#include "backend/id_types.hpp" +#include "handlers/common.hpp" +#include "render/cooking/ingredients_spending.hpp" +#include "render/cooking_planning/view.hpp" +#include "states.hpp" +#include "utils/parsing.hpp" +#include "utils/utils.hpp" + +#include +#include +#include +#include + +namespace cookcookhnya::handlers::cooking { + +using namespace render::cooking_planning; +using namespace render::cooking; +using states::helpers::SelectableIngredient; +using namespace std::literals; +using namespace std::views; +using std::ranges::to; + +void handleCookingIngredientsSpendingCQ( + CookingIngredientsSpending& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + auto chatId = cq.message->chat->id; + auto userId = cq.from->id; + + if (cq.data == "back") { + bot.answerCallbackQuery(cq.id); + renderCookingPlanning(state.prevState.availability, state.prevState.recipeId, userId, chatId, bot, api); + stateManager.put(auto{std::move(state.prevState)}); + return; + } + + if (cq.data == "remove") { + if (!state.storageId) + return; + auto selected = state.ingredients | filter(&SelectableIngredient::selected) | + transform(&SelectableIngredient::id) | to(); + api.getIngredientsApi().deleteMultipleFromStorage(userId, *state.storageId, selected); + bot.answerCallbackQuery(cq.id, utils::utf8str(u8"Успешно удалено из выбранного хранилища")); + return; + } + + if (cq.data == "to_shopping_list") { + auto selected = state.ingredients | filter(&SelectableIngredient::selected) | + transform(&SelectableIngredient::id) | to(); + api.getShoppingListApi().put(userId, selected); + bot.answerCallbackQuery(cq.id, utils::utf8str(u8"Успешно добавлено")); + return; + } + + if (cq.data.starts_with("ingredient_")) { + auto mIngredientId = + utils::parseSafe(std::string_view{cq.data}.substr("ingredient_"sv.size())); + if (!mIngredientId) + return; + auto ingredientIter = std::ranges::find(state.ingredients, *mIngredientId, &SelectableIngredient::id); + if (ingredientIter == state.ingredients.end()) + return; + ingredientIter->selected = !ingredientIter->selected; + renderIngredientsSpending(state.ingredients, state.storageId.has_value(), userId, chatId, bot); + return; + } +} + +} // namespace cookcookhnya::handlers::cooking diff --git a/src/handlers/cooking/ingredients_spending.hpp b/src/handlers/cooking/ingredients_spending.hpp new file mode 100644 index 00000000..93361715 --- /dev/null +++ b/src/handlers/cooking/ingredients_spending.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "backend/api/api.hpp" +#include "handlers/common.hpp" + +namespace cookcookhnya::handlers::cooking { + +void handleCookingIngredientsSpendingCQ( + CookingIngredientsSpending& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api); + +} // namespace cookcookhnya::handlers::cooking diff --git a/src/handlers/cooking_planning/view.cpp b/src/handlers/cooking_planning/view.cpp index 15c58002..54e15e97 100644 --- a/src/handlers/cooking_planning/view.cpp +++ b/src/handlers/cooking_planning/view.cpp @@ -4,11 +4,14 @@ #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" #include "handlers/common.hpp" +#include "render/cooking/ingredients_spending.hpp" #include "render/cooking_planning/add_storage.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" #include "states.hpp" +#include +#include #include #include #include @@ -18,18 +21,35 @@ namespace cookcookhnya::handlers::cooking_planning { using namespace render::recipes_suggestions; using namespace render::shopping_list; using namespace render::cooking_planning; +using namespace render::cooking; + using namespace api::models::ingredient; using IngredientAvailability = states::CookingPlanning::IngredientAvailability; using AvailabilityType = states::CookingPlanning::AvailabilityType; +using states::helpers::SelectableIngredient; + +using namespace std::views; +using std::ranges::to; void handleCookingPlanningCQ( CookingPlanning& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { + bot.answerCallbackQuery(cq.id); const std::string data = cq.data; auto chatId = cq.message->chat->id; auto userId = cq.from->id; if (data == "start_cooking") { - // TODO: add state of begginig of cooking + std::optional theOnlyStorage; + if (state.prevState.selectedStorages.size() == 1) + theOnlyStorage = state.prevState.selectedStorages.front().id; + auto ingredients = + state.availability | + transform([](const auto& ia) { return SelectableIngredient{{ia.ingredient.id, ia.ingredient.name}}; }) | + to(); + renderIngredientsSpending(ingredients, theOnlyStorage.has_value(), userId, chatId, bot); + CookingIngredientsSpending newState{ + .prevState = std::move(state), .storageId = theOnlyStorage, .ingredients = std::move(ingredients)}; + stateManager.put(std::move(newState)); return; } @@ -48,14 +68,12 @@ void handleCookingPlanningCQ( .selectedIngredients = selectedIngredients, .allIngredients = allIngredients, }); - bot.answerCallbackQuery(cq.id); return; } if (data == "back_from_recipe_view") { renderRecipesSuggestion(state.prevState.selectedStorages, state.prevState.pageNo, userId, chatId, bot, api); stateManager.put(auto{std::move(state.prevState)}); - bot.answerCallbackQuery(cq.id); return; } diff --git a/src/handlers/handlers_list.hpp b/src/handlers/handlers_list.hpp index 1e6e6384..99142f0f 100644 --- a/src/handlers/handlers_list.hpp +++ b/src/handlers/handlers_list.hpp @@ -53,6 +53,8 @@ #include "recipe/view.hpp" +#include "cooking/ingredients_spending.hpp" + #include #include @@ -74,6 +76,7 @@ using namespace handlers::storages_selection; using namespace handlers::recipes_suggestions; using namespace handlers::recipes_search; using namespace handlers::recipe; +using namespace handlers::cooking; using namespace tg_stater; @@ -141,7 +144,7 @@ using storageIngredientsListIQHandler = Handler; -// RecipeView +// Cooking planning using cookingPlanningCQHandler = Handler; using cookingPlanningStorageAdditionCQHandler = Handler; @@ -177,4 +180,7 @@ using recipesSearchIQHandler = Handler; +// Cooking +using cookingIngredientsSpendingCQHandler = Handler; + } // namespace cookcookhnya::handlers::bot_handlers diff --git a/src/main.cpp b/src/main.cpp index 8ae0c4ab..d1df02b8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,7 +78,8 @@ int main(int argc, char* argv[]) { shoppingListIngredientSearchIQHandler, recipesSearchCQHandler, recipesSearchIQHandler, - recipeViewCQHandler> + recipeViewCQHandler, + cookingIngredientsSpendingCQHandler> bot{{}, {ApiClient{utils::getenvWithError("API_URL")}}}; TgBot::Bot tgBot{utils::getenvWithError("BOT_TOKEN")}; // sdf diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index faaea959..9faecd9e 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -44,4 +44,6 @@ target_sources(main PRIVATE src/render/recipes_search/view.cpp src/render/recipe/view.cpp + + src/render/cooking/ingredients_spending.cpp ) diff --git a/src/render/cooking/ingredients_spending.cpp b/src/render/cooking/ingredients_spending.cpp new file mode 100644 index 00000000..251b8907 --- /dev/null +++ b/src/render/cooking/ingredients_spending.cpp @@ -0,0 +1,55 @@ +#include "ingredients_spending.hpp" + +#include "message_tracker.hpp" +#include "render/common.hpp" +#include "states.hpp" +#include "utils/utils.hpp" + +#include +#include + +namespace cookcookhnya::render::cooking { + +using namespace std::views; +using SelectableIngedient = states::helpers::SelectableShoppingListItem; + +void renderIngredientsSpending(const std::vector& ingredients, + bool canRemove, + UserId userId, + ChatId chatId, + BotRef bot) { + const std::string text = utils::utf8str( + canRemove ? u8"🧾 Вы можете убрать из хранилища продукты, если они закончились после готовки, а также сразу " + u8"добавить их в список покупок" + : u8"🧾 Вы можете добавить закончившиеся после готовки продукты в список покупок"); + + const bool anySelected = std::ranges::any_of(ingredients, &states::helpers::SelectableIngredient::selected); + + InlineKeyboardBuilder keyboard{2 + ((ingredients.size() / 2) + 1)}; // remove and/or buy, list (n/2), back + + if (anySelected) { + if (canRemove) + keyboard << makeCallbackButton(u8"🗑 Убрать", "remove"); + keyboard << makeCallbackButton(u8"🛒 В список покупок", "to_shopping_list") << NewRow{}; + } + + for (auto row : ingredients | chunk(2)) { + for (const auto& ing : row) { + const char8_t* const selectedMark = ing.selected ? u8"[ + ] " : u8"[ᅠ] "; // not empty! + keyboard << makeCallbackButton(utils::utf8str(selectedMark) + ing.name, + "ingredient_" + utils::to_string(ing.id)); + } + keyboard << NewRow{}; + } + + keyboard << makeCallbackButton(u8"↩️ Назад", "back"); + + if (auto messageId = message::getMessageId(userId)) { + bot.editMessageText(text, chatId, *messageId, std::move(keyboard)); + } else { + auto message = bot.sendMessage(chatId, text, std::move(keyboard)); + message::addMessageId(userId, message->messageId); + } +} + +} // namespace cookcookhnya::render::cooking diff --git a/src/render/cooking/ingredients_spending.hpp b/src/render/cooking/ingredients_spending.hpp new file mode 100644 index 00000000..e4aca33c --- /dev/null +++ b/src/render/cooking/ingredients_spending.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "render/common.hpp" +#include "states.hpp" + +#include + +namespace cookcookhnya::render::cooking { + +void renderIngredientsSpending(const std::vector& ingredients, + bool canRemove, + UserId userId, + ChatId chatId, + BotRef bot); + +} // namespace cookcookhnya::render::cooking diff --git a/src/states.hpp b/src/states.hpp index 6149bebb..cdc1db2a 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -38,6 +38,12 @@ struct SelectableShoppingListItem : api::models::shopping_list::ShoppingListItem : ShoppingListItem{std::move(item)} {} }; +struct SelectableIngredient : api::models::ingredient::Ingredient { + bool selected = false; + SelectableIngredient(api::models::ingredient::Ingredient item) // NOLINT(*explicit*) + : Ingredient{std::move(item)} {} +}; + } // namespace helpers struct MainMenu {}; @@ -203,6 +209,12 @@ struct RecipeView { api::models::recipe::RecipeDetails recipe; }; +struct CookingIngredientsSpending { // NOLINT(*member-init) + CookingPlanning prevState; + std::optional storageId; + std::vector ingredients; +}; + using State = std::variant; + RecipeView, + CookingIngredientsSpending>; using StateManager = tg_stater::StateProxy>; From c27ed7ba06aa8a0b8bf53153b557d5aad08a27ce Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Thu, 24 Jul 2025 05:22:53 +0300 Subject: [PATCH 105/106] feat: share button for suggested recipes --- src/handlers/cooking_planning/view.cpp | 2 +- src/render/cooking_planning/add_storage.cpp | 20 ++++++-- src/render/cooking_planning/add_storage.hpp | 8 --- src/render/cooking_planning/view.cpp | 56 +++++++++++++++------ src/render/cooking_planning/view.hpp | 12 ----- 5 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/handlers/cooking_planning/view.cpp b/src/handlers/cooking_planning/view.cpp index 54e15e97..bc0752a1 100644 --- a/src/handlers/cooking_planning/view.cpp +++ b/src/handlers/cooking_planning/view.cpp @@ -71,7 +71,7 @@ void handleCookingPlanningCQ( return; } - if (data == "back_from_recipe_view") { + if (data == "back") { renderRecipesSuggestion(state.prevState.selectedStorages, state.prevState.pageNo, userId, chatId, bot, api); stateManager.put(auto{std::move(state.prevState)}); return; diff --git a/src/render/cooking_planning/add_storage.cpp b/src/render/cooking_planning/add_storage.cpp index 0f01f071..0f4c4c0b 100644 --- a/src/render/cooking_planning/add_storage.cpp +++ b/src/render/cooking_planning/add_storage.cpp @@ -8,7 +8,6 @@ #include "render/common.hpp" #include "states.hpp" #include "utils/utils.hpp" -#include "view.hpp" #include #include @@ -24,7 +23,15 @@ using namespace api::models::storage; using IngredientAvailability = states::CookingPlanning::IngredientAvailability; using AvailabilityType = states::CookingPlanning::AvailabilityType; -TextGenInfo storageAdditionView(const std::vector& inStoragesAvailability, +namespace { + +struct CookingInfo { + std::string renderText; + bool isIngredientNotAvailable; + bool isIngredientInOtherStorages; +}; + +CookingInfo storageAdditionView(const std::vector& inStoragesAvailability, const std::vector& selectedStorages, api::RecipeId recipeId, UserId userId, @@ -61,11 +68,13 @@ TextGenInfo storageAdditionView(const std::vector& inSto if (recipe.link) text += utils::utf8str(u8"\n🌐 Источник: ") + *recipe.link; - return {.text = text, + return {.renderText = text, .isIngredientNotAvailable = isIngredientNotAvailable, - .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; + .isIngredientInOtherStorages = isIngredientIsOtherStorages}; } +} // namespace + void renderStoragesSuggestion(const std::vector& inStoragesAvailability, const std::vector& selectedStorages, const std::vector& addedStorages, @@ -107,7 +116,8 @@ void renderStoragesSuggestion(const std::vector& inStora auto messageId = message::getMessageId(userId); if (messageId) { - bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); + bot.editMessageText( + textGen.renderText, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); } } } // namespace cookcookhnya::render::cooking_planning diff --git a/src/render/cooking_planning/add_storage.hpp b/src/render/cooking_planning/add_storage.hpp index 00539a63..b34595bd 100644 --- a/src/render/cooking_planning/add_storage.hpp +++ b/src/render/cooking_planning/add_storage.hpp @@ -4,20 +4,12 @@ #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "render/common.hpp" -#include "render/cooking_planning/view.hpp" #include "states.hpp" #include namespace cookcookhnya::render::cooking_planning { -TextGenInfo -storageAdditionView(const std::vector& inStoragesAvailability, - const std::vector& selectedStorages, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api); - void renderStoragesSuggestion( const std::vector& inStoragesAvailability, const std::vector& selectedStorages, diff --git a/src/render/cooking_planning/view.cpp b/src/render/cooking_planning/view.cpp index 8791c00d..0118c4d3 100644 --- a/src/render/cooking_planning/view.cpp +++ b/src/render/cooking_planning/view.cpp @@ -6,9 +6,11 @@ #include "message_tracker.hpp" #include "render/common.hpp" #include "states.hpp" +#include "utils/u8format.hpp" #include "utils/utils.hpp" -#include +#include + #include #include #include @@ -20,7 +22,16 @@ using namespace api::models::recipe; using IngredientAvailability = states::CookingPlanning::IngredientAvailability; using AvailabilityType = states::CookingPlanning::AvailabilityType; -TextGenInfo recipeView(const std::vector& inStoragesAvailability, +namespace { + +struct CookingInfo { + std::string renderText; + std::string recipeName; + bool isIngredientNotAvailable; + bool isIngredientInOtherStorages; +}; + +CookingInfo recipeView(const std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, api::ApiClientRef api) { @@ -45,36 +56,49 @@ TextGenInfo recipeView(const std::vector& inStoragesAvai if (recipeIngredients.link) text += utils::utf8str(u8"\n🌐 Источник: ") + *recipeIngredients.link; - return {.text = text, + return {.renderText = text, + .recipeName = recipeName, .isIngredientNotAvailable = isIngredientNotAvailable, - .isIngredientIsOtherStorages = isIngredientIsOtherStorages}; + .isIngredientInOtherStorages = isIngredientIsOtherStorages}; } +} // namespace + void renderCookingPlanning(std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, BotRef bot, api::ApiClientRef api) { - auto textGen = recipeView(inStoragesAvailability, recipeId, userId, api); - const std::size_t buttonRows = textGen.isIngredientNotAvailable ? 3 : 2; - InlineKeyboard keyboard(buttonRows); + auto cookingInfo = recipeView(inStoragesAvailability, recipeId, userId, api); + InlineKeyboardBuilder keyboard{4}; // Cook + add storages, shopping list, share, back - keyboard[0].push_back(makeCallbackButton(u8"🧑‍🍳 Готовить", "start_cooking")); + keyboard << makeCallbackButton(u8"🧑‍🍳 Готовить", "start_cooking"); + if (cookingInfo.isIngredientInOtherStorages) + keyboard << makeCallbackButton(u8"?", "add_storages"); + keyboard << NewRow{}; - if (textGen.isIngredientIsOtherStorages) { - keyboard[0].push_back(makeCallbackButton(u8"?", "add_storages")); - } - if (textGen.isIngredientNotAvailable) { - keyboard[1].push_back(makeCallbackButton(u8"📝 Составить список продуктов", "shopping_list")); - } + if (cookingInfo.isIngredientNotAvailable) + keyboard << makeCallbackButton(u8"📝 Составить список продуктов", "shopping_list") << NewRow{}; + + auto shareButton = std::make_shared(); + shareButton->text = utils::utf8str(u8"📤 Поделиться"); + const std::string botAlias = bot.getUnderlying().getMe()->username; + const std::string recipeUrl = std::format("https://t.me/{}?start=recipe_{}", botAlias, recipeId); + const std::string shareText = + utils::u8format("{} **{}**", u8"Хочу поделиться с тобой рецептом", cookingInfo.recipeName); + + boost::urls::url url{"https://t.me/share/url"}; + url.params().append({"url", recipeUrl}); + url.params().append({"text", shareText}); + shareButton->url = url.buffer(); - keyboard[buttonRows - 1].push_back(makeCallbackButton(u8"↩️ Назад", "back_from_recipe_view")); + keyboard << std::move(shareButton) << NewRow{} << makeCallbackButton(u8"↩️ Назад", "back"); auto messageId = message::getMessageId(userId); if (messageId) { // Only on difference between function above - bot.editMessageText(textGen.text, chatId, *messageId, makeKeyboardMarkup(std::move(keyboard)), "Markdown"); + bot.editMessageText(cookingInfo.renderText, chatId, *messageId, std::move(keyboard), "Markdown"); } } diff --git a/src/render/cooking_planning/view.hpp b/src/render/cooking_planning/view.hpp index d39f9179..8c57424b 100644 --- a/src/render/cooking_planning/view.hpp +++ b/src/render/cooking_planning/view.hpp @@ -5,17 +5,10 @@ #include "render/common.hpp" #include "states.hpp" -#include #include namespace cookcookhnya::render::cooking_planning { -struct TextGenInfo { - std::string text; - bool isIngredientNotAvailable; - bool isIngredientIsOtherStorages; -}; - void renderCookingPlanning(std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, @@ -23,9 +16,4 @@ void renderCookingPlanning(std::vector& inStoragesAvailability, - api::RecipeId recipeId, - UserId userId, - api::ApiClientRef api); - } // namespace cookcookhnya::render::cooking_planning From ce179be97d5e74ab3706efc14205f02dc8222af6 Mon Sep 17 00:00:00 2001 From: Maxim Fomin Date: Thu, 24 Jul 2025 09:20:03 +0300 Subject: [PATCH 106/106] feat: cooking from global search --- src/handlers/commands/start.cpp | 2 +- src/handlers/commands/wanna_eat.cpp | 12 +-- src/handlers/cooking_planning/add_storage.cpp | 30 ++++++- src/handlers/cooking_planning/view.cpp | 68 ++++++++++++--- src/handlers/main_menu/view.cpp | 13 ++- src/handlers/recipe/view.cpp | 32 +++++++ src/handlers/recipes_search/view.cpp | 3 +- src/handlers/recipes_suggestions/view.cpp | 43 ++++++---- src/handlers/storage/delete.cpp | 14 ++- src/handlers/storage/ingredients/view.cpp | 3 +- src/handlers/storage/members/view.cpp | 4 +- src/handlers/storage/view.cpp | 7 +- src/handlers/storages_list/view.cpp | 4 +- src/handlers/storages_selection/view.cpp | 37 ++++++-- src/render/cooking_planning/view.cpp | 2 +- src/render/cooking_planning/view.hpp | 2 +- src/render/recipe/view.cpp | 5 +- src/render/recipes_search/view.cpp | 4 +- src/render/recipes_suggestions/view.cpp | 9 +- src/render/recipes_suggestions/view.hpp | 4 +- src/states.hpp | 86 +++++++++++++++---- src/utils/ingredients_availability.cpp | 7 +- src/utils/ingredients_availability.hpp | 2 +- 23 files changed, 290 insertions(+), 103 deletions(-) diff --git a/src/handlers/commands/start.cpp b/src/handlers/commands/start.cpp index 8749ae54..05d32231 100644 --- a/src/handlers/commands/start.cpp +++ b/src/handlers/commands/start.cpp @@ -61,7 +61,7 @@ void handleStartCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClient auto recipe = api.getRecipesApi().get(userId, *mRecipeId); message::deleteMessageId(userId); renderRecipeView(recipe, *mRecipeId, userId, chatId, bot); - stateManager.put(RecipeView{.prevState = std::nullopt, .recipe = std::move(recipe)}); + stateManager.put(RecipeView{.prevState = std::nullopt, .recipe = std::move(recipe), .recipeId = *mRecipeId}); return; } }; diff --git a/src/handlers/commands/wanna_eat.cpp b/src/handlers/commands/wanna_eat.cpp index a33c15ae..52deb48f 100644 --- a/src/handlers/commands/wanna_eat.cpp +++ b/src/handlers/commands/wanna_eat.cpp @@ -10,12 +10,15 @@ #include "utils/utils.hpp" #include +#include +#include namespace cookcookhnya::handlers::commands { using namespace render::select_storages; using namespace render::main_menu; using namespace render::recipes_suggestions; +using namespace std::views; void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiClientRef api) { auto storages = api.getStoragesApi().getStoragesList(m.from->id); @@ -25,15 +28,12 @@ void handleWannaEatCmd(MessageRef m, BotRef bot, SMRef stateManager, api::ApiCli stateManager.put(MainMenu{}); } else if (storages.size() == 1) { message::deleteMessageId(m.from->id); - renderRecipesSuggestion({storages}, 0, m.from->id, m.chat->id, bot, api); + renderRecipesSuggestion({storages[0].id}, 0, m.from->id, m.chat->id, bot, api); stateManager.put(SuggestedRecipesList{ - .selectedStorages = storages, - .pageNo = 0, - .fromStorage = false, - }); + .prevState = SuggestedRecipesList::FromMainMenuData{{}, std::move(storages[0])}, .pageNo = 0}); } else { message::deleteMessageId(m.from->id); - auto newState = StoragesSelection{}; + auto newState = StoragesSelection{.prevState = MainMenu{}, .selectedStorages = {}}; renderStorageSelection(newState, m.from->id, m.chat->id, bot, api); stateManager.put(newState); } diff --git a/src/handlers/cooking_planning/add_storage.cpp b/src/handlers/cooking_planning/add_storage.cpp index 9db0d5be..af68e142 100644 --- a/src/handlers/cooking_planning/add_storage.cpp +++ b/src/handlers/cooking_planning/add_storage.cpp @@ -40,14 +40,27 @@ void handleCookingPlanningStorageAdditionCQ( const StorageSummary newStorage = {.id = *newStorageId, .name = newStorageDetails.name}; state.prevState.addedStorages.push_back(newStorage); utils::addStorage(state.prevState.availability, newStorage); + + using StoragesList = std::vector; + auto selectedStorages = state.prevState.getStorages(); + const StoragesList* selectedStoragesPtr = nullptr; + // very optimized decision! (no) + if (auto* storagesVal = std::get_if(&selectedStorages)) + selectedStoragesPtr = storagesVal; + else if (auto* storagesRef = std::get_if>(&selectedStorages)) + selectedStoragesPtr = &storagesRef->get(); + else + return; + renderStoragesSuggestion(state.prevState.availability, - state.prevState.prevState.selectedStorages, + *selectedStoragesPtr, state.prevState.addedStorages, state.prevState.recipeId, userId, chatId, bot, api); + return; } } @@ -59,14 +72,27 @@ void handleCookingPlanningStorageAdditionCQ( state.prevState.addedStorages.erase(std::ranges::find( state.prevState.addedStorages, newStorageId, &api::models::storage::StorageSummary::id)); utils::deleteStorage(state.prevState.availability, newStorage); + + using StoragesList = std::vector; + auto selectedStorages = state.prevState.getStorages(); + const StoragesList* selectedStoragesPtr = nullptr; + // very optimized decision! (no) + if (auto* storagesVal = std::get_if(&selectedStorages)) + selectedStoragesPtr = storagesVal; + else if (auto* storagesRef = std::get_if>(&selectedStorages)) + selectedStoragesPtr = &storagesRef->get(); + else + return; + renderStoragesSuggestion(state.prevState.availability, - state.prevState.prevState.selectedStorages, + *selectedStoragesPtr, state.prevState.addedStorages, state.prevState.recipeId, userId, chatId, bot, api); + return; } } } diff --git a/src/handlers/cooking_planning/view.cpp b/src/handlers/cooking_planning/view.cpp index bc0752a1..ae5773e6 100644 --- a/src/handlers/cooking_planning/view.cpp +++ b/src/handlers/cooking_planning/view.cpp @@ -3,17 +3,22 @@ #include "backend/api/api.hpp" #include "backend/id_types.hpp" #include "backend/models/ingredient.hpp" +#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/cooking/ingredients_spending.hpp" #include "render/cooking_planning/add_storage.hpp" +#include "render/main_menu/view.hpp" +#include "render/recipes_search/view.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/shopping_list/create.hpp" #include "states.hpp" +#include #include #include #include #include +#include #include namespace cookcookhnya::handlers::cooking_planning { @@ -22,8 +27,10 @@ using namespace render::recipes_suggestions; using namespace render::shopping_list; using namespace render::cooking_planning; using namespace render::cooking; +using namespace render::recipes_search; using namespace api::models::ingredient; +using namespace api::models::storage; using IngredientAvailability = states::CookingPlanning::IngredientAvailability; using AvailabilityType = states::CookingPlanning::AvailabilityType; using states::helpers::SelectableIngredient; @@ -31,6 +38,28 @@ using states::helpers::SelectableIngredient; using namespace std::views; using std::ranges::to; +namespace { + +std::optional getTheOnlyStorage(const CookingPlanning& state) { + // what a pattern matching hell (c) Team lead + if (const auto* prevState = std::get_if(&state.prevState)) { + if (const auto* prevPrevState = std::get_if(&prevState->prevState)) + return prevPrevState->storageId; + if (const auto* prevPrevState = std::get_if(&prevState->prevState)) + return prevPrevState->second.id; + if (const auto* prevPrevState = std::get_if(&prevState->prevState)) + if (prevPrevState->selectedStorages.size() == 1) + return prevPrevState->selectedStorages.front().id; + } else if (const auto* prevState = std::get_if(&state.prevState)) { + if (prevState->second.size() == 1) + return prevState->second[0].id; + return {}; + } + return {}; +} + +} // namespace + void handleCookingPlanningCQ( CookingPlanning& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); @@ -39,13 +68,11 @@ void handleCookingPlanningCQ( auto userId = cq.from->id; if (data == "start_cooking") { - std::optional theOnlyStorage; - if (state.prevState.selectedStorages.size() == 1) - theOnlyStorage = state.prevState.selectedStorages.front().id; auto ingredients = state.availability | transform([](const auto& ia) { return SelectableIngredient{{ia.ingredient.id, ia.ingredient.name}}; }) | to(); + std::optional theOnlyStorage = getTheOnlyStorage(state); renderIngredientsSpending(ingredients, theOnlyStorage.has_value(), userId, chatId, bot); CookingIngredientsSpending newState{ .prevState = std::move(state), .storageId = theOnlyStorage, .ingredients = std::move(ingredients)}; @@ -72,20 +99,35 @@ void handleCookingPlanningCQ( } if (data == "back") { - renderRecipesSuggestion(state.prevState.selectedStorages, state.prevState.pageNo, userId, chatId, bot, api); - stateManager.put(auto{std::move(state.prevState)}); + if (auto* prevState = std::get_if(&state.prevState)) { + renderRecipesSuggestion(prevState->getStorageIds(), prevState->pageNo, userId, chatId, bot, api); + stateManager.put(auto{std::move(*prevState)}); + } else if (auto* prevState = std::get_if(&state.prevState)) { + if (auto& mSearchState = prevState->first.prevState) { + renderRecipesSearch(mSearchState->pagination, mSearchState->page, userId, chatId, bot); + stateManager.put(auto{std::move(*mSearchState)}); + } else { + render::main_menu::renderMainMenu(true, std::nullopt, userId, chatId, bot, api); + stateManager.put(MainMenu{}); + } + } return; } if (data == "add_storages") { - renderStoragesSuggestion(state.availability, - state.prevState.selectedStorages, - state.addedStorages, - state.recipeId, - userId, - chatId, - bot, - api); + using StoragesList = std::vector; + auto selectedStorages = state.getStorages(); + const StoragesList* selectedStoragesPtr = nullptr; + // very optimized decision! (no) + if (auto* storagesVal = std::get_if(&selectedStorages)) + selectedStoragesPtr = storagesVal; + else if (auto* storagesRef = std::get_if>(&selectedStorages)) + selectedStoragesPtr = &storagesRef->get(); + else + return; + + renderStoragesSuggestion( + state.availability, *selectedStoragesPtr, state.addedStorages, state.recipeId, userId, chatId, bot, api); stateManager.put(CookingPlanningStorageAddition{.prevState = std::move(state)}); return; } diff --git a/src/handlers/main_menu/view.cpp b/src/handlers/main_menu/view.cpp index 392d2993..a51a07ac 100644 --- a/src/handlers/main_menu/view.cpp +++ b/src/handlers/main_menu/view.cpp @@ -1,8 +1,6 @@ #include "view.hpp" #include "backend/api/api.hpp" -#include "backend/api/storages.hpp" -#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/personal_account/view.hpp" #include "render/recipes_search/view.hpp" @@ -24,8 +22,7 @@ using namespace render::personal_account; using namespace render::recipes_search; using namespace std::views; -void handleMainMenuCQ( - MainMenu& /*unused*/, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { +void handleMainMenuCQ(MainMenu& state, CallbackQueryRef cq, BotRef& bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto userId = cq.from->id; auto chatId = cq.message->chat->id; @@ -39,13 +36,13 @@ void handleMainMenuCQ( if (cq.data == "wanna_eat") { if (storages.size() == 1) { - std::vector storage = {storages[0]}; - renderRecipesSuggestion(storage, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{.selectedStorages = storage, .pageNo = 0, .fromStorage = false}); + renderRecipesSuggestion({storages[0].id}, 0, userId, chatId, bot, api); + stateManager.put(SuggestedRecipesList{ + .prevState = SuggestedRecipesList::FromMainMenuData{state, std::move(storages[0])}, .pageNo = 0}); return; } renderStorageSelection({}, userId, chatId, bot, api); - stateManager.put(StoragesSelection{.selectedStorages = std::vector{}}); + stateManager.put(StoragesSelection{.prevState = state, .selectedStorages = {}}); return; } diff --git a/src/handlers/recipe/view.cpp b/src/handlers/recipe/view.cpp index 45cf7fcf..a57e4e87 100644 --- a/src/handlers/recipe/view.cpp +++ b/src/handlers/recipe/view.cpp @@ -1,16 +1,24 @@ #include "view.hpp" #include "backend/api/api.hpp" +#include "backend/id_types.hpp" #include "handlers/common.hpp" +#include "render/cooking_planning/view.hpp" #include "render/main_menu/view.hpp" #include "render/recipes_search/view.hpp" +#include "render/storages_selection/view.hpp" +#include "states.hpp" +#include "utils/ingredients_availability.hpp" #include #include +#include namespace cookcookhnya::handlers::recipe { using namespace render::recipes_search; +using namespace render::cooking_planning; +using namespace render::select_storages; using namespace render::main_menu; void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { @@ -28,6 +36,30 @@ void handleRecipeViewCQ(RecipeView& state, CallbackQueryRef cq, BotRef bot, SMRe } return; } + + if (cq.data == "cook") { + auto storages = api.getStoragesApi().getStoragesList(userId); + if (storages.size() <= 1) { + std::vector storagesIds; + if (!storages.empty()) + storagesIds.push_back(storages[0].id); + + const api::RecipeId recipeId = state.recipeId; + auto availability = utils::inStoragesAvailability(storagesIds, recipeId, userId, api); + + renderCookingPlanning(availability, recipeId, userId, chatId, bot, api); + stateManager.put( + CookingPlanning{.prevState = CookingPlanning::FromRecipeViewData{std::move(state), std::move(storages)}, + .addedStorages = {}, + .availability = std::move(availability), + .recipeId = recipeId}); + return; + } + StoragesSelection newState{.prevState = std::move(state), .selectedStorages = {}}; + renderStorageSelection(newState, userId, chatId, bot, api); + stateManager.put(std::move(newState)); + return; + } } } // namespace cookcookhnya::handlers::recipe diff --git a/src/handlers/recipes_search/view.cpp b/src/handlers/recipes_search/view.cpp index 0317e1f1..a5e99a13 100644 --- a/src/handlers/recipes_search/view.cpp +++ b/src/handlers/recipes_search/view.cpp @@ -59,7 +59,8 @@ void handleRecipesSearchCQ( return; auto recipe = api.getRecipesApi().get(userId, *mRecipeId); renderRecipeView(recipe, *mRecipeId, userId, chatId, bot); - stateManager.put(RecipeView{.prevState = {std::move(state)}, .recipe = std::move(recipe)}); + stateManager.put( + RecipeView{.prevState = {std::move(state)}, .recipe = std::move(recipe), .recipeId = *mRecipeId}); return; } } diff --git a/src/handlers/recipes_suggestions/view.cpp b/src/handlers/recipes_suggestions/view.cpp index d88a7bdb..ed737970 100644 --- a/src/handlers/recipes_suggestions/view.cpp +++ b/src/handlers/recipes_suggestions/view.cpp @@ -2,6 +2,7 @@ #include "backend/api/api.hpp" #include "backend/id_types.hpp" +#include "backend/models/storage.hpp" #include "handlers/common.hpp" #include "render/cooking_planning/view.hpp" #include "render/main_menu/view.hpp" @@ -12,8 +13,11 @@ #include "utils/parsing.hpp" #include +#include #include #include +#include +#include namespace cookcookhnya::handlers::recipes_suggestions { @@ -22,28 +26,30 @@ using namespace render::select_storages; using namespace render::storage; using namespace render::cooking_planning; using namespace render::main_menu; +using namespace api::models::storage; +using namespace std::views; void handleSuggestedRecipesListCQ( SuggestedRecipesList& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { bot.answerCallbackQuery(cq.id); auto chatId = cq.message->chat->id; auto userId = cq.from->id; - - auto data = cq.data; + const std::string& data = cq.data; if (data == "back") { - if (state.fromStorage) { - renderStorageView(state.selectedStorages[0].id, userId, chatId, bot, api); - stateManager.put(StorageView{state.selectedStorages[0].id}); // Go to the only one storage - } else { - if (api.getStoragesApi().getStoragesList(userId).size() == 1) { + if (auto* prevState = std::get_if(&state.prevState)) { + renderStorageView(prevState->storageId, userId, chatId, bot, api); + std::string storageName = api.getStoragesApi().get(userId, prevState->storageId).name; + stateManager.put(StorageView{prevState->storageId, std::move(storageName)}); + } else if (auto* prevState = std::get_if(&state.prevState)) { + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); + stateManager.put(prevState->first); + } else if (auto* prevState = std::get_if(&state.prevState)) { + if (auto* prevPrevState = std::get_if(&prevState->prevState)) { renderMainMenu(true, std::nullopt, userId, chatId, bot, api); - stateManager.put(MainMenu{}); - } else { - auto newState = StoragesSelection{.selectedStorages = std::move(state.selectedStorages)}; - renderStorageSelection(newState, userId, chatId, bot, api); - stateManager.put(std::move(newState)); + stateManager.put(auto{*prevPrevState}); } + throw std::runtime_error{"Unreachable path reached"}; } bot.answerCallbackQuery(cq.id); return; @@ -53,14 +59,13 @@ void handleSuggestedRecipesListCQ( auto recipeId = utils::parseSafe(data.substr(sizeof("recipe_") - 1)); if (!recipeId) return; - auto inStorage = utils::inStoragesAvailability(state.selectedStorages, *recipeId, userId, api); + + std::vector inStorage = + utils::inStoragesAvailability(state.getStorageIds(), *recipeId, userId, api); + renderCookingPlanning(inStorage, *recipeId, userId, chatId, bot, api); stateManager.put(CookingPlanning{ - .prevState = std::move(state), - .addedStorages = {}, - .availability = inStorage, - .recipeId = *recipeId, - }); + .prevState = std::move(state), .addedStorages = {}, .availability = inStorage, .recipeId = *recipeId}); return; } @@ -69,7 +74,7 @@ void handleSuggestedRecipesListCQ( state.pageNo--; else if (data == "page_right") state.pageNo++; - renderRecipesSuggestion(state.selectedStorages, state.pageNo, userId, chatId, bot, api); + renderRecipesSuggestion(state.getStorageIds(), state.pageNo, userId, chatId, bot, api); return; } } diff --git a/src/handlers/storage/delete.cpp b/src/handlers/storage/delete.cpp index 3440bc0e..9e6dfc71 100644 --- a/src/handlers/storage/delete.cpp +++ b/src/handlers/storage/delete.cpp @@ -6,6 +6,7 @@ #include "render/storage/view.hpp" #include "render/storages_list/view.hpp" #include "states.hpp" +#include namespace cookcookhnya::handlers::storage { @@ -15,14 +16,19 @@ using namespace render::storage; void handleStorageDeletionCQ( StorageDeletion& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::StorageApiRef storageApi) { bot.answerCallbackQuery(cq.id); + auto userId = cq.from->id; + auto chatId = cq.message->chat->id; + if (cq.data == "confirm") { - storageApi.delete_(cq.from->id, state.storageId); - renderStorageList(true, cq.from->id, cq.message->chat->id, bot, storageApi); + storageApi.delete_(userId, state.storageId); + renderStorageList(true, userId, chatId, bot, storageApi); stateManager.put(StorageList{}); } + if (cq.data == "back") { - renderStorageView(state.storageId, cq.from->id, cq.message->chat->id, bot, storageApi); - stateManager.put(StorageView{state.storageId}); + renderStorageView(state.storageId, userId, chatId, bot, storageApi); + std::string storageName = storageApi.get(userId, state.storageId).name; + stateManager.put(StorageView{state.storageId, std::move(storageName)}); } }; diff --git a/src/handlers/storage/ingredients/view.cpp b/src/handlers/storage/ingredients/view.cpp index e72e3575..1efd88ac 100644 --- a/src/handlers/storage/ingredients/view.cpp +++ b/src/handlers/storage/ingredients/view.cpp @@ -74,7 +74,8 @@ void handleStorageIngredientsListCQ( if (cq.data == "back") { renderStorageView(state.storageId, userId, chatId, bot, api); - stateManager.put(StorageView{state.storageId}); + std::string storageName = api.getStoragesApi().get(userId, state.storageId).name; + stateManager.put(StorageView{state.storageId, std::move(storageName)}); return; } diff --git a/src/handlers/storage/members/view.cpp b/src/handlers/storage/members/view.cpp index ffa8156e..76e61803 100644 --- a/src/handlers/storage/members/view.cpp +++ b/src/handlers/storage/members/view.cpp @@ -5,6 +5,7 @@ #include "render/storage/members/add.hpp" #include "render/storage/members/delete.hpp" #include "render/storage/view.hpp" +#include namespace cookcookhnya::handlers::storage::members { @@ -24,7 +25,8 @@ void handleStorageMemberViewCQ( stateManager.put(StorageMemberDeletion{state.storageId}); } else if (cq.data == "back") { renderStorageView(state.storageId, userId, chatId, bot, storageApi); - stateManager.put(StorageView{state.storageId}); + std::string storageName = storageApi.get(userId, state.storageId).name; + stateManager.put(StorageView{state.storageId, std::move(storageName)}); } }; diff --git a/src/handlers/storage/view.cpp b/src/handlers/storage/view.cpp index d3e88451..ece4ea12 100644 --- a/src/handlers/storage/view.cpp +++ b/src/handlers/storage/view.cpp @@ -51,11 +51,8 @@ void handleStorageViewCQ( } if (cq.data == "wanna_eat") { - auto storageDetails = api.getStoragesApi().get(userId, state.storageId); - const StorageSummary storage = {.id = state.storageId, .name = storageDetails.name}; - std::vector storages = {storage}; - renderRecipesSuggestion(storages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{.selectedStorages = storages, .pageNo = 0, .fromStorage = true}); + renderRecipesSuggestion({state.storageId}, 0, userId, chatId, bot, api); + stateManager.put(SuggestedRecipesList{.prevState = state, .pageNo = 0}); return; } diff --git a/src/handlers/storages_list/view.cpp b/src/handlers/storages_list/view.cpp index 1abfc1fb..190c7618 100644 --- a/src/handlers/storages_list/view.cpp +++ b/src/handlers/storages_list/view.cpp @@ -9,6 +9,7 @@ #include "utils/parsing.hpp" #include +#include namespace cookcookhnya::handlers::storages_list { @@ -38,7 +39,8 @@ void handleStorageListCQ( auto storageId = utils::parseSafe(cq.data); if (storageId) { renderStorageView(*storageId, cq.from->id, chatId, bot, api); - stateManager.put(StorageView{*storageId}); + std::string storageName = api.getStoragesApi().get(userId, *storageId).name; + stateManager.put(StorageView{*storageId, std::move(storageName)}); } } diff --git a/src/handlers/storages_selection/view.cpp b/src/handlers/storages_selection/view.cpp index 271cbba6..d89360a0 100644 --- a/src/handlers/storages_selection/view.cpp +++ b/src/handlers/storages_selection/view.cpp @@ -4,14 +4,19 @@ #include "backend/id_types.hpp" #include "backend/models/storage.hpp" #include "handlers/common.hpp" +#include "render/cooking_planning/view.hpp" #include "render/main_menu/view.hpp" +#include "render/recipe/view.hpp" #include "render/recipes_suggestions/view.hpp" #include "render/storages_selection/view.hpp" +#include "utils/ingredients_availability.hpp" #include "utils/parsing.hpp" #include #include +#include #include +#include #include namespace cookcookhnya::handlers::storages_selection { @@ -20,6 +25,10 @@ using api::models::storage::StorageSummary; using namespace render::recipes_suggestions; using namespace render::select_storages; using namespace render::main_menu; +using namespace render::cooking_planning; +using namespace render::recipe; +using namespace std::views; +using std::ranges::to; void handleStoragesSelectionCQ( StoragesSelection& state, CallbackQueryRef cq, BotRef bot, SMRef stateManager, api::ApiClientRef api) { @@ -28,15 +37,33 @@ void handleStoragesSelectionCQ( auto userId = cq.from->id; if (cq.data == "confirm") { - renderRecipesSuggestion(state.selectedStorages, 0, userId, chatId, bot, api); - stateManager.put(SuggestedRecipesList{ - .selectedStorages = std::move(state.selectedStorages), .pageNo = 0, .fromStorage = false}); + auto storagesIds = state.selectedStorages | transform(&StorageSummary::id) | to(); + if (auto* prevState = std::get_if(&state.prevState)) { + const api::RecipeId recipeId = prevState->recipeId; + auto availability = utils::inStoragesAvailability(storagesIds, recipeId, userId, api); + + renderCookingPlanning(availability, recipeId, userId, chatId, bot, api); + stateManager.put( + CookingPlanning{.prevState = CookingPlanning::FromRecipeViewData{std::move(*prevState), + std::move(state.selectedStorages)}, + .addedStorages = {}, + .availability = std::move(availability), + .recipeId = recipeId}); + } else if (std::holds_alternative(state.prevState)) { + renderRecipesSuggestion(storagesIds, 0, userId, chatId, bot, api); + stateManager.put(SuggestedRecipesList{.prevState = std::move(state), .pageNo = 0}); + } return; } if (cq.data == "cancel") { - renderMainMenu(true, std::nullopt, userId, chatId, bot, api); - stateManager.put(MainMenu{}); + if (auto* prevState = std::get_if(&state.prevState)) { + renderMainMenu(true, std::nullopt, userId, chatId, bot, api); + stateManager.put(auto{*prevState}); + } else if (auto* prevState = std::get_if(&state.prevState)) { + renderRecipeView(prevState->recipe, prevState->recipeId, userId, chatId, bot); + stateManager.put(auto{std::move(*prevState)}); + } return; } diff --git a/src/render/cooking_planning/view.cpp b/src/render/cooking_planning/view.cpp index 0118c4d3..87ac6bd1 100644 --- a/src/render/cooking_planning/view.cpp +++ b/src/render/cooking_planning/view.cpp @@ -64,7 +64,7 @@ CookingInfo recipeView(const std::vector& inStoragesAvai } // namespace -void renderCookingPlanning(std::vector& inStoragesAvailability, +void renderCookingPlanning(const std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, diff --git a/src/render/cooking_planning/view.hpp b/src/render/cooking_planning/view.hpp index 8c57424b..1c08b3b4 100644 --- a/src/render/cooking_planning/view.hpp +++ b/src/render/cooking_planning/view.hpp @@ -9,7 +9,7 @@ namespace cookcookhnya::render::cooking_planning { -void renderCookingPlanning(std::vector& inStoragesAvailability, +void renderCookingPlanning(const std::vector& inStoragesAvailability, api::RecipeId recipeId, UserId userId, ChatId chatId, diff --git a/src/render/recipe/view.cpp b/src/render/recipe/view.cpp index fc5992ff..4783a329 100644 --- a/src/render/recipe/view.cpp +++ b/src/render/recipe/view.cpp @@ -27,7 +27,7 @@ void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, if (recipe.creator) text += utils::u8format("\n{}: {}\n", u8"👤 Автор", recipe.creator->fullName); - InlineKeyboardBuilder keyboard{2}; // share, back + InlineKeyboardBuilder keyboard{3}; // cook, share, back auto shareButton = std::make_shared(); shareButton->text = utils::utf8str(u8"📤 Поделиться"); @@ -40,7 +40,8 @@ void renderRecipeView(const api::models::recipe::RecipeDetails& recipe, url.params().append({"text", shareText}); shareButton->url = url.buffer(); - keyboard << std::move(shareButton) << NewRow{} << makeCallbackButton(u8"↩️ Назад", "back"); + keyboard << makeCallbackButton(u8"🧑‍🍳 Хочу приготовить", "cook") << NewRow{} + << std::move(shareButton) << NewRow{} << makeCallbackButton(u8"↩️ Назад", "back"); if (auto mMessageId = message::getMessageId(userId)) bot.editMessageText(text, chatId, *mMessageId, std::move(keyboard), "Markdown"); diff --git a/src/render/recipes_search/view.cpp b/src/render/recipes_search/view.cpp index 7dfce0da..8a9a3fbc 100644 --- a/src/render/recipes_search/view.cpp +++ b/src/render/recipes_search/view.cpp @@ -7,6 +7,7 @@ #include "states.hpp" #include "utils/utils.hpp" +#include #include namespace cookcookhnya::render::recipes_search { @@ -30,7 +31,8 @@ void renderRecipesSearch(const states::helpers::Pagination& pagination, auto makeRecipeButton = [](const RecipeSummary& r) { return makeCallbackButton(utils::utf8str(u8"🔖 ") + r.name, "recipe_" + utils::to_string(r.id)); }; - keyboard << constructPagination(pagination.pageNo, page.size(), pagination.totalItems, page, makeRecipeButton); + const std::size_t pageSize = 5; + keyboard << constructPagination(pagination.pageNo, pageSize, pagination.totalItems, page, makeRecipeButton); keyboard << makeCallbackButton(u8"↩️ Назад", "back"); diff --git a/src/render/recipes_suggestions/view.cpp b/src/render/recipes_suggestions/view.cpp index 901e5d6f..576ad5eb 100644 --- a/src/render/recipes_suggestions/view.cpp +++ b/src/render/recipes_suggestions/view.cpp @@ -1,8 +1,8 @@ #include "view.hpp" #include "backend/api/recipes.hpp" +#include "backend/id_types.hpp" #include "backend/models/recipe.hpp" -#include "backend/models/storage.hpp" #include "message_tracker.hpp" #include "render/common.hpp" #include "render/pagination.hpp" @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -24,7 +23,6 @@ namespace cookcookhnya::render::recipes_suggestions { using namespace api::models::storage; using namespace api::models::recipe; using namespace std::views; -using std::ranges::to; namespace { @@ -42,7 +40,7 @@ constructKeyboard(std::size_t pageNo, std::size_t pageSize, RecipesListWithIngre } // namespace -void renderRecipesSuggestion(std::vector& storages, +void renderRecipesSuggestion(const std::vector& storages, std::size_t pageNo, UserId userId, ChatId chatId, @@ -50,9 +48,8 @@ void renderRecipesSuggestion(std::vector& storages, api::RecipesApiRef recipesApi) { const std::size_t numOfRecipesOnPage = 5; - auto storagesIds = storages | transform(&StorageSummary::id) | to(); auto recipesList = - recipesApi.getSuggestedRecipes(userId, storagesIds, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); + recipesApi.getSuggestedRecipes(userId, storages, numOfRecipesOnPage, pageNo * numOfRecipesOnPage); const std::string text = utils::utf8str(recipesList.found > 0 ? u8"🔪 Рецепты подобранные специально для вас" diff --git a/src/render/recipes_suggestions/view.hpp b/src/render/recipes_suggestions/view.hpp index 34491c7d..6c91d193 100644 --- a/src/render/recipes_suggestions/view.hpp +++ b/src/render/recipes_suggestions/view.hpp @@ -1,7 +1,7 @@ #pragma once #include "backend/api/recipes.hpp" -#include "backend/models/storage.hpp" +#include "backend/id_types.hpp" #include "render/common.hpp" #include @@ -11,7 +11,7 @@ namespace cookcookhnya::render::recipes_suggestions { using namespace tg_types; -void renderRecipesSuggestion(std::vector& storages, +void renderRecipesSuggestion(const std::vector& storages, std::size_t pageNo, UserId userId, ChatId chatId, diff --git a/src/states.hpp b/src/states.hpp index cdc1db2a..b7a75ffa 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -8,11 +8,14 @@ #include "utils/fast_sorted_db.hpp" #include "utils/utils.hpp" +#include +#include #include #include #include #include +#include #include #include #include @@ -48,6 +51,18 @@ struct SelectableIngredient : api::models::ingredient::Ingredient { struct MainMenu {}; +struct RecipesSearch { + std::string query; + helpers::Pagination pagination; + std::vector page; +}; + +struct RecipeView { + std::optional prevState; + api::models::recipe::RecipeDetails recipe; + api::RecipeId recipeId; +}; + struct PersonalAccountMenu {}; struct TotalPublicationHistory { std::size_t pageNo; @@ -61,7 +76,6 @@ struct CustomIngredientConfirmation { // All optionals are for "back" from this menu, so this state won't erase all info std::optional recipeFrom; std::optional> ingredients; - std::optional storageFrom; explicit CustomIngredientConfirmation( @@ -77,7 +91,9 @@ struct CustomIngredientPublish {}; struct StorageList {}; struct StorageCreationEnterName {}; -struct StorageView : helpers::StorageIdMixin {}; +struct StorageView : helpers::StorageIdMixin { + std::string name; +}; struct StorageDeletion : helpers::StorageIdMixin {}; struct StorageMemberView : helpers::StorageIdMixin {}; struct StorageMemberAddition : helpers::StorageIdMixin {}; @@ -105,26 +121,71 @@ struct StorageIngredientsDeletion : helpers::StorageIdMixin { }; struct StoragesSelection { + std::variant prevState; std::vector selectedStorages; }; struct SuggestedRecipesList { - std::vector selectedStorages; + private: + using StorageSummary = api::models::storage::StorageSummary; + + public: + using FromMainMenuData = std::pair; + std::variant prevState; std::size_t pageNo; - bool fromStorage; + + [[nodiscard]] std::vector getStorageIds() const { + if (const auto* prevState_ = std::get_if(&prevState)) + return {prevState_->storageId}; + + if (const auto* prevState_ = std::get_if(&prevState)) + return {prevState_->second.id}; + + if (const auto* prevState_ = std::get_if(&prevState)) + return prevState_->selectedStorages | std::views::transform(&StorageSummary::id) | + std::ranges::to(); + return {}; + } + + [[nodiscard]] std::variant>, std::vector> + getStorages() const { + if (const auto* prevState_ = std::get_if(&prevState)) + return std::vector{StorageSummary{.id = prevState_->storageId, .name = prevState_->name}}; + if (const auto* prevState_ = std::get_if(&prevState)) + return std::vector{prevState_->second}; + if (const auto* prevState_ = std::get_if(&prevState)) + return std::ref(prevState_->selectedStorages); + return std::vector{}; + } }; struct CookingPlanning { + private: + using StorageSummary = api::models::storage::StorageSummary; + + public: enum struct AvailabilityType : std::uint8_t { NOT_AVAILABLE, AVAILABLE, OTHER_STORAGES }; struct IngredientAvailability { cookcookhnya::api::models::recipe::IngredientInRecipe ingredient; AvailabilityType available = AvailabilityType::NOT_AVAILABLE; - std::vector storages; + std::vector storages; }; - SuggestedRecipesList prevState; - std::vector addedStorages; + using FromRecipeViewData = std::pair>; + + std::variant prevState; + std::vector addedStorages; std::vector availability; api::RecipeId recipeId; + + [[nodiscard]] std::variant>, std::vector> + getStorages() const { + if (const auto* prevState_ = std::get_if(&prevState)) + return prevState_->getStorages(); + if (const auto* prevState_ = std::get_if(&prevState)) { + return std::ref(prevState_->second); + } + return std::vector{}; + } }; struct CookingPlanningStorageAddition { @@ -198,17 +259,6 @@ struct CustomRecipePublicationHistory { std::string errorReport; }; -struct RecipesSearch { - std::string query; - helpers::Pagination pagination; - std::vector page; -}; - -struct RecipeView { - std::optional prevState; - api::models::recipe::RecipeDetails recipe; -}; - struct CookingIngredientsSpending { // NOLINT(*member-init) CookingPlanning prevState; std::optional storageId; diff --git a/src/utils/ingredients_availability.cpp b/src/utils/ingredients_availability.cpp index dcd284a4..f677f024 100644 --- a/src/utils/ingredients_availability.cpp +++ b/src/utils/ingredients_availability.cpp @@ -20,17 +20,16 @@ using namespace api::models::storage; using namespace tg_types; using IngredientAvailability = states::CookingPlanning::IngredientAvailability; using AvailabilityType = states::CookingPlanning::AvailabilityType; -using namespace std::views; -using namespace std::ranges; +using std::ranges::to; -std::vector inStoragesAvailability(std::vector& selectedStorages, +std::vector inStoragesAvailability(const std::vector& selectedStorages, RecipeId recipeId, UserId userId, const api::ApiClient& api) { auto allStorages = api.getStoragesApi().getStoragesList(userId); auto recipe = api.getRecipesApi().getSuggested(userId, recipeId); - auto selectedStoragesSet = selectedStorages | views::transform(&StorageSummary::id) | to(); + auto selectedStoragesSet = selectedStorages | to(); std::unordered_map allStoragesMap; for (const auto& storage : allStorages) { diff --git a/src/utils/ingredients_availability.hpp b/src/utils/ingredients_availability.hpp index 0a8cdfb0..5b800a50 100644 --- a/src/utils/ingredients_availability.hpp +++ b/src/utils/ingredients_availability.hpp @@ -11,7 +11,7 @@ namespace cookcookhnya::utils { std::vector -inStoragesAvailability(std::vector& selectedStorages, +inStoragesAvailability(const std::vector& selectedStorages, api::RecipeId recipeId, tg_types::UserId userId, const api::ApiClient& api);