mirror of
https://github.com/escalante29/healthy-fit.git
synced 2026-03-21 12:28:46 +01:00
Add supplements, kettlebell, calendar, push notifications, and PWA support
- Supplement tracking: CRUD endpoints, /today, /logs, Supplements page - Kettlebell workouts: session tracking, analytics endpoint, ActiveSession page - Calendar module: events CRUD, calendar components - Push notifications: VAPID keys, PushSubscription model, APScheduler reminders, service worker with push/notificationclick handlers, Profile notifications UI - PWA: vite-plugin-pwa, manifest, icons, service worker generation - Frontend: TypeScript types, API modules, ConfirmModal, toast notifications - Auth fixes: password hashing, nutrition endpoint auth - CLAUDE.md: project documentation and development guide Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
from datetime import date as date_type
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import litellm
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import Session
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.ai.nutrition import NutritionalInfo, analyze_nutrition_from_image, nutrition_module
|
||||
from app.api import deps
|
||||
@@ -16,9 +18,18 @@ class AnalyzeRequest(BaseModel):
|
||||
description: str
|
||||
|
||||
|
||||
class FoodLogCreate(BaseModel):
|
||||
name: str
|
||||
calories: float
|
||||
protein: float
|
||||
carbs: float
|
||||
fats: float
|
||||
|
||||
|
||||
@router.post("/analyze", response_model=NutritionalInfo)
|
||||
def analyze_food(
|
||||
request: AnalyzeRequest,
|
||||
current_user: deps.CurrentUser,
|
||||
) -> Any:
|
||||
"""
|
||||
Analyze food description and return nutritional info using DSPy.
|
||||
@@ -32,6 +43,7 @@ def analyze_food(
|
||||
|
||||
@router.post("/analyze/image", response_model=NutritionalInfo)
|
||||
async def analyze_food_image(
|
||||
current_user: deps.CurrentUser,
|
||||
file: UploadFile = File(...),
|
||||
description: str = Form(""),
|
||||
) -> Any:
|
||||
@@ -51,7 +63,7 @@ async def analyze_food_image(
|
||||
def log_food(
|
||||
*,
|
||||
session: Session = Depends(deps.get_session),
|
||||
nutrition_info: NutritionalInfo,
|
||||
nutrition_info: FoodLogCreate,
|
||||
current_user: deps.CurrentUser,
|
||||
) -> Any:
|
||||
"""
|
||||
@@ -81,8 +93,6 @@ def read_logs(
|
||||
"""
|
||||
Get food logs for current user.
|
||||
"""
|
||||
from sqlmodel import select
|
||||
|
||||
statement = (
|
||||
select(FoodLog)
|
||||
.where(FoodLog.user_id == current_user.id)
|
||||
@@ -91,3 +101,55 @@ def read_logs(
|
||||
.limit(limit)
|
||||
)
|
||||
return session.exec(statement).all()
|
||||
|
||||
|
||||
class NutritionSummary(BaseModel):
|
||||
date: str
|
||||
total_calories: float
|
||||
total_protein: float
|
||||
total_carbs: float
|
||||
total_fats: float
|
||||
log_count: int
|
||||
target_calories: float | None
|
||||
target_protein: float | None
|
||||
target_carbs: float | None
|
||||
target_fat: float | None
|
||||
|
||||
|
||||
@router.get("/summary", response_model=NutritionSummary)
|
||||
def get_nutrition_summary(
|
||||
current_user: deps.CurrentUser,
|
||||
session: Session = Depends(deps.get_session),
|
||||
date: str = Query(default=None, description="Date in YYYY-MM-DD format, defaults to today"),
|
||||
) -> Any:
|
||||
"""
|
||||
Get aggregated macro totals for a given day.
|
||||
"""
|
||||
if date:
|
||||
target_date = datetime.strptime(date, "%Y-%m-%d").date()
|
||||
else:
|
||||
target_date = date_type.today()
|
||||
|
||||
start = datetime(target_date.year, target_date.month, target_date.day, 0, 0, 0)
|
||||
end = datetime(target_date.year, target_date.month, target_date.day, 23, 59, 59)
|
||||
|
||||
statement = (
|
||||
select(FoodLog)
|
||||
.where(FoodLog.user_id == current_user.id)
|
||||
.where(FoodLog.timestamp >= start)
|
||||
.where(FoodLog.timestamp <= end)
|
||||
)
|
||||
logs = session.exec(statement).all()
|
||||
|
||||
return NutritionSummary(
|
||||
date=target_date.isoformat(),
|
||||
total_calories=sum(log.calories for log in logs),
|
||||
total_protein=sum(log.protein for log in logs),
|
||||
total_carbs=sum(log.carbs for log in logs),
|
||||
total_fats=sum(log.fats for log in logs),
|
||||
log_count=len(logs),
|
||||
target_calories=getattr(current_user, "target_calories", None),
|
||||
target_protein=getattr(current_user, "target_protein", None),
|
||||
target_carbs=getattr(current_user, "target_carbs", None),
|
||||
target_fat=getattr(current_user, "target_fat", None),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user