Files
malenia/handlers/ads.py
hexdev 606aa4b89f 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
2026-04-15 20:19:07 +07:00

70 lines
2.2 KiB
Python

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)))