Files
remnawave-bedolaga-telegram…/app/webapi/schemas/contests.py
gy9vin b3cdd3c03a Расширение функционала конкурсов: разнообразие наград, напоминания, многоязычность
Изменения:
- ContestTemplate: prize_days заменен на prize_type и prize_value для поддержки разных типов наград (days, balance, custom)
- _award_prize: обновлена логика выдачи призов для всех типов наград
- DEFAULT_TEMPLATES: обновлены для использования prize_type/prize_value
- upsert_template: обновлена сигнатура для новых полей
- _announce_round_start: добавлена локализация и напоминания о конкурсах
- handle_text_answer: исправлена гонка условий с атомарным инкрементом победителей
- Локализация: добавлены ключи CONTEST_START_ANNOUNCEMENT, CONTEST_PRIZE, DAYS, CONTEST_WINNERS, CONTEST_ATTEMPTS, CONTEST_ELIGIBILITY, REMINDER, CONTEST_REMINDER_TEXT в ru.json и en.json
- API схемы: обновлены ContestTemplateResponse и ContestTemplateUpdateRequest

Требуется миграция БД для новых колонок prize_type и prize_value.
2025-12-23 19:15:40 +03:00

214 lines
5.6 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 __future__ import annotations
from datetime import date, datetime, time
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
class ContestTemplateResponse(BaseModel):
id: int
name: str
slug: str
description: Optional[str] = None
prize_type: str
prize_value: str
max_winners: int
attempts_per_user: int
times_per_day: int
schedule_times: Optional[str] = None
cooldown_hours: int
payload: Dict[str, Any] = Field(default_factory=dict)
is_enabled: bool
created_at: datetime
updated_at: datetime
class ContestTemplateListResponse(BaseModel):
items: List[ContestTemplateResponse]
class ContestTemplateUpdateRequest(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
prize_type: Optional[str] = None
prize_value: Optional[str] = None
max_winners: Optional[int] = Field(None, ge=1)
attempts_per_user: Optional[int] = Field(None, ge=1)
times_per_day: Optional[int] = Field(None, ge=1)
schedule_times: Optional[str] = None
cooldown_hours: Optional[int] = Field(None, ge=1)
payload: Optional[Dict[str, Any]] = None
is_enabled: Optional[bool] = None
class StartRoundRequest(BaseModel):
starts_at: Optional[datetime] = None
ends_at: Optional[datetime] = None
cooldown_hours: Optional[int] = Field(None, ge=1)
payload: Optional[Dict[str, Any]] = None
force: bool = False
class ContestRoundResponse(BaseModel):
id: int
template_id: int
template_slug: str
template_name: Optional[str] = None
starts_at: datetime
ends_at: datetime
status: str
payload: Dict[str, Any] = Field(default_factory=dict)
winners_count: int
max_winners: int
attempts_per_user: int
created_at: datetime
updated_at: datetime
class ContestRoundListResponse(BaseModel):
items: List[ContestRoundResponse]
total: int
limit: int
offset: int
class ContestAttemptUser(BaseModel):
id: int
telegram_id: Optional[int] = None
username: Optional[str] = None
full_name: Optional[str] = None
class ContestAttemptResponse(BaseModel):
id: int
round_id: int
user: ContestAttemptUser
answer: Optional[str] = None
is_winner: bool
created_at: datetime
class ContestAttemptListResponse(BaseModel):
items: List[ContestAttemptResponse]
total: int
limit: int
offset: int
class ReferralContestResponse(BaseModel):
id: int
title: str
description: Optional[str] = None
prize_text: Optional[str] = None
contest_type: str
start_at: datetime
end_at: datetime
daily_summary_time: time
daily_summary_times: Optional[str] = None
timezone: str
is_active: bool
last_daily_summary_date: Optional[date] = None
last_daily_summary_at: Optional[datetime] = None
final_summary_sent: bool
created_by: Optional[int] = None
created_at: datetime
updated_at: datetime
class ReferralContestListResponse(BaseModel):
items: List[ReferralContestResponse]
total: int
limit: int
offset: int
class ReferralContestCreateRequest(BaseModel):
title: str
description: Optional[str] = None
prize_text: Optional[str] = None
contest_type: str = Field("referral_paid", min_length=1)
start_at: datetime
end_at: datetime
daily_summary_time: time = Field(default=time(hour=12))
daily_summary_times: Optional[str] = Field(
default=None, description="Список времён ЧЧ:ММ через запятую (например, 12:00,18:00)"
)
timezone: str = Field(default="UTC")
is_active: bool = True
created_by: Optional[int] = None
class ReferralContestUpdateRequest(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
prize_text: Optional[str] = None
contest_type: Optional[str] = Field(None, min_length=1)
start_at: Optional[datetime] = None
end_at: Optional[datetime] = None
daily_summary_time: Optional[time] = None
daily_summary_times: Optional[str] = Field(
default=None, description="Список времён ЧЧ:ММ через запятую"
)
timezone: Optional[str] = None
is_active: Optional[bool] = None
final_summary_sent: Optional[bool] = None
created_by: Optional[int] = None
class ReferralContestLeaderboardItem(BaseModel):
user_id: int
telegram_id: Optional[int] = None
username: Optional[str] = None
full_name: Optional[str] = None
referrals_count: int
total_amount_kopeks: int
total_amount_rubles: float
class ReferralContestDetailResponse(ReferralContestResponse):
total_events: Optional[int] = None
leaderboard: Optional[List[ReferralContestLeaderboardItem]] = None
class ReferralContestEventUser(BaseModel):
id: int
telegram_id: Optional[int] = None
username: Optional[str] = None
full_name: Optional[str] = None
class ReferralContestEventResponse(BaseModel):
id: int
contest_id: int
referrer: ReferralContestEventUser
referral: ReferralContestEventUser
event_type: str
amount_kopeks: int
amount_rubles: float
occurred_at: datetime
class ReferralContestEventListResponse(BaseModel):
items: List[ReferralContestEventResponse]
total: int
limit: int
offset: int
class ReferralContestParticipant(BaseModel):
referrer_id: int
full_name: str
total_referrals: int
paid_referrals: int
unpaid_referrals: int
total_paid_amount: int
class ReferralContestDetailedStatsResponse(BaseModel):
total_participants: int
total_invited: int
total_paid_amount: int
total_unpaid: int
participants: List[ReferralContestParticipant]