diff --git a/sql/schema.sql b/sql/schema.sql index dc4e600..a7a9343 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -25,7 +25,7 @@ CREATE TABLE tasks ( CREATE TABLE ideas ( id SERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - idea_name VARCHAR(255) NOT NULL, + idea_name VARCHAR(255) NOT NULL UNIQUE, creation_date TIMESTAMP NOT NULL DEFAULT NOW() ); diff --git a/telegram_bot_project/bot/buttons.py b/telegram_bot_project/bot/buttons.py index c1cb1b1..83dfb00 100644 --- a/telegram_bot_project/bot/buttons.py +++ b/telegram_bot_project/bot/buttons.py @@ -17,17 +17,17 @@ def get_language_keyboard() -> InlineKeyboardMarkup: def menu_reply_keyboard() -> ReplyKeyboardMarkup: - reply_keyboard = ReplyKeyboardMarkup(keyboard=[], resize_keyboard=True, row_width=2) + menu_reply_keyboard = ReplyKeyboardMarkup(keyboard=[], resize_keyboard=True, row_width=2) button_settings = KeyboardButton(text=BUTTON_SETTINGS) button_idea = KeyboardButton(text=BUTTON_IDEA) button_day = KeyboardButton(text=BUTTON_MYDAY) button_task = KeyboardButton(text=BUTTON_ADD_TASK) - reply_keyboard.keyboard.append([button_settings, button_idea]) - reply_keyboard.keyboard.append([button_day, button_task]) + menu_reply_keyboard.keyboard.append([button_settings, button_idea]) + menu_reply_keyboard.keyboard.append([button_day, button_task]) - return reply_keyboard + return menu_reply_keyboard def get_idea_conf_keyboard() -> InlineKeyboardMarkup: inline_markup = InlineKeyboardMarkup(inline_keyboard=[], row_width=2) @@ -36,4 +36,17 @@ def get_idea_conf_keyboard() -> InlineKeyboardMarkup: save_button: InlineKeyboardButton = InlineKeyboardButton(text=SAVE_BUTTON, callback_data="save_idea") inline_markup.inline_keyboard.append([delete_button, save_button]) - return inline_markup \ No newline at end of file + return inline_markup + +def idea_reply_keyboard() -> ReplyKeyboardMarkup: + idea_reply_keyboard = ReplyKeyboardMarkup(keyboard=[], resize_keyboard=True, row_width=2) + + button_menu = KeyboardButton(text=MENU_BUTTON) + button_delete = KeyboardButton(text=DEL_BUTTON) + button_add = KeyboardButton(text=BUTTON_IDEA) + button_all_ideas = KeyboardButton(text=ALL_IDEAS) + + idea_reply_keyboard.keyboard.append([button_delete, button_add]) + idea_reply_keyboard.keyboard.append([button_menu, button_all_ideas]) + + return idea_reply_keyboard \ No newline at end of file diff --git a/telegram_bot_project/bot/callbacks.py b/telegram_bot_project/bot/callbacks.py index 9de9246..64de294 100644 --- a/telegram_bot_project/bot/callbacks.py +++ b/telegram_bot_project/bot/callbacks.py @@ -2,7 +2,7 @@ from aiogram import types from aiogram.fsm.context import FSMContext -from bot.buttons import menu_reply_keyboard +from bot.buttons import menu_reply_keyboard, idea_reply_keyboard from messages import MESSAGES from service.idea import IdeaService from service.user import UserService @@ -53,7 +53,7 @@ async def callback_idea_process(callback_query: types.CallbackQuery, state: FSMC match callback_query.data: case "delete_idea": print(f"--[INFO] - User {user_id} ({user_name}) deleted idea.") - await callback_query.message.answer(MESSAGES[language]["IDEA_DELETE"], reply_markup=menu_reply_keyboard()) + await callback_query.message.answer(MESSAGES[language]["IDEA_DELETE"], reply_markup=idea_reply_keyboard()) await state.clear() case "save_idea": try: @@ -62,16 +62,16 @@ async def callback_idea_process(callback_query: types.CallbackQuery, state: FSMC if not idea: print(f"--[INFO] - User {user_id} ({user_name}) sent empty idea.") - await callback_query.message.answer(MESSAGES[language]["IDEA_PROBLEM"], reply_markup=menu_reply_keyboard()) + await callback_query.message.answer(MESSAGES[language]["IDEA_PROBLEM"], reply_markup=idea_reply_keyboard()) return else: await IdeaService.create_user_idea(user_id, idea) print(f"--[INFO] - User {user_id} ({user_name}) saved idea: {idea}") - await callback_query.message.answer(MESSAGES[language]["IDEA_SAVED"], reply_markup=menu_reply_keyboard()) + await callback_query.message.answer(MESSAGES[language]["IDEA_SAVED"], reply_markup=idea_reply_keyboard()) await state.clear() except Exception as e: await callback_query.message.answer(MESSAGES[language]["IDEA_PROBLEM"]) case _: print(f"--[INFO] - User {user_id} ({user_name}) sent invalid callback: {callback_query.data}") - await callback_query.message.answer(MESSAGES[language]["IDEA_PROBLEM"], reply_markup=menu_reply_keyboard()) + await callback_query.message.answer(MESSAGES[language]["IDEA_PROBLEM"], reply_markup=idea_reply_keyboard()) diff --git a/telegram_bot_project/bot/commands.py b/telegram_bot_project/bot/commands.py index d5e7b48..1f0e2e8 100644 --- a/telegram_bot_project/bot/commands.py +++ b/telegram_bot_project/bot/commands.py @@ -6,7 +6,7 @@ from messages import MESSAGES from service.idea import IdeaService from service.user import UserService -from bot.buttons import get_language_keyboard, menu_reply_keyboard +from bot.buttons import get_language_keyboard, menu_reply_keyboard, idea_reply_keyboard from states import DialogStates # Start Command Handler @@ -88,8 +88,8 @@ async def ideas_command(message: types.Message): dividers: str = "\n" + ("-" * int(len(MESSAGES[language]['IDEAS_SHOW']) * 1.65)) formatted_ideas = MESSAGES[language]['IDEAS_SHOW'] + dividers + "\n" + "\n".join( - f"# {num}. {idea['idea_name']}\n[{format_date(idea['creation_date'])}]" + f"# {num}. {idea['idea_name']}\n\n - 📅 - [{format_date(idea['creation_date'])}]\n" for num, idea in enumerate(ideas, start=1) ) - await message.answer(formatted_ideas, reply_markup=menu_reply_keyboard()) + await message.answer(formatted_ideas, reply_markup=idea_reply_keyboard()) diff --git a/telegram_bot_project/bot/handlers.py b/telegram_bot_project/bot/handlers.py index 111bd45..a856f9b 100644 --- a/telegram_bot_project/bot/handlers.py +++ b/telegram_bot_project/bot/handlers.py @@ -1,8 +1,9 @@ from aiogram.fsm.context import FSMContext -from aiogram.types import Message, InlineKeyboardMarkup +from aiogram.types import Message -from bot.buttons import get_idea_conf_keyboard +from bot.buttons import get_idea_conf_keyboard, menu_reply_keyboard from messages import MESSAGES +from service.idea import IdeaService from service.user import UserService async def process_idea_save(message: Message, state: FSMContext) -> None: @@ -12,16 +13,36 @@ async def process_idea_save(message: Message, state: FSMContext) -> None: await message.answer(MESSAGES["ENGLISH"]['AUTHORIZATION_PROBLEM']) return - language = await UserService.get_user_language(user_id) - idea = message.text + language = await UserService.get_user_language(user_id) or "ENGLISH" + idea = message.text.strip() + + exist = await IdeaService.get_by_idea_name(idea) + if exist: + await message.answer( + MESSAGES[language].get('IDEA_EXIST', "⚠️ This idea already exists."), + reply_markup=menu_reply_keyboard() + ) + return + + print(f"--[INFO] User with id: {user_id} provided idea.") - print(f"--[INFO] User with id: {user_id} - provide idea.") try: await state.update_data(idea=idea) - dividers: str = "\n" + ("-" * int(len(MESSAGES[language]['IDEA_ACTION']) * 1.65)) - keyboard: InlineKeyboardMarkup = get_idea_conf_keyboard() - await message.answer(MESSAGES[language]['IDEA_ACTION'] + dividers + f"\nIDEA: {idea}", reply_markup=keyboard) + divider = "\n" + ("-" * int(len(MESSAGES[language]['IDEA_ACTION']) * 1.65)) + + keyboard = get_idea_conf_keyboard() + + await message.answer( + f"{MESSAGES[language]['IDEA_ACTION']}{divider}\n💡 *Idea:* {idea}", + reply_markup=keyboard + ) + except Exception as e: print(f"[ERROR] Saving idea failed: {e}") - await message.answer(MESSAGES[language].get('ERROR_SAVING_IDEA', 'Ошибка при сохранении идеи. Попробуйте позже.')) + await message.answer( + MESSAGES[language].get( + 'ERROR_SAVING_IDEA', + "⚠️ Error saving the idea. Please try again later." + ) + ) diff --git a/telegram_bot_project/messages.py b/telegram_bot_project/messages.py index b20f6fd..59958c4 100644 --- a/telegram_bot_project/messages.py +++ b/telegram_bot_project/messages.py @@ -18,6 +18,7 @@ "IDEA_DELETE": "🗑️ Ідею було видалено.", "IDEA_PROBLEM": "⚠️ Виникла проблема із збереженням ідеї. Спробуйте ще раз.", "IDEAS_SHOW": "💡 Усі ваші ідеї: ", + "IDEA_EXISTS": "⚠️ Ця ідея вже існує.", "NO_IDEAS": "📝 Ви ще не маєте ідей.", "LANGUAGE_ASK": ( "🌐 **Оберіть мову інтерфейсу:**\n" @@ -49,6 +50,7 @@ "IDEA_DELETE": "🗑️ Idea has been deleted.", "IDEA_PROBLEM": "⚠️ There was an issue saving your idea. Please try again.", "IDEAS_SHOW": "💡 Here are your ideas: ", + "IDEA_EXISTS": "⚠️ This idea already exists", "NO_IDEAS": "📝 You don't have any ideas yet. Be the first to save one!", "LANGUAGE_ASK": ( "🌐 **Please choose your interface language:**\n" @@ -74,3 +76,5 @@ BUTTON_EN_LANG: str = "🇬🇧 English" DEL_BUTTON: str = "🗑️ Delete" SAVE_BUTTON: str = "💾 Save" +MENU_BUTTON: str = "Menu" +ALL_IDEAS: str = "All ideas" \ No newline at end of file diff --git a/telegram_bot_project/service/idea.py b/telegram_bot_project/service/idea.py index 51d0932..6e4a0e2 100644 --- a/telegram_bot_project/service/idea.py +++ b/telegram_bot_project/service/idea.py @@ -1,4 +1,5 @@ from sqlalchemy import text +from typing import Optional from config import get_session class IdeaService: @@ -17,6 +18,22 @@ async def create_user_idea(user_id: int, idea_name: str) -> int: await session.commit() return result.scalar() + @staticmethod + async def get_by_idea_name(idea_name: str) -> Optional[int]: + async with get_session() as session: + query = text( + """ + SELECT id + FROM ideas + WHERE idea_name = :idea_name + """ + ) + + result = await session.execute(query, {"idea_name": idea_name}) + row = result.first() + + return row[0] if row else None + @staticmethod async def update_user_idea(idea_id: int, new_name: str) -> None: async with get_session() as session: