Little fixes, add exception handler
This commit is contained in:
@@ -18,13 +18,17 @@ Run
|
|||||||
uv run python -m birthday_pool_bot -e .env run
|
uv run python -m birthday_pool_bot -e .env run
|
||||||
```
|
```
|
||||||
|
|
||||||
## TODO
|
## For test
|
||||||
|
|
||||||
* Поддержать рассылку пользователям о том, что появились новые сборы для их подписки (если они ещё
|
* Поддержать рассылку пользователям о том, что появились новые сборы для их подписки (если они ещё
|
||||||
не участвуют в сборе)
|
не участвуют в сборе)
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
* Добавить возможность добавлять ссылку на сбор денег Сбера, когда происходит оповещение о
|
* Добавить возможность добавлять ссылку на сбор денег Сбера, когда происходит оповещение о
|
||||||
наступающем дне рождения
|
наступающем дне рождения
|
||||||
* Добавить раздел с настройками времени оповещения о предстоящих событиях
|
* Добавить раздел с настройками времени оповещения о предстоящих событиях
|
||||||
* Добавить возможность инициировать сбор денег по другому поводу (кроме ДР)
|
* Добавить возможность инициировать сбор денег по другому поводу (кроме ДР)
|
||||||
* Добавить возможность отписаться во время нотификации о предстоящем ДР
|
* Добавить возможность отписаться во время нотификации о предстоящем ДР
|
||||||
* Добавить возможность синхронизировать календарь с днями рождения
|
* Добавить возможность синхронизировать календарь с днями рождения
|
||||||
|
* Добавить возможность организовывать поздравления для участников группы
|
||||||
|
|||||||
@@ -73,3 +73,4 @@ class SubscriptionFilter(BaseFilter):
|
|||||||
to_user_id: set[uuid.UUID]
|
to_user_id: set[uuid.UUID]
|
||||||
to_user_id__gt: uuid.UUID
|
to_user_id__gt: uuid.UUID
|
||||||
pool_id: set[uuid.UUID]
|
pool_id: set[uuid.UUID]
|
||||||
|
pool_id__null: bool
|
||||||
|
|||||||
@@ -35,20 +35,22 @@ class NotificationsService(facet.AsyncioServiceMixin):
|
|||||||
users_repository = self._repositories_container.users
|
users_repository = self._repositories_container.users
|
||||||
subscriptions_repository = self._repositories_container.subscriptions
|
subscriptions_repository = self._repositories_container.subscriptions
|
||||||
|
|
||||||
async with users_repository.transaction():
|
users_generator = users_repository.get_users_by_birthdays(
|
||||||
users = await users_repository.get_users_by_birthdays(
|
|
||||||
birthday=birthday,
|
birthday=birthday,
|
||||||
)
|
)
|
||||||
|
async with users_repository.transaction():
|
||||||
|
users = [user async for user in users_generator]
|
||||||
if not users:
|
if not users:
|
||||||
return
|
return
|
||||||
|
|
||||||
async with subscriptions_repository.transaction():
|
subscriptions_generator = subscriptions_repository.get_to_users_subscriptions(
|
||||||
subscriptions = await subscriptions_repository.get_to_users_subscriptions(
|
|
||||||
to_users_ids={user.id for user in users},
|
to_users_ids={user.id for user in users},
|
||||||
with_from_user=True,
|
with_from_user=True,
|
||||||
with_to_user=True,
|
with_to_user=True,
|
||||||
with_pool=True,
|
with_pool=True,
|
||||||
)
|
)
|
||||||
|
async with subscriptions_repository.transaction():
|
||||||
|
subscriptions = [subscription async for subscription in subscriptions_generator]
|
||||||
if not subscriptions:
|
if not subscriptions:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -54,12 +54,16 @@ class SubscriptionsRepository(BaseRepository[Subscription, SubscriptionFilter]):
|
|||||||
async def get_to_users_subscriptions(
|
async def get_to_users_subscriptions(
|
||||||
self,
|
self,
|
||||||
to_users_ids: Iterable[uuid.UUID],
|
to_users_ids: Iterable[uuid.UUID],
|
||||||
|
have_pool: bool | None = None,
|
||||||
|
pagination: BasePagination | None = None,
|
||||||
with_from_user: bool = False,
|
with_from_user: bool = False,
|
||||||
with_to_user: bool = False,
|
with_to_user: bool = False,
|
||||||
with_pool: bool = False,
|
with_pool: bool = False,
|
||||||
with_pool_owner: bool = False,
|
with_pool_owner: bool = False,
|
||||||
) -> list[Subscription]:
|
) -> AsyncGenerator[Subscription, None]:
|
||||||
filter_ = SubscriptionFilter(to_user_id=set(to_users_ids))
|
filter_ = SubscriptionFilter(to_user_id=set(to_users_ids))
|
||||||
|
if have_pool is not None:
|
||||||
|
filter_.pool_id__null = not have_pool
|
||||||
options = []
|
options = []
|
||||||
if with_from_user:
|
if with_from_user:
|
||||||
options.append(joinedload(DBSubscription.from_user))
|
options.append(joinedload(DBSubscription.from_user))
|
||||||
@@ -72,10 +76,12 @@ class SubscriptionsRepository(BaseRepository[Subscription, SubscriptionFilter]):
|
|||||||
|
|
||||||
subscriptions_generator = self.get_items(
|
subscriptions_generator = self.get_items(
|
||||||
filter_=filter_,
|
filter_=filter_,
|
||||||
|
pagination=pagination,
|
||||||
options=options,
|
options=options,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [subscription async for subscription in subscriptions_generator]
|
async for subscription in subscriptions_generator:
|
||||||
|
yield subscription
|
||||||
|
|
||||||
async def get_subscription(
|
async def get_subscription(
|
||||||
self,
|
self,
|
||||||
@@ -134,3 +140,20 @@ class SubscriptionsRepository(BaseRepository[Subscription, SubscriptionFilter]):
|
|||||||
to_user_id={to_user_id},
|
to_user_id={to_user_id},
|
||||||
)
|
)
|
||||||
await self.delete_items(filter_=filter_)
|
await self.delete_items(filter_=filter_)
|
||||||
|
|
||||||
|
async def set_pool_for_subscription(
|
||||||
|
self,
|
||||||
|
from_user_id: uuid.UUID,
|
||||||
|
to_user_id: uuid.UUID,
|
||||||
|
pool_id: uuid.UUID,
|
||||||
|
) -> Subscription | None:
|
||||||
|
filter_ = SubscriptionFilter(
|
||||||
|
from_user_id={from_user_id},
|
||||||
|
to_user_id={to_user_id},
|
||||||
|
)
|
||||||
|
subscriptions_generator = self.update_items(
|
||||||
|
filter_=filter_,
|
||||||
|
pool_id=pool_id,
|
||||||
|
)
|
||||||
|
async for subscription in subscriptions_generator:
|
||||||
|
return subscription
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Container, Sequence, TypeVar
|
from typing import AsyncGenerator, Sequence, TypeVar
|
||||||
|
|
||||||
from pydantic_filters import BasePagination, OffsetPagination
|
from pydantic_filters import OffsetPagination
|
||||||
from sqlalchemy import Column, func, inspect, or_, select, update
|
from sqlalchemy import Column, func, inspect, or_, select, update
|
||||||
from sqlmodel import SQLModel
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
@@ -38,22 +38,12 @@ class UsersRepository(BaseRepository[User, UserFilter]):
|
|||||||
async for user in self.get_items(filter_=filter_, pagination=pagination):
|
async for user in self.get_items(filter_=filter_, pagination=pagination):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
async def get_users_by_ids(
|
|
||||||
self,
|
|
||||||
user_ids: Container[uuid.UUID],
|
|
||||||
pagination: BasePagination | None = None,
|
|
||||||
) -> list[User]:
|
|
||||||
filter_ = UserFilter(id=set(user_ids))
|
|
||||||
users_generator = self.get_items(filter_=filter_, pagination=pagination)
|
|
||||||
users = [user async for user in users_generator]
|
|
||||||
return users
|
|
||||||
|
|
||||||
async def get_users_by_primary_keys(
|
async def get_users_by_primary_keys(
|
||||||
self,
|
self,
|
||||||
user_id: uuid.UUID | None = None,
|
user_id: uuid.UUID | None = None,
|
||||||
telegram_id: int | None = None,
|
telegram_id: int | None = None,
|
||||||
phone: str | None = None,
|
phone: str | None = None,
|
||||||
) -> list[User]:
|
) -> AsyncGenerator[User, None]:
|
||||||
filters = []
|
filters = []
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
filters.append(DBUser.id == user_id)
|
filters.append(DBUser.id == user_id)
|
||||||
@@ -62,24 +52,24 @@ class UsersRepository(BaseRepository[User, UserFilter]):
|
|||||||
if phone is not None:
|
if phone is not None:
|
||||||
filters.append(DBUser.phone == phone)
|
filters.append(DBUser.phone == phone)
|
||||||
if not filters:
|
if not filters:
|
||||||
return []
|
return
|
||||||
|
|
||||||
statement = select(DBUser).where(or_(filters))
|
statement = select(DBUser).where(or_(*filters))
|
||||||
result = await self.session.exec(statement)
|
result = await self.session.exec(statement)
|
||||||
|
for db_user in result.all():
|
||||||
return [db_user.to_item() for db_user in result.all()]
|
yield db_user.to_item()
|
||||||
|
|
||||||
async def get_users_by_birthdays(
|
async def get_users_by_birthdays(
|
||||||
self,
|
self,
|
||||||
birthday: date | None = None,
|
birthday: date | None = None,
|
||||||
) -> list[User]:
|
) -> AsyncGenerator[User, None]:
|
||||||
statement = select(DBUser).where(
|
statement = select(DBUser).where(
|
||||||
func.extract("month", DBUser.birthday) == birthday.month,
|
func.extract("month", DBUser.birthday) == birthday.month,
|
||||||
func.extract("day", DBUser.birthday) == birthday.day,
|
func.extract("day", DBUser.birthday) == birthday.day,
|
||||||
)
|
)
|
||||||
result = await self.session.execute(statement)
|
result = await self.session.execute(statement)
|
||||||
|
for (db_user,) in result.all():
|
||||||
return [db_user.to_item() for (db_user,) in result.all()]
|
yield db_user.to_item()
|
||||||
|
|
||||||
async def create_user(self, user: User) -> User:
|
async def create_user(self, user: User) -> User:
|
||||||
return await self.create_item(item=user)
|
return await self.create_item(item=user)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import redis.asyncio import redis
|
import redis.asyncio as redis
|
||||||
from aiogram.fsm.storage.base import BaseStorage
|
from aiogram.fsm.storage.base import BaseStorage
|
||||||
from aiogram.fsm.storage.memory import MemoryStorage
|
from aiogram.fsm.storage.memory import MemoryStorage
|
||||||
from aiogram.fsm.storage.redis import RedisStorage
|
from aiogram.fsm.storage.redis import RedisStorage
|
||||||
@@ -12,14 +12,12 @@ def get_telegram_bot_fsm_storage(settings: StorageSettings) -> BaseStorage:
|
|||||||
case StorageTypeEnum.MEMORY.value:
|
case StorageTypeEnum.MEMORY.value:
|
||||||
return MemoryStorage()
|
return MemoryStorage()
|
||||||
case StorageTypeEnum.REDIS.value:
|
case StorageTypeEnum.REDIS.value:
|
||||||
redis_connection_pool = redis.ConnectionPool(
|
redis_client = redis.Redis(
|
||||||
host=settings.host,
|
host=settings.host,
|
||||||
port=settings.port,
|
port=settings.port,
|
||||||
db=settings.database,
|
db=settings.database,
|
||||||
password=settings.password,
|
password=settings.password,
|
||||||
max_connections=settings.max_connections,
|
|
||||||
)
|
)
|
||||||
redis_client = redis.Redis(connection_pool=redis_connection_pool)
|
|
||||||
return RedisStorage(redis=redis_client)
|
return RedisStorage(redis=redis_client)
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"Unsupported telegram bot storage type: {settings.type}")
|
raise ValueError(f"Unsupported telegram bot storage type: {settings.type}")
|
||||||
|
|||||||
@@ -19,4 +19,3 @@ class RedisStorageSettings(StorageSettings):
|
|||||||
port: int = 6379
|
port: int = 6379
|
||||||
database: int = 0
|
database: int = 0
|
||||||
password: str | None = None
|
password: str | None = None
|
||||||
max_connections: int = 10
|
|
||||||
|
|||||||
@@ -57,3 +57,14 @@ class PoolActionEnum(str, enum.Enum):
|
|||||||
class PoolCallbackData(CallbackData, prefix="pool"):
|
class PoolCallbackData(CallbackData, prefix="pool"):
|
||||||
id: uuid.UUID
|
id: uuid.UUID
|
||||||
action: PoolActionEnum = PoolActionEnum.SHOW
|
action: PoolActionEnum = PoolActionEnum.SHOW
|
||||||
|
|
||||||
|
|
||||||
|
class SubscribeToNewPoolAnswerEnum(str, enum.Enum):
|
||||||
|
YES = "yes"
|
||||||
|
NO = "no"
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyNewPoolCallbackData(CallbackData, prefix="snp"):
|
||||||
|
user_id: uuid.UUID
|
||||||
|
pool_id: uuid.UUID
|
||||||
|
answer: SubscribeToNewPoolAnswerEnum
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ from .callback_data import (
|
|||||||
MenuCallbackData,
|
MenuCallbackData,
|
||||||
NewSubscriptionCallbackData,
|
NewSubscriptionCallbackData,
|
||||||
NewSubscriptionConfirmCallbackData,
|
NewSubscriptionConfirmCallbackData,
|
||||||
|
NotifyNewPoolCallbackData,
|
||||||
PoolActionEnum,
|
PoolActionEnum,
|
||||||
PoolCallbackData,
|
PoolCallbackData,
|
||||||
PoolsBackCallbackData,
|
PoolsBackCallbackData,
|
||||||
PoolsCallbackData,
|
PoolsCallbackData,
|
||||||
|
SubscribeToNewPoolAnswerEnum,
|
||||||
SubscriptionActionEnum,
|
SubscriptionActionEnum,
|
||||||
SubscriptionCallbackData,
|
SubscriptionCallbackData,
|
||||||
SubscriptionsCallbackData,
|
SubscriptionsCallbackData,
|
||||||
@@ -19,6 +21,7 @@ from .callback_data import (
|
|||||||
from .middlewares import (
|
from .middlewares import (
|
||||||
AuthMiddleware,
|
AuthMiddleware,
|
||||||
DependsMiddleware,
|
DependsMiddleware,
|
||||||
|
ExceptionHandlerMiddleware,
|
||||||
# TypingMiddleware,
|
# TypingMiddleware,
|
||||||
)
|
)
|
||||||
from .states import (
|
from .states import (
|
||||||
@@ -36,6 +39,7 @@ def setup_dispatcher(
|
|||||||
dispatcher: aiogram.Dispatcher,
|
dispatcher: aiogram.Dispatcher,
|
||||||
repositories_container: RepositoriesContainer,
|
repositories_container: RepositoriesContainer,
|
||||||
):
|
):
|
||||||
|
exception_handler_middleware = ExceptionHandlerMiddleware()
|
||||||
# typing_middleware = TypingMiddleware()
|
# typing_middleware = TypingMiddleware()
|
||||||
auth_middleware = AuthMiddleware(users_repository=repositories_container.users)
|
auth_middleware = AuthMiddleware(users_repository=repositories_container.users)
|
||||||
users_repository_middleware = DependsMiddleware(
|
users_repository_middleware = DependsMiddleware(
|
||||||
@@ -51,6 +55,7 @@ def setup_dispatcher(
|
|||||||
object_=repositories_container.subscriptions,
|
object_=repositories_container.subscriptions,
|
||||||
)
|
)
|
||||||
middlewares = [
|
middlewares = [
|
||||||
|
exception_handler_middleware,
|
||||||
# typing_middleware,
|
# typing_middleware,
|
||||||
auth_middleware,
|
auth_middleware,
|
||||||
users_repository_middleware,
|
users_repository_middleware,
|
||||||
@@ -333,6 +338,16 @@ def setup_router(router: aiogram.Router) -> aiogram.Router:
|
|||||||
PoolsCallbackData.filter(),
|
PoolsCallbackData.filter(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Notification about new pool
|
||||||
|
router.callback_query.register(
|
||||||
|
handlers.subscribe_to_new_pool_callback_handler,
|
||||||
|
NotifyNewPoolCallbackData.filter(aiogram.F.answer == SubscribeToNewPoolAnswerEnum.YES),
|
||||||
|
)
|
||||||
|
router.callback_query.register(
|
||||||
|
handlers.hide_new_pool_notification,
|
||||||
|
NotifyNewPoolCallbackData.filter(aiogram.F.answer == SubscribeToNewPoolAnswerEnum.NO),
|
||||||
|
)
|
||||||
|
|
||||||
# Fallback
|
# Fallback
|
||||||
router.message.register(handlers.fallback_message_handler)
|
router.message.register(handlers.fallback_message_handler)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from aiogram import types
|
import asyncio
|
||||||
|
|
||||||
|
from aiogram import Bot, types
|
||||||
from aiogram.enums import MessageOriginType, ParseMode
|
from aiogram.enums import MessageOriginType, ParseMode
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
@@ -14,7 +16,11 @@ from birthday_pool_bot.repositories.repositories import (
|
|||||||
UsersRepository,
|
UsersRepository,
|
||||||
)
|
)
|
||||||
from . import constants, logic
|
from . import constants, logic
|
||||||
from .callback_data import PoolCallbackData, SubscriptionCallbackData
|
from .callback_data import (
|
||||||
|
NotifyNewPoolCallbackData,
|
||||||
|
PoolCallbackData,
|
||||||
|
SubscriptionCallbackData,
|
||||||
|
)
|
||||||
from .exceptions import FlowInternalError
|
from .exceptions import FlowInternalError
|
||||||
from .states import (
|
from .states import (
|
||||||
NewSubscriptionPoolState,
|
NewSubscriptionPoolState,
|
||||||
@@ -337,10 +343,11 @@ async def set_new_subscription_user_message_handler(
|
|||||||
|
|
||||||
async with users_repository.transaction():
|
async with users_repository.transaction():
|
||||||
if message.contact is not None:
|
if message.contact is not None:
|
||||||
subscription_users = await users_repository.get_users_by_primary_keys(
|
subscription_users_generator = users_repository.get_users_by_primary_keys(
|
||||||
phone=message.contact.phone_number,
|
phone=message.contact.phone_number,
|
||||||
telegram_id=message.contact.user_id,
|
telegram_id=message.contact.user_id,
|
||||||
)
|
)
|
||||||
|
subscription_users = [user async for user in subscription_users_generator]
|
||||||
subscription_user = await users_repository.merge_users(*subscription_users)
|
subscription_user = await users_repository.merge_users(*subscription_users)
|
||||||
user_name = get_telegram_user_full_name(message.contact)
|
user_name = get_telegram_user_full_name(message.contact)
|
||||||
user_phone = message.contact.phone_number
|
user_phone = message.contact.phone_number
|
||||||
@@ -401,7 +408,7 @@ async def set_new_subscription_user_message_handler(
|
|||||||
return
|
return
|
||||||
|
|
||||||
await state.update_data(
|
await state.update_data(
|
||||||
subscription_user_id=subscription_user.id,
|
subscription_user_id=str(subscription_user.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
if subscription_user.birthday is None:
|
if subscription_user.birthday is None:
|
||||||
@@ -577,7 +584,7 @@ async def ask_new_subscription_pool_decision_callback_handler(
|
|||||||
subscription_user_id=subscription_user_id,
|
subscription_user_id=subscription_user_id,
|
||||||
)
|
)
|
||||||
except FlowInternalError:
|
except FlowInternalError:
|
||||||
await message.reply(text=constants.ERROR_MESSAGE)
|
await callback_query.answer(text=constants.ERROR_MESSAGE)
|
||||||
await logic.ask_new_subscription_user(message=callback_query.message)
|
await logic.ask_new_subscription_user(message=callback_query.message)
|
||||||
await state.set_state(NewSubscriptionState.WAITING_FOR_PHONE)
|
await state.set_state(NewSubscriptionState.WAITING_FOR_PHONE)
|
||||||
return
|
return
|
||||||
@@ -652,6 +659,8 @@ async def show_new_subscription_choosing_pools_callback_handler(
|
|||||||
async def show_new_subscription_choosing_pool_callback_handler(
|
async def show_new_subscription_choosing_pool_callback_handler(
|
||||||
callback_query: types.CallbackQuery,
|
callback_query: types.CallbackQuery,
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
|
user: User,
|
||||||
|
users_repository: UsersRepository,
|
||||||
pools_repository: PoolsRepository,
|
pools_repository: PoolsRepository,
|
||||||
):
|
):
|
||||||
callback_data = PoolCallbackData.unpack(callback_query.data)
|
callback_data = PoolCallbackData.unpack(callback_query.data)
|
||||||
@@ -668,7 +677,7 @@ async def show_new_subscription_choosing_pool_callback_handler(
|
|||||||
user=user,
|
user=user,
|
||||||
users_repository=users_repository,
|
users_repository=users_repository,
|
||||||
pools_repository=pools_repository,
|
pools_repository=pools_repository,
|
||||||
subscription_user_id=subscription_user_id,
|
subscription_user_id=callback_data.user_id,
|
||||||
)
|
)
|
||||||
await state.set_state(NewSubscriptionState.WAITING_FOR_CHOOSE_POOL)
|
await state.set_state(NewSubscriptionState.WAITING_FOR_CHOOSE_POOL)
|
||||||
return
|
return
|
||||||
@@ -679,6 +688,7 @@ async def show_new_subscription_choosing_pool_callback_handler(
|
|||||||
async def choose_new_subscription_pool_callback_handler(
|
async def choose_new_subscription_pool_callback_handler(
|
||||||
callback_query: types.CallbackQuery,
|
callback_query: types.CallbackQuery,
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
|
user: User,
|
||||||
users_repository: UsersRepository,
|
users_repository: UsersRepository,
|
||||||
pools_repository: PoolsRepository,
|
pools_repository: PoolsRepository,
|
||||||
):
|
):
|
||||||
@@ -686,8 +696,8 @@ async def choose_new_subscription_pool_callback_handler(
|
|||||||
subscription_user_id = state_data.get("subscription_user_id")
|
subscription_user_id = state_data.get("subscription_user_id")
|
||||||
subscription_name = state_data.get("subscription_name")
|
subscription_name = state_data.get("subscription_name")
|
||||||
if not all((subscription_user_id, subscription_name)):
|
if not all((subscription_user_id, subscription_name)):
|
||||||
await message.reply(text=constants.ERROR_MESSAGE)
|
await callback_query.answer(text=constants.ERROR_MESSAGE)
|
||||||
await logic.ask_new_subscription_user(message=message)
|
await logic.ask_new_subscription_user(message=callback_query.message)
|
||||||
await state.set_state(NewSubscriptionState.WAITING_FOR_PHONE)
|
await state.set_state(NewSubscriptionState.WAITING_FOR_PHONE)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -696,8 +706,8 @@ async def choose_new_subscription_pool_callback_handler(
|
|||||||
user_id=subscription_user_id,
|
user_id=subscription_user_id,
|
||||||
)
|
)
|
||||||
if subscription_user is None:
|
if subscription_user is None:
|
||||||
await message.reply(text=constants.ERROR_MESSAGE)
|
await callback_query.answer(text=constants.ERROR_MESSAGE)
|
||||||
await logic.ask_new_subscription_user(message=message)
|
await logic.ask_new_subscription_user(message=callback_query.message)
|
||||||
await state.set_state(NewSubscriptionState.WAITING_FOR_PHONE)
|
await state.set_state(NewSubscriptionState.WAITING_FOR_PHONE)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -720,7 +730,7 @@ async def choose_new_subscription_pool_callback_handler(
|
|||||||
return
|
return
|
||||||
|
|
||||||
await state.update_data(
|
await state.update_data(
|
||||||
subscription_pool_id=callback_data.id,
|
subscription_pool_id=str(callback_data.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -960,6 +970,7 @@ async def ask_new_subscription_confirmation_message_handler(
|
|||||||
async def confirm_new_subscription_callback_handler(
|
async def confirm_new_subscription_callback_handler(
|
||||||
callback_query: types.CallbackQuery,
|
callback_query: types.CallbackQuery,
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
|
bot: Bot,
|
||||||
user: User,
|
user: User,
|
||||||
pools_repository: PoolsRepository,
|
pools_repository: PoolsRepository,
|
||||||
subscriptions_repository: SubscriptionsRepository,
|
subscriptions_repository: SubscriptionsRepository,
|
||||||
@@ -1032,3 +1043,65 @@ async def confirm_new_subscription_callback_handler(
|
|||||||
)
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
await state.set_state(MenuState.SUBSCRIPTIONS)
|
await state.set_state(MenuState.SUBSCRIPTIONS)
|
||||||
|
|
||||||
|
subscriptions_generator = subscriptions_repository.get_to_users_subscriptions(
|
||||||
|
to_users_ids={subscription_user_id},
|
||||||
|
have_pool=False,
|
||||||
|
with_from_user=True,
|
||||||
|
with_pool=True,
|
||||||
|
with_pool_owner=True,
|
||||||
|
)
|
||||||
|
async with subscriptions_repository.transaction():
|
||||||
|
coroutines = [
|
||||||
|
logic.notify_user_about_new_subscription_pool(
|
||||||
|
subscription=subscription,
|
||||||
|
user=subscription.from_user,
|
||||||
|
pool=subscription.pool,
|
||||||
|
pool_owner=subscription.pool.owner,
|
||||||
|
bot=bot,
|
||||||
|
)
|
||||||
|
async for subscription in subscriptions_generator
|
||||||
|
]
|
||||||
|
await asyncio.gather(*coroutines, return_exceptions=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def subscribe_to_new_pool_callback_handler(
|
||||||
|
callback_query: types.CallbackQuery,
|
||||||
|
user: User,
|
||||||
|
subscriptions_repository: SubscriptionsRepository,
|
||||||
|
pools_repository: PoolsRepository,
|
||||||
|
):
|
||||||
|
callback_data = NotifyNewPoolCallbackData.unpack(
|
||||||
|
callback_query.data,
|
||||||
|
)
|
||||||
|
|
||||||
|
async with subscriptions_repository.transaction():
|
||||||
|
subscription = await subscriptions_repository.get_subscription(
|
||||||
|
from_user_id=user.id,
|
||||||
|
to_user_id=callback_data.user_id,
|
||||||
|
)
|
||||||
|
async with pools_repository.transaction():
|
||||||
|
pool = await pools_repository.get_pool_by_id(
|
||||||
|
pool_id=callback_data.pool_id,
|
||||||
|
)
|
||||||
|
if not all((subscription, pool)):
|
||||||
|
await callback_query.answer(text=constants.ERROR_MESSAGE)
|
||||||
|
return
|
||||||
|
|
||||||
|
async with subscriptions_repository.transaction():
|
||||||
|
subscription = await subscriptions_repository.set_pool_for_subscription(
|
||||||
|
from_user_id=subscription.from_user_id,
|
||||||
|
to_user_id=subscription.to_user_id,
|
||||||
|
pool_id=pool.id,
|
||||||
|
)
|
||||||
|
if subscription is None:
|
||||||
|
await callback_query.answer(text=constants.ERROR_MESSAGE)
|
||||||
|
return
|
||||||
|
|
||||||
|
await callback_query.answer(text="Вы присоединились к сбору")
|
||||||
|
|
||||||
|
|
||||||
|
async def hide_new_pool_notification(
|
||||||
|
callback_query: types.CallbackQuery,
|
||||||
|
):
|
||||||
|
await callback_query.message.delete()
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from aiogram import types
|
from aiogram import Bot, types
|
||||||
from aiogram.enums import ParseMode
|
from aiogram.enums import ParseMode
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
|
||||||
from pydantic_filters import PagePagination
|
from pydantic_filters import PagePagination
|
||||||
|
|
||||||
from birthday_pool_bot.dto import BankEnum, User
|
from birthday_pool_bot.dto import BankEnum, Pool, Subscription, User
|
||||||
from birthday_pool_bot.repositories.repositories import (
|
from birthday_pool_bot.repositories.repositories import (
|
||||||
PoolsRepository,
|
PoolsRepository,
|
||||||
SubscriptionsRepository,
|
SubscriptionsRepository,
|
||||||
@@ -17,12 +17,14 @@ from .callback_data import (
|
|||||||
MenuCallbackData,
|
MenuCallbackData,
|
||||||
NewSubscriptionCallbackData,
|
NewSubscriptionCallbackData,
|
||||||
NewSubscriptionConfirmCallbackData,
|
NewSubscriptionConfirmCallbackData,
|
||||||
|
NotifyNewPoolCallbackData,
|
||||||
PoolActionEnum,
|
PoolActionEnum,
|
||||||
PoolCallbackData,
|
PoolCallbackData,
|
||||||
PoolsCallbackData,
|
PoolsCallbackData,
|
||||||
PoolsBackCallbackData,
|
PoolsBackCallbackData,
|
||||||
SubscriptionActionEnum,
|
SubscriptionActionEnum,
|
||||||
SubscriptionCallbackData,
|
SubscriptionCallbackData,
|
||||||
|
SubscribeToNewPoolAnswerEnum,
|
||||||
SubscriptionsCallbackData,
|
SubscriptionsCallbackData,
|
||||||
)
|
)
|
||||||
from .exceptions import FlowInternalError
|
from .exceptions import FlowInternalError
|
||||||
@@ -759,3 +761,44 @@ async def ask_new_subscription_confirmation(
|
|||||||
parse_mode=ParseMode.MARKDOWN,
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
reply_markup=reply_markup,
|
reply_markup=reply_markup,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def notify_user_about_new_subscription_pool(
|
||||||
|
subscription: Subscription,
|
||||||
|
user: User,
|
||||||
|
pool: Pool,
|
||||||
|
pool_owner: User,
|
||||||
|
bot: Bot,
|
||||||
|
):
|
||||||
|
if user.telegram_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
f"Создан новый сбор для {subscription.name}\n\n"
|
||||||
|
"Собирает пользователь: **\n\n"
|
||||||
|
"Сбор денег происходит на:\n"
|
||||||
|
f"_Телефон_: {pool.payment_data.phone}\n"
|
||||||
|
f"_Банк_: {pool.payment_data.bank}\n\n"
|
||||||
|
)
|
||||||
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
keyboard.button(
|
||||||
|
text="Подписаться",
|
||||||
|
callback_data=NotifyNewPoolCallbackData(
|
||||||
|
action=SubscribeToNewPoolAnswerEnum.YES,
|
||||||
|
).pack(),
|
||||||
|
)
|
||||||
|
keyboard.button(
|
||||||
|
text="Закрыть",
|
||||||
|
callback_data=NotifyNewPoolCallbackData(
|
||||||
|
action=SubscribeToNewPoolAnswerEnum.NO,
|
||||||
|
).pack()
|
||||||
|
)
|
||||||
|
keyboard.adjust(2)
|
||||||
|
reply_markup = keyboard.as_markup()
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=user.telegram_id,
|
||||||
|
text=text,
|
||||||
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from .auth import AuthMiddleware
|
from .auth import AuthMiddleware
|
||||||
from .depends import DependsMiddleware
|
from .depends import DependsMiddleware
|
||||||
|
from .exception_handler import ExceptionHandlerMiddleware
|
||||||
from .typing import TypingMiddleware
|
from .typing import TypingMiddleware
|
||||||
|
|
||||||
|
|
||||||
@@ -8,6 +9,8 @@ __all__ = (
|
|||||||
"AuthMiddleware",
|
"AuthMiddleware",
|
||||||
# depends
|
# depends
|
||||||
"DependsMiddleware",
|
"DependsMiddleware",
|
||||||
|
# exception_handler,
|
||||||
|
"ExceptionHandlerMiddleware",
|
||||||
# typing
|
# typing
|
||||||
"TypingMiddleware",
|
"TypingMiddleware",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import traceback
|
||||||
|
from typing import Any, Awaitable, Callable
|
||||||
|
|
||||||
|
import aiogram
|
||||||
|
from aiogram import types
|
||||||
|
from aiogram.enums import ParseMode
|
||||||
|
|
||||||
|
from birthday_pool_bot.telegram_bot.ui import constants
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionHandlerMiddleware(aiogram.BaseMiddleware):
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
handler: Callable[[types.TelegramObject, dict[str, Any]], Awaitable[Any]],
|
||||||
|
event: types.TelegramObject,
|
||||||
|
data: dict[str, Any],
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
await handler(event, data)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
if isinstance(event, types.Message):
|
||||||
|
await event.reply(
|
||||||
|
text=constants.ERROR_MESSAGE,
|
||||||
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
|
)
|
||||||
|
elif isinstance(event, types.CallbackQuery) is not None:
|
||||||
|
await event.answer(
|
||||||
|
text=constants.ERROR_MESSAGE,
|
||||||
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
|
)
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from aiogram import types
|
|
||||||
from aiogram.types import User as TelegramUser
|
from aiogram.types import User as TelegramUser
|
||||||
from pydantic_core import PydanticCustomError
|
from pydantic_core import PydanticCustomError
|
||||||
from pydantic_extra_types.phone_numbers import PhoneNumber
|
from pydantic_extra_types.phone_numbers import PhoneNumber
|
||||||
|
|||||||
Reference in New Issue
Block a user