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
c7d283c0c9
|
|
@ -17,6 +17,11 @@ from starlette.requests import Request
|
||||||
|
|
||||||
from db import get_db, get_cursor, r2d, init_db
|
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 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"))
|
DATA_DIR = Path(os.getenv("DATA_DIR", "./data"))
|
||||||
PHOTOS_DIR = Path(os.getenv("PHOTOS_DIR", "./photos"))
|
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")
|
raise HTTPException(400, "Kein Profil gefunden")
|
||||||
|
|
||||||
# ── Models ────────────────────────────────────────────────────────────────────
|
# ── 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 ──────────────────────────────────────────────────────────────────
|
# ── Profiles ──────────────────────────────────────────────────────────────────
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
# Models moved to models.py
|
||||||
# Auth functions moved to auth.py
|
# Auth functions moved to auth.py
|
||||||
|
|
||||||
@app.get("/api/profiles")
|
@app.get("/api/profiles")
|
||||||
|
|
@ -1092,17 +1047,6 @@ def get_ai_usage(x_profile_id: Optional[str]=Header(default=None), session: dict
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Auth ──────────────────────────────────────────────────────────────────────
|
# ── 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")
|
@app.post("/api/auth/login")
|
||||||
@limiter.limit("5/minute")
|
@limiter.limit("5/minute")
|
||||||
async def login(req: LoginRequest, request: Request):
|
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"}
|
return {"ok": True, "message": "Passwort erfolgreich zurückgesetzt"}
|
||||||
|
|
||||||
# ── Admin ─────────────────────────────────────────────────────────────────────
|
# ── 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")
|
@app.get("/api/admin/profiles")
|
||||||
def admin_list_profiles(session: dict=Depends(require_admin)):
|
def admin_list_profiles(session: dict=Depends(require_admin)):
|
||||||
"""Admin: List all profiles with stats."""
|
"""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