285 lines
8.0 KiB
Python
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"}
|