Skip to content

Commit 5e8348b

Browse files
Fix re-import from URL creating orphan recipe
The 'Re-import from URL' button called importRecipeFromUrl which always created a new DB record, then updated the original recipe separately — leaving an orphaned recipe behind. Fix: add dry_run flag to POST /recipes/import. When true, the backend parses the URL and returns the recipe data (id=0) without persisting to the DB. The re-import path now passes dry_run=true so no orphan is created. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3dd41f8 commit 5e8348b

3 files changed

Lines changed: 29 additions & 2 deletions

File tree

backend/src/routes/parse_recipe.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ pub struct ImportFromUrlReq {
2525
/// Optional model override (e.g., "deepseek/deepseek-chat-v3.1")
2626
#[serde(default)]
2727
pub model: Option<String>,
28+
/// When true, parse the URL but do NOT persist to the database.
29+
/// Returns a Recipe with id=0. Use this for re-import (updating an existing recipe).
30+
#[serde(default)]
31+
pub dry_run: bool,
2832
}
2933

3034
/// # Errors
@@ -110,6 +114,28 @@ pub async fn import_from_url(
110114
instructions: norm.instructions,
111115
};
112116

117+
if req.dry_run {
118+
// Caller wants the parsed data but will manage persistence themselves.
119+
// Return a transient Recipe (id=0) without writing to the database.
120+
let recipe = Recipe {
121+
id: 0,
122+
title: payload.title,
123+
source: payload.source,
124+
r#yield: payload.r#yield,
125+
notes: payload.notes,
126+
created_at: String::new(),
127+
updated_at: String::new(),
128+
ingredients: payload.ingredients,
129+
instructions: payload.instructions,
130+
image_path_small: None,
131+
image_path_full: None,
132+
macros: None,
133+
share_token: None,
134+
prep_reminders: None,
135+
};
136+
return Ok(Json(recipe));
137+
}
138+
113139
let created = recipes::create(State(state.clone()), Json(payload)).await?;
114140
let recipe_id = created.0.id;
115141

flutter/lib/src/api.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,14 +601,15 @@ Future<List<Ingredient>> reparseIngredients(int id) async {
601601

602602

603603

604-
Future<Recipe> importRecipeFromUrl({required String url, String? model}) async {
604+
Future<Recipe> importRecipeFromUrl({required String url, String? model, bool dryRun = false}) async {
605605
final uri = Uri.parse('$baseUrl/recipes/import');
606606
final resp = await http.post(
607607
uri,
608608
headers: _headers(const {'Content-Type': 'application/json'}),
609609
body: jsonEncode({
610610
'url': url,
611611
if (model != null && model.isNotEmpty) 'model': model,
612+
if (dryRun) 'dry_run': true,
612613
}),
613614
);
614615

flutter/lib/src/views/recipe_detail_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class _RecipeDetailPageState extends State<RecipeDetailPage> {
105105
);
106106

107107
try {
108-
final imported = await api.importRecipeFromUrl(url: r.source);
108+
final imported = await api.importRecipeFromUrl(url: r.source, dryRun: true);
109109
await api.updateRecipe(
110110
id: r.id,
111111
title: imported.title,

0 commit comments

Comments
 (0)