fix(pantry): make public ingredient endpoint idempotent
CI / backend (pytest + alembic) (push) Has been cancelled
CI / frontend (build) (push) Has been cancelled

Backend:
- POST /api/ingredients now checks name_lower and aliases before inserting
- Returns existing ingredient on 409 instead of throwing error

Frontend:
- Removed fragile 409-recovery logic from Pantry.tsx handleAdd
- Added aliases field to Ingredient type for case-insensitive matching

Fixes pantry add for ingredients like 'Carrots' whose canonical name is 'Carrot'
This commit is contained in:
2026-05-18 17:16:57 -07:00
parent 54b3e785b3
commit 69acd70188
4 changed files with 39 additions and 8 deletions
+8 -7
View File
@@ -31,10 +31,15 @@ export default function Pantry() {
queryFn: () => mealPlannerApi.recipes.listIngredients().then(r => r.data),
})
function _ingredientMatchesName(ing: Ingredient, search: string): boolean {
const s = search.toLowerCase()
return ing.name.toLowerCase() === s || (ing.aliases ?? []).some(a => a.toLowerCase() === s)
}
/* fuzzy match existing ingredient */
const matchedIngredient = ingredientName.trim().length > 0
? ingredients?.find(
ing => ing.name.toLowerCase() === ingredientName.trim().toLowerCase()
ing => _ingredientMatchesName(ing, ingredientName.trim())
)
: undefined
@@ -86,12 +91,8 @@ export default function Pantry() {
ingredientId = created?.id
// refresh ingredient list so next add sees it
await queryClient.invalidateQueries({ queryKey: ['ingredients'] })
} catch (err: any) {
if (err?.response?.status === 409) {
showToast.error('Ingredient name already exists')
} else {
showToast.error('Failed to create ingredient')
}
} catch {
showToast.error('Failed to create ingredient')
return
}
}