Add events
This commit is contained in:
@@ -46,6 +46,21 @@ from app.schemas.activity import (
|
||||
ActivityResponse,
|
||||
FeedResponse,
|
||||
)
|
||||
from app.schemas.event import (
|
||||
EventCreate,
|
||||
EventResponse,
|
||||
EventEffects,
|
||||
ActiveEventResponse,
|
||||
SwapRequest,
|
||||
SwapCandidate,
|
||||
CommonEnemyLeaderboard,
|
||||
EVENT_INFO,
|
||||
COMMON_ENEMY_BONUSES,
|
||||
SwapRequestCreate,
|
||||
SwapRequestResponse,
|
||||
SwapRequestChallengeInfo,
|
||||
MySwapRequests,
|
||||
)
|
||||
from app.schemas.common import (
|
||||
MessageResponse,
|
||||
ErrorResponse,
|
||||
@@ -95,6 +110,20 @@ __all__ = [
|
||||
# Activity
|
||||
"ActivityResponse",
|
||||
"FeedResponse",
|
||||
# Event
|
||||
"EventCreate",
|
||||
"EventResponse",
|
||||
"EventEffects",
|
||||
"ActiveEventResponse",
|
||||
"SwapRequest",
|
||||
"SwapCandidate",
|
||||
"CommonEnemyLeaderboard",
|
||||
"EVENT_INFO",
|
||||
"COMMON_ENEMY_BONUSES",
|
||||
"SwapRequestCreate",
|
||||
"SwapRequestResponse",
|
||||
"SwapRequestChallengeInfo",
|
||||
"MySwapRequests",
|
||||
# Common
|
||||
"MessageResponse",
|
||||
"ErrorResponse",
|
||||
|
||||
174
backend/app/schemas/event.py
Normal file
174
backend/app/schemas/event.py
Normal file
@@ -0,0 +1,174 @@
|
||||
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",
|
||||
"rematch",
|
||||
]
|
||||
|
||||
|
||||
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", "rematch"
|
||||
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.REMATCH: {
|
||||
"name": "Реванш",
|
||||
"description": "Можно переделать проваленный челлендж за 50% очков",
|
||||
"default_duration": 240,
|
||||
"points_multiplier": 0.5,
|
||||
"drop_free": False,
|
||||
"special_action": "rematch",
|
||||
},
|
||||
}
|
||||
|
||||
# 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
|
||||
challenge_title: str
|
||||
challenge_description: str
|
||||
challenge_points: int
|
||||
challenge_difficulty: str
|
||||
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 info for swap request display"""
|
||||
title: str
|
||||
description: str
|
||||
points: int
|
||||
difficulty: str
|
||||
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]
|
||||
@@ -22,6 +22,7 @@ class MarathonUpdate(BaseModel):
|
||||
start_date: datetime | None = None
|
||||
is_public: bool | None = None
|
||||
game_proposal_mode: str | None = Field(None, pattern="^(all_participants|organizer_only)$")
|
||||
auto_events_enabled: bool | None = None
|
||||
|
||||
|
||||
class ParticipantInfo(BaseModel):
|
||||
@@ -47,6 +48,7 @@ class MarathonResponse(MarathonBase):
|
||||
invite_code: str
|
||||
is_public: bool
|
||||
game_proposal_mode: str
|
||||
auto_events_enabled: bool
|
||||
start_date: datetime | None
|
||||
end_date: datetime | None
|
||||
participants_count: int
|
||||
|
||||
Reference in New Issue
Block a user