import type Database from 'better-sqlite3'; import type { Ingredient, Recipe, Step } from '$lib/types'; type RecipeRow = { id: number; title: string; description: string | null; source_url: string | null; source_domain: string | null; image_path: string | null; servings_default: number | null; servings_unit: string | null; prep_time_min: number | null; cook_time_min: number | null; total_time_min: number | null; cuisine: string | null; category: string | null; }; function ensureTagIds(db: Database.Database, names: string[]): number[] { const insert = db.prepare('INSERT OR IGNORE INTO tag(name) VALUES (?)'); const select = db.prepare('SELECT id FROM tag WHERE name = ?'); const ids: number[] = []; for (const name of names) { const trimmed = name.trim(); if (!trimmed) continue; insert.run(trimmed); const row = select.get(trimmed) as { id: number }; ids.push(row.id); } return ids; } function refreshFts(db: Database.Database, recipeId: number): void { // Trigger the AFTER UPDATE trigger which rebuilds the FTS row with current ingredients + tags. db.prepare('UPDATE recipe SET title = title WHERE id = ?').run(recipeId); } export function insertRecipe(db: Database.Database, recipe: Recipe): number { const tx = db.transaction((): number => { const info = db .prepare( `INSERT INTO recipe (title, description, source_url, source_domain, image_path, servings_default, servings_unit, prep_time_min, cook_time_min, total_time_min, cuisine, category) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ) .run( recipe.title, recipe.description, recipe.source_url, recipe.source_domain, recipe.image_path, recipe.servings_default, recipe.servings_unit, recipe.prep_time_min, recipe.cook_time_min, recipe.total_time_min, recipe.cuisine, recipe.category ); const id = Number(info.lastInsertRowid); const insIng = db.prepare( `INSERT INTO ingredient(recipe_id, position, quantity, unit, name, note, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?)` ); for (const ing of recipe.ingredients) { insIng.run(id, ing.position, ing.quantity, ing.unit, ing.name, ing.note, ing.raw_text); } const insStep = db.prepare( 'INSERT INTO step(recipe_id, position, text) VALUES (?, ?, ?)' ); for (const step of recipe.steps) { insStep.run(id, step.position, step.text); } const tagIds = ensureTagIds(db, recipe.tags); const linkTag = db.prepare( 'INSERT OR IGNORE INTO recipe_tag(recipe_id, tag_id) VALUES (?, ?)' ); for (const tid of tagIds) linkTag.run(id, tid); refreshFts(db, id); return id; }); return tx(); } export function getRecipeById(db: Database.Database, id: number): Recipe | null { const row = db .prepare( `SELECT id, title, description, source_url, source_domain, image_path, servings_default, servings_unit, prep_time_min, cook_time_min, total_time_min, cuisine, category FROM recipe WHERE id = ?` ) .get(id) as RecipeRow | undefined; if (!row) return null; const ingredients = db .prepare( `SELECT position, quantity, unit, name, note, raw_text FROM ingredient WHERE recipe_id = ? ORDER BY position` ) .all(id) as Ingredient[]; const steps = db .prepare('SELECT position, text FROM step WHERE recipe_id = ? ORDER BY position') .all(id) as Step[]; const tagRows = db .prepare( `SELECT t.name FROM tag t JOIN recipe_tag rt ON rt.tag_id = t.id WHERE rt.recipe_id = ? ORDER BY t.name` ) .all(id) as { name: string }[]; return { id: row.id, title: row.title, description: row.description, source_url: row.source_url, source_domain: row.source_domain, image_path: row.image_path, servings_default: row.servings_default, servings_unit: row.servings_unit, prep_time_min: row.prep_time_min, cook_time_min: row.cook_time_min, total_time_min: row.total_time_min, cuisine: row.cuisine, category: row.category, ingredients, steps, tags: tagRows.map((t) => t.name) }; } export function getRecipeIdBySourceUrl( db: Database.Database, url: string ): number | null { const row = db.prepare('SELECT id FROM recipe WHERE source_url = ?').get(url) as | { id: number } | undefined; return row?.id ?? null; } export function deleteRecipe(db: Database.Database, id: number): void { db.prepare('DELETE FROM recipe WHERE id = ?').run(id); }