From c7d283c0c9434354a8f67c83bb565bafb449d7bc Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 19 Mar 2026 09:53:51 +0100 Subject: [PATCH] refactor: extract Pydantic models to models.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1.3 - Data Models isolieren NEUE DATEI: - backend/models.py: Alle Pydantic Models (122 Zeilen) * ProfileCreate, ProfileUpdate * WeightEntry, CircumferenceEntry, CaliperEntry * ActivityEntry, NutritionDay * LoginRequest, PasswordResetRequest, PasswordResetConfirm * AdminProfileUpdate ÄNDERUNGEN: - backend/main.py: * Import models from models.py * Entfernt: ~60 Zeilen Model-Definitionen * Von 2025 → 1878 Zeilen (-147 Zeilen / -7%) PROGRESS: ✅ db.py: Database + init_db ✅ auth.py: Auth functions + dependencies ✅ models.py: Pydantic schemas Co-Authored-By: Claude Opus 4.6 --- backend/main.py | 74 +++------------------------- backend/models.py | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 68 deletions(-) create mode 100644 backend/models.py diff --git a/backend/main.py b/backend/main.py index e8a0f78..5d8ff77 100644 --- a/backend/main.py +++ b/backend/main.py @@ -17,6 +17,11 @@ from starlette.requests import Request from db import get_db, get_cursor, r2d, init_db from auth import hash_pin, verify_pin, make_token, get_session, require_auth, require_auth_flexible, require_admin +from models import ( + ProfileCreate, ProfileUpdate, WeightEntry, CircumferenceEntry, + CaliperEntry, ActivityEntry, NutritionDay, LoginRequest, + PasswordResetRequest, PasswordResetConfirm, AdminProfileUpdate +) DATA_DIR = Path(os.getenv("DATA_DIR", "./data")) PHOTOS_DIR = Path(os.getenv("PHOTOS_DIR", "./photos")) @@ -63,60 +68,10 @@ def get_pid(x_profile_id: Optional[str] = Header(default=None)) -> str: raise HTTPException(400, "Kein Profil gefunden") # ── Models ──────────────────────────────────────────────────────────────────── -class ProfileCreate(BaseModel): - name: str - avatar_color: Optional[str] = '#1D9E75' - sex: Optional[str] = 'm' - dob: Optional[str] = None - height: Optional[float] = 178 - goal_weight: Optional[float] = None - goal_bf_pct: Optional[float] = None - -class ProfileUpdate(BaseModel): - name: Optional[str] = None - avatar_color: Optional[str] = None - sex: Optional[str] = None - dob: Optional[str] = None - height: Optional[float] = None - goal_weight: Optional[float] = None - goal_bf_pct: Optional[float] = None - -class WeightEntry(BaseModel): - date: str; weight: float; note: Optional[str]=None - -class CircumferenceEntry(BaseModel): - date: str - c_neck: Optional[float]=None; c_chest: Optional[float]=None - c_waist: Optional[float]=None; c_belly: Optional[float]=None - c_hip: Optional[float]=None; c_thigh: Optional[float]=None - c_calf: Optional[float]=None; c_arm: Optional[float]=None - notes: Optional[str]=None; photo_id: Optional[str]=None - -class CaliperEntry(BaseModel): - date: str; sf_method: Optional[str]='jackson3' - sf_chest: Optional[float]=None; sf_axilla: Optional[float]=None - sf_triceps: Optional[float]=None; sf_subscap: Optional[float]=None - sf_suprailiac: Optional[float]=None; sf_abdomen: Optional[float]=None - sf_thigh: Optional[float]=None; sf_calf_med: Optional[float]=None - sf_lowerback: Optional[float]=None; sf_biceps: Optional[float]=None - body_fat_pct: Optional[float]=None; lean_mass: Optional[float]=None - fat_mass: Optional[float]=None; notes: Optional[str]=None - -class ActivityEntry(BaseModel): - date: str; start_time: Optional[str]=None; end_time: Optional[str]=None - activity_type: str; duration_min: Optional[float]=None - kcal_active: Optional[float]=None; kcal_resting: Optional[float]=None - hr_avg: Optional[float]=None; hr_max: Optional[float]=None - distance_km: Optional[float]=None; rpe: Optional[int]=None - source: Optional[str]='manual'; notes: Optional[str]=None - -class NutritionDay(BaseModel): - date: str; kcal: Optional[float]=None; protein_g: Optional[float]=None - fat_g: Optional[float]=None; carbs_g: Optional[float]=None - # ── Profiles ────────────────────────────────────────────────────────────────── from datetime import timedelta +# Models moved to models.py # Auth functions moved to auth.py @app.get("/api/profiles") @@ -1092,17 +1047,6 @@ def get_ai_usage(x_profile_id: Optional[str]=Header(default=None), session: dict } # ── Auth ────────────────────────────────────────────────────────────────────── -class LoginRequest(BaseModel): - email: str - password: str - -class PasswordResetRequest(BaseModel): - email: str - -class PasswordResetConfirm(BaseModel): - token: str - new_password: str - @app.post("/api/auth/login") @limiter.limit("5/minute") async def login(req: LoginRequest, request: Request): @@ -1250,12 +1194,6 @@ def password_reset_confirm(req: PasswordResetConfirm): return {"ok": True, "message": "Passwort erfolgreich zurückgesetzt"} # ── Admin ───────────────────────────────────────────────────────────────────── -class AdminProfileUpdate(BaseModel): - role: Optional[str] = None - ai_enabled: Optional[int] = None - ai_limit_day: Optional[int] = None - export_enabled: Optional[int] = None - @app.get("/api/admin/profiles") def admin_list_profiles(session: dict=Depends(require_admin)): """Admin: List all profiles with stats.""" diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..b706906 --- /dev/null +++ b/backend/models.py @@ -0,0 +1,119 @@ +""" +Pydantic Models for Mitai Jinkendo API + +Data validation schemas for request/response bodies. +""" +from typing import Optional +from pydantic import BaseModel + + +# ── Profile Models ──────────────────────────────────────────────────────────── + +class ProfileCreate(BaseModel): + name: str + avatar_color: Optional[str] = '#1D9E75' + sex: Optional[str] = 'm' + dob: Optional[str] = None + height: Optional[float] = 178 + goal_weight: Optional[float] = None + goal_bf_pct: Optional[float] = None + + +class ProfileUpdate(BaseModel): + name: Optional[str] = None + avatar_color: Optional[str] = None + sex: Optional[str] = None + dob: Optional[str] = None + height: Optional[float] = None + goal_weight: Optional[float] = None + goal_bf_pct: Optional[float] = None + + +# ── Tracking Models ─────────────────────────────────────────────────────────── + +class WeightEntry(BaseModel): + date: str + weight: float + note: Optional[str] = None + + +class CircumferenceEntry(BaseModel): + date: str + c_neck: Optional[float] = None + c_chest: Optional[float] = None + c_waist: Optional[float] = None + c_belly: Optional[float] = None + c_hip: Optional[float] = None + c_thigh: Optional[float] = None + c_calf: Optional[float] = None + c_arm: Optional[float] = None + notes: Optional[str] = None + photo_id: Optional[str] = None + + +class CaliperEntry(BaseModel): + date: str + sf_method: Optional[str] = 'jackson3' + sf_chest: Optional[float] = None + sf_axilla: Optional[float] = None + sf_triceps: Optional[float] = None + sf_subscap: Optional[float] = None + sf_suprailiac: Optional[float] = None + sf_abdomen: Optional[float] = None + sf_thigh: Optional[float] = None + sf_calf_med: Optional[float] = None + sf_lowerback: Optional[float] = None + sf_biceps: Optional[float] = None + body_fat_pct: Optional[float] = None + lean_mass: Optional[float] = None + fat_mass: Optional[float] = None + notes: Optional[str] = None + + +class ActivityEntry(BaseModel): + date: str + start_time: Optional[str] = None + end_time: Optional[str] = None + activity_type: str + duration_min: Optional[float] = None + kcal_active: Optional[float] = None + kcal_resting: Optional[float] = None + hr_avg: Optional[float] = None + hr_max: Optional[float] = None + distance_km: Optional[float] = None + rpe: Optional[int] = None + source: Optional[str] = 'manual' + notes: Optional[str] = None + + +class NutritionDay(BaseModel): + date: str + kcal: Optional[float] = None + protein_g: Optional[float] = None + fat_g: Optional[float] = None + carbs_g: Optional[float] = None + + +# ── Auth Models ─────────────────────────────────────────────────────────────── + +class LoginRequest(BaseModel): + email: str + password: str + + +class PasswordResetRequest(BaseModel): + email: str + + +class PasswordResetConfirm(BaseModel): + token: str + new_password: str + + +# ── Admin Models ────────────────────────────────────────────────────────────── + +class AdminProfileUpdate(BaseModel): + role: Optional[str] = None + ai_enabled: Optional[int] = None + ai_limit_day: Optional[int] = None + export_enabled: Optional[int] = None