Files
game-marathon/backend/app/schemas/event.py
mamonov.ep 9f79daf796 Добавлена поддержка обмена играми с типом прохождения (playthrough)
- Обновлены схемы SwapCandidate и SwapRequestChallengeInfo для поддержки прохождений
- get_swap_candidates теперь возвращает и челленджи, и прохождения
- accept_swap_request теперь корректно меняет challenge_id, game_id, is_playthrough и bonus_assignments
- Обновлён UI для отображения прохождений в списке кандидатов и запросах обмена

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 15:56:34 +03:00

187 lines
5.2 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 datetime import datetime
from pydantic import BaseModel, Field
from typing import Literal
from app.models.event import EventType
from app.schemas.user import UserPublic
# Event type literals for Pydantic
EventTypeLiteral = Literal[
"golden_hour",
"common_enemy",
"double_risk",
"jackpot",
"swap",
"game_choice",
]
class EventCreate(BaseModel):
type: EventTypeLiteral
duration_minutes: int | None = Field(
None,
description="Duration in minutes. If not provided, uses default for event type."
)
challenge_id: int | None = Field(
None,
description="For common_enemy event - the challenge everyone will get"
)
class EventEffects(BaseModel):
points_multiplier: float = 1.0
drop_free: bool = False
special_action: str | None = None # "swap", "game_choice"
description: str = ""
class EventResponse(BaseModel):
id: int
type: EventTypeLiteral
start_time: datetime
end_time: datetime | None
is_active: bool
created_by: UserPublic | None
data: dict | None
created_at: datetime
class Config:
from_attributes = True
class ActiveEventResponse(BaseModel):
event: EventResponse | None
effects: EventEffects
time_remaining_seconds: int | None = None
class SwapRequest(BaseModel):
target_participant_id: int
class CommonEnemyLeaderboard(BaseModel):
participant_id: int
user: UserPublic
completed_at: datetime | None
rank: int | None
bonus_points: int
# Event descriptions and default durations
EVENT_INFO = {
EventType.GOLDEN_HOUR: {
"name": "Золотой час",
"description": "Все очки x1.5!",
"default_duration": 45,
"points_multiplier": 1.5,
"drop_free": False,
},
EventType.COMMON_ENEMY: {
"name": "Общий враг",
"description": "Все получают одинаковый челлендж. Первые 3 получают бонус!",
"default_duration": None, # Until all complete
"points_multiplier": 1.0,
"drop_free": False,
},
EventType.DOUBLE_RISK: {
"name": "Безопасная игра",
"description": "Дропы бесплатны, но очки x0.5",
"default_duration": 120,
"points_multiplier": 0.5,
"drop_free": True,
},
EventType.JACKPOT: {
"name": "Джекпот",
"description": "Следующий спин — сложный челлендж с x3 очками!",
"default_duration": None, # 1 spin
"points_multiplier": 3.0,
"drop_free": False,
},
EventType.SWAP: {
"name": "Обмен",
"description": "Можно поменяться заданием с другим участником",
"default_duration": 60,
"points_multiplier": 1.0,
"drop_free": False,
"special_action": "swap",
},
EventType.GAME_CHOICE: {
"name": "Выбор игры",
"description": "Выбери игру и один из 3 челленджей. Можно заменить текущее задание без штрафа!",
"default_duration": 120,
"points_multiplier": 1.0,
"drop_free": True, # Free replacement of current assignment
"special_action": "game_choice",
},
}
# Bonus points for Common Enemy top 3
COMMON_ENEMY_BONUSES = {
1: 50,
2: 30,
3: 15,
}
class SwapCandidate(BaseModel):
"""Participant available for assignment swap"""
participant_id: int
user: UserPublic
is_playthrough: bool = False
# Challenge fields (used when is_playthrough=False)
challenge_title: str | None = None
challenge_description: str | None = None
challenge_points: int | None = None
challenge_difficulty: str | None = None
# Playthrough fields (used when is_playthrough=True)
playthrough_description: str | None = None
playthrough_points: int | None = None
# Common field
game_title: str
# Two-sided swap confirmation schemas
SwapRequestStatusLiteral = Literal["pending", "accepted", "declined", "cancelled"]
class SwapRequestCreate(BaseModel):
"""Request to swap assignment with another participant"""
target_participant_id: int
class SwapRequestChallengeInfo(BaseModel):
"""Challenge or playthrough info for swap request display"""
is_playthrough: bool = False
# Challenge fields (used when is_playthrough=False)
title: str | None = None
description: str | None = None
points: int | None = None
difficulty: str | None = None
# Playthrough fields (used when is_playthrough=True)
playthrough_description: str | None = None
playthrough_points: int | None = None
# Common field
game_title: str
class SwapRequestResponse(BaseModel):
"""Response for a swap request"""
id: int
status: SwapRequestStatusLiteral
from_user: UserPublic
to_user: UserPublic
from_challenge: SwapRequestChallengeInfo
to_challenge: SwapRequestChallengeInfo
created_at: datetime
responded_at: datetime | None
class Config:
from_attributes = True
class MySwapRequests(BaseModel):
"""User's incoming and outgoing swap requests"""
incoming: list[SwapRequestResponse]
outgoing: list[SwapRequestResponse]