gifts for more users in admin-panel
This commit is contained in:
+5
-1
@@ -16,6 +16,8 @@ async def store_gift_link(
|
||||
expiry_time: datetime,
|
||||
gift_link: str,
|
||||
tariff_id: int | None = None,
|
||||
is_unlimited: bool = False,
|
||||
max_usages: int | None = None,
|
||||
):
|
||||
try:
|
||||
stmt = insert(Gift).values(
|
||||
@@ -28,10 +30,12 @@ async def store_gift_link(
|
||||
created_at=datetime.utcnow(),
|
||||
is_used=False,
|
||||
tariff_id=tariff_id,
|
||||
is_unlimited=is_unlimited,
|
||||
max_usages=max_usages,
|
||||
)
|
||||
await session.execute(stmt)
|
||||
await session.commit()
|
||||
logger.info(f"🎁 Подарок {gift_id} сохранён (tariff_id={tariff_id})")
|
||||
logger.info(f"🎁 Подарок {gift_id} сохранён (tariff_id={tariff_id}, max_usages={max_usages})")
|
||||
return True
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"❌ Ошибка при сохранении подарка {gift_id}: {e}")
|
||||
|
||||
@@ -152,9 +152,19 @@ class Gift(DictLikeMixin, Base):
|
||||
gift_link = Column(String)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
is_used = Column(Boolean, default=False)
|
||||
is_unlimited = Column(Boolean, default=False)
|
||||
max_usages = Column(Integer, nullable=True)
|
||||
tariff_id: Mapped[int | None] = mapped_column(ForeignKey("tariffs.id"))
|
||||
|
||||
|
||||
class GiftUsage(DictLikeMixin, Base):
|
||||
__tablename__ = "gift_usages"
|
||||
|
||||
gift_id = Column(String, ForeignKey("gifts.gift_id"), primary_key=True)
|
||||
tg_id = Column(BigInteger, primary_key=True)
|
||||
used_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class ManualBan(DictLikeMixin, Base):
|
||||
__tablename__ = "manual_bans"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from .servers import router as servers_router
|
||||
from .stats import router as stats_router
|
||||
from .tariffs import router as tariffs_router
|
||||
from .users import router as users_router
|
||||
from .gifts import router as gifts_router
|
||||
|
||||
router = Router(name="admins_main_router")
|
||||
|
||||
@@ -32,4 +33,5 @@ router.include_routers(
|
||||
bans_router,
|
||||
ads_router,
|
||||
tariffs_router,
|
||||
gifts_router
|
||||
)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
__all__ = ("router",)
|
||||
|
||||
from .gifts_handler import router
|
||||
@@ -0,0 +1,207 @@
|
||||
from aiogram import F, Router, types, Bot
|
||||
from aiogram.types import CallbackQuery
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, delete, func
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ..panel.keyboard import AdminPanelCallback
|
||||
from .keyboard import build_admin_gifts_kb, build_gifts_list_kb
|
||||
from database.models import Tariff, Gift, GiftUsage
|
||||
from handlers.payments.gift import finalize_gift
|
||||
|
||||
router = Router()
|
||||
|
||||
class GiftCreationState(StatesGroup):
|
||||
waiting_for_gift_limit = State()
|
||||
waiting_for_limit_input_or_unlimited = State()
|
||||
|
||||
@router.callback_query(AdminPanelCallback.filter(F.action == "gifts"))
|
||||
async def admin_gift_menu(callback: CallbackQuery):
|
||||
await callback.message.edit_text(
|
||||
text="🎁 <b>Подарки</b>\nВыберите, что хотите сделать:",
|
||||
reply_markup=build_admin_gifts_kb()
|
||||
)
|
||||
|
||||
@router.callback_query(F.data == "admin_gift_create")
|
||||
async def admin_create_gift_step1(callback: CallbackQuery, session: AsyncSession):
|
||||
result = await session.execute(
|
||||
select(Tariff)
|
||||
.where(Tariff.group_code == "gifts", Tariff.is_active == True)
|
||||
.order_by(Tariff.id)
|
||||
)
|
||||
tariffs = result.scalars().all()
|
||||
|
||||
if not tariffs:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
|
||||
await callback.message.edit_text("❌ Нет активных тарифов в группе 'gifts'.", reply_markup=builder.as_markup())
|
||||
return
|
||||
|
||||
kb = InlineKeyboardBuilder()
|
||||
for t in tariffs:
|
||||
kb.button(text=f"{t.name} – {t.duration_days // 30} мес", callback_data=f"admin_gift_select|{t.id}")
|
||||
kb.row(types.InlineKeyboardButton(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack()
|
||||
))
|
||||
|
||||
await callback.message.edit_text(
|
||||
"🎁 Выберите тариф для подарка:",
|
||||
reply_markup=kb.as_markup()
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("admin_gift_select|"))
|
||||
async def handle_tariff_selection(callback: CallbackQuery, state: FSMContext):
|
||||
tariff_id = int(callback.data.split("|")[1])
|
||||
await state.update_data(tariff_id=tariff_id)
|
||||
await state.set_state(GiftCreationState.waiting_for_limit_input_or_unlimited)
|
||||
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="🔙 Назад", callback_data="admin_gift_create")
|
||||
await callback.message.edit_text(
|
||||
"🔢 Введите максимальное количество активаций подарка:",
|
||||
reply_markup=kb.as_markup()
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "gift_limit_unlimited")
|
||||
async def handle_unlimited_gift(callback: CallbackQuery, state: FSMContext, bot: Bot):
|
||||
data = await state.get_data()
|
||||
session: AsyncSession = callback.bot["session"]
|
||||
await state.clear()
|
||||
await finalize_gift(callback.message, session, bot, data, is_unlimited=True)
|
||||
|
||||
@router.message(GiftCreationState.waiting_for_limit_input_or_unlimited)
|
||||
async def handle_limited_gift_input(message: types.Message, session: AsyncSession, state: FSMContext, bot: Bot):
|
||||
try:
|
||||
max_usages = int(message.text.strip())
|
||||
if max_usages <= 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
await message.answer("❌ Введите корректное положительное число.")
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
data["max_usages"] = max_usages
|
||||
await state.clear()
|
||||
await finalize_gift(message, session, bot, data, is_unlimited=False)
|
||||
|
||||
@router.callback_query(F.data == "admin_gifts_all")
|
||||
async def show_gifts_page(callback: CallbackQuery, session: AsyncSession):
|
||||
await show_gift_list(callback, session, page=1)
|
||||
|
||||
@router.callback_query(F.data.startswith("gifts_page|"))
|
||||
async def paginate_gifts(callback: CallbackQuery, session: AsyncSession):
|
||||
page = int(callback.data.split("|")[1])
|
||||
await show_gift_list(callback, session, page)
|
||||
|
||||
|
||||
async def show_gift_list(callback: CallbackQuery, session: AsyncSession, page: int):
|
||||
limit = 10
|
||||
offset = (page - 1) * limit
|
||||
|
||||
stmt = select(Gift).order_by(Gift.created_at.desc()).offset(offset).limit(limit)
|
||||
result = await session.execute(stmt)
|
||||
gifts = result.scalars().all()
|
||||
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if not gifts:
|
||||
builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
|
||||
await callback.message.edit_text(
|
||||
"❌ Подарки не найдены.",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
return
|
||||
|
||||
keyboard = build_gifts_list_kb(gifts, page, total=len(gifts))
|
||||
|
||||
builder.inline_keyboard.extend(keyboard.inline_keyboard)
|
||||
builder.row(types.InlineKeyboardButton(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack()))
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"🎁 <b>Список подарков</b>\nСтраница {page}:",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
|
||||
async def show_gift_list(callback: CallbackQuery, session: AsyncSession, page: int):
|
||||
limit = 10
|
||||
offset = (page - 1) * limit
|
||||
|
||||
stmt = select(Gift).order_by(Gift.created_at.desc()).offset(offset).limit(limit)
|
||||
result = await session.execute(stmt)
|
||||
gifts = result.scalars().all()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if not gifts:
|
||||
builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
|
||||
await callback.message.edit_text(
|
||||
"❌ Подарки не найдены.",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
return
|
||||
|
||||
keyboard = build_gifts_list_kb(gifts, page, total=len(gifts))
|
||||
|
||||
for row in keyboard.inline_keyboard:
|
||||
builder.row(*row)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"🎁 <b>Список подарков</b>\nСтраница {page}:",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("gift_view|"))
|
||||
async def view_gift(callback: CallbackQuery, session: AsyncSession):
|
||||
gift_id = callback.data.split("|")[1]
|
||||
|
||||
result = await session.execute(select(Gift).where(Gift.gift_id == gift_id))
|
||||
gift = result.scalar_one_or_none()
|
||||
|
||||
if not gift:
|
||||
await callback.message.edit_text("❌ Подарок не найден.")
|
||||
return
|
||||
|
||||
usage_result = await session.execute(
|
||||
select(func.count()).select_from(GiftUsage).where(GiftUsage.gift_id == gift_id)
|
||||
)
|
||||
used_count = usage_result.scalar_one()
|
||||
|
||||
usage_text = f"{used_count}/{gift.max_usages}" if gift.max_usages else "∞"
|
||||
|
||||
text = (
|
||||
f"🎁 <b>Подарок</b>\n"
|
||||
f"ID: <code>{gift.gift_id}</code>\n"
|
||||
f"Месяцев: <b>{gift.selected_months}</b>\n"
|
||||
f"Активаций: <b>{usage_text}</b>\n"
|
||||
f"Срок действия: <i>{gift.expiry_time.strftime('%d.%m.%Y')}</i>\n"
|
||||
f"<b>Ссылка для активации:</b>\n<blockquote>{gift.gift_link}</blockquote>"
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🗑 Удалить", callback_data=f"gift_delete|{gift_id}")
|
||||
builder.button(text="🔙 Назад", callback_data="admin_gifts_all")
|
||||
|
||||
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("gift_delete|"))
|
||||
async def delete_gift(callback: CallbackQuery, session: AsyncSession):
|
||||
gift_id = callback.data.split("|")[1]
|
||||
|
||||
await session.execute(delete(GiftUsage).where(GiftUsage.gift_id == gift_id))
|
||||
await session.execute(delete(Gift).where(Gift.gift_id == gift_id))
|
||||
await session.commit()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔙 Назад к списку", callback_data="admin_gifts_all")
|
||||
|
||||
await callback.message.edit_text(
|
||||
"✅ Подарок удалён.",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
@@ -0,0 +1,55 @@
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from database.models import Tariff, Gift
|
||||
from ..panel.keyboard import AdminPanelCallback
|
||||
|
||||
from handlers.buttons import BACK
|
||||
|
||||
def build_admin_gifts_kb() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🎁 Создать подарок", callback_data="admin_gift_create")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📦 Все подарки", callback_data="admin_gifts_all"),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=BACK, callback_data="admin")
|
||||
)
|
||||
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_gift_tariffs_kb(tariffs: list[Tariff]) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
for tariff in tariffs:
|
||||
builder.button(
|
||||
text=f"{tariff.name} — {tariff.duration_days // 30} мес.",
|
||||
callback_data=f"admin_gift_confirm|{tariff.id}",
|
||||
)
|
||||
builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def build_gifts_list_kb(gifts: list[Gift], page: int, total: int) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for gift in gifts:
|
||||
button_text = f"{gift.gift_id[:6]}... — {gift.selected_months} мес."
|
||||
builder.button(
|
||||
text=button_text,
|
||||
callback_data=f"gift_view|{gift.gift_id}",
|
||||
)
|
||||
|
||||
nav = []
|
||||
if page > 1:
|
||||
nav.append(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"gifts_page|{page - 1}"))
|
||||
if len(gifts) == 10:
|
||||
nav.append(InlineKeyboardButton(text="➡️ Далее", callback_data=f"gifts_page|{page + 1}"))
|
||||
|
||||
builder.row(*nav)
|
||||
builder.button(text="🔙 Назад", callback_data=AdminPanelCallback(action="gifts").pack())
|
||||
|
||||
return builder.as_markup()
|
||||
@@ -49,6 +49,11 @@ def build_panel_kb() -> InlineKeyboardMarkup:
|
||||
text="💸 Тарифы", callback_data=AdminPanelCallback(action="tariffs").pack()
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🎁 Подарки", callback_data=AdminPanelCallback(action="gifts").pack()
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📊 Статистика",
|
||||
@@ -59,7 +64,7 @@ def build_panel_kb() -> InlineKeyboardMarkup:
|
||||
),
|
||||
)
|
||||
builder.button(text=MAIN_MENU, callback_data="profile")
|
||||
builder.adjust(1, 1, 2, 2, 1, 2, 1)
|
||||
builder.adjust(1, 1, 2, 2, 1, 1, 2, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ async def handle_admin_callback_query(callback_query: CallbackQuery, state: FSMC
|
||||
|
||||
|
||||
@router.callback_query(F.data == "admin", IsAdminFilter())
|
||||
async def handle_admin_callback_query(callback_query: CallbackQuery, state: FSMContext):
|
||||
await handle_admin_message(callback_query.message, state)
|
||||
async def handle_admin_callback_query_simple(callback_query: CallbackQuery, state: FSMContext):
|
||||
await handle_admin_callback_query(callback_query, state)
|
||||
|
||||
|
||||
@router.message(Command("admin"), IsAdminFilter())
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user