mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-04 03:00:26 +00:00
Изменения: - 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.
214 lines
5.6 KiB
Python
214 lines
5.6 KiB
Python
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]
|