from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any # ============================================================ # AUTH / USERS # ============================================================ class UserCreate(BaseModel): username: str password: str display_name: Optional[str] = "" role: Optional[str] = "personal" class UserLogin(BaseModel): username: str password: str class UserOut(BaseModel): id: int username: str display_name: str role: str created_at: str class Config: from_attributes = True class Token(BaseModel): access_token: str token_type: str = "bearer" user: UserOut # ============================================================ # WORKOUT TYPES # ============================================================ class WorkoutTypeBase(BaseModel): name: str sort_order: Optional[int] = 0 class WorkoutTypeCreate(WorkoutTypeBase): sync_id: Optional[str] = None class WorkoutTypeOut(WorkoutTypeBase): id: int sync_id: str created_at: str class Config: from_attributes = True # ============================================================ # METRIC TYPES # ============================================================ class MetricTypeBase(BaseModel): name: str unit: Optional[str] = "см" category: Optional[str] = "body" sort_order: Optional[int] = 0 class MetricTypeCreate(MetricTypeBase): sync_id: Optional[str] = None class MetricTypeOut(MetricTypeBase): id: int sync_id: str created_at: str class Config: from_attributes = True # ============================================================ # CLIENTS # ============================================================ class ClientBase(BaseModel): full_name: str birth_date: Optional[str] = None goal: Optional[str] = "" notes: Optional[str] = "" color: Optional[str] = None is_active: Optional[int] = 1 class ClientCreate(ClientBase): sync_id: Optional[str] = None class ClientUpdate(BaseModel): full_name: Optional[str] = None birth_date: Optional[str] = None goal: Optional[str] = None notes: Optional[str] = None color: Optional[str] = None is_active: Optional[int] = None class ClientOut(ClientBase): id: int sync_id: str owner_id: Optional[int] = None created_at: str updated_at: str class Config: from_attributes = True # ============================================================ # EXERCISES # ============================================================ class ExerciseBase(BaseModel): name: str workout_type_id: Optional[int] = None class ExerciseCreate(ExerciseBase): sync_id: Optional[str] = None class ExerciseUpdate(BaseModel): name: Optional[str] = None workout_type_id: Optional[int] = None class ExerciseOut(ExerciseBase): id: int sync_id: str owner_id: Optional[int] = None created_at: str updated_at: str class Config: from_attributes = True # ============================================================ # DROPSET ITEMS # ============================================================ class DropsetItemBase(BaseModel): weight: float = Field(gt=0) reps: int = Field(gt=0) sort_order: int = 0 class DropsetItemCreate(DropsetItemBase): pass class DropsetItemOut(DropsetItemBase): id: int class Config: from_attributes = True # ============================================================ # SETS # ============================================================ class SetBase(BaseModel): is_dropset: int = 0 weight: Optional[float] = None reps: Optional[int] = None rating: Optional[int] = Field(None, ge=1, le=5) notes: Optional[str] = "" sort_order: int = 0 timestamp: str class SetCreate(SetBase): sync_id: Optional[str] = None dropset_items: List[DropsetItemCreate] = Field(default_factory=list) class SetOut(SetBase): id: int sync_id: str created_at: str dropset_items: List[DropsetItemOut] = Field(default_factory=list) class Config: from_attributes = True # ============================================================ # WORKOUT EXERCISES # ============================================================ class WorkoutExerciseBase(BaseModel): exercise_name: str exercise_id: Optional[int] = None exercise_sync_id: Optional[str] = None sort_order: int = 0 class WorkoutExerciseCreate(WorkoutExerciseBase): sync_id: Optional[str] = None sets: List[SetCreate] = Field(default_factory=list) class WorkoutExerciseOut(WorkoutExerciseBase): id: int sync_id: str created_at: str sets: List[SetOut] = Field(default_factory=list) class Config: from_attributes = True # ============================================================ # CARDIO # ============================================================ class CardioBase(BaseModel): cardio_type: str purpose: str duration: Optional[int] = None distance: Optional[float] = None speed: Optional[float] = None incline: Optional[float] = None resistance: Optional[int] = None notes: Optional[str] = "" sort_order: int = 0 class CardioCreate(CardioBase): sync_id: Optional[str] = None class CardioOut(CardioBase): id: int sync_id: str created_at: str class Config: from_attributes = True # ============================================================ # WORKOUTS # ============================================================ class WorkoutBase(BaseModel): start_time: str end_time: Optional[str] = None notes: Optional[str] = "" workout_type_id: Optional[int] = None class WorkoutCreate(WorkoutBase): sync_id: Optional[str] = None client_id: Optional[int] = None client_sync_id: Optional[str] = None workout_exercises: List[WorkoutExerciseCreate] = Field(default_factory=list) cardio_sessions: List[CardioCreate] = Field(default_factory=list) class WorkoutUpdate(BaseModel): workout_type_id: Optional[int] = None end_time: Optional[str] = None notes: Optional[str] = None synced: Optional[int] = None class WorkoutOut(WorkoutBase): id: int sync_id: str owner_id: Optional[int] = None client_id: Optional[int] = None synced: int created_at: str updated_at: str workout_exercises: List[WorkoutExerciseOut] = Field(default_factory=list) cardio_sessions: List[CardioOut] = Field(default_factory=list) class Config: from_attributes = True # ============================================================ # BODY METRIC VALUES # ============================================================ class BodyMetricValueBase(BaseModel): value: float class BodyMetricValueCreate(BodyMetricValueBase): metric_type_id: Optional[int] = None metric_type_sync_id: Optional[str] = None class BodyMetricValueOut(BodyMetricValueBase): id: int metric_type_id: int class Config: from_attributes = True # ============================================================ # BODY METRICS # ============================================================ class BodyMetricBase(BaseModel): metric_date: str notes: Optional[str] = "" class BodyMetricCreate(BodyMetricBase): sync_id: Optional[str] = None client_id: Optional[int] = None client_sync_id: Optional[str] = None values: List[BodyMetricValueCreate] = Field(default_factory=list) class BodyMetricOut(BodyMetricBase): id: int sync_id: str owner_id: Optional[int] = None client_id: Optional[int] = None created_at: str values: List[BodyMetricValueOut] = Field(default_factory=list) class Config: from_attributes = True # ============================================================ # SYNC # ============================================================ class SyncPayload(BaseModel): clients: List[ClientCreate] = Field(default_factory=list) exercises: List[ExerciseCreate] = Field(default_factory=list) workouts: List[WorkoutCreate] = Field(default_factory=list) body_metrics: List[BodyMetricCreate] = Field(default_factory=list) class SyncResponse(BaseModel): status: str created: Dict[str, int] updated: Dict[str, int] warnings: List[str] = Field(default_factory=list)