This commit is contained in:
2025-12-14 02:38:35 +07:00
commit 5343a8f2c3
84 changed files with 7406 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
from app.schemas.user import (
UserRegister,
UserLogin,
UserUpdate,
UserPublic,
UserWithTelegram,
TokenResponse,
TelegramLink,
)
from app.schemas.marathon import (
MarathonCreate,
MarathonUpdate,
MarathonResponse,
MarathonListItem,
ParticipantInfo,
ParticipantWithUser,
JoinMarathon,
LeaderboardEntry,
)
from app.schemas.game import (
GameCreate,
GameUpdate,
GameResponse,
GameShort,
)
from app.schemas.challenge import (
ChallengeCreate,
ChallengeUpdate,
ChallengeResponse,
ChallengeGenerated,
)
from app.schemas.assignment import (
CompleteAssignment,
AssignmentResponse,
SpinResult,
CompleteResult,
DropResult,
)
from app.schemas.activity import (
ActivityResponse,
FeedResponse,
)
from app.schemas.common import (
MessageResponse,
ErrorResponse,
PaginationParams,
)
__all__ = [
# User
"UserRegister",
"UserLogin",
"UserUpdate",
"UserPublic",
"UserWithTelegram",
"TokenResponse",
"TelegramLink",
# Marathon
"MarathonCreate",
"MarathonUpdate",
"MarathonResponse",
"MarathonListItem",
"ParticipantInfo",
"ParticipantWithUser",
"JoinMarathon",
"LeaderboardEntry",
# Game
"GameCreate",
"GameUpdate",
"GameResponse",
"GameShort",
# Challenge
"ChallengeCreate",
"ChallengeUpdate",
"ChallengeResponse",
"ChallengeGenerated",
# Assignment
"CompleteAssignment",
"AssignmentResponse",
"SpinResult",
"CompleteResult",
"DropResult",
# Activity
"ActivityResponse",
"FeedResponse",
# Common
"MessageResponse",
"ErrorResponse",
"PaginationParams",
]

View File

@@ -0,0 +1,21 @@
from datetime import datetime
from pydantic import BaseModel
from app.schemas.user import UserPublic
class ActivityResponse(BaseModel):
id: int
type: str
user: UserPublic
data: dict | None = None
created_at: datetime
class Config:
from_attributes = True
class FeedResponse(BaseModel):
items: list[ActivityResponse]
total: int
has_more: bool

View File

@@ -0,0 +1,50 @@
from datetime import datetime
from pydantic import BaseModel
from app.schemas.game import GameResponse
from app.schemas.challenge import ChallengeResponse
class AssignmentBase(BaseModel):
pass
class CompleteAssignment(BaseModel):
proof_url: str | None = None
comment: str | None = None
class AssignmentResponse(BaseModel):
id: int
challenge: ChallengeResponse
status: str
proof_url: str | None = None
proof_comment: str | None = None
points_earned: int
streak_at_completion: int | None = None
started_at: datetime
completed_at: datetime | None = None
class Config:
from_attributes = True
class SpinResult(BaseModel):
assignment_id: int
game: GameResponse
challenge: ChallengeResponse
can_drop: bool
drop_penalty: int
class CompleteResult(BaseModel):
points_earned: int
streak_bonus: int
total_points: int
new_streak: int
class DropResult(BaseModel):
penalty: int
total_points: int
new_drop_count: int

View File

@@ -0,0 +1,53 @@
from datetime import datetime
from pydantic import BaseModel, Field
from app.models.challenge import ChallengeType, Difficulty, ProofType
from app.schemas.game import GameShort
class ChallengeBase(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
description: str = Field(..., min_length=1)
type: ChallengeType
difficulty: Difficulty
points: int = Field(..., ge=1, le=500)
estimated_time: int | None = Field(None, ge=1) # minutes
proof_type: ProofType
proof_hint: str | None = None
class ChallengeCreate(ChallengeBase):
pass
class ChallengeUpdate(BaseModel):
title: str | None = Field(None, min_length=1, max_length=100)
description: str | None = None
type: ChallengeType | None = None
difficulty: Difficulty | None = None
points: int | None = Field(None, ge=1, le=500)
estimated_time: int | None = None
proof_type: ProofType | None = None
proof_hint: str | None = None
class ChallengeResponse(ChallengeBase):
id: int
game: GameShort
is_generated: bool
created_at: datetime
class Config:
from_attributes = True
class ChallengeGenerated(BaseModel):
"""Schema for GPT-generated challenges"""
title: str
description: str
type: str
difficulty: str
points: int
estimated_time: int | None = None
proof_type: str
proof_hint: str | None = None

View File

@@ -0,0 +1,14 @@
from pydantic import BaseModel
class MessageResponse(BaseModel):
message: str
class ErrorResponse(BaseModel):
detail: str
class PaginationParams(BaseModel):
limit: int = 20
offset: int = 0

View File

@@ -0,0 +1,40 @@
from datetime import datetime
from pydantic import BaseModel, Field, HttpUrl
from app.schemas.user import UserPublic
class GameBase(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
download_url: str = Field(..., min_length=1)
genre: str | None = Field(None, max_length=50)
class GameCreate(GameBase):
cover_url: str | None = None
class GameUpdate(BaseModel):
title: str | None = Field(None, min_length=1, max_length=100)
download_url: str | None = None
genre: str | None = None
class GameShort(BaseModel):
id: int
title: str
cover_url: str | None = None
class Config:
from_attributes = True
class GameResponse(GameBase):
id: int
cover_url: str | None = None
added_by: UserPublic | None = None
challenges_count: int = 0
created_at: datetime
class Config:
from_attributes = True

View File

@@ -0,0 +1,76 @@
from datetime import datetime
from pydantic import BaseModel, Field
from app.schemas.user import UserPublic
class MarathonBase(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
description: str | None = None
class MarathonCreate(MarathonBase):
start_date: datetime
duration_days: int = Field(default=30, ge=1, le=365)
class MarathonUpdate(BaseModel):
title: str | None = Field(None, min_length=1, max_length=100)
description: str | None = None
start_date: datetime | None = None
class ParticipantInfo(BaseModel):
id: int
total_points: int
current_streak: int
drop_count: int
joined_at: datetime
class Config:
from_attributes = True
class ParticipantWithUser(ParticipantInfo):
user: UserPublic
class MarathonResponse(MarathonBase):
id: int
organizer: UserPublic
status: str
invite_code: str
start_date: datetime | None
end_date: datetime | None
participants_count: int
games_count: int
created_at: datetime
my_participation: ParticipantInfo | None = None
class Config:
from_attributes = True
class MarathonListItem(BaseModel):
id: int
title: str
status: str
participants_count: int
start_date: datetime | None
end_date: datetime | None
class Config:
from_attributes = True
class JoinMarathon(BaseModel):
invite_code: str
class LeaderboardEntry(BaseModel):
rank: int
user: UserPublic
total_points: int
current_streak: int
completed_count: int
dropped_count: int

View File

@@ -0,0 +1,54 @@
from datetime import datetime
from pydantic import BaseModel, Field, field_validator
import re
class UserBase(BaseModel):
nickname: str = Field(..., min_length=2, max_length=50)
class UserRegister(UserBase):
login: str = Field(..., min_length=3, max_length=50)
password: str = Field(..., min_length=6, max_length=100)
@field_validator("login")
@classmethod
def validate_login(cls, v: str) -> str:
if not re.match(r"^[a-zA-Z0-9_]+$", v):
raise ValueError("Login can only contain letters, numbers, and underscores")
return v.lower()
class UserLogin(BaseModel):
login: str
password: str
class UserUpdate(BaseModel):
nickname: str | None = Field(None, min_length=2, max_length=50)
class UserPublic(UserBase):
id: int
login: str
avatar_url: str | None = None
created_at: datetime
class Config:
from_attributes = True
class UserWithTelegram(UserPublic):
telegram_id: int | None = None
telegram_username: str | None = None
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: UserPublic
class TelegramLink(BaseModel):
telegram_id: int
telegram_username: str | None = None