diff --git a/backend/app/api/meals.py b/backend/app/api/meals.py index 9f4e819..b901788 100644 --- a/backend/app/api/meals.py +++ b/backend/app/api/meals.py @@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import HTMLResponse from html import escape as _html_escape from pydantic import BaseModel, Field +from sqlalchemy import text from sqlalchemy.orm import Session, joinedload from app.database import get_db from app.models import ( @@ -351,9 +352,35 @@ def move_meal_item( ) if conflict: - # swap day/type - conflict.day_of_week = item.day_of_week - conflict.meal_type = item.meal_type + # Atomic swap via single SQL CASE statement — no intermediate + # unique-constraint violations. + db.execute( + text( + """ + UPDATE meal_plan_item + SET day_of_week = CASE + WHEN id = :item_id THEN :new_day + WHEN id = :conflict_id THEN :old_day + END, + meal_type = CASE + WHEN id = :item_id THEN :new_type + WHEN id = :conflict_id THEN :old_type + END + WHERE id IN (:item_id, :conflict_id) + """ + ), + { + "item_id": str(item_id), + "conflict_id": str(conflict.id), + "new_day": new_day_of_week, + "new_type": new_meal_type, + "old_day": item.day_of_week, + "old_type": item.meal_type, + }, + ) + db.commit() + db.refresh(item) + return {"message": "Meal swapped", "item": item} item.day_of_week = new_day_of_week item.meal_type = new_meal_type