fix: atomic SQL swap for meal move endpoint

Replace two-step ORM update with single UPDATE ... CASE statement.
Eliminates IntegrityError from SQLAlchemy flush order violating the
unique constraint (meal_plan_id, day_of_week, meal_type).
This commit is contained in:
2026-05-14 15:56:47 -07:00
parent 301e984336
commit ddcdb962ca
+30 -3
View File
@@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from html import escape as _html_escape from html import escape as _html_escape
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from sqlalchemy import text
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from app.database import get_db from app.database import get_db
from app.models import ( from app.models import (
@@ -351,9 +352,35 @@ def move_meal_item(
) )
if conflict: if conflict:
# swap day/type # Atomic swap via single SQL CASE statement — no intermediate
conflict.day_of_week = item.day_of_week # unique-constraint violations.
conflict.meal_type = item.meal_type 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.day_of_week = new_day_of_week
item.meal_type = new_meal_type item.meal_type = new_meal_type