From ae5e156d352b4c5734ed66b8a6bf7072167e14af Mon Sep 17 00:00:00 2001 From: gnatykdm Date: Thu, 24 Jul 2025 16:44:21 +0200 Subject: [PATCH] Settings-Configuration[Routine-Menu-Handler+SQL-Update] --- sitemap(1).xml | 16 ++ sql/schema.sql | 16 ++ telegram_bot_project/.idea/dataSources.xml | 19 ++ .../.idea/dictionaries/project.xml | 8 + .../inspectionProfiles/Project_Default.xml | 13 + .../inspectionProfiles/profiles_settings.xml | 6 + telegram_bot_project/.idea/misc.xml | 7 + telegram_bot_project/.idea/modules.xml | 8 + telegram_bot_project/.idea/sqldialects.xml | 9 + .../.idea/telegram_bot_project.iml | 10 + telegram_bot_project/.idea/vcs.xml | 6 + telegram_bot_project/bot/buttons.py | 12 +- telegram_bot_project/bot/commands.py | 12 + telegram_bot_project/config.py | 5 +- telegram_bot_project/main.py | 8 + telegram_bot_project/messages.py | 7 +- telegram_bot_project/service/routine.py | 228 ++++++++++++++++++ 17 files changed, 386 insertions(+), 4 deletions(-) create mode 100644 sitemap(1).xml create mode 100644 telegram_bot_project/.idea/dataSources.xml create mode 100644 telegram_bot_project/.idea/dictionaries/project.xml create mode 100644 telegram_bot_project/.idea/inspectionProfiles/Project_Default.xml create mode 100644 telegram_bot_project/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 telegram_bot_project/.idea/misc.xml create mode 100644 telegram_bot_project/.idea/modules.xml create mode 100644 telegram_bot_project/.idea/sqldialects.xml create mode 100644 telegram_bot_project/.idea/telegram_bot_project.iml create mode 100644 telegram_bot_project/.idea/vcs.xml create mode 100644 telegram_bot_project/service/routine.py diff --git a/sitemap(1).xml b/sitemap(1).xml new file mode 100644 index 0000000..ee47ae8 --- /dev/null +++ b/sitemap(1).xml @@ -0,0 +1,16 @@ + + + + + + + https://www.wohlfahrt.com.ua/ + 2025-07-22T09:54:26+00:00 + + + + \ No newline at end of file diff --git a/sql/schema.sql b/sql/schema.sql index 4068d04..1b3c13d 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -2,6 +2,7 @@ -- ENUM's -- CREATE TYPE lan AS ENUM ('UKRANIAN', 'ENGLISH'); +CREATE TYPE routine_type AS ENUM ('morning', 'evening'); -- TABLE users CREATE TABLE users ( @@ -13,6 +14,21 @@ CREATE TABLE users ( sleep_time TIME DEFAULT NULL ); +CREATE TABLE routines ( + id SERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + routine_type routine_type NOT NULL, + routine_name VARCHAR(255) NOT NULL +); + +CREATE TABLE routine_steps ( + id SERIAL PRIMARY KEY, + routine_id INT NOT NULL REFERENCES routines(id) ON DELETE CASCADE, + step_order INT NOT NULL, + step_description TEXT NOT NULL, + UNIQUE(routine_id, step_order) +); + -- TABLE tasks CREATE TABLE tasks ( id SERIAL PRIMARY KEY, diff --git a/telegram_bot_project/.idea/dataSources.xml b/telegram_bot_project/.idea/dataSources.xml new file mode 100644 index 0000000..ba43709 --- /dev/null +++ b/telegram_bot_project/.idea/dataSources.xml @@ -0,0 +1,19 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/onetaskbotdb + $ProjectFileDir$ + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/onetaskbotdb + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/dictionaries/project.xml b/telegram_bot_project/.idea/dictionaries/project.xml new file mode 100644 index 0000000..bdb1f2b --- /dev/null +++ b/telegram_bot_project/.idea/dictionaries/project.xml @@ -0,0 +1,8 @@ + + + + taskmenu + ukranian + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/inspectionProfiles/Project_Default.xml b/telegram_bot_project/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..eedd4e4 --- /dev/null +++ b/telegram_bot_project/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/inspectionProfiles/profiles_settings.xml b/telegram_bot_project/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/telegram_bot_project/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/misc.xml b/telegram_bot_project/.idea/misc.xml new file mode 100644 index 0000000..ede822f --- /dev/null +++ b/telegram_bot_project/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/modules.xml b/telegram_bot_project/.idea/modules.xml new file mode 100644 index 0000000..f45318c --- /dev/null +++ b/telegram_bot_project/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/sqldialects.xml b/telegram_bot_project/.idea/sqldialects.xml new file mode 100644 index 0000000..1406f52 --- /dev/null +++ b/telegram_bot_project/.idea/sqldialects.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/telegram_bot_project.iml b/telegram_bot_project/.idea/telegram_bot_project.iml new file mode 100644 index 0000000..7f32c6e --- /dev/null +++ b/telegram_bot_project/.idea/telegram_bot_project.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/telegram_bot_project/.idea/vcs.xml b/telegram_bot_project/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/telegram_bot_project/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/telegram_bot_project/bot/buttons.py b/telegram_bot_project/bot/buttons.py index 5e7d348..b55396e 100644 --- a/telegram_bot_project/bot/buttons.py +++ b/telegram_bot_project/bot/buttons.py @@ -103,4 +103,14 @@ def routine_time_keyboard() -> ReplyKeyboardMarkup: routine_time_keyboard.keyboard.append([wake_time_button, sleep_time_button]) routine_time_keyboard.keyboard.append([my_routine_button, settings_menu_button]) - return routine_time_keyboard \ No newline at end of file + return routine_time_keyboard + +def routine_menu_keyboard() -> InlineKeyboardMarkup: + routine_markup: InlineKeyboardMarkup = InlineKeyboardMarkup(inline_keyboard=[], row_width=2) + + morning_routine_btn: InlineKeyboardButton = InlineKeyboardButton(text=ROUTINE_MORNING_VIEW, callback_data="morning_view") + evening_routine_btn: InlineKeyboardButton = InlineKeyboardButton(text=ROUTINE_EVENING_VIEW, callback_data="evening_view") + + routine_markup.inline_keyboard.append([morning_routine_btn, evening_routine_btn]) + + return routine_markup \ No newline at end of file diff --git a/telegram_bot_project/bot/commands.py b/telegram_bot_project/bot/commands.py index b471e70..f2360aa 100644 --- a/telegram_bot_project/bot/commands.py +++ b/telegram_bot_project/bot/commands.py @@ -279,3 +279,15 @@ async def set_sleep_time_command(message: types.Message, state: FSMContext): else: await message.answer(MESSAGES[language]['SET_SLEEP_TIME_MSG']) await state.set_state(DialogStates.set_sleep_time) + + +# Routine Menu Command Handler +async def routine_menu_command(message: types.Message): + 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) + + if not user_find: + await message.answer(MESSAGES['ENGLISH']['AUTHORIZATION_PROBLEM']) + else: + await message.answer(MESSAGES[language]['ROUTINE_MENU_DAY'], reply_markup=routine_menu_keyboard()) \ No newline at end of file diff --git a/telegram_bot_project/config.py b/telegram_bot_project/config.py index 05c018b..72d56c7 100644 --- a/telegram_bot_project/config.py +++ b/telegram_bot_project/config.py @@ -1,4 +1,5 @@ import os +import logging from dotenv import load_dotenv from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker @@ -6,10 +7,12 @@ load_dotenv() +logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING) + TOKEN: str = os.getenv("BOT_TOKEN") DB_URL: str = os.getenv("DATABASE_URL") -engine = create_async_engine(DB_URL, echo=True) +engine = create_async_engine(DB_URL, echo=False) async_session = sessionmaker( engine, diff --git a/telegram_bot_project/main.py b/telegram_bot_project/main.py index 221968c..2deb69d 100644 --- a/telegram_bot_project/main.py +++ b/telegram_bot_project/main.py @@ -145,6 +145,14 @@ async def set_sleep_time(message: Message, state: FSMContext): async def set_waketime(message: Message, state: FSMContext): await set_wake_time_command(message, state) +@dp.message(Command("routine")) +async def routine(message: Message): + await routine_menu_command(message) + +@dp.message(lambda m: m.text == SETTINGS_BUTTON_ROUTINE) +async def routine(message: Message): + await routine_menu_command(message) + @dp.callback_query(F.data.in_({"lang_ua", "lang_en"})) async def callback_language(callback_query: CallbackQuery): await start_callback_language(callback_query) diff --git a/telegram_bot_project/messages.py b/telegram_bot_project/messages.py index 29d0e87..4a49fd5 100644 --- a/telegram_bot_project/messages.py +++ b/telegram_bot_project/messages.py @@ -52,11 +52,12 @@ "UPDATE_TASK_NAME_MSG": "📝 Введіть нову назву завдання:", "UPDATE_TASK_NAME_INVALID": "❌ Неправильна назва! 🔄", "SETTINGS_MENU": "⚙️ Ласкаво просимо в Налаштування", + "ROUTINE_MENU_DAY": "Встановіть розпорядок за кнопками -> Morning / Evening", "ROUTINE_TIME": ( "⏰ Ваш розпорядок:\n" "• Час підйому: {}\n" "• Час сну: {}\n" - "• Загальна кількість годин неспання: {}" + "• Загальна кількість годин: {}" ), "ROUTINE_TIME_NOT": "⚠️ Ви ще не встановили час підйому та сну. Налаштуйте їх, щоб тримати розпорядок під контролем!", "ROUTINE_MENU": "🛠 Ласкаво просимо до налаштувань розпорядку! Тут ви можете встановити час підйому та сну.", @@ -127,6 +128,7 @@ "UPDATE_TASK_NAME_MSG": "📝 Enter the new task name:", "UPDATE_TASK_NAME_INVALID": "❌ Invalid task name! 🔄", "SETTINGS_MENU": "⚙️ Welcome to Settings", + "ROUTINE_MENU_DAY": "Set the routine from buttons -> Morning/Evening", "ROUTINE_TIME": ( "⏰ Your routine:\n" "• Wake-up time: {}\n" @@ -179,4 +181,5 @@ ROUTINE_SET_WAKE_BUTTON = "🌅 Set Wake-Up Time" ROUTINE_SET_SLEEP_BUTTON = "🌙 Set Sleep Time" ROUTINE_MY_TIME = "🕒 My Routine" - +ROUTINE_MORNING_VIEW = "Morning Routine" +ROUTINE_EVENING_VIEW = "Afternoon Routine" \ No newline at end of file diff --git a/telegram_bot_project/service/routine.py b/telegram_bot_project/service/routine.py new file mode 100644 index 0000000..dcfb088 --- /dev/null +++ b/telegram_bot_project/service/routine.py @@ -0,0 +1,228 @@ +from sqlalchemy import text +from config import get_session +from typing import Any, List, Optional + +class RoutineService: + @staticmethod + async def create_routine(user_id: int, routine_type: str, routine_name: str) -> int: + async with get_session() as session: + result: Any = await session.execute( + text( + """ + INSERT INTO routines (user_id, routine_type, routine_name) + VALUES (:user_id, :routine_type, :routine_name) + RETURNING id + """ + ), + { + "user_id": user_id, + "routine_type": routine_type, + "routine_name": routine_name + } + ) + routine_id: int = result.scalar_one() + await session.commit() + return routine_id + + @staticmethod + async def get_routine_by_id(routine_id: int) -> Optional[dict]: + async with get_session() as session: + result: Any = await session.execute( + text( + """ + SELECT id, user_id, routine_type, routine_name + FROM routines + WHERE id = :routine_id + """ + ), + {"routine_id": routine_id} + ) + routine = result.fetchone() + if routine: + return { + "id": routine.id, + "user_id": routine.user_id, + "routine_type": routine.routine_type, + "routine_name": routine.routine_name + } + return None + + @staticmethod + async def get_user_routines(user_id: int, routine_type: Optional[str] = None) -> List[dict]: + async with get_session() as session: + if routine_type: + result: Any = await session.execute( + text( + """ + SELECT id, user_id, routine_type, routine_name + FROM routines + WHERE user_id = :user_id AND routine_type = :routine_type + ORDER BY id DESC + """ + ), + {"user_id": user_id, "routine_type": routine_type} + ) + else: + result: Any = await session.execute( + text( + """ + SELECT id, user_id, routine_type, routine_name + FROM routines + WHERE user_id = :user_id + ORDER BY id DESC + """ + ), + {"user_id": user_id} + ) + routines = result.fetchall() + return [ + { + "id": r.id, + "user_id": r.user_id, + "routine_type": r.routine_type, + "routine_name": r.routine_name + } + for r in routines + ] + + @staticmethod + async def update_routine(routine_id: int, routine_name: Optional[str] = None, + routine_type: Optional[str] = None) -> bool: + async with get_session() as session: + exists: Any = await session.execute( + text("SELECT 1 FROM routines WHERE id = :routine_id"), + {"routine_id": routine_id} + ) + if not exists.scalar(): + return False + + update_fields: dict = {} + if routine_name is not None: + update_fields["routine_name"] = routine_name + if routine_type is not None: + update_fields["routine_type"] = routine_type + + if update_fields: + query = text( + f""" + UPDATE routines + SET {', '.join(f'{k} = :{k}' for k in update_fields.keys())} + WHERE id = :routine_id + """ + ) + update_fields["routine_id"] = routine_id + await session.execute(query, update_fields) + await session.commit() + return True + + @staticmethod + async def delete_routine(routine_id: int) -> bool: + async with get_session() as session: + result: Any = await session.execute( + text( + """ + DELETE FROM routines + WHERE id = :routine_id + RETURNING id + """ + ), + {"routine_id": routine_id} + ) + deleted = result.scalar_one_or_none() + await session.commit() + return deleted is not None + + +class RoutineStepService: + @staticmethod + async def add_step(routine_id: int, step_order: int, step_description: str) -> int: + async with get_session() as session: + result: Any = await session.execute( + text( + """ + INSERT INTO routine_steps (routine_id, step_order, step_description) + VALUES (:routine_id, :step_order, :step_description) + RETURNING id + """ + ), + { + "routine_id": routine_id, + "step_order": step_order, + "step_description": step_description + } + ) + step_id: int = result.scalar_one() + await session.commit() + return step_id + + @staticmethod + async def get_steps_by_routine(routine_id: int) -> List[dict]: + async with get_session() as session: + result: Any = await session.execute( + text( + """ + SELECT id, routine_id, step_order, step_description + FROM routine_steps + WHERE routine_id = :routine_id + ORDER BY step_order ASC + """ + ), + {"routine_id": routine_id} + ) + steps = result.fetchall() + return [ + { + "id": s.id, + "routine_id": s.routine_id, + "step_order": s.step_order, + "step_description": s.step_description + } + for s in steps + ] + + @staticmethod + async def update_step(step_id: int, step_order: Optional[int] = None, + step_description: Optional[str] = None) -> bool: + async with get_session() as session: + exists: Any = await session.execute( + text("SELECT 1 FROM routine_steps WHERE id = :step_id"), + {"step_id": step_id} + ) + if not exists.scalar(): + return False + + update_fields: dict = {} + if step_order is not None: + update_fields["step_order"] = step_order + if step_description is not None: + update_fields["step_description"] = step_description + + if update_fields: + query = text( + f""" + UPDATE routine_steps + SET {', '.join(f'{k} = :{k}' for k in update_fields.keys())} + WHERE id = :step_id + """ + ) + update_fields["step_id"] = step_id + await session.execute(query, update_fields) + await session.commit() + return True + + @staticmethod + async def delete_step(step_id: int) -> bool: + async with get_session() as session: + result: Any = await session.execute( + text( + """ + DELETE FROM routine_steps + WHERE id = :step_id + RETURNING id + """ + ), + {"step_id": step_id} + ) + deleted = result.scalar_one_or_none() + await session.commit() + return deleted is not None