Browse Source

TG-55 ref #55: Add saved_meals/meal_items schema and POST /api/meals persistence endpoint

FerRo988 3 ngày trước cách đây
mục cha
commit
684f17763e
2 tập tin đã thay đổi với 74 bổ sung0 xóa
  1. 54 0
      database.py
  2. 20 0
      main.py

+ 54 - 0
database.py

@@ -94,8 +94,32 @@ def create_tables():
         )
         ''')
         
+        # Create user-named meals table for Sprint 8
+        cursor.execute('''
+        CREATE TABLE IF NOT EXISTS saved_meals (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            user_id INTEGER NOT NULL,
+            name TEXT NOT NULL,
+            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+            FOREIGN KEY (user_id) REFERENCES users (id)
+        )
+        ''')
+        
+        # Create meal items table to link multiple foods to a single saved meal
+        cursor.execute('''
+        CREATE TABLE IF NOT EXISTS meal_items (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            meal_id INTEGER NOT NULL,
+            food_id INTEGER NOT NULL,
+            amount_g REAL NOT NULL,
+            FOREIGN KEY (meal_id) REFERENCES saved_meals (id) ON DELETE CASCADE,
+            FOREIGN KEY (food_id) REFERENCES foods (id)
+        )
+        ''')
+        
         # Create index for rapid fuzzy search compatibility
         cursor.execute('CREATE INDEX IF NOT EXISTS idx_food_name ON foods(name COLLATE NOCASE)')
+        cursor.execute('CREATE INDEX IF NOT EXISTS idx_saved_meals_user ON saved_meals(user_id)')
         
         conn.commit()
         logger.info("Database and tables initialized successfully.")
@@ -105,6 +129,36 @@ def create_tables():
     finally:
         if conn:
             conn.close()
+def save_user_meal(user_id: int, name: str, items: List[Dict[str, Any]]) -> Optional[int]:
+    """Persist a collection of food items as a named meal list for a user"""
+    conn = None
+    try:
+        conn = get_db_connection()
+        cursor = conn.cursor()
+        
+        # 1. Create the meal header
+        cursor.execute(
+            "INSERT INTO saved_meals (user_id, name) VALUES (?, ?)",
+            (user_id, name)
+        )
+        meal_id = cursor.lastrowid
+        
+        # 2. Add each item linked to this meal
+        for item in items:
+            cursor.execute(
+                "INSERT INTO meal_items (meal_id, food_id, amount_g) VALUES (?, ?, ?)",
+                (meal_id, item['food_id'], item['amount_g'])
+            )
+        
+        conn.commit()
+        return meal_id
+    except Exception as e:
+        logger.error(f"Error saving user meal: {e}")
+        if conn: conn.rollback()
+        return None
+    finally:
+        if conn: conn.close()
+
 
 def get_user_by_username(username: str) -> Optional[Dict[str, Any]]:
     """Retrieve user dictionary if they exist"""

+ 20 - 0
main.py

@@ -142,6 +142,10 @@ class MealItemInput(BaseModel):
 class MealCalculateRequest(BaseModel):
     items: List[MealItemInput]
 
+class MealSaveRequest(BaseModel):
+    name: str
+    items: List[MealItemInput]
+
 @app.get("/", response_class=HTMLResponse)
 async def read_root():
     """Serve the chat interface HTML"""
@@ -399,6 +403,22 @@ async def calculate_meal(request: MealCalculateRequest, current_user: dict = Dep
             
     return totals
 
+@app.post("/api/meals")
+async def save_meal(request: MealSaveRequest, current_user: dict = Depends(get_current_user)):
+    """Securely save a named meal list for the authenticated user"""
+    if not request.name.strip():
+        raise HTTPException(status_code=400, detail="Meal name cannot be empty")
+    if not request.items:
+        raise HTTPException(status_code=400, detail="Meal items cannot be empty")
+        
+    items_list = [item.model_dump() for item in request.items]
+    meal_id = save_user_meal(current_user['id'], request.name.strip(), items_list)
+    
+    if meal_id is None:
+        raise HTTPException(status_code=500, detail="Failed to save meal to database")
+        
+    return {"status": "success", "meal_id": meal_id, "name": request.name.strip()}
+
 if __name__ == "__main__":
     import uvicorn
     uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)