diff --git a/telegram_bot_project/bot/commands.py b/telegram_bot_project/bot/commands.py index 86c7be4..316ce5a 100644 --- a/telegram_bot_project/bot/commands.py +++ b/telegram_bot_project/bot/commands.py @@ -1,8 +1,12 @@ from typing import Any from aiogram import types +from aiogram.fsm.context import FSMContext + from messages import MESSAGES from service.user import UserService from bot.buttons import get_language_keyboard, menu_reply_keyboard +from states import DialogStates + # Start Command Handler async def start_command(message: types.Message): @@ -36,7 +40,7 @@ async def language_command(message: types.Message): user_find = await UserService.get_user_by_id(user_id) language: str = await UserService.get_user_language(user_id) if not user_find: - await message.answer(MESSAGES[language]['AUTHORIZATION_PROBLEM']) + await message.answer(MESSAGES['ENGLISH']['AUTHORIZATION_PROBLEM']) else: keyboard = get_language_keyboard() await message.answer(MESSAGES[language]['LANGUAGE_ASK'], reply_markup=keyboard) @@ -47,10 +51,20 @@ async def menu_command(message: types.Message): user_find: Any = await UserService.get_user_by_id(user_id) language: str = await UserService.get_user_language(user_id) if not user_find: - await message.answer(MESSAGES[language]['AUTHORIZATION_PROBLEM']) + await message.answer(MESSAGES['ENGLISH']['AUTHORIZATION_PROBLEM']) else: await message.answer(MESSAGES[language]['MENU_MSG'], reply_markup=menu_reply_keyboard()) # Idea Command Handler -async def idea_command(message: types.Message): - pass +async def idea_command(message: types.Message, state: FSMContext): + user_id: int = message.from_user.id + user_find: Any = await UserService.get_user_by_id(user_id) + language: str = await UserService.get_user_language(user_id) + + print(f"--[INFO] - User with id: {user_id} - opened /idea.") + if not user_find: + await message.answer(MESSAGES['ENGLISH']['AUTHORIZATION_PROBLEM']) + else: + await message.answer(MESSAGES[language]['IDEA_RESPONSE']) + await state.set_state(DialogStates.waiting_for_idea) + diff --git a/telegram_bot_project/bot/handlers.py b/telegram_bot_project/bot/handlers.py index e69de29..6e62b4c 100644 --- a/telegram_bot_project/bot/handlers.py +++ b/telegram_bot_project/bot/handlers.py @@ -0,0 +1,29 @@ +from aiogram.fsm.context import FSMContext +from aiogram.types import Message + +from bot.buttons import 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: + user_id = message.from_user.id + user_name = message.from_user.username or "unknown" + + user_find = await UserService.get_user_by_id(user_id) + if not user_find or not user_find.get("id"): + await message.answer(MESSAGES["ENGLISH"]['AUTHORIZATION_PROBLEM']) + return + + language = await UserService.get_user_language(user_id) + idea = message.text + + print(f"--[INFO] User with id: {user_id} - provide idea.") + try: + await IdeaService.create_user_idea(user_id, idea) + print(f"--[INFO] - User {user_id} ({user_name}) saved idea: {idea}") + await message.answer(MESSAGES[language]['IDEA_SAVED'], reply_markup=menu_reply_keyboard()) + await state.clear() + except Exception as e: + print(f"[ERROR] Saving idea failed: {e}") + await message.answer(MESSAGES[language].get('ERROR_SAVING_IDEA', 'Ошибка при сохранении идеи. Попробуйте позже.')) diff --git a/telegram_bot_project/config.py b/telegram_bot_project/config.py index 2112671..05c018b 100644 --- a/telegram_bot_project/config.py +++ b/telegram_bot_project/config.py @@ -2,6 +2,7 @@ from dotenv import load_dotenv from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker +from contextlib import asynccontextmanager load_dotenv() @@ -19,6 +20,7 @@ def get_token() -> str: return TOKEN +@asynccontextmanager async def get_session() -> AsyncSession: async with async_session() as session: yield session diff --git a/telegram_bot_project/main.py b/telegram_bot_project/main.py index cd9fbe7..9d106bb 100644 --- a/telegram_bot_project/main.py +++ b/telegram_bot_project/main.py @@ -1,13 +1,17 @@ import asyncio -from aiogram import Dispatcher, Bot +from aiogram import Dispatcher, Bot, F from aiogram.filters import Command +from aiogram.fsm.context import FSMContext from aiogram.fsm.storage.memory import MemoryStorage from aiogram.types import Message, CallbackQuery from config import TOKEN -from bot.commands import start_command, help_command, menu_command, language_command +from bot.commands import start_command, help_command, menu_command, language_command, idea_command +from bot.handlers import process_idea_save from bot.callbacks import start_callback_language +from states import DialogStates -dp = Dispatcher(storage=MemoryStorage()) +storage: MemoryStorage = MemoryStorage() +dp = Dispatcher(storage=storage) # Command Handlers @dp.message(Command("start")) @@ -26,10 +30,21 @@ async def menu(message: Message): async def language(message: Message): await language_command(message) -@dp.callback_query(lambda c: c.data in ["lang_ua", "lang_en"]) +@dp.message(Command("idea")) +async def idea(message: Message, state: FSMContext): + await idea_command(message, state) + +@dp.callback_query(F.data.in_({"lang_ua", "lang_en"})) async def callback_language(callback_query: CallbackQuery): await start_callback_language(callback_query) +@dp.message() +async def process_idea_fallback(message: Message, state: FSMContext): + current_state = await state.get_state() + print(f"[DEBUG] Current state: {current_state}") + if current_state == DialogStates.waiting_for_idea.state: + await process_idea_save(message, state) + # Main Function async def main(): bot = Bot(token=TOKEN) diff --git a/telegram_bot_project/messages.py b/telegram_bot_project/messages.py index b444b2b..e189490 100644 --- a/telegram_bot_project/messages.py +++ b/telegram_bot_project/messages.py @@ -12,6 +12,7 @@ "SETTINGS_RESPONSE": "🔧 Відкриваю налаштування...", "MYDAY_RESPONSE": "📅 Ось ваш план на сьогодні...", "IDEA_RESPONSE": "💡 Поділіться своєю ідеєю, я все запишу!", + "IDEA_SAVED": "💡 Ідея успішно збережена.", "ADD_TASK_RESPONSE": "📝 Створюємо нову задачу...", "LANGUAGE_ASK": ( "🌐 **Оберіть мову інтерфейсу:**\n" @@ -37,6 +38,7 @@ "SETTINGS_RESPONSE": "🔧 Opening your settings...", "MYDAY_RESPONSE": "📅 Here’s your plan for today...", "IDEA_RESPONSE": "💡 Tell me your idea, I’ll save it for you!", + "IDEA_SAVED": "💡 Idea saved successfully.", "ADD_TASK_RESPONSE": "📝 Creating a new task...", "LANGUAGE_ASK": ( "🌐 **Choose your language:**\n" diff --git a/telegram_bot_project/service/idea.py b/telegram_bot_project/service/idea.py index 0c4fcb8..51d0932 100644 --- a/telegram_bot_project/service/idea.py +++ b/telegram_bot_project/service/idea.py @@ -1,6 +1,63 @@ from sqlalchemy import text from config import get_session -# Must implement later :) class IdeaService: - pass \ No newline at end of file + @staticmethod + async def create_user_idea(user_id: int, idea_name: str) -> int: + async with get_session() as session: + query = text(""" + INSERT INTO ideas (user_id, idea_name) + VALUES (:user_id, :idea_name) + RETURNING id + """) + result = await session.execute(query, { + "user_id": user_id, + "idea_name": idea_name + }) + await session.commit() + return result.scalar() + + @staticmethod + async def update_user_idea(idea_id: int, new_name: str) -> None: + async with get_session() as session: + query = text(""" + UPDATE ideas + SET idea_name = :new_name + WHERE id = :idea_id + """) + await session.execute(query, { + "idea_id": idea_id, + "new_name": new_name + }) + await session.commit() + + @staticmethod + async def delete_user_idea(idea_id: int) -> None: + async with get_session() as session: + query = text(""" + DELETE FROM ideas + WHERE id = :idea_id + """) + await session.execute(query, {"idea_id": idea_id}) + await session.commit() + + @staticmethod + async def get_all_ideas_by_user_id(user_id: int) -> list[dict]: + async with get_session() as session: + query = text(""" + SELECT id, idea_name, creation_date + FROM ideas + WHERE user_id = :user_id + ORDER BY creation_date DESC + """) + result = await session.execute(query, {"user_id": user_id}) + rows = result.fetchall() + ideas = [ + { + "id": row.id, + "idea_name": row.idea_name, + "creation_date": row.creation_date + } + for row in rows + ] + return ideas diff --git a/telegram_bot_project/service/task.py b/telegram_bot_project/service/task.py index c432ec5..39f541e 100644 --- a/telegram_bot_project/service/task.py +++ b/telegram_bot_project/service/task.py @@ -6,7 +6,7 @@ class TaskService: @staticmethod async def create_task(user_id: int, task_name: str, task_status: bool = False, start_time: Optional[datetime] = None) -> int: - async for session in get_session(): + async with get_session() as session: result: Any = await session.execute( text( """ @@ -25,11 +25,10 @@ async def create_task(user_id: int, task_name: str, task_status: bool = False, s task_id: int = result.scalar_one() await session.commit() return task_id - return None @staticmethod async def get_task_by_id(task_id: int) -> Optional[dict]: - async for session in get_session(): + async with get_session() as session: result: Any = await session.execute( text( """ @@ -40,7 +39,7 @@ async def get_task_by_id(task_id: int) -> Optional[dict]: ), {"task_id": task_id} ) - task: int = result.fetchone() + task = result.fetchone() if task: return { "id": task.id, @@ -51,11 +50,10 @@ async def get_task_by_id(task_id: int) -> Optional[dict]: "creation_date": task.creation_date } return None - return None @staticmethod async def get_user_tasks(user_id: int) -> List[dict]: - async for session in get_session(): + async with get_session() as session: result: Any = await session.execute( text( """ @@ -67,6 +65,7 @@ async def get_user_tasks(user_id: int) -> List[dict]: ), {"user_id": user_id} ) + tasks = result.fetchall() return [ { "id": task.id, @@ -76,15 +75,14 @@ async def get_user_tasks(user_id: int) -> List[dict]: "start_time": task.start_time, "creation_date": task.creation_date } - for task in result.fetchall() + for task in tasks ] - return None @staticmethod async def update_task(task_id: int, task_name: Optional[str] = None, - status: Optional[bool] = None, - start_time: Optional[datetime] = None) -> bool: - async for session in get_session(): + status: Optional[bool] = None, + start_time: Optional[datetime] = None) -> bool: + async with get_session() as session: exists: Any = await session.execute( text("SELECT 1 FROM tasks WHERE id = :task_id"), {"task_id": task_id} @@ -92,7 +90,7 @@ async def update_task(task_id: int, task_name: Optional[str] = None, if not exists.scalar(): return False - update_fields: Any = {} + update_fields: dict = {} if task_name is not None: update_fields["task_name"] = task_name if status is not None: @@ -101,7 +99,7 @@ async def update_task(task_id: int, task_name: Optional[str] = None, update_fields["start_time"] = start_time if update_fields: - query: Any = text( + query = text( f""" UPDATE tasks SET {', '.join(f'{k} = :{k}' for k in update_fields.keys())} @@ -112,11 +110,10 @@ async def update_task(task_id: int, task_name: Optional[str] = None, await session.execute(query, update_fields) await session.commit() return True - return None @staticmethod async def delete_task(task_id: int) -> bool: - async for session in get_session(): + async with get_session() as session: result: Any = await session.execute( text( """ @@ -127,14 +124,13 @@ async def delete_task(task_id: int) -> bool: ), {"task_id": task_id} ) - deleted: Any = result.scalar_one_or_none() + deleted = result.scalar_one_or_none() await session.commit() return deleted is not None - return None @staticmethod async def toggle_task_status(task_id: int) -> bool: - async for session in get_session(): + async with get_session() as session: result: Any = await session.execute( text( """ @@ -146,7 +142,6 @@ async def toggle_task_status(task_id: int) -> bool: ), {"task_id": task_id} ) - updated: Any = result.scalar_one_or_none() + updated = result.scalar_one_or_none() await session.commit() return updated is not None - return None \ No newline at end of file diff --git a/telegram_bot_project/service/user.py b/telegram_bot_project/service/user.py index c4b06a6..b6bb491 100644 --- a/telegram_bot_project/service/user.py +++ b/telegram_bot_project/service/user.py @@ -4,7 +4,7 @@ class UserService: @staticmethod async def create_user(user_id: int, user_name: str, language: str = "ENGLISH") -> int: - async for session in get_session(): + async with get_session() as session: result = await session.execute( text(""" INSERT INTO users (id, user_name, language) @@ -16,22 +16,20 @@ async def create_user(user_id: int, user_name: str, language: str = "ENGLISH") - await session.commit() inserted_id = result.scalar_one() return inserted_id - return None @staticmethod async def get_user_by_id(user_id: int): - async for session in get_session(): + async with get_session() as session: result = await session.execute( text("SELECT * FROM users WHERE id = :id"), {"id": user_id} ) row = result.first() return dict(row._mapping) if row else None - return None @staticmethod async def update_user_language(user_id: int, new_language: str) -> bool: - async for session in get_session(): + async with get_session() as session: result = await session.execute( text(""" UPDATE users @@ -42,14 +40,12 @@ async def update_user_language(user_id: int, new_language: str) -> bool: ) await session.commit() return result.rowcount == 1 - return None @staticmethod async def get_user_language(user_id: int) -> str: - async for session in get_session(): + async with get_session() as session: result = await session.execute( text("SELECT language FROM users WHERE id = :id"), {"id": user_id} ) return result.scalar_one_or_none() - return None diff --git a/telegram_bot_project/states.py b/telegram_bot_project/states.py new file mode 100644 index 0000000..ec2dfea --- /dev/null +++ b/telegram_bot_project/states.py @@ -0,0 +1,4 @@ +from aiogram.fsm.state import StatesGroup, State + +class DialogStates(StatesGroup): + waiting_for_idea = State()