""" Pydantic Models for Shinkan Jinkendo API Request/Response schemas for all endpoints """ from pydantic import BaseModel, EmailStr, Field from typing import Optional, List, Dict, Any from datetime import date, time, datetime # ============================================================================ # Auth & Profiles (von Mitai übernommen) # ============================================================================ class LoginRequest(BaseModel): email: EmailStr password: str class RegisterRequest(BaseModel): email: EmailStr password: str name: Optional[str] = None requested_club_id: Optional[int] = Field(default=None, ge=1) class PasswordResetRequest(BaseModel): email: str class PasswordResetConfirm(BaseModel): token: str new_password: str = Field(min_length=8, max_length=128) class ProfileCreate(BaseModel): """Nur für POST /api/profiles (Plattform-Admin): neues Nutzerprofil ohne Self-Registration.""" name: str = Field(min_length=2, max_length=200) email: EmailStr class ProfileUpdate(BaseModel): name: Optional[str] = None email: Optional[str] = None active_club_id: Optional[int] = None role: Optional[str] = Field( default=None, description="Portal-Rolle: user, trainer, admin, superadmin (nur Plattform-Admin)", ) tier: Optional[str] = Field(default=None, max_length=50) exercise_list_prefs: Optional[Dict[str, Any]] = Field( default=None, description="JSON: gespeicherte Standardfilter für die Übungsliste", ) class ProfileResponse(BaseModel): id: int email: str name: Optional[str] role: str tier: str email_verified: bool created_at: datetime # ============================================================================ # Clubs & Groups # ============================================================================ class ClubCreate(BaseModel): name: str abbreviation: Optional[str] = None description: Optional[str] = None class ClubResponse(BaseModel): id: int name: str abbreviation: Optional[str] description: Optional[str] status: str created_at: datetime class TrainingGroupCreate(BaseModel): club_id: int division_id: Optional[int] = None name: str focus: Optional[str] = None level: Optional[str] = None age_group: Optional[str] = None weekday: Optional[str] = None time_start: Optional[time] = None time_end: Optional[time] = None location: Optional[str] = None trainer_id: Optional[int] = None co_trainer_ids: Optional[List[int]] = [] class TrainingGroupResponse(BaseModel): id: int club_id: int name: str focus: Optional[str] level: Optional[str] age_group: Optional[str] weekday: Optional[str] time_start: Optional[time] time_end: Optional[time] location: Optional[str] trainer_id: Optional[int] status: str created_at: datetime # ============================================================================ # Skills & Methods # ============================================================================ class SkillCreate(BaseModel): name: str category: Optional[str] = None description: Optional[str] = None importance: Optional[int] = Field(None, ge=1, le=5) keywords: Optional[List[str]] = [] class SkillResponse(BaseModel): id: int name: str category: Optional[str] description: Optional[str] importance: Optional[int] keywords: Optional[List[str]] status: str created_at: datetime class MethodCreate(BaseModel): name: str abbreviation: Optional[str] = None category: Optional[str] = None description: Optional[str] = None typical_duration: Optional[int] = None typical_group_size: Optional[str] = None related_skills: Optional[List[int]] = [] keywords: Optional[List[str]] = [] class MethodResponse(BaseModel): id: int name: str abbreviation: Optional[str] category: Optional[str] description: Optional[str] typical_duration: Optional[int] typical_group_size: Optional[str] related_skills: Optional[List[int]] keywords: Optional[List[str]] status: str created_at: datetime # ============================================================================ # Exercises (Kernobjekt) # ============================================================================ class ExerciseCreate(BaseModel): title: str summary: Optional[str] = None goal: str execution: str preparation: Optional[str] = None trainer_notes: Optional[str] = None equipment: Optional[List[str]] = [] duration_min: Optional[int] = None duration_max: Optional[int] = None group_size_min: Optional[int] = None group_size_max: Optional[int] = None age_groups: Optional[List[str]] = [] focus_area: Optional[str] = None secondary_areas: Optional[List[str]] = [] training_character: Optional[str] = None primary_method_id: Optional[int] = None secondary_method_ids: Optional[List[int]] = [] visibility: Optional[str] = "private" club_id: Optional[int] = None class ExerciseResponse(BaseModel): id: int title: str summary: Optional[str] goal: str execution: str preparation: Optional[str] trainer_notes: Optional[str] equipment: Optional[List[str]] duration_min: Optional[int] duration_max: Optional[int] group_size_min: Optional[int] group_size_max: Optional[int] age_groups: Optional[List[str]] focus_area: Optional[str] secondary_areas: Optional[List[str]] training_character: Optional[str] primary_method_id: Optional[int] secondary_method_ids: Optional[List[int]] visibility: str status: str created_by: int club_id: Optional[int] created_at: datetime updated_at: datetime class ExerciseSkillCreate(BaseModel): exercise_id: int skill_id: int is_primary: bool = False intensity: Optional[int] = Field(None, ge=1, le=5) development_contribution: Optional[str] = None required_level: Optional[int] = None target_level: Optional[int] = None # ============================================================================ # Training Planning # ============================================================================ class TrainingUnitCreate(BaseModel): group_id: int date: date time_start: Optional[time] = None time_end: Optional[time] = None derived_from_template_id: Optional[int] = None derived_from_unit_id: Optional[int] = None title: Optional[str] = None goal: Optional[str] = None focus_areas: Optional[List[str]] = [] class TrainingUnitResponse(BaseModel): id: int group_id: int date: date time_start: Optional[time] time_end: Optional[time] title: Optional[str] goal: Optional[str] focus_areas: Optional[List[str]] completion_status: str created_by: int created_at: datetime updated_at: datetime # ============================================================================ # Import # ============================================================================ class WikiImportRequest(BaseModel): import_type: str # skill, method, exercise wiki_url: Optional[str] = None dry_run: bool = False class WikiImportResponse(BaseModel): import_status: str items_total: int items_imported: int items_failed: int error_log: Optional[List[str]]