diff --git a/src/backend/repositories/RecipeRepository.ts b/src/backend/repositories/RecipeRepository.ts index 0344a46..52adc9b 100644 --- a/src/backend/repositories/RecipeRepository.ts +++ b/src/backend/repositories/RecipeRepository.ts @@ -5,6 +5,17 @@ import type { Tag } from '../types/tag.js'; export class RecipeRepository { constructor(private db: Database) {} + private toNullableSql(value: string | null | undefined): SqlValue { + return value ?? null; + } + + private toRequiredSqlText(value: string | null | undefined, fieldName: string): string { + if (value === undefined || value === null || value.trim() === '') { + throw new Error(`${fieldName} is required`); + } + return value; + } + findAll(filters: RecipeFilters = {}): Recipe[] { const { search, offset = 0, limit = 50 } = filters; let sql = 'SELECT * FROM recipes'; @@ -38,18 +49,20 @@ export class RecipeRepository { if (input.ingredients) { input.ingredients.forEach((ing, i) => { this.db.run('INSERT INTO ingredients (recipe_id, position, quantity, unit, item, notes) VALUES (?, ?, ?, ?, ?, ?)', - [id, i, - typeof ing.quantity === 'undefined' ? null : ing.quantity, - typeof ing.unit === 'undefined' ? null : ing.unit, - ing.item, - typeof ing.notes === 'undefined' ? null : ing.notes + [ + id, + i, + this.toNullableSql(ing.quantity), + this.toNullableSql(ing.unit), + this.toRequiredSqlText(ing.item, 'ingredient.item'), + this.toNullableSql(ing.notes) ]); }); } if (input.steps) { input.steps.forEach((step, i) => { this.db.run('INSERT INTO steps (recipe_id, position, instruction) VALUES (?, ?, ?)', - [id, i, step.instruction]); + [id, i, this.toRequiredSqlText(step.instruction, 'step.instruction')]); }); } if (input.tagIds && input.tagIds.length > 0) { @@ -79,18 +92,20 @@ export class RecipeRepository { this.db.run('DELETE FROM ingredients WHERE recipe_id = ?', [id]); input.ingredients.forEach((ing, i) => { this.db.run('INSERT INTO ingredients (recipe_id, position, quantity, unit, item, notes) VALUES (?, ?, ?, ?, ?, ?)', - [id, i, - typeof ing.quantity === 'undefined' ? null : ing.quantity, - typeof ing.unit === 'undefined' ? null : ing.unit, - ing.item, - typeof ing.notes === 'undefined' ? null : ing.notes + [ + id, + i, + this.toNullableSql(ing.quantity), + this.toNullableSql(ing.unit), + this.toRequiredSqlText(ing.item, 'ingredient.item'), + this.toNullableSql(ing.notes) ]); }); } if (input.steps !== undefined) { this.db.run('DELETE FROM steps WHERE recipe_id = ?', [id]); input.steps.forEach((step, i) => { - this.db.run('INSERT INTO steps (recipe_id, position, instruction) VALUES (?, ?, ?)', [id, i, step.instruction]); + this.db.run('INSERT INTO steps (recipe_id, position, instruction) VALUES (?, ?, ?)', [id, i, this.toRequiredSqlText(step.instruction, 'step.instruction')]); }); } if (input.tagIds !== undefined) { @@ -146,7 +161,7 @@ export class RecipeRepository { instruction: r[3] as string })) : []; const tagsRes = this.db.exec('SELECT t.* FROM tags t INNER JOIN recipe_tags rt ON rt.tag_id = t.id WHERE rt.recipe_id = ?', [id]); - const tags: Tag[] = tagsRes.length ? tagsRes[0].values.map(r => ({id: r[0] as number, name: r[1] as string })) : []; + const tags: Tag[] = tagsRes.length ? tagsRes[0].values.map(r => ({ id: r[0] as number, name: r[1] as string })) : []; return { id, title: map.title as string,