All checks were successful
continuous-integration/drone/push Build is passing
Changes: - Fix nutrition service: add is_active column and Pydantic validation for UUID/datetime - Add location-based alerts feature: users can now see alerts within 1km radius - Fix CORS and response serialization in nutrition service - Add getCurrentLocation() and loadAlertsNearby() functions - Improve UI for nearby alerts display with distance and response count
205 lines
5.1 KiB
Python
205 lines
5.1 KiB
Python
from datetime import date
|
|
from enum import Enum
|
|
from typing import List, Optional
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field, root_validator, field_serializer
|
|
|
|
|
|
class MealType(str, Enum):
|
|
BREAKFAST = "breakfast"
|
|
LUNCH = "lunch"
|
|
DINNER = "dinner"
|
|
SNACK = "snack"
|
|
|
|
|
|
class ActivityIntensity(str, Enum):
|
|
LOW = "low"
|
|
MEDIUM = "medium"
|
|
HIGH = "high"
|
|
|
|
|
|
class GoalType(str, Enum):
|
|
LOSE_WEIGHT = "lose_weight"
|
|
MAINTAIN = "maintain"
|
|
GAIN_WEIGHT = "gain_weight"
|
|
HEALTH = "health"
|
|
|
|
|
|
# Схемы для FoodItem
|
|
class FoodItemBase(BaseModel):
|
|
name: str
|
|
brand: Optional[str] = None
|
|
description: Optional[str] = None
|
|
food_type: Optional[str] = None
|
|
serving_size: Optional[str] = None
|
|
serving_weight_grams: Optional[float] = None
|
|
calories: Optional[float] = None
|
|
protein_grams: Optional[float] = None
|
|
fat_grams: Optional[float] = None
|
|
carbs_grams: Optional[float] = None
|
|
fiber_grams: Optional[float] = None
|
|
sugar_grams: Optional[float] = None
|
|
sodium_mg: Optional[float] = None
|
|
cholesterol_mg: Optional[float] = None
|
|
ingredients: Optional[str] = None
|
|
|
|
|
|
class FoodItemCreate(FoodItemBase):
|
|
fatsecret_id: Optional[str] = None
|
|
is_verified: bool = False
|
|
|
|
|
|
class FoodItemResponse(FoodItemBase):
|
|
id: int
|
|
uuid: str
|
|
fatsecret_id: Optional[str] = None
|
|
is_verified: bool
|
|
created_at: str
|
|
updated_at: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Схемы для UserNutritionEntry
|
|
class UserNutritionEntryBase(BaseModel):
|
|
entry_date: date
|
|
meal_type: MealType
|
|
quantity: float = Field(gt=0)
|
|
unit: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class UserNutritionEntryCreate(UserNutritionEntryBase):
|
|
food_item_id: Optional[int] = None
|
|
custom_food_name: Optional[str] = None
|
|
calories: Optional[float] = None
|
|
protein_grams: Optional[float] = None
|
|
fat_grams: Optional[float] = None
|
|
carbs_grams: Optional[float] = None
|
|
|
|
@root_validator(skip_on_failure=True)
|
|
def check_food_info(cls, values):
|
|
food_item_id = values.get("food_item_id")
|
|
custom_food_name = values.get("custom_food_name")
|
|
|
|
if food_item_id is None and not custom_food_name:
|
|
raise ValueError("Either food_item_id or custom_food_name must be provided")
|
|
return values
|
|
|
|
|
|
class UserNutritionEntryResponse(UserNutritionEntryBase):
|
|
id: int
|
|
uuid: str
|
|
user_id: int
|
|
food_item_id: Optional[int] = None
|
|
custom_food_name: Optional[str] = None
|
|
calories: Optional[float] = None
|
|
protein_grams: Optional[float] = None
|
|
fat_grams: Optional[float] = None
|
|
carbs_grams: Optional[float] = None
|
|
created_at: str
|
|
updated_at: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Схемы для WaterIntake
|
|
class WaterIntakeBase(BaseModel):
|
|
entry_date: date
|
|
amount_ml: int = Field(gt=0)
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class WaterIntakeCreate(WaterIntakeBase):
|
|
pass
|
|
|
|
|
|
class WaterIntakeResponse(WaterIntakeBase):
|
|
id: int
|
|
uuid: str
|
|
user_id: int
|
|
entry_time: str
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Схемы для UserActivityEntry
|
|
class UserActivityEntryBase(BaseModel):
|
|
entry_date: date
|
|
activity_type: str
|
|
duration_minutes: int = Field(gt=0)
|
|
distance_km: Optional[float] = None
|
|
steps: Optional[int] = None
|
|
intensity: Optional[ActivityIntensity] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class UserActivityEntryCreate(UserActivityEntryBase):
|
|
calories_burned: Optional[float] = None
|
|
|
|
|
|
class UserActivityEntryResponse(UserActivityEntryBase):
|
|
id: int
|
|
uuid: str
|
|
user_id: int
|
|
calories_burned: Optional[float] = None
|
|
created_at: str
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Схемы для NutritionGoal
|
|
class NutritionGoalBase(BaseModel):
|
|
daily_calorie_goal: Optional[int] = None
|
|
protein_goal_grams: Optional[int] = None
|
|
fat_goal_grams: Optional[int] = None
|
|
carbs_goal_grams: Optional[int] = None
|
|
water_goal_ml: Optional[int] = None
|
|
activity_goal_minutes: Optional[int] = None
|
|
weight_goal_kg: Optional[float] = None
|
|
goal_type: Optional[GoalType] = None
|
|
|
|
|
|
class NutritionGoalCreate(NutritionGoalBase):
|
|
pass
|
|
|
|
|
|
class NutritionGoalResponse(NutritionGoalBase):
|
|
id: int
|
|
user_id: int
|
|
created_at: str
|
|
updated_at: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Схемы для запросов к FatSecret API
|
|
class FoodSearchQuery(BaseModel):
|
|
query: str
|
|
page_number: int = 0
|
|
max_results: int = 10
|
|
|
|
|
|
class FoodDetailsQuery(BaseModel):
|
|
food_id: str
|
|
|
|
|
|
# Схемы для сводных данных
|
|
class DailyNutritionSummary(BaseModel):
|
|
date: date
|
|
total_calories: float = 0
|
|
total_protein_grams: float = 0
|
|
total_fat_grams: float = 0
|
|
total_carbs_grams: float = 0
|
|
total_water_ml: int = 0
|
|
total_activity_minutes: int = 0
|
|
estimated_calories_burned: float = 0
|
|
meals: List[UserNutritionEntryResponse] = []
|
|
water_entries: List[WaterIntakeResponse] = []
|
|
activity_entries: List[UserActivityEntryResponse] = [] |