This commit is contained in:
@@ -0,0 +1,609 @@
|
||||
import html
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user
|
||||
from app.api.schemas import LotteryCreate, LotteryResponse, LotteryUpdate, PrizeResponse, WinnerResponse
|
||||
from app.core.database import get_db
|
||||
from app.models.lottery import Lottery, LotteryStatus
|
||||
from app.models.participant import Participant
|
||||
from app.models.prize import Prize
|
||||
from app.models.user import User
|
||||
from app.models.winner import Winner
|
||||
from app.services.draw_service import execute_draw
|
||||
|
||||
router = APIRouter(prefix="/lotteries", tags=["lotteries"])
|
||||
|
||||
|
||||
async def _revoke_invite_links(db: AsyncSession, chat_ids: set[int]) -> None:
|
||||
from app.bot.router import bot
|
||||
from app.models.managed_chat import ManagedChat
|
||||
for chat_id in chat_ids:
|
||||
result = await db.execute(select(ManagedChat).where(ManagedChat.id == chat_id))
|
||||
managed = result.scalar_one_or_none()
|
||||
if managed and managed.invite_link:
|
||||
try:
|
||||
await bot.revoke_chat_invite_link(chat_id, managed.invite_link)
|
||||
except Exception:
|
||||
pass
|
||||
managed.invite_link = None
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def _build_lottery_response(
|
||||
db: AsyncSession, lottery: Lottery, current_user_id: int | None = None
|
||||
) -> LotteryResponse:
|
||||
count_result = await db.execute(
|
||||
select(func.count(Participant.id)).where(Participant.lottery_id == lottery.id)
|
||||
)
|
||||
participant_count = count_result.scalar() or 0
|
||||
|
||||
prize_result = await db.execute(
|
||||
select(Prize).where(Prize.lottery_id == lottery.id).order_by(Prize.sort_order)
|
||||
)
|
||||
prizes = prize_result.scalars().all()
|
||||
total_prizes = sum(p.quantity for p in prizes)
|
||||
|
||||
is_winner: bool | None = None
|
||||
if current_user_id is not None and lottery.status == LotteryStatus.FINISHED:
|
||||
w = await db.execute(
|
||||
select(Winner).where(Winner.lottery_id == lottery.id, Winner.user_id == current_user_id).limit(1)
|
||||
)
|
||||
is_winner = w.scalar_one_or_none() is not None
|
||||
|
||||
return LotteryResponse(
|
||||
id=lottery.id,
|
||||
creator_id=lottery.creator_id,
|
||||
title=lottery.title,
|
||||
description=lottery.description,
|
||||
contact_info=lottery.contact_info,
|
||||
status=lottery.status,
|
||||
target_chat_id=lottery.target_chat_id,
|
||||
target_chat_title=lottery.target_chat_title,
|
||||
require_membership=lottery.require_membership,
|
||||
required_chat_ids=lottery.required_chat_ids or [],
|
||||
required_chat_titles=lottery.required_chat_titles or [],
|
||||
required_chat_types=lottery.required_chat_types or [],
|
||||
required_chat_usernames=lottery.required_chat_usernames or [],
|
||||
announcement_targets=lottery.announcement_targets or [],
|
||||
allow_multiple_wins=lottery.allow_multiple_wins,
|
||||
invite_link_chat_ids=lottery.invite_link_chat_ids or [],
|
||||
required_chat_passphrases=lottery.required_chat_passphrases or [],
|
||||
max_participants=lottery.max_participants,
|
||||
draw_at=lottery.draw_at,
|
||||
auto_draw_count=lottery.auto_draw_count,
|
||||
creator_timezone=lottery.creator_timezone,
|
||||
participant_count=participant_count,
|
||||
prize_count=len(prizes),
|
||||
total_prizes=total_prizes,
|
||||
created_at=lottery.created_at,
|
||||
drawn_at=lottery.drawn_at,
|
||||
draw_trigger=lottery.draw_trigger,
|
||||
prizes=[PrizeResponse.model_validate(p) for p in prizes],
|
||||
is_winner=is_winner,
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=list[LotteryResponse])
|
||||
async def list_my_lotteries(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> list[LotteryResponse]:
|
||||
result = await db.execute(
|
||||
select(Lottery)
|
||||
.where(Lottery.creator_id == current_user.id)
|
||||
.order_by(Lottery.created_at.desc())
|
||||
)
|
||||
lotteries = result.scalars().all()
|
||||
return [await _build_lottery_response(db, l, current_user.id) for l in lotteries]
|
||||
|
||||
|
||||
@router.get("/joined", response_model=list[LotteryResponse])
|
||||
async def list_joined_lotteries(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> list[LotteryResponse]:
|
||||
result = await db.execute(
|
||||
select(Lottery)
|
||||
.join(Participant, Participant.lottery_id == Lottery.id)
|
||||
.where(Participant.user_id == current_user.id)
|
||||
.order_by(Lottery.created_at.desc())
|
||||
)
|
||||
lotteries = result.scalars().all()
|
||||
return [await _build_lottery_response(db, l, current_user.id) for l in lotteries]
|
||||
|
||||
|
||||
@router.get("/active", response_model=list[LotteryResponse])
|
||||
async def list_active_lotteries(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> list[LotteryResponse]:
|
||||
"""Active lotteries created by others that the user can join."""
|
||||
result = await db.execute(
|
||||
select(Lottery)
|
||||
.where(Lottery.status == LotteryStatus.ACTIVE)
|
||||
.where(Lottery.creator_id != current_user.id)
|
||||
.order_by(Lottery.created_at.desc())
|
||||
)
|
||||
lotteries = result.scalars().all()
|
||||
return [await _build_lottery_response(db, l) for l in lotteries]
|
||||
|
||||
|
||||
@router.post("", response_model=LotteryResponse)
|
||||
async def create_lottery(
|
||||
data: LotteryCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> LotteryResponse:
|
||||
lottery = Lottery(creator_id=current_user.id, **data.model_dump())
|
||||
db.add(lottery)
|
||||
await db.commit()
|
||||
await db.refresh(lottery)
|
||||
return await _build_lottery_response(db, lottery)
|
||||
|
||||
|
||||
@router.get("/{lottery_id}", response_model=LotteryResponse)
|
||||
async def get_lottery(
|
||||
lottery_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> LotteryResponse:
|
||||
result = await db.execute(select(Lottery).where(Lottery.id == lottery_id))
|
||||
lottery = result.scalar_one_or_none()
|
||||
if lottery is None:
|
||||
raise HTTPException(404, "Lottery not found")
|
||||
return await _build_lottery_response(db, lottery)
|
||||
|
||||
|
||||
@router.put("/{lottery_id}", response_model=LotteryResponse)
|
||||
async def update_lottery(
|
||||
lottery_id: uuid.UUID,
|
||||
data: LotteryUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> LotteryResponse:
|
||||
result = await db.execute(select(Lottery).where(Lottery.id == lottery_id))
|
||||
lottery = result.scalar_one_or_none()
|
||||
|
||||
if lottery is None:
|
||||
raise HTTPException(404, "Lottery not found")
|
||||
if lottery.creator_id != current_user.id:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
if lottery.status in (LotteryStatus.FINISHED, LotteryStatus.CANCELLED):
|
||||
raise HTTPException(400, "Cannot edit a finished or cancelled lottery")
|
||||
|
||||
old_invite_ids: set[int] = set(lottery.invite_link_chat_ids or [])
|
||||
|
||||
for field, value in data.model_dump(exclude_unset=True).items():
|
||||
setattr(lottery, field, value)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(lottery)
|
||||
|
||||
new_invite_ids: set[int] = set(lottery.invite_link_chat_ids or [])
|
||||
removed_ids = old_invite_ids - new_invite_ids
|
||||
if removed_ids:
|
||||
await _revoke_invite_links(db, removed_ids)
|
||||
|
||||
# Auto-sync announcement messages whenever the lottery is edited
|
||||
if lottery.announcement_targets or lottery.announcement_message_id:
|
||||
try:
|
||||
await sync_announcement(db, lottery, force_send=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return await _build_lottery_response(db, lottery)
|
||||
|
||||
|
||||
@router.post("/{lottery_id}/activate", response_model=LotteryResponse)
|
||||
async def activate_lottery(
|
||||
lottery_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> LotteryResponse:
|
||||
result = await db.execute(select(Lottery).where(Lottery.id == lottery_id))
|
||||
lottery = result.scalar_one_or_none()
|
||||
|
||||
if lottery is None:
|
||||
raise HTTPException(404, "Lottery not found")
|
||||
if lottery.creator_id != current_user.id:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
if lottery.status != LotteryStatus.DRAFT:
|
||||
raise HTTPException(400, "Lottery is already activated")
|
||||
|
||||
prize_result = await db.execute(
|
||||
select(Prize).where(Prize.lottery_id == lottery_id).limit(1)
|
||||
)
|
||||
if prize_result.scalar_one_or_none() is None:
|
||||
raise HTTPException(400, "Add at least one prize before activating")
|
||||
|
||||
lottery.status = LotteryStatus.ACTIVE
|
||||
await db.commit()
|
||||
await db.refresh(lottery)
|
||||
return await _build_lottery_response(db, lottery)
|
||||
|
||||
|
||||
@router.post("/{lottery_id}/draw", response_model=list[WinnerResponse])
|
||||
async def draw_lottery(
|
||||
lottery_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> list[WinnerResponse]:
|
||||
result = await db.execute(select(Lottery).where(Lottery.id == lottery_id))
|
||||
lottery = result.scalar_one_or_none()
|
||||
|
||||
if lottery is None:
|
||||
raise HTTPException(404, "Lottery not found")
|
||||
if lottery.creator_id != current_user.id:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
if lottery.status != LotteryStatus.ACTIVE:
|
||||
raise HTTPException(400, "Lottery is not active")
|
||||
|
||||
try:
|
||||
winners_data = await execute_draw(db, lottery_id, trigger="manual")
|
||||
except ValueError as e:
|
||||
raise HTTPException(400, str(e))
|
||||
|
||||
# Announce winners to all announcement targets
|
||||
targets = lottery.announcement_targets or (
|
||||
[{"chat_id": lottery.target_chat_id, "chat_title": lottery.target_chat_title or "", "message_id": lottery.announcement_message_id}]
|
||||
if lottery.target_chat_id else []
|
||||
)
|
||||
if targets and winners_data:
|
||||
from app.bot.router import bot
|
||||
from aiogram.types import ReplyParameters
|
||||
lines = []
|
||||
for w in winners_data:
|
||||
full_name = html.escape(
|
||||
w["first_name"] + (f" {w['last_name']}" if w.get("last_name") else "")
|
||||
)
|
||||
safe_name = f'<a href="tg://user?id={w["user_id"]}">{full_name}</a>'
|
||||
safe_prize = html.escape(w["prize_name"])
|
||||
lines.append(f"🏆 {safe_name} - {safe_prize}")
|
||||
text = (
|
||||
f"🎉 <b>Draw Results: {html.escape(lottery.title)}</b>\n\n"
|
||||
+ "\n".join(lines)
|
||||
+ "\n\nCongratulations to all winners! 🎊"
|
||||
)
|
||||
for t in targets:
|
||||
try:
|
||||
reply = (
|
||||
ReplyParameters(message_id=t["message_id"], allow_sending_without_reply=True)
|
||||
if t.get("message_id")
|
||||
else None
|
||||
)
|
||||
await bot.send_message(
|
||||
t["chat_id"],
|
||||
text,
|
||||
parse_mode="HTML",
|
||||
reply_parameters=reply,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if winners_data:
|
||||
from app.services.scheduler import notify_winners
|
||||
import asyncio
|
||||
asyncio.create_task(notify_winners(lottery.title, winners_data, lottery.contact_info, lottery_id=lottery.id))
|
||||
|
||||
return [WinnerResponse(**w) for w in winners_data]
|
||||
|
||||
|
||||
@router.get("/{lottery_id}/winners", response_model=list[WinnerResponse])
|
||||
async def get_winners(
|
||||
lottery_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> list[WinnerResponse]:
|
||||
result = await db.execute(
|
||||
select(Winner, User)
|
||||
.join(User, Winner.user_id == User.id)
|
||||
.where(Winner.lottery_id == lottery_id)
|
||||
.order_by(Winner.drawn_at)
|
||||
)
|
||||
return [
|
||||
WinnerResponse(
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
first_name=user.first_name,
|
||||
last_name=user.last_name,
|
||||
prize_name=winner.prize_name,
|
||||
drawn_at=winner.drawn_at,
|
||||
)
|
||||
for winner, user in result.all()
|
||||
]
|
||||
|
||||
|
||||
async def _build_announce_text(db: AsyncSession, lottery: Lottery, include_count: bool = True) -> str:
|
||||
prize_result = await db.execute(
|
||||
select(Prize).where(Prize.lottery_id == lottery.id).order_by(Prize.sort_order)
|
||||
)
|
||||
prizes = prize_result.scalars().all()
|
||||
count_result = await db.execute(
|
||||
select(func.count(Participant.id)).where(Participant.lottery_id == lottery.id)
|
||||
)
|
||||
participant_count = count_result.scalar() or 0
|
||||
|
||||
text = f"🎰 <b>{html.escape(lottery.title)}</b>"
|
||||
|
||||
if lottery.description:
|
||||
text += f"\n\n{html.escape(lottery.description)}"
|
||||
|
||||
# Membership requirements
|
||||
required_ids = lottery.required_chat_ids or []
|
||||
required_titles = lottery.required_chat_titles or []
|
||||
required_usernames = lottery.required_chat_usernames or []
|
||||
required_passphrases = lottery.required_chat_passphrases or []
|
||||
invite_link_ids = set(lottery.invite_link_chat_ids or [])
|
||||
|
||||
if lottery.require_membership and required_ids:
|
||||
from app.models.managed_chat import ManagedChat
|
||||
mc_result = await db.execute(select(ManagedChat).where(ManagedChat.id.in_(required_ids)))
|
||||
managed_chats = {mc.id: mc for mc in mc_result.scalars().all()}
|
||||
|
||||
text += "\n\n📮 <b>Requirements:</b>"
|
||||
for i, chat_id in enumerate(required_ids):
|
||||
title = required_titles[i] if i < len(required_titles) else str(chat_id)
|
||||
username = required_usernames[i] if i < len(required_usernames) else None
|
||||
mc = managed_chats.get(chat_id)
|
||||
|
||||
if chat_id in invite_link_ids and mc and mc.invite_link:
|
||||
link: str | None = mc.invite_link
|
||||
elif username:
|
||||
link = f"https://t.me/{username}"
|
||||
elif mc:
|
||||
channel_id = str(abs(chat_id))[3:]
|
||||
link = f"https://t.me/c/{channel_id}/{mc.last_message_id or 1}"
|
||||
else:
|
||||
link = None
|
||||
|
||||
label = f'<a href="{link}">{html.escape(title)}</a>' if link else html.escape(title)
|
||||
text += f"\n 🎫 Join - {label}"
|
||||
if i < len(required_passphrases) and required_passphrases[i]:
|
||||
text += f"\n └ 🔑 Send passphrase ﹝<code>{html.escape(required_passphrases[i])}</code>﹞"
|
||||
elif lottery.require_membership and lottery.target_chat_title:
|
||||
text += f"\n\n📮 <b>Requirements:</b>\n 🎫 Join - {html.escape(lottery.target_chat_title)}"
|
||||
|
||||
# Prizes
|
||||
prize_lines = "\n".join(f" - {html.escape(p.name)} ×{p.quantity}" for p in prizes)
|
||||
text += f"\n\n🎁 <b>Prizes:</b>\n{prize_lines}"
|
||||
|
||||
# Participant count
|
||||
if include_count:
|
||||
text += f"\n\n👥 Participants: {participant_count}"
|
||||
|
||||
# Draw conditions
|
||||
if lottery.draw_at:
|
||||
from datetime import timezone as _tz
|
||||
draw_dt = lottery.draw_at.replace(tzinfo=_tz.utc)
|
||||
tz_label = "UTC"
|
||||
if lottery.creator_timezone:
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
draw_dt = draw_dt.astimezone(ZoneInfo(lottery.creator_timezone))
|
||||
offset = draw_dt.utcoffset()
|
||||
if offset is not None:
|
||||
total_min = int(offset.total_seconds() / 60)
|
||||
sign = "+" if total_min >= 0 else "-"
|
||||
h, m = divmod(abs(total_min), 60)
|
||||
tz_label = f"UTC{sign}{h}" if m == 0 else f"UTC{sign}{h}:{m:02d}"
|
||||
except Exception:
|
||||
pass
|
||||
text += f"\n\n📅 <b>Draw time:</b> {draw_dt.strftime('%Y/%m/%d %H:%M')} ({tz_label})"
|
||||
if lottery.auto_draw_count:
|
||||
text += f"\n🎯 Auto-draw at {lottery.auto_draw_count} participants"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
async def _sync_single_target(
|
||||
bot,
|
||||
text: str,
|
||||
keyboard,
|
||||
target: dict,
|
||||
force_send: bool,
|
||||
) -> int | None:
|
||||
"""Sync announcement to one chat. Returns the resulting message_id (or None)."""
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
|
||||
chat_id = target["chat_id"]
|
||||
msg_id = target.get("message_id")
|
||||
|
||||
if msg_id:
|
||||
try:
|
||||
await bot.edit_message_text(
|
||||
chat_id=chat_id,
|
||||
message_id=msg_id,
|
||||
text=text,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
return msg_id
|
||||
except TelegramBadRequest as e:
|
||||
if "message is not modified" in str(e).lower():
|
||||
return msg_id
|
||||
# Message deleted or inaccessible
|
||||
if not force_send:
|
||||
return msg_id # keep existing id, silently skip
|
||||
msg_id = None # will resend below
|
||||
|
||||
if force_send:
|
||||
msg = await bot.send_message(
|
||||
chat_id,
|
||||
text,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await _update_chat_last_message(chat_id, msg.message_id)
|
||||
return msg.message_id
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def _update_chat_last_message(chat_id: int, message_id: int) -> None:
|
||||
from app.core.database import AsyncSessionLocal
|
||||
from app.models.managed_chat import ManagedChat
|
||||
from sqlalchemy import select
|
||||
async with AsyncSessionLocal() as db:
|
||||
result = await db.execute(select(ManagedChat).where(ManagedChat.id == chat_id))
|
||||
managed = result.scalar_one_or_none()
|
||||
if managed and message_id > (managed.last_message_id or 0):
|
||||
managed.last_message_id = message_id
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def sync_announcement(db: AsyncSession, lottery: Lottery, force_send: bool = False) -> None:
|
||||
"""Edit or send announcement messages to all announcement_targets."""
|
||||
from app.bot.router import bot
|
||||
from app.bot.keyboards.inline import lottery_announce_keyboard
|
||||
|
||||
# Build target list - backward compat with old single-target lotteries
|
||||
targets: list[dict] = lottery.announcement_targets or []
|
||||
if not targets and lottery.target_chat_id:
|
||||
targets = [{
|
||||
"chat_id": lottery.target_chat_id,
|
||||
"chat_title": lottery.target_chat_title or "",
|
||||
"message_id": lottery.announcement_message_id,
|
||||
}]
|
||||
|
||||
if not targets:
|
||||
return
|
||||
|
||||
text = await _build_announce_text(db, lottery)
|
||||
keyboard = lottery_announce_keyboard(str(lottery.id))
|
||||
|
||||
updated: list[dict] = []
|
||||
changed = False
|
||||
for t in targets:
|
||||
new_msg_id = await _sync_single_target(bot, text, keyboard, t, force_send)
|
||||
updated.append({**t, "message_id": new_msg_id})
|
||||
if new_msg_id != t.get("message_id"):
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
lottery.announcement_targets = updated
|
||||
await db.commit()
|
||||
|
||||
|
||||
class _AnnounceChatItem(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
|
||||
|
||||
class _AnnounceRequest(BaseModel):
|
||||
chats: list[_AnnounceChatItem]
|
||||
|
||||
|
||||
@router.post("/{lottery_id}/announce")
|
||||
async def announce_lottery(
|
||||
lottery_id: uuid.UUID,
|
||||
data: _AnnounceRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
from aiogram.exceptions import TelegramAPIError
|
||||
|
||||
result = await db.execute(select(Lottery).where(Lottery.id == lottery_id))
|
||||
lottery = result.scalar_one_or_none()
|
||||
|
||||
if lottery is None:
|
||||
raise HTTPException(404, "Lottery not found")
|
||||
if lottery.creator_id != current_user.id:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
if not data.chats:
|
||||
raise HTTPException(400, "Select at least one chat")
|
||||
if lottery.status == LotteryStatus.CANCELLED:
|
||||
raise HTTPException(400, "Cannot announce a cancelled lottery")
|
||||
|
||||
# Build new targets, preserving existing message_ids for same chats
|
||||
existing = {t["chat_id"]: t for t in (lottery.announcement_targets or [])}
|
||||
new_targets = [
|
||||
{
|
||||
"chat_id": c.id,
|
||||
"chat_title": c.title,
|
||||
"message_id": existing.get(c.id, {}).get("message_id"),
|
||||
}
|
||||
for c in data.chats
|
||||
]
|
||||
lottery.announcement_targets = new_targets
|
||||
# Keep legacy target_chat_id pointing to first chat for draw-result compat
|
||||
lottery.target_chat_id = data.chats[0].id
|
||||
lottery.target_chat_title = data.chats[0].title
|
||||
await db.commit()
|
||||
await db.refresh(lottery)
|
||||
|
||||
try:
|
||||
await sync_announcement(db, lottery, force_send=True)
|
||||
except TelegramAPIError as e:
|
||||
raise HTTPException(500, f"Failed to send message: {e}")
|
||||
|
||||
return {"status": "announced"}
|
||||
|
||||
|
||||
@router.post("/{lottery_id}/prepare-share")
|
||||
async def prepare_share(
|
||||
lottery_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = await db.execute(select(Lottery).where(Lottery.id == lottery_id))
|
||||
lottery = result.scalar_one_or_none()
|
||||
|
||||
if lottery is None:
|
||||
raise HTTPException(404, "Lottery not found")
|
||||
if lottery.status == LotteryStatus.CANCELLED:
|
||||
raise HTTPException(400, "Cannot share a cancelled lottery")
|
||||
|
||||
from app.bot.router import bot
|
||||
from app.bot.keyboards.inline import lottery_announce_keyboard
|
||||
from aiogram.types import InlineQueryResultArticle, InputTextMessageContent
|
||||
|
||||
text = await _build_announce_text(db, lottery, include_count=False)
|
||||
keyboard = lottery_announce_keyboard(str(lottery.id))
|
||||
|
||||
try:
|
||||
prepared = await bot.save_prepared_inline_message(
|
||||
user_id=current_user.id,
|
||||
result=InlineQueryResultArticle(
|
||||
id=str(lottery.id),
|
||||
title=lottery.title,
|
||||
input_message_content=InputTextMessageContent(
|
||||
message_text=text,
|
||||
parse_mode="HTML",
|
||||
),
|
||||
reply_markup=keyboard,
|
||||
),
|
||||
allow_user_chats=True,
|
||||
allow_bot_chats=False,
|
||||
allow_group_chats=True,
|
||||
allow_channel_chats=True,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
return {"prepared_message_id": prepared.id}
|
||||
|
||||
|
||||
@router.delete("/{lottery_id}")
|
||||
async def cancel_lottery(
|
||||
lottery_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = await db.execute(select(Lottery).where(Lottery.id == lottery_id))
|
||||
lottery = result.scalar_one_or_none()
|
||||
|
||||
if lottery is None:
|
||||
raise HTTPException(404, "Lottery not found")
|
||||
if lottery.creator_id != current_user.id:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
if lottery.status == LotteryStatus.FINISHED:
|
||||
raise HTTPException(400, "Cannot cancel a finished lottery")
|
||||
|
||||
lottery.status = LotteryStatus.CANCELLED
|
||||
await db.commit()
|
||||
return {"status": "cancelled"}
|
||||
Reference in New Issue
Block a user