connect database

This commit is contained in:
2025-09-27 09:13:20 +03:00
parent 5982351dd2
commit 1a49545fff
16 changed files with 180 additions and 38 deletions

View File

@@ -7,19 +7,25 @@ from aiogram_dialog import setup_dialogs
from app.bot.dialogs.flows import dialogs_router
from app.bot.handlers.commands import commands_router
from app.bot.middlewares import GetUserMiddleware
from app.bot.middlewares import DbSessionMiddleware, GetUserMiddleware
from config.config import settings
bot = Bot(token=settings.bot_token, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN_V2))
bot = Bot(
token=settings.bot_token,
default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN_V2),
)
dp = Dispatcher()
async def main():
setup_dialogs(dp)
dp.update.outer_middleware(DbSessionMiddleware())
dp.update.outer_middleware(GetUserMiddleware())
dp.include_router(commands_router)
dp.include_router(dialogs_router)
dp.update.outer_middleware(GetUserMiddleware())
await dp.start_polling(bot)

View File

@@ -1,23 +1,39 @@
from aiogram_dialog import Dialog, Window
from aiogram_dialog.widgets.kbd import Column, Select
from aiogram_dialog.widgets.kbd import Back, Cancel, Column, Select
from aiogram_dialog.widgets.text import Const, Format
from .getters import events_getter
from .states import EventsSG
async def on_event_selected(
c,
widget: Select,
manager,
item_id: str,
):
manager.dialog_data["selected_event"] = item_id
await manager.next()
events_dialog = Dialog(
Window(
Const("События"),
Column(
Cancel(Const("Назад")),
Select(
Format("{item}"),
id="categ",
item_id_getter=lambda x: x,
items="events",
)
on_click=on_event_selected,
),
),
getter=events_getter,
state=EventsSG.events_list,
),
# Window(state=EventsSG.event),
Window(
Format("{dialog_data[selected_event]}"),
Back(Const("Назад")),
state=EventsSG.event,
),
)

View File

@@ -3,13 +3,15 @@ from aiogram_dialog.widgets.input import TextInput
from aiogram_dialog.widgets.kbd import Cancel, SwitchTo
from aiogram_dialog.widgets.text import Const, Format
from app.bot.dialogs.widgets.getters import user_getter
from .states import ProfileSG
profile_dialog = Dialog(
Window(
Const("*Профиль*"),
Format("Имя:"),
Format("Телефон:"),
Format("Имя: {user.fullname}"),
Format("Телефон: {user.phone}"),
SwitchTo(
Const("изменить имя"),
id="change_name",
@@ -22,6 +24,7 @@ profile_dialog = Dialog(
),
Cancel(Const("назад")),
state=ProfileSG.profile,
getter=user_getter,
),
Window(
Const("Введите имя"),

View File

@@ -4,17 +4,17 @@ from aiogram_dialog.widgets.text import Const, Format
from app.bot.dialogs.flows.events.dialogs import EventsSG
from app.bot.dialogs.flows.profile.dialogs import ProfileSG
from app.bot.dialogs.widgets.getters import is_admin, username_getter
from app.bot.dialogs.widgets.getters import is_admin_getter, user_getter
from .states import StartSG
start_dialog = Dialog(
Window(
Format("Привет, {username}"),
Format("Привет, {user.fullname}"),
Start(Const("события"), id="events", state=EventsSG.events_list),
Button(Const("создать событие"), id="create_event", when="is_admin"),
Start(Const("профиль"), id="profile", state=ProfileSG.profile),
getter=[username_getter, is_admin],
getter=[user_getter, is_admin_getter],
state=StartSG.start,
)
)

View File

@@ -1,13 +1,16 @@
from aiogram.types import User
from aiogram_dialog import DialogManager
from app.infrastructure.database.models import User as UserModel
async def username_getter(
dialog_manager: DialogManager, event_from_user: User, **kwargs
) -> dict[str, str]:
return {"username": event_from_user.username}
async def is_admin(
dialog_manager: DialogManager, event_from_user: User, **kwargs
async def user_getter(
dialog_manager: DialogManager, event_from_user: User, user: UserModel, **kwargs
) -> dict[str, str]:
return {"is_admin": False}
return {"user": user}
async def is_admin_getter(
dialog_manager: DialogManager, event_from_user: User, user: UserModel, **kwargs
) -> dict[str, str]:
return {"is_admin": user.role == "admin"}

View File

@@ -1,3 +1,4 @@
from .database import DbSessionMiddleware
from .get_user import GetUserMiddleware
__all__ = ["GetUserMiddleware"]
__all__ = ["DbSessionMiddleware", "GetUserMiddleware"]

View File

@@ -0,0 +1,18 @@
from typing import Any, Awaitable, Callable
from aiogram import BaseMiddleware
from aiogram.types import Update
from app.infrastructure.database import get_session
class DbSessionMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[Update, dict[str, Any]], Awaitable[Any]],
event: Update,
data: dict[str, Any],
):
async with get_session() as session:
data["session"] = session
return await handler(event, data)

View File

@@ -1,7 +1,9 @@
from typing import Any, Awaitable, Callable
from aiogram import BaseMiddleware
from aiogram.types import Update
from aiogram.types import Update, User
from app.infrastructure.database.crud import create_user, get_user_by_tg_id
class GetUserMiddleware(BaseMiddleware):
@@ -11,5 +13,14 @@ class GetUserMiddleware(BaseMiddleware):
event: Update,
data: dict[str, Any],
):
data.update(user={"is_admin": False})
return await handler(event, data)
session = data["session"]
tg_user: User = data.get("event_from_user")
user = await get_user_by_tg_id(session, tg_user.id)
if user is None:
user = await create_user(
session,
tg_user.id,
tg_user.username,
)
data["user"] = user
return await handler(event, data)

View File

@@ -0,0 +1,4 @@
from .models import Base
from .session import async_session, engine, get_session
__all__ = ["Base", "async_session", "engine", "get_session"]

View File

@@ -0,0 +1,22 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from .models import User
async def get_user_by_tg_id(session: AsyncSession, user_tg_id: int) -> User | None:
result = await session.execute(select(User).where(User.tg_id == user_tg_id))
return result.scalar_one_or_none()
async def create_user(
session: AsyncSession,
tg_id: int,
fullname: str,
phone: str = "",
role: str = "user",
) -> User:
user = User(tg_id=tg_id, fullname=fullname, role=role, phone=phone)
session.add(user)
await session.commit()
return user

View File

@@ -1,13 +0,0 @@
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from config.config import DATABASE_URL
engine = create_async_engine(DATABASE_URL, echo=True)
Base = declarative_base()
async_session = sessionmaker(bind=engine, expire_on_commit=False, class_=AsyncSession)
async def get_session() -> AsyncSession:
async with async_session() as session:
yield session

View File

@@ -0,0 +1,13 @@
import asyncio
from .models import Base
from .session import engine
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
if __name__ == "__main__":
asyncio.run(init_db())

View File

@@ -6,16 +6,17 @@ from sqlalchemy import (
ForeignKey,
func,
)
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import Mapped, declarative_base, mapped_column
from app.db.database import Base
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
tg_id: Mapped[int] = mapped_column(BigInteger, unique=True, nullable=False)
fullname: Mapped[str] = mapped_column()
tg_id: Mapped[int] = mapped_column(BigInteger)
role: Mapped[str] = mapped_column(default="user")
phone: Mapped[str] = mapped_column(nullable=True)

View File

@@ -0,0 +1,20 @@
from contextlib import asynccontextmanager
from typing import AsyncIterator
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from config.config import settings
engine = create_async_engine(settings.database_url, echo=True)
async_session = sessionmaker(
bind=engine,
expire_on_commit=False,
class_=AsyncSession,
)
@asynccontextmanager
async def get_session() -> AsyncIterator[AsyncSession]:
async with async_session() as session:
yield session

View File

@@ -9,4 +9,10 @@ dependencies = [
"dynaconf>=3.2.11",
"psycopg>=3.2.10",
"sqlalchemy>=2.0.43",
"structlog>=25.4.0",
]
[dependency-groups]
dev = [
"aiosqlite>=0.21.0",
]

31
uv.lock generated
View File

@@ -115,6 +115,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
]
[[package]]
name = "aiosqlite"
version = "0.21.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
@@ -578,6 +590,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" },
]
[[package]]
name = "structlog"
version = "25.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/79/b9/6e672db4fec07349e7a8a8172c1a6ae235c58679ca29c3f86a61b5e59ff3/structlog-25.4.0.tar.gz", hash = "sha256:186cd1b0a8ae762e29417095664adf1d6a31702160a46dacb7796ea82f7409e4", size = 1369138, upload-time = "2025-06-02T08:21:12.971Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/4a/97ee6973e3a73c74c8120d59829c3861ea52210667ec3e7a16045c62b64d/structlog-25.4.0-py3-none-any.whl", hash = "sha256:fe809ff5c27e557d14e613f45ca441aabda051d119ee5a0102aaba6ce40eed2c", size = 68720, upload-time = "2025-06-02T08:21:11.43Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
@@ -619,6 +640,12 @@ dependencies = [
{ name = "dynaconf" },
{ name = "psycopg" },
{ name = "sqlalchemy" },
{ name = "structlog" },
]
[package.dev-dependencies]
dev = [
{ name = "aiosqlite" },
]
[package.metadata]
@@ -629,8 +656,12 @@ requires-dist = [
{ name = "dynaconf", specifier = ">=3.2.11" },
{ name = "psycopg", specifier = ">=3.2.10" },
{ name = "sqlalchemy", specifier = ">=2.0.43" },
{ name = "structlog", specifier = ">=25.4.0" },
]
[package.metadata.requires-dev]
dev = [{ name = "aiosqlite", specifier = ">=0.21.0" }]
[[package]]
name = "yarl"
version = "1.20.1"