Files
Solo_bot/database/gifts.py
T
2026-04-14 07:19:14 +00:00

116 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from datetime import datetime
from sqlalchemy import func, insert, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from database.access.resolution import resolve_user_optional
from database.models import Gift, GiftUsage
from logger import logger
async def store_gift_link(
session: AsyncSession,
gift_id: str,
sender_legacy_ref: int,
selected_months: int,
expiry_time: datetime,
gift_link: str,
tariff_id: int | None = None,
is_unlimited: bool = False,
max_usages: int | None = None,
selected_device_limit: int | None = None,
selected_traffic_gb: int | None = None,
selected_price_rub: int | None = None,
) -> bool:
u = await resolve_user_optional(session, sender_legacy_ref)
if u is None:
raise ValueError(f"sender not found for gift: {sender_legacy_ref}")
stmt = insert(Gift).values(
gift_id=gift_id,
sender_user_id=u.id,
sender_tg_id=u.tg_id,
recipient_user_id=None,
selected_months=selected_months,
expiry_time=expiry_time,
gift_link=gift_link,
created_at=datetime.utcnow(),
is_used=False,
tariff_id=tariff_id,
is_unlimited=is_unlimited,
max_usages=max_usages,
selected_device_limit=selected_device_limit,
selected_traffic_gb=selected_traffic_gb,
selected_price_rub=selected_price_rub,
)
await session.execute(stmt)
logger.info(
f"🎁 Подарок {gift_id} сохранён "
f"(tariff_id={tariff_id}, max_usages={max_usages}, "
f"device={selected_device_limit}, traffic={selected_traffic_gb}, price={selected_price_rub})"
)
return True
async def get_gift_locked(session: AsyncSession, gift_id: str) -> Gift | None:
"""SELECT FOR UPDATE по gift_id — берёт row-lock для atomic redemption.
Используется в `services.gifts.redeem_gift` чтобы два параллельных запроса
на активацию одного и того же подарка не смогли обойти проверку `is_used`.
"""
result = await session.execute(select(Gift).where(Gift.gift_id == gift_id).with_for_update())
return result.scalar_one_or_none()
async def get_gift_usage(session: AsyncSession, gift_id: str, user_id: int) -> GiftUsage | None:
"""Возвращает запись об использовании подарка конкретным пользователем, если есть."""
result = await session.execute(
select(GiftUsage).where(
GiftUsage.gift_id == gift_id,
GiftUsage.user_id == user_id,
)
)
return result.scalar_one_or_none()
async def count_gift_usages(session: AsyncSession, gift_id: str) -> int:
"""Сколько раз подарок был активирован (для `is_unlimited=False` с лимитом)."""
result = await session.execute(select(func.count()).select_from(GiftUsage).where(GiftUsage.gift_id == gift_id))
return int(result.scalar_one() or 0)
async def record_gift_usage(
session: AsyncSession,
gift_id: str,
user_id: int,
tg_id: int | None,
) -> None:
"""Вставляет запись о применении подарка. Композитный ключ (gift_id, user_id)."""
await session.execute(
insert(GiftUsage).values(
gift_id=gift_id,
user_id=user_id,
tg_id=tg_id,
)
)
async def mark_gift_fully_redeemed(
session: AsyncSession,
gift_id: str,
recipient_user_id: int,
recipient_tg_id: int | None,
) -> None:
"""Помечает подарок как полностью использованный (is_used=True) и фиксирует получателя.
Вызывается для non-unlimited подарков, когда набрали max_usages.
"""
await session.execute(
update(Gift)
.where(Gift.gift_id == gift_id)
.values(
is_used=True,
recipient_user_id=recipient_user_id,
recipient_tg_id=recipient_tg_id,
)
)