feat: add admin promotion broadcast system and psql health check
- Add `/push` command for admins to broadcast messages to all users with approve/decline confirmation flow (handlers/ads.py) - Add common handler for undefined commands and delete callback - Add `UserRepository.get_users()` to fetch all users - Add `UserService.send_promotion_to_users()` with error logging - Add promotion confirmation keyboard and FSM state - Add PostgreSQL availability check in startup.sh before migrations - Add new text constants for promotion flow
This commit is contained in:
69
handlers/ads.py
Normal file
69
handlers/ads.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import settings
|
||||
from keyboards.admins import promotion_confirmation
|
||||
from keyboards.client import build_return_menu
|
||||
from schemas.di import DependenciesDTO
|
||||
from schemas.promotions import NewPromotionContext
|
||||
from states.admins import AdminStorage
|
||||
from texts import (
|
||||
ADMIN_PROMOTION_CONFIRMATION,
|
||||
ADMIN_PROMOTION_INIT,
|
||||
ADMIN_STANDARD_FALLBACK,
|
||||
SUCCESSFULLY_SENT_PROMOTION,
|
||||
)
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(
|
||||
F.chat.id == settings.admin_group_id, F.message_thread_id.is_(None), Command("push")
|
||||
)
|
||||
async def push_cmd(msg: Message, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
ctx = NewPromotionContext(creator_id=msg.from_user.id)
|
||||
await msg.delete()
|
||||
await msg.answer(ADMIN_PROMOTION_INIT, reply_markup=build_return_menu("delete", "❌"))
|
||||
|
||||
await state.set_state(AdminStorage.promotion_msg)
|
||||
await state.set_data({"ctx": ctx})
|
||||
|
||||
|
||||
@router.message(F.chat.id == settings.admin_group_id, AdminStorage.promotion_msg)
|
||||
async def send_promotion(msg: Message, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
|
||||
ctx: NewPromotionContext | None = data.get("ctx")
|
||||
if not ctx:
|
||||
await msg.answer(ADMIN_STANDARD_FALLBACK)
|
||||
return
|
||||
|
||||
ctx.message = msg
|
||||
await msg.reply(ADMIN_PROMOTION_CONFIRMATION, reply_markup=promotion_confirmation)
|
||||
|
||||
await state.set_state(AdminStorage.promotion_msg)
|
||||
await state.set_data({"ctx": ctx})
|
||||
|
||||
|
||||
@router.callback_query(AdminStorage.promotion_msg, F.data.in_(["approve", "decline"]))
|
||||
async def promotion_action(
|
||||
cb: CallbackQuery, state: FSMContext, session: AsyncSession, deps: DependenciesDTO
|
||||
):
|
||||
data = await state.get_data()
|
||||
await state.clear()
|
||||
|
||||
await cb.message.delete()
|
||||
if cb.data == "decline":
|
||||
return
|
||||
ctx: NewPromotionContext | None = data.get("ctx")
|
||||
if not (ctx and ctx.creator_id and ctx.message):
|
||||
await cb.message.answer(ADMIN_STANDARD_FALLBACK)
|
||||
return
|
||||
|
||||
sent = await deps.user_service.send_promotion_to_users(session, ctx.message)
|
||||
await cb.message.answer(SUCCESSFULLY_SENT_PROMOTION.format(user_count=len(sent)))
|
||||
Reference in New Issue
Block a user