connect database
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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("Введите имя"),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .database import DbSessionMiddleware
|
||||
from .get_user import GetUserMiddleware
|
||||
|
||||
__all__ = ["GetUserMiddleware"]
|
||||
__all__ = ["DbSessionMiddleware", "GetUserMiddleware"]
|
||||
|
||||
18
app/bot/middlewares/database.py
Normal file
18
app/bot/middlewares/database.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
4
app/infrastructure/database/__init__.py
Normal file
4
app/infrastructure/database/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .models import Base
|
||||
from .session import async_session, engine, get_session
|
||||
|
||||
__all__ = ["Base", "async_session", "engine", "get_session"]
|
||||
22
app/infrastructure/database/crud.py
Normal file
22
app/infrastructure/database/crud.py
Normal 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
|
||||
@@ -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
|
||||
13
app/infrastructure/database/init_db.py
Normal file
13
app/infrastructure/database/init_db.py
Normal 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())
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
20
app/infrastructure/database/session.py
Normal file
20
app/infrastructure/database/session.py
Normal 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
|
||||
@@ -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
31
uv.lock
generated
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user