Files
gym-bro/backend/routers/metrics.py
T
2026-10-03 09:44:11 +03:00

285 lines
8.0 KiB
Python

import uuid
from datetime import datetime
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, joinedload
import auth
import database
import models
import schemas
router = APIRouter(
tags=["metrics"],
dependencies=[Depends(auth.get_current_user)],
)
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 get_owned_client(
db: Session,
client_id: int,
user: models.User,
) -> models.Client:
client = (
db.query(models.Client)
.filter(
models.Client.id == client_id,
models.Client.owner_id == user.id,
)
.first()
)
if not client:
raise HTTPException(status_code=404, detail="Client not found")
return client
def resolve_metric_type(
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
# ============================================================
# METRIC TYPES
# ============================================================
@router.post("/metric-types/", response_model=schemas.MetricTypeOut)
def create_metric_type(
metric_type: schemas.MetricTypeCreate,
db: Session = Depends(database.get_db),
):
sync_id = metric_type.sync_id or new_sync_id()
existing = db.query(models.MetricType).filter(models.MetricType.sync_id == sync_id).first()
if existing:
existing.name = metric_type.name
existing.unit = metric_type.unit or existing.unit
existing.category = metric_type.category or existing.category
existing.sort_order = metric_type.sort_order if metric_type.sort_order is not None else existing.sort_order
db.commit()
db.refresh(existing)
return existing
db_mt = models.MetricType(
sync_id=sync_id,
name=metric_type.name,
unit=metric_type.unit or "см",
category=metric_type.category or "body",
sort_order=metric_type.sort_order or 0,
created_at=get_current_timestamp(),
)
db.add(db_mt)
db.commit()
db.refresh(db_mt)
return db_mt
@router.get("/metric-types/", response_model=List[schemas.MetricTypeOut])
def list_metric_types(db: Session = Depends(database.get_db)):
return db.query(models.MetricType).order_by(
models.MetricType.sort_order,
models.MetricType.name,
).all()
# ============================================================
# BODY METRICS
# ============================================================
@router.post("/body-metrics/", response_model=schemas.BodyMetricOut)
def create_body_metric(
metric: schemas.BodyMetricCreate,
current_user: models.User = Depends(auth.get_current_user),
db: Session = Depends(database.get_db),
):
sync_id = metric.sync_id or new_sync_id()
timestamp = get_current_timestamp()
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.metric_date
existing.notes = metric.notes or ""
existing.client_id = metric.client_id
db.query(models.BodyMetricValue).filter(
models.BodyMetricValue.body_metric_id == existing.id
).delete()
for value in metric.values or []:
mt = resolve_metric_type(db, value)
if not mt:
raise HTTPException(status_code=422, detail="Metric type not found")
db.add(
models.BodyMetricValue(
body_metric_id=existing.id,
metric_type_id=mt.id,
value=value.value,
)
)
db.commit()
db.refresh(existing)
return existing
client_id = metric.client_id
if client_id:
get_owned_client(db, client_id, current_user)
db_metric = models.BodyMetric(
sync_id=sync_id,
owner_id=current_user.id,
client_id=client_id,
metric_date=metric.metric_date,
notes=metric.notes or "",
created_at=timestamp,
)
db.add(db_metric)
db.flush()
for value in metric.values or []:
mt = resolve_metric_type(db, value)
if not mt:
raise HTTPException(status_code=422, detail="Metric type not found")
db.add(
models.BodyMetricValue(
body_metric_id=db_metric.id,
metric_type_id=mt.id,
value=value.value,
)
)
db.commit()
db.refresh(db_metric)
return db_metric
@router.get("/body-metrics/", response_model=List[schemas.BodyMetricOut])
def list_body_metrics(
client_id: Optional[int] = None,
current_user: models.User = Depends(auth.get_current_user),
db: Session = Depends(database.get_db),
):
query = db.query(models.BodyMetric).options(
joinedload(models.BodyMetric.values)
).filter(
models.BodyMetric.owner_id == current_user.id
)
if client_id:
get_owned_client(db, client_id, current_user)
query = query.filter(models.BodyMetric.client_id == client_id)
return query.order_by(models.BodyMetric.metric_date.desc()).all()
@router.get("/body-metrics/history")
def get_metric_history(
client_id: int,
metric_type_id: int,
limit: int = 50,
current_user: models.User = Depends(auth.get_current_user),
db: Session = Depends(database.get_db),
):
get_owned_client(db, client_id, current_user)
metric_type = db.get(models.MetricType, metric_type_id)
if not metric_type:
raise HTTPException(status_code=404, detail="Metric type not found")
results = (
db.query(
models.BodyMetric.metric_date,
models.BodyMetricValue.value,
)
.join(
models.BodyMetricValue,
models.BodyMetricValue.body_metric_id == models.BodyMetric.id,
)
.filter(
models.BodyMetric.owner_id == current_user.id,
models.BodyMetric.client_id == client_id,
models.BodyMetricValue.metric_type_id == metric_type_id,
)
.order_by(models.BodyMetric.metric_date.desc())
.limit(limit)
.all()
)
return [
{
"date": r.metric_date,
"value": r.value,
"metric_type_id": metric_type_id,
"metric_type_name": metric_type.name,
"unit": metric_type.unit,
}
for r in results
]
@router.get("/body-metrics/{metric_id}", response_model=schemas.BodyMetricOut)
def get_body_metric(
metric_id: int,
current_user: models.User = Depends(auth.get_current_user),
db: Session = Depends(database.get_db),
):
metric = (
db.query(models.BodyMetric)
.options(joinedload(models.BodyMetric.values))
.filter(
models.BodyMetric.id == metric_id,
models.BodyMetric.owner_id == current_user.id,
)
.first()
)
if not metric:
raise HTTPException(status_code=404, detail="Body metric not found")
return metric
@router.delete("/body-metrics/{metric_id}")
def delete_body_metric(
metric_id: int,
current_user: models.User = Depends(auth.get_current_user),
db: Session = Depends(database.get_db),
):
metric = (
db.query(models.BodyMetric)
.filter(
models.BodyMetric.id == metric_id,
models.BodyMetric.owner_id == current_user.id,
)
.first()
)
if not metric:
raise HTTPException(status_code=404, detail="Body metric not found")
db.delete(metric)
db.commit()
return {"status": "deleted"}