shinkan-jinkendo/backend/models.py
Lars f4f5642c21
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
feat(profiles): add training planning preferences to user profile
- Introduced `training_planning_prefs` field in the ProfileUpdate model to store user-specific UI options for training planning.
- Updated the backend to handle the new preferences during profile updates, ensuring proper validation and storage.
- Enhanced the frontend to allow users to select their preferred display mode for training modules in the Account Settings page.
- Updated version to 0.8.98 and adjusted database schema version accordingly, reflecting the new feature integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:36:19 +02:00

258 lines
7.5 KiB
Python

"""
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",
)
training_planning_prefs: Optional[Dict[str, Any]] = Field(
default=None,
description="JSON: UI-Optionen Trainingsplanung (z.B. Darstellung kopierter Module)",
)
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]]