possibly auth
This commit is contained in:
+509
-144
@@ -1,186 +1,551 @@
|
||||
#!/usr/bin/env python3
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import auth
|
||||
import database
|
||||
import models
|
||||
import schemas
|
||||
from datetime import datetime
|
||||
|
||||
router = APIRouter(prefix="/sync", tags=["sync"])
|
||||
|
||||
def get_current_timestamp():
|
||||
return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%fZ')
|
||||
router = APIRouter(
|
||||
prefix="/sync",
|
||||
tags=["sync"],
|
||||
dependencies=[Depends(auth.get_current_user)],
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_CLIENT_COLOR = "#bb86fc"
|
||||
|
||||
|
||||
def get_current_timestamp() -> str:
|
||||
return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%fZ")
|
||||
|
||||
|
||||
def new_sync_id() -> str:
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
def normalize_name(name: str) -> str:
|
||||
return (name or "").strip().lower()
|
||||
|
||||
|
||||
def resolve_workout_type_id(
|
||||
db: Session,
|
||||
workout_type_id: Optional[int],
|
||||
) -> Optional[int]:
|
||||
if not workout_type_id:
|
||||
return None
|
||||
|
||||
wt = db.get(models.WorkoutType, workout_type_id)
|
||||
return wt.id if wt else None
|
||||
|
||||
|
||||
def upsert_client(
|
||||
db: Session,
|
||||
user: models.User,
|
||||
data: schemas.ClientCreate,
|
||||
timestamp: str,
|
||||
) -> Tuple[models.Client, str]:
|
||||
sync_id = data.sync_id or new_sync_id()
|
||||
|
||||
existing = db.query(models.Client).filter(models.Client.sync_id == sync_id).first()
|
||||
if existing:
|
||||
if existing.owner_id != user.id:
|
||||
raise HTTPException(status_code=409, detail="Client sync_id belongs to another user")
|
||||
|
||||
existing.full_name = data.full_name
|
||||
existing.birth_date = data.birth_date
|
||||
existing.goal = data.goal or ""
|
||||
existing.notes = data.notes or ""
|
||||
existing.color = data.color or existing.color or DEFAULT_CLIENT_COLOR
|
||||
existing.is_active = data.is_active if data.is_active is not None else existing.is_active
|
||||
existing.updated_at = timestamp
|
||||
|
||||
db.flush()
|
||||
return existing, "updated"
|
||||
|
||||
obj = models.Client(
|
||||
sync_id=sync_id,
|
||||
owner_id=user.id,
|
||||
full_name=data.full_name,
|
||||
birth_date=data.birth_date,
|
||||
goal=data.goal or "",
|
||||
notes=data.notes or "",
|
||||
color=data.color or DEFAULT_CLIENT_COLOR,
|
||||
is_active=data.is_active if data.is_active is not None else 1,
|
||||
created_at=timestamp,
|
||||
updated_at=timestamp,
|
||||
)
|
||||
|
||||
db.add(obj)
|
||||
db.flush()
|
||||
return obj, "created"
|
||||
|
||||
|
||||
def find_exercise_by_name(
|
||||
db: Session,
|
||||
user: models.User,
|
||||
name: str,
|
||||
) -> Optional[models.Exercise]:
|
||||
n = normalize_name(name)
|
||||
if not n:
|
||||
return None
|
||||
|
||||
owned = (
|
||||
db.query(models.Exercise)
|
||||
.filter(
|
||||
models.Exercise.owner_id == user.id,
|
||||
func.lower(models.Exercise.name) == n,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if owned:
|
||||
return owned
|
||||
|
||||
return (
|
||||
db.query(models.Exercise)
|
||||
.filter(
|
||||
models.Exercise.owner_id.is_(None),
|
||||
func.lower(models.Exercise.name) == n,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
def upsert_exercise(
|
||||
db: Session,
|
||||
user: models.User,
|
||||
data: schemas.ExerciseCreate,
|
||||
timestamp: str,
|
||||
) -> Tuple[models.Exercise, str]:
|
||||
sync_id = data.sync_id or new_sync_id()
|
||||
workout_type_id = resolve_workout_type_id(db, data.workout_type_id)
|
||||
|
||||
existing = db.query(models.Exercise).filter(models.Exercise.sync_id == sync_id).first()
|
||||
if existing:
|
||||
if existing.owner_id is not None and existing.owner_id != user.id:
|
||||
raise HTTPException(status_code=409, detail="Exercise sync_id belongs to another user")
|
||||
|
||||
if existing.owner_id == user.id:
|
||||
existing.name = (data.name or existing.name).strip()
|
||||
existing.workout_type_id = workout_type_id
|
||||
existing.updated_at = timestamp
|
||||
db.flush()
|
||||
return existing, "updated"
|
||||
|
||||
# Глобальное упражнение: не обновляем, просто переиспользуем
|
||||
return existing, "global"
|
||||
|
||||
by_name = find_exercise_by_name(db, user, data.name)
|
||||
if by_name:
|
||||
if by_name.owner_id == user.id:
|
||||
by_name.sync_id = by_name.sync_id or sync_id
|
||||
by_name.name = (data.name or by_name.name).strip()
|
||||
by_name.workout_type_id = workout_type_id
|
||||
by_name.updated_at = timestamp
|
||||
db.flush()
|
||||
return by_name, "updated"
|
||||
|
||||
# Нашли глобальное с тем же именем
|
||||
return by_name, "global"
|
||||
|
||||
obj = models.Exercise(
|
||||
sync_id=sync_id,
|
||||
owner_id=user.id,
|
||||
name=(data.name or "Без названия").strip(),
|
||||
workout_type_id=workout_type_id,
|
||||
created_at=timestamp,
|
||||
updated_at=timestamp,
|
||||
)
|
||||
|
||||
db.add(obj)
|
||||
db.flush()
|
||||
return obj, "created"
|
||||
|
||||
|
||||
def resolve_client_id_from_sync_id(
|
||||
db: Session,
|
||||
user: models.User,
|
||||
client_sync_id: Optional[str],
|
||||
client_map: Dict[str, int],
|
||||
) -> Optional[int]:
|
||||
if not client_sync_id:
|
||||
return None
|
||||
|
||||
if client_sync_id in client_map:
|
||||
return client_map[client_sync_id]
|
||||
|
||||
client = (
|
||||
db.query(models.Client)
|
||||
.filter(
|
||||
models.Client.sync_id == client_sync_id,
|
||||
models.Client.owner_id == user.id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
return client.id if client else None
|
||||
|
||||
|
||||
def resolve_exercise_id(
|
||||
db: Session,
|
||||
user: models.User,
|
||||
exercise_sync_id: Optional[str],
|
||||
exercise_name: Optional[str],
|
||||
exercise_map: Dict[str, int],
|
||||
) -> Optional[int]:
|
||||
if exercise_sync_id:
|
||||
if exercise_sync_id in exercise_map:
|
||||
return exercise_map[exercise_sync_id]
|
||||
|
||||
ex = db.query(models.Exercise).filter(models.Exercise.sync_id == exercise_sync_id).first()
|
||||
if ex and (ex.owner_id is None or ex.owner_id == user.id):
|
||||
return ex.id
|
||||
|
||||
if exercise_name:
|
||||
ex = find_exercise_by_name(db, user, exercise_name)
|
||||
if ex:
|
||||
return ex.id
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def create_set_from_payload(
|
||||
db: Session,
|
||||
workout_exercise_id: int,
|
||||
data: schemas.SetCreate,
|
||||
timestamp: str,
|
||||
warnings: list,
|
||||
) -> models.Set:
|
||||
is_dropset = 1 if data.is_dropset else 0
|
||||
|
||||
if is_dropset:
|
||||
if data.weight is not None or data.reps is not None:
|
||||
warnings.append("Dropset top-level weight/reps ignored")
|
||||
data.weight = None
|
||||
data.reps = None
|
||||
|
||||
if not data.dropset_items:
|
||||
raise HTTPException(status_code=422, detail="Dropset must have items")
|
||||
else:
|
||||
if data.weight is None or data.reps is None:
|
||||
raise HTTPException(status_code=422, detail="Regular set must have weight and reps")
|
||||
|
||||
db_set = models.Set(
|
||||
sync_id=data.sync_id or new_sync_id(),
|
||||
workout_exercise_id=workout_exercise_id,
|
||||
is_dropset=is_dropset,
|
||||
weight=None if is_dropset else float(data.weight),
|
||||
reps=None if is_dropset else int(data.reps),
|
||||
rating=data.rating or 3,
|
||||
notes=data.notes or "",
|
||||
sort_order=data.sort_order or 0,
|
||||
timestamp=data.timestamp or timestamp,
|
||||
created_at=timestamp,
|
||||
)
|
||||
|
||||
db.add(db_set)
|
||||
db.flush()
|
||||
|
||||
if is_dropset:
|
||||
for idx, item in enumerate(data.dropset_items or []):
|
||||
db.add(
|
||||
models.DropsetItem(
|
||||
set_id=db_set.id,
|
||||
weight=float(item.weight),
|
||||
reps=int(item.reps),
|
||||
sort_order=item.sort_order if item.sort_order is not None else idx,
|
||||
)
|
||||
)
|
||||
|
||||
return db_set
|
||||
|
||||
|
||||
def create_cardio_from_payload(
|
||||
db: Session,
|
||||
workout_id: int,
|
||||
data: schemas.CardioCreate,
|
||||
timestamp: str,
|
||||
) -> models.Cardio:
|
||||
db_cardio = models.Cardio(
|
||||
sync_id=data.sync_id or new_sync_id(),
|
||||
workout_id=workout_id,
|
||||
cardio_type=data.cardio_type,
|
||||
purpose=data.purpose,
|
||||
duration=data.duration,
|
||||
distance=data.distance,
|
||||
speed=data.speed,
|
||||
incline=data.incline,
|
||||
resistance=data.resistance,
|
||||
notes=data.notes or "",
|
||||
sort_order=data.sort_order or 0,
|
||||
created_at=timestamp,
|
||||
)
|
||||
db.add(db_cardio)
|
||||
return db_cardio
|
||||
|
||||
|
||||
def resolve_metric_type_for_sync(
|
||||
db: Session,
|
||||
value: schemas.BodyMetricValueCreate,
|
||||
) -> Optional[models.MetricType]:
|
||||
if value.metric_type_id:
|
||||
return db.get(models.MetricType, value.metric_type_id)
|
||||
|
||||
if value.metric_type_sync_id:
|
||||
return (
|
||||
db.query(models.MetricType)
|
||||
.filter(models.MetricType.sync_id == value.metric_type_sync_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@router.post("/", response_model=schemas.SyncResponse)
|
||||
def sync_data(payload: schemas.SyncPayload, db: Session = Depends(database.get_db)):
|
||||
"""
|
||||
Endpoint для синхронизации офлайн-очереди с клиента.
|
||||
Использует sync_id для идемпотентности — если запись с таким sync_id уже есть, пропускаем.
|
||||
"""
|
||||
def sync_data(
|
||||
payload: schemas.SyncPayload,
|
||||
current_user: models.User = Depends(auth.get_current_user),
|
||||
db: Session = Depends(database.get_db),
|
||||
):
|
||||
timestamp = get_current_timestamp()
|
||||
warnings = []
|
||||
|
||||
created = {
|
||||
"clients": 0,
|
||||
"workout_types": 0,
|
||||
"exercises": 0,
|
||||
"workouts": 0,
|
||||
"metric_types": 0,
|
||||
"body_metrics": 0
|
||||
"body_metrics": 0,
|
||||
}
|
||||
updated = {
|
||||
"clients": 0,
|
||||
"exercises": 0,
|
||||
"workouts": 0,
|
||||
"body_metrics": 0,
|
||||
}
|
||||
updated = {k: 0 for k in created}
|
||||
|
||||
# Синхронизация клиентов
|
||||
client_map: Dict[str, int] = {}
|
||||
exercise_map: Dict[str, int] = {}
|
||||
|
||||
# ========================================================
|
||||
# 1. Clients
|
||||
# ========================================================
|
||||
for client_data in payload.clients or []:
|
||||
existing = db.query(models.Client).filter(models.Client.sync_id == client_data.sync_id).first()
|
||||
if not existing:
|
||||
db_client = models.Client(
|
||||
**client_data.dict(),
|
||||
created_at=timestamp,
|
||||
updated_at=timestamp
|
||||
)
|
||||
db.add(db_client)
|
||||
obj, status = upsert_client(db, current_user, client_data, timestamp)
|
||||
client_map[obj.sync_id] = obj.id
|
||||
|
||||
if status == "created":
|
||||
created["clients"] += 1
|
||||
elif status == "updated":
|
||||
updated["clients"] += 1
|
||||
|
||||
# Синхронизация типов тренировок
|
||||
for wt_data in payload.workout_types or []:
|
||||
existing = db.query(models.WorkoutType).filter(models.WorkoutType.sync_id == wt_data.sync_id).first()
|
||||
if not existing:
|
||||
db_wt = models.WorkoutType(
|
||||
**wt_data.dict(),
|
||||
created_at=timestamp
|
||||
)
|
||||
db.add(db_wt)
|
||||
created["workout_types"] += 1
|
||||
# ========================================================
|
||||
# 2. Exercises
|
||||
# ========================================================
|
||||
for exercise_data in payload.exercises or []:
|
||||
obj, status = upsert_exercise(db, current_user, exercise_data, timestamp)
|
||||
exercise_map[obj.sync_id] = obj.id
|
||||
|
||||
# Синхронизация упражнений
|
||||
for ex_data in payload.exercises or []:
|
||||
existing = db.query(models.Exercise).filter(models.Exercise.sync_id == ex_data.sync_id).first()
|
||||
if not existing:
|
||||
db_ex = models.Exercise(
|
||||
**ex_data.dict(),
|
||||
created_at=timestamp,
|
||||
updated_at=timestamp
|
||||
)
|
||||
db.add(db_ex)
|
||||
if status == "created":
|
||||
created["exercises"] += 1
|
||||
elif status == "updated":
|
||||
updated["exercises"] += 1
|
||||
|
||||
# Синхронизация типов замеров
|
||||
for mt_data in payload.metric_types or []:
|
||||
existing = db.query(models.MetricType).filter(models.MetricType.sync_id == mt_data.sync_id).first()
|
||||
if not existing:
|
||||
db_mt = models.MetricType(
|
||||
**mt_data.dict(),
|
||||
created_at=timestamp
|
||||
)
|
||||
db.add(db_mt)
|
||||
created["metric_types"] += 1
|
||||
|
||||
# Синхронизация тренировок (с упражнениями и подходами)
|
||||
# ========================================================
|
||||
# 3. Workouts
|
||||
# ========================================================
|
||||
for workout_data in payload.workouts or []:
|
||||
existing = db.query(models.Workout).filter(models.Workout.sync_id == workout_data.sync_id).first()
|
||||
if not existing:
|
||||
db_workout = models.Workout(
|
||||
sync_id=workout_data.sync_id,
|
||||
client_id=workout_data.client_id,
|
||||
workout_type_id=workout_data.workout_type_id,
|
||||
start_time=workout_data.start_time,
|
||||
end_time=workout_data.end_time,
|
||||
notes=workout_data.notes,
|
||||
sync_id = workout_data.sync_id or new_sync_id()
|
||||
|
||||
existing = db.query(models.Workout).filter(models.Workout.sync_id == sync_id).first()
|
||||
if existing:
|
||||
if existing.owner_id != current_user.id:
|
||||
raise HTTPException(status_code=409, detail="Workout sync_id belongs to another user")
|
||||
|
||||
existing.start_time = workout_data.start_time or existing.start_time
|
||||
existing.end_time = workout_data.end_time if workout_data.end_time is not None else existing.end_time
|
||||
existing.notes = workout_data.notes if workout_data.notes is not None else existing.notes
|
||||
existing.workout_type_id = resolve_workout_type_id(db, workout_data.workout_type_id)
|
||||
existing.synced = 1
|
||||
existing.updated_at = timestamp
|
||||
|
||||
updated["workouts"] += 1
|
||||
continue
|
||||
|
||||
client_id = resolve_client_id_from_sync_id(
|
||||
db,
|
||||
current_user,
|
||||
workout_data.client_sync_id,
|
||||
client_map,
|
||||
)
|
||||
|
||||
if workout_data.client_id and not client_id:
|
||||
client = (
|
||||
db.query(models.Client)
|
||||
.filter(
|
||||
models.Client.id == workout_data.client_id,
|
||||
models.Client.owner_id == current_user.id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
client_id = client.id if client else None
|
||||
|
||||
if workout_data.client_sync_id and not client_id:
|
||||
warnings.append(
|
||||
f"Client sync_id {workout_data.client_sync_id} not found for workout {sync_id}"
|
||||
)
|
||||
|
||||
db_workout = models.Workout(
|
||||
sync_id=sync_id,
|
||||
owner_id=current_user.id,
|
||||
client_id=client_id,
|
||||
workout_type_id=resolve_workout_type_id(db, workout_data.workout_type_id),
|
||||
start_time=workout_data.start_time,
|
||||
end_time=workout_data.end_time,
|
||||
notes=workout_data.notes or "",
|
||||
synced=1,
|
||||
created_at=timestamp,
|
||||
updated_at=timestamp,
|
||||
)
|
||||
|
||||
db.add(db_workout)
|
||||
db.flush()
|
||||
created["workouts"] += 1
|
||||
|
||||
for we_data in workout_data.workout_exercises or []:
|
||||
exercise_id = resolve_exercise_id(
|
||||
db,
|
||||
current_user,
|
||||
we_data.exercise_sync_id,
|
||||
we_data.exercise_name,
|
||||
exercise_map,
|
||||
)
|
||||
|
||||
db_we = models.WorkoutExercise(
|
||||
sync_id=we_data.sync_id or new_sync_id(),
|
||||
workout_id=db_workout.id,
|
||||
exercise_id=exercise_id,
|
||||
exercise_name=we_data.exercise_name,
|
||||
sort_order=we_data.sort_order or 0,
|
||||
created_at=timestamp,
|
||||
updated_at=timestamp
|
||||
)
|
||||
db.add(db_workout)
|
||||
|
||||
db.add(db_we)
|
||||
db.flush()
|
||||
|
||||
# Упражнения
|
||||
for we_data in workout_data.workout_exercises or []:
|
||||
db_we = models.WorkoutExercise(
|
||||
sync_id=we_data.sync_id,
|
||||
workout_id=db_workout.id,
|
||||
exercise_id=we_data.exercise_id,
|
||||
exercise_name=we_data.exercise_name,
|
||||
sort_order=we_data.sort_order,
|
||||
created_at=timestamp
|
||||
)
|
||||
db.add(db_we)
|
||||
db.flush()
|
||||
for set_data in we_data.sets or []:
|
||||
create_set_from_payload(db, db_we.id, set_data, timestamp, warnings)
|
||||
|
||||
# Подходы
|
||||
for set_data in we_data.sets or []:
|
||||
db_set = models.Set(
|
||||
sync_id=set_data.sync_id,
|
||||
workout_exercise_id=db_we.id,
|
||||
is_dropset=set_data.is_dropset,
|
||||
weight=set_data.weight,
|
||||
reps=set_data.reps,
|
||||
rating=set_data.rating,
|
||||
notes=set_data.notes,
|
||||
sort_order=set_data.sort_order,
|
||||
timestamp=set_data.timestamp,
|
||||
created_at=timestamp
|
||||
)
|
||||
db.add(db_set)
|
||||
db.flush()
|
||||
for cardio_data in workout_data.cardio_sessions or []:
|
||||
create_cardio_from_payload(db, db_workout.id, cardio_data, timestamp)
|
||||
|
||||
# Дроп-сет элементы
|
||||
if set_data.is_dropset:
|
||||
for item_data in set_data.dropset_items or []:
|
||||
db_item = models.DropsetItem(
|
||||
set_id=db_set.id,
|
||||
weight=item_data.weight,
|
||||
reps=item_data.reps,
|
||||
sort_order=item_data.sort_order
|
||||
)
|
||||
db.add(db_item)
|
||||
|
||||
# Кардио
|
||||
for cardio_data in workout_data.cardio_sessions or []:
|
||||
db_cardio = models.Cardio(
|
||||
sync_id=cardio_data.sync_id,
|
||||
workout_id=db_workout.id,
|
||||
cardio_type=cardio_data.cardio_type,
|
||||
purpose=cardio_data.purpose,
|
||||
duration=cardio_data.duration,
|
||||
distance=cardio_data.distance,
|
||||
speed=cardio_data.speed,
|
||||
incline=cardio_data.incline,
|
||||
resistance=cardio_data.resistance,
|
||||
notes=cardio_data.notes,
|
||||
sort_order=cardio_data.sort_order,
|
||||
created_at=timestamp
|
||||
)
|
||||
db.add(db_cardio)
|
||||
|
||||
created["workouts"] += 1
|
||||
|
||||
# Синхронизация замеров тела
|
||||
# ========================================================
|
||||
# 4. Body metrics
|
||||
# ========================================================
|
||||
for metric_data in payload.body_metrics or []:
|
||||
existing = db.query(models.BodyMetric).filter(models.BodyMetric.sync_id == metric_data.sync_id).first()
|
||||
if not existing:
|
||||
db_metric = models.BodyMetric(
|
||||
sync_id=metric_data.sync_id,
|
||||
client_id=metric_data.client_id,
|
||||
metric_date=metric_data.metric_date,
|
||||
notes=metric_data.notes,
|
||||
created_at=timestamp
|
||||
sync_id = metric_data.sync_id or new_sync_id()
|
||||
|
||||
existing = db.query(models.BodyMetric).filter(models.BodyMetric.sync_id == sync_id).first()
|
||||
if existing:
|
||||
if existing.owner_id != current_user.id:
|
||||
raise HTTPException(status_code=409, detail="BodyMetric sync_id belongs to another user")
|
||||
|
||||
existing.metric_date = metric_data.metric_date
|
||||
existing.notes = metric_data.notes or ""
|
||||
|
||||
client_id = resolve_client_id_from_sync_id(
|
||||
db,
|
||||
current_user,
|
||||
metric_data.client_sync_id,
|
||||
client_map,
|
||||
)
|
||||
db.add(db_metric)
|
||||
db.flush()
|
||||
if metric_data.client_id and not client_id:
|
||||
client = (
|
||||
db.query(models.Client)
|
||||
.filter(
|
||||
models.Client.id == metric_data.client_id,
|
||||
models.Client.owner_id == current_user.id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
client_id = client.id if client else None
|
||||
|
||||
existing.client_id = client_id
|
||||
|
||||
db.query(models.BodyMetricValue).filter(
|
||||
models.BodyMetricValue.body_metric_id == existing.id
|
||||
).delete()
|
||||
|
||||
for value_data in metric_data.values or []:
|
||||
db_value = models.BodyMetricValue(
|
||||
body_metric_id=db_metric.id,
|
||||
metric_type_id=value_data.metric_type_id,
|
||||
value=value_data.value
|
||||
)
|
||||
db.add(db_value)
|
||||
mt = resolve_metric_type_for_sync(db, value_data)
|
||||
if not mt:
|
||||
warnings.append("Body metric value skipped: metric type not found")
|
||||
continue
|
||||
|
||||
created["body_metrics"] += 1
|
||||
db.add(
|
||||
models.BodyMetricValue(
|
||||
body_metric_id=existing.id,
|
||||
metric_type_id=mt.id,
|
||||
value=float(value_data.value),
|
||||
)
|
||||
)
|
||||
|
||||
updated["body_metrics"] += 1
|
||||
continue
|
||||
|
||||
client_id = resolve_client_id_from_sync_id(
|
||||
db,
|
||||
current_user,
|
||||
metric_data.client_sync_id,
|
||||
client_map,
|
||||
)
|
||||
if metric_data.client_id and not client_id:
|
||||
client = (
|
||||
db.query(models.Client)
|
||||
.filter(
|
||||
models.Client.id == metric_data.client_id,
|
||||
models.Client.owner_id == current_user.id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
client_id = client.id if client else None
|
||||
|
||||
db_metric = models.BodyMetric(
|
||||
sync_id=sync_id,
|
||||
owner_id=current_user.id,
|
||||
client_id=client_id,
|
||||
metric_date=metric_data.metric_date,
|
||||
notes=metric_data.notes or "",
|
||||
created_at=timestamp,
|
||||
)
|
||||
|
||||
db.add(db_metric)
|
||||
db.flush()
|
||||
created["body_metrics"] += 1
|
||||
|
||||
for value_data in metric_data.values or []:
|
||||
mt = resolve_metric_type_for_sync(db, value_data)
|
||||
if not mt:
|
||||
warnings.append("Body metric value skipped: metric type not found")
|
||||
continue
|
||||
|
||||
db.add(
|
||||
models.BodyMetricValue(
|
||||
body_metric_id=db_metric.id,
|
||||
metric_type_id=mt.id,
|
||||
value=float(value_data.value),
|
||||
)
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
return schemas.SyncResponse(
|
||||
status="success",
|
||||
created=created,
|
||||
updated=updated
|
||||
updated=updated,
|
||||
warnings=warnings,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user