mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-03 10:40:25 +00:00
- Создание попытки сразу при показе вопроса (cipher/emoji/anagram) - Проверка attempt.answer is not None для блокировки повторного ответа - Обновление существующей попытки вместо создания новой - Добавлена функция update_attempt() в CRUD
229 lines
6.3 KiB
Python
229 lines
6.3 KiB
Python
import logging
|
|
from datetime import datetime
|
|
from typing import List, Optional, Sequence, Tuple
|
|
|
|
from sqlalchemy import and_, delete, desc, func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.database.models import ContestTemplate, ContestRound, ContestAttempt, User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Templates
|
|
async def get_template_by_id(db: AsyncSession, template_id: int) -> Optional[ContestTemplate]:
|
|
result = await db.execute(
|
|
select(ContestTemplate).where(ContestTemplate.id == template_id)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def get_template_by_slug(db: AsyncSession, slug: str) -> Optional[ContestTemplate]:
|
|
result = await db.execute(
|
|
select(ContestTemplate).where(ContestTemplate.slug == slug)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def list_templates(db: AsyncSession, enabled_only: bool = True) -> List[ContestTemplate]:
|
|
query = select(ContestTemplate).order_by(ContestTemplate.id)
|
|
if enabled_only:
|
|
query = query.where(ContestTemplate.is_enabled.is_(True))
|
|
result = await db.execute(query)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
async def upsert_template(
|
|
db: AsyncSession,
|
|
*,
|
|
slug: str,
|
|
name: str,
|
|
description: str = "",
|
|
prize_type: str = "days",
|
|
prize_value: str = "1",
|
|
max_winners: int = 1,
|
|
attempts_per_user: int = 1,
|
|
times_per_day: int = 1,
|
|
schedule_times: Optional[str] = None,
|
|
cooldown_hours: int = 24,
|
|
payload: Optional[dict] = None,
|
|
is_enabled: Optional[bool] = None,
|
|
) -> ContestTemplate:
|
|
template = await get_template_by_slug(db, slug)
|
|
if not template:
|
|
template = ContestTemplate(slug=slug)
|
|
db.add(template)
|
|
|
|
template.name = name
|
|
template.description = description
|
|
template.prize_type = prize_type
|
|
template.prize_value = prize_value
|
|
template.max_winners = max_winners
|
|
template.attempts_per_user = attempts_per_user
|
|
template.times_per_day = times_per_day
|
|
template.schedule_times = schedule_times
|
|
template.cooldown_hours = cooldown_hours
|
|
template.payload = payload or {}
|
|
if is_enabled is not None:
|
|
template.is_enabled = is_enabled
|
|
await db.commit()
|
|
await db.refresh(template)
|
|
return template
|
|
|
|
|
|
async def update_template_fields(
|
|
db: AsyncSession,
|
|
template: ContestTemplate,
|
|
**fields: object,
|
|
) -> ContestTemplate:
|
|
for key, value in fields.items():
|
|
if hasattr(template, key):
|
|
setattr(template, key, value)
|
|
await db.commit()
|
|
await db.refresh(template)
|
|
return template
|
|
|
|
|
|
# Rounds
|
|
async def create_round(
|
|
db: AsyncSession,
|
|
*,
|
|
template: ContestTemplate,
|
|
starts_at: datetime,
|
|
ends_at: datetime,
|
|
payload: dict,
|
|
) -> ContestRound:
|
|
round_obj = ContestRound(
|
|
template_id=template.id,
|
|
starts_at=starts_at,
|
|
ends_at=ends_at,
|
|
status="active",
|
|
payload=payload,
|
|
max_winners=template.max_winners,
|
|
attempts_per_user=template.attempts_per_user,
|
|
)
|
|
db.add(round_obj)
|
|
await db.commit()
|
|
await db.refresh(round_obj)
|
|
return round_obj
|
|
|
|
|
|
async def get_active_rounds(db: AsyncSession) -> List[ContestRound]:
|
|
now = datetime.utcnow()
|
|
result = await db.execute(
|
|
select(ContestRound)
|
|
.options(selectinload(ContestRound.template))
|
|
.where(
|
|
and_(
|
|
ContestRound.status == "active",
|
|
ContestRound.starts_at <= now,
|
|
ContestRound.ends_at >= now,
|
|
)
|
|
)
|
|
.order_by(ContestRound.starts_at)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
async def get_active_round_by_template(db: AsyncSession, template_id: int) -> Optional[ContestRound]:
|
|
now = datetime.utcnow()
|
|
result = await db.execute(
|
|
select(ContestRound)
|
|
.options(selectinload(ContestRound.template))
|
|
.where(
|
|
and_(
|
|
ContestRound.template_id == template_id,
|
|
ContestRound.status == "active",
|
|
ContestRound.starts_at <= now,
|
|
ContestRound.ends_at >= now,
|
|
)
|
|
)
|
|
.order_by(desc(ContestRound.starts_at))
|
|
)
|
|
return result.scalars().first()
|
|
|
|
|
|
async def finish_round(db: AsyncSession, round_obj: ContestRound) -> ContestRound:
|
|
round_obj.status = "finished"
|
|
await db.commit()
|
|
await db.refresh(round_obj)
|
|
return round_obj
|
|
|
|
|
|
async def increment_winner_count(db: AsyncSession, round_obj: ContestRound) -> ContestRound:
|
|
round_obj.winners_count += 1
|
|
await db.commit()
|
|
await db.refresh(round_obj)
|
|
return round_obj
|
|
|
|
|
|
# Attempts
|
|
async def get_attempt(db: AsyncSession, round_id: int, user_id: int) -> Optional[ContestAttempt]:
|
|
result = await db.execute(
|
|
select(ContestAttempt).where(
|
|
and_(
|
|
ContestAttempt.round_id == round_id,
|
|
ContestAttempt.user_id == user_id,
|
|
)
|
|
)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def create_attempt(
|
|
db: AsyncSession,
|
|
*,
|
|
round_id: int,
|
|
user_id: int,
|
|
answer: Optional[str],
|
|
is_winner: bool,
|
|
) -> ContestAttempt:
|
|
attempt = ContestAttempt(
|
|
round_id=round_id,
|
|
user_id=user_id,
|
|
answer=answer,
|
|
is_winner=is_winner,
|
|
)
|
|
db.add(attempt)
|
|
await db.commit()
|
|
await db.refresh(attempt)
|
|
return attempt
|
|
|
|
|
|
async def update_attempt(
|
|
db: AsyncSession,
|
|
attempt: ContestAttempt,
|
|
*,
|
|
answer: Optional[str] = None,
|
|
is_winner: bool = False,
|
|
) -> ContestAttempt:
|
|
"""Update existing attempt with answer and winner status."""
|
|
if answer is not None:
|
|
attempt.answer = answer
|
|
attempt.is_winner = is_winner
|
|
await db.commit()
|
|
await db.refresh(attempt)
|
|
return attempt
|
|
|
|
|
|
async def clear_attempts(db: AsyncSession, round_id: int) -> int:
|
|
result = await db.execute(delete(ContestAttempt).where(ContestAttempt.round_id == round_id))
|
|
deleted_count = result.rowcount
|
|
await db.commit()
|
|
return deleted_count
|
|
|
|
|
|
async def list_winners(db: AsyncSession, round_id: int) -> Sequence[Tuple[User, ContestAttempt]]:
|
|
result = await db.execute(
|
|
select(User, ContestAttempt)
|
|
.join(ContestAttempt, ContestAttempt.user_id == User.id)
|
|
.where(
|
|
and_(
|
|
ContestAttempt.round_id == round_id,
|
|
ContestAttempt.is_winner.is_(True),
|
|
)
|
|
)
|
|
)
|
|
return result.all()
|