diff --git a/src/greek_lang/database/migrations/versions/20250816_1940_9a2898513cf2_.py b/src/greek_lang/database/migrations/versions/20250816_1940_9a2898513cf2_.py index e5fe758..94d670f 100644 --- a/src/greek_lang/database/migrations/versions/20250816_1940_9a2898513cf2_.py +++ b/src/greek_lang/database/migrations/versions/20250816_1940_9a2898513cf2_.py @@ -5,6 +5,7 @@ Revises: 747797032526 Create Date: 2025-08-16 19:40:06.376743 """ + from typing import Sequence, Union from alembic import op @@ -12,8 +13,8 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = '9a2898513cf2' -down_revision: Union[str, None] = '747797032526' +revision: str = "9a2898513cf2" +down_revision: Union[str, None] = "747797032526" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -21,51 +22,91 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Upgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.create_table('srs_progress', - sa.Column('id', sa.BigInteger(), nullable=False), - sa.Column('user_id', sa.BigInteger(), nullable=False), - sa.Column('word_id', sa.BigInteger(), nullable=False), - sa.Column('due_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), - sa.Column('interval_days', sa.Integer(), nullable=False), - sa.Column('ease', sa.Float(), nullable=False), - sa.Column('reps', sa.Integer(), nullable=False), - sa.Column('lrn_step', sa.Integer(), nullable=False), - sa.Column('state', sa.Enum('learning', 'review', 'lapsed', name='reviewstate', native_enum=False), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_srs_progress_user_id_users')), - sa.ForeignKeyConstraint(['word_id'], ['glossary_word.id'], name=op.f('fk_srs_progress_word_id_glossary_word')), - sa.PrimaryKeyConstraint('id', name=op.f('pk_srs_progress')), - sa.UniqueConstraint('user_id', 'word_id', name='uq_srs_user_word') + op.create_table( + "srs_progress", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=False), + sa.Column("word_id", sa.BigInteger(), nullable=False), + sa.Column( + "due_at", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column("interval_days", sa.Integer(), nullable=False), + sa.Column("ease", sa.Float(), nullable=False), + sa.Column("reps", sa.Integer(), nullable=False), + sa.Column("lrn_step", sa.Integer(), nullable=False), + sa.Column( + "state", + sa.Enum( + "learning", "review", "lapsed", name="reviewstate", native_enum=False + ), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["user_id"], ["users.id"], name=op.f("fk_srs_progress_user_id_users") + ), + sa.ForeignKeyConstraint( + ["word_id"], + ["glossary_word.id"], + name=op.f("fk_srs_progress_word_id_glossary_word"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_srs_progress")), + sa.UniqueConstraint("user_id", "word_id", name="uq_srs_user_word"), ) - op.create_index(op.f('ix_srs_progress_due_at'), 'srs_progress', ['due_at'], unique=False) - op.create_index(op.f('ix_srs_progress_user_id'), 'srs_progress', ['user_id'], unique=False) - op.create_index(op.f('ix_srs_progress_word_id'), 'srs_progress', ['word_id'], unique=False) - op.create_table('srs_review_log', - sa.Column('id', sa.BigInteger(), nullable=False), - sa.Column('user_id', sa.BigInteger(), nullable=False), - sa.Column('word_id', sa.BigInteger(), nullable=False), - sa.Column('ts', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), - sa.Column('grade', sa.Integer(), nullable=False), - sa.Column('prev_interval', sa.Integer(), nullable=False), - sa.Column('new_interval', sa.Integer(), nullable=False), - sa.Column('prev_ease', sa.Float(), nullable=False), - sa.Column('new_ease', sa.Float(), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_srs_review_log_user_id_users')), - sa.ForeignKeyConstraint(['word_id'], ['glossary_word.id'], name=op.f('fk_srs_review_log_word_id_glossary_word')), - sa.PrimaryKeyConstraint('id', name=op.f('pk_srs_review_log')) + op.create_index( + op.f("ix_srs_progress_due_at"), "srs_progress", ["due_at"], unique=False + ) + op.create_index( + op.f("ix_srs_progress_user_id"), "srs_progress", ["user_id"], unique=False + ) + op.create_index( + op.f("ix_srs_progress_word_id"), "srs_progress", ["word_id"], unique=False + ) + op.create_table( + "srs_review_log", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=False), + sa.Column("word_id", sa.BigInteger(), nullable=False), + sa.Column( + "ts", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column("grade", sa.Integer(), nullable=False), + sa.Column("prev_interval", sa.Integer(), nullable=False), + sa.Column("new_interval", sa.Integer(), nullable=False), + sa.Column("prev_ease", sa.Float(), nullable=False), + sa.Column("new_ease", sa.Float(), nullable=False), + sa.ForeignKeyConstraint( + ["user_id"], ["users.id"], name=op.f("fk_srs_review_log_user_id_users") + ), + sa.ForeignKeyConstraint( + ["word_id"], + ["glossary_word.id"], + name=op.f("fk_srs_review_log_word_id_glossary_word"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_srs_review_log")), + ) + op.create_index( + op.f("ix_srs_review_log_user_id"), "srs_review_log", ["user_id"], unique=False + ) + op.create_index( + op.f("ix_srs_review_log_word_id"), "srs_review_log", ["word_id"], unique=False ) - op.create_index(op.f('ix_srs_review_log_user_id'), 'srs_review_log', ['user_id'], unique=False) - op.create_index(op.f('ix_srs_review_log_word_id'), 'srs_review_log', ['word_id'], unique=False) # ### end Alembic commands ### def downgrade() -> None: """Downgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_srs_review_log_word_id'), table_name='srs_review_log') - op.drop_index(op.f('ix_srs_review_log_user_id'), table_name='srs_review_log') - op.drop_table('srs_review_log') - op.drop_index(op.f('ix_srs_progress_word_id'), table_name='srs_progress') - op.drop_index(op.f('ix_srs_progress_user_id'), table_name='srs_progress') - op.drop_index(op.f('ix_srs_progress_due_at'), table_name='srs_progress') - op.drop_table('srs_progress') + op.drop_index(op.f("ix_srs_review_log_word_id"), table_name="srs_review_log") + op.drop_index(op.f("ix_srs_review_log_user_id"), table_name="srs_review_log") + op.drop_table("srs_review_log") + op.drop_index(op.f("ix_srs_progress_word_id"), table_name="srs_progress") + op.drop_index(op.f("ix_srs_progress_user_id"), table_name="srs_progress") + op.drop_index(op.f("ix_srs_progress_due_at"), table_name="srs_progress") + op.drop_table("srs_progress") # ### end Alembic commands ### diff --git a/src/greek_lang/tg_bot/app.py b/src/greek_lang/tg_bot/app.py index dff7d6f..74c9309 100644 --- a/src/greek_lang/tg_bot/app.py +++ b/src/greek_lang/tg_bot/app.py @@ -3,6 +3,7 @@ from aiogram import Bot, Dispatcher, BaseMiddleware from aiogram.fsm.storage.base import BaseStorage, DefaultKeyBuilder from aiogram.fsm.storage.redis import RedisStorage from aiogram.types import BotCommandScopeAllPrivateChats +from aiogram_dialog import setup_dialogs from dependency_injector.wiring import Provide, inject from ..configs.container import ConfigContainer @@ -24,6 +25,7 @@ def create_bot( async def create_dispatcher() -> Dispatcher: from .router import router as root_router + from .dialogs import dialog fsm_storage = await create_fsm_storage() @@ -35,7 +37,10 @@ async def create_dispatcher() -> Dispatcher: for middleware in middlewares: dp.update.middleware(middleware) - dp.include_routers(root_router) + dp.include_routers(dialog, root_router) + + setup_dialogs(dp) + return dp diff --git a/src/greek_lang/tg_bot/commands.py b/src/greek_lang/tg_bot/commands.py index 7d221fe..a149bfa 100644 --- a/src/greek_lang/tg_bot/commands.py +++ b/src/greek_lang/tg_bot/commands.py @@ -1,10 +1,9 @@ -from gettext import gettext - from aiogram import Router -from aiogram.enums import ParseMode from aiogram.filters import CommandStart from aiogram.types import Message +from aiogram_dialog import DialogManager, StartMode +from greek_lang.tg_bot.dialogs.states import States from greek_lang.users.manager import get_or_create_telegram_user @@ -12,12 +11,9 @@ router = Router() @router.message(CommandStart()) -async def start(message: Message) -> None: +async def start(message: Message, dialog_manager: DialogManager) -> None: user = message.from_user if user is None: return await get_or_create_telegram_user(user) - await message.answer( - text=gettext("Регистрация прошла.").format(user=user.full_name), - parse_mode=ParseMode.HTML, - ) + await dialog_manager.start(States.main_menu, mode=StartMode.RESET_STACK) diff --git a/src/greek_lang/tg_bot/dialogs/__init__.py b/src/greek_lang/tg_bot/dialogs/__init__.py new file mode 100644 index 0000000..c0dd330 --- /dev/null +++ b/src/greek_lang/tg_bot/dialogs/__init__.py @@ -0,0 +1,8 @@ +from aiogram_dialog import Dialog + +from .main_menu import windows as main_windows + + +dialog = Dialog( + main_windows.main_window, +) diff --git a/src/greek_lang/tg_bot/dialogs/main_menu/__init__.py b/src/greek_lang/tg_bot/dialogs/main_menu/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/greek_lang/tg_bot/dialogs/main_menu/handlers.py b/src/greek_lang/tg_bot/dialogs/main_menu/handlers.py new file mode 100644 index 0000000..e69de29 diff --git a/src/greek_lang/tg_bot/dialogs/main_menu/windows.py b/src/greek_lang/tg_bot/dialogs/main_menu/windows.py new file mode 100644 index 0000000..1520c79 --- /dev/null +++ b/src/greek_lang/tg_bot/dialogs/main_menu/windows.py @@ -0,0 +1,23 @@ +from typing import Any + +from aiogram.enums import ParseMode +from aiogram_dialog import DialogManager, Window +from aiogram_dialog.widgets.text import Format + +from ..states import States + + +async def main_getter(dialog_manager: DialogManager, **kwargs: Any) -> dict[str, Any]: + return {} + + +main_window = Window( + Format( + "Выбери действие:", + when=lambda data, widget, dialog_manager: data["dialog_data"].get("action") + is None, + ), + state=States.main_menu, + getter=main_getter, + parse_mode=ParseMode.HTML, +) diff --git a/src/greek_lang/tg_bot/dialogs/states.py b/src/greek_lang/tg_bot/dialogs/states.py new file mode 100644 index 0000000..6914ee5 --- /dev/null +++ b/src/greek_lang/tg_bot/dialogs/states.py @@ -0,0 +1,5 @@ +from aiogram.fsm.state import State, StatesGroup + + +class States(StatesGroup): + main_menu = State()