610 lines
22 KiB
Python
610 lines
22 KiB
Python
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"}
|