refactor: extract Pydantic models to models.py
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 <noreply@anthropic.com>
This commit is contained in:
parent
d826524789
commit
d2791fe0bf
|
|
@ -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."""
|
||||
|
|
|
|||
119
backend/models.py
Normal file
119
backend/models.py
Normal file
|
|
@ -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
|
||||
Loading…
Reference in New Issue
Block a user