Skip to content

fix(recipes): make URL import idempotent per-user (no duplicate saves) - #139

Merged
windoze95 merged 1 commit into
mainfrom
fix/import-idempotent-per-user
Jul 8, 2026
Merged

fix(recipes): make URL import idempotent per-user (no duplicate saves)#139
windoze95 merged 1 commit into
mainfrom
fix/import-idempotent-per-user

Conversation

@windoze95

Copy link
Copy Markdown
Owner

Problem

A user saving the same recipe twice (found while recording the ChatGPT demo — the agent called save_recipe twice) got duplicate rows in their collection. Source-URL uniqueness is enforced only on the extraction cache (canonical_recipes.normalized_url), never on the user's recipes collection — and ImportFromURL → createImportedRecipe → CreateRecipe is a plain insert with no per-user existence check.

Fix

  • New RecipeRepo.GetUserRecipeByCanonical(userID, canonicalID) (repo + mock).
  • createImportedRecipe: when canonicalID != nil (URL imports), return the user's existing recipe for that canonical instead of inserting a dupe. Vision/copypasta/video imports (nil canonical) and fork/regen/generate are untouched.
  • Dedup-lookup failure logs + proceeds (never blocks an import).
  • Mock CreateRecipe now sets CreatedByID from the association (mirrors GORM).

Test

TestImportIdempotent_SameCanonicalNoDuplicate: same canonical twice → 1 row + same id; different canonical → new row. Full suite green (16 pkgs).

Follow-up (optional): a partial unique index (created_by_id, canonical_id) WHERE deleted_at IS NULL AND canonical_id IS NOT NULL once the existing stray dupe is deleted.

🤖 Generated with Claude Code

Source-URL uniqueness was enforced only on the extraction cache (canonical_recipes.normalized_url), NOT on a user's collection — so saving the same recipe twice (e.g. an MCP agent calling save_recipe more than once) created duplicate recipe rows pointing at the same canonical. There was no per-user dedup anywhere: ImportFromURL -> createImportedRecipe -> CreateRecipe is a plain insert.

- Add RecipeRepo.GetUserRecipeByCanonical(userID, canonicalID) (repo + mock).

- In createImportedRecipe, when canonicalID != nil (URL imports), return the user's existing recipe for that canonical instead of inserting a duplicate. Vision/copypasta/video imports (nil canonical) and fork/regen/generate are unaffected — they legitimately create new rows.

- Mock CreateRecipe now populates CreatedByID from the association (mirrors GORM) so lookups behave like the DB.

- Dedup-lookup failure logs and proceeds (never blocks an import). A partial unique index on (created_by_id, canonical_id) is a sensible follow-up once existing dupes are cleaned.

Test: import same canonical twice -> 1 row + same id returned; different canonical -> new row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@windoze95
windoze95 merged commit 75e3227 into main Jul 8, 2026
1 check passed
@windoze95
windoze95 deleted the fix/import-idempotent-per-user branch July 8, 2026 01:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant