Initial project scaffold: FastAPI backend + Svelte 5 frontend
Backend (uv): FastAPI app with routers (foods, log, targets, OFF proxy), SQLAlchemy models, Pydantic schemas, migration runner + 0001 initial schema, example pytest suite (7 tests). Frontend (npm): Vite 7 + Svelte 5 runes, lib/ (api, stores, scanner, format) per spec §7, placeholder components, example vitest suite (4 tests). SPEC.md §1: registered uvicorn and Vitest (rule §8.3.3).
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""Pydantic request/response schemas — the API contract (spec §8.3 rule 1).
|
||||
|
||||
Defined separately from ORM models; services convert via from_attributes.
|
||||
Validation happens here at the boundary (spec §8.1 rule 11).
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
UnitType = Literal["weight", "count"]
|
||||
FoodSource = Literal["openfoodfacts", "manual", "meal"]
|
||||
MealSlot = Literal["breakfast", "lunch", "dinner", "snack"]
|
||||
|
||||
|
||||
class FoodCreate(BaseModel):
|
||||
name: str
|
||||
brand: str | None = None
|
||||
barcode: str | None = None
|
||||
source: FoodSource = "manual"
|
||||
unit_type: UnitType = "weight"
|
||||
calories_per_unit: float | None = Field(default=None, gt=0)
|
||||
protein_per_unit: float | None = Field(default=None, ge=0)
|
||||
carbs_per_unit: float | None = Field(default=None, ge=0)
|
||||
fat_per_unit: float | None = Field(default=None, ge=0)
|
||||
serving_size_g: float | None = Field(default=None, gt=0)
|
||||
serving_name: str | None = None
|
||||
|
||||
|
||||
class FoodRead(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
name: str
|
||||
brand: str | None
|
||||
barcode: str | None
|
||||
source: FoodSource
|
||||
is_meal: bool
|
||||
unit_type: UnitType
|
||||
calories_per_unit: float | None
|
||||
protein_per_unit: float | None
|
||||
carbs_per_unit: float | None
|
||||
fat_per_unit: float | None
|
||||
serving_size_g: float | None
|
||||
serving_name: str | None
|
||||
deleted_at: datetime | None
|
||||
|
||||
|
||||
class LogEntryCreate(BaseModel):
|
||||
food_id: int
|
||||
quantity: float = Field(gt=0)
|
||||
meal_slot: MealSlot | None = None
|
||||
date: date # client-supplied YYYY-MM-DD (spec §8.1 rule 8)
|
||||
|
||||
|
||||
class LogEntryRead(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
date: date
|
||||
food_id: int
|
||||
quantity: float
|
||||
meal_slot: MealSlot | None
|
||||
sort_order: int
|
||||
|
||||
|
||||
class TargetRead(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
start_date: date
|
||||
end_date: date | None
|
||||
calories: int
|
||||
protein_g: float | None
|
||||
carbs_g: float | None
|
||||
fat_g: float | None
|
||||
Reference in New Issue
Block a user