fix(recipe-repo): normalize SQL params to avoid undefined binds
This commit is contained in:
parent
3248e52057
commit
055c7ddd1f
|
|
@ -5,6 +5,17 @@ import type { Tag } from '../types/tag.js';
|
||||||
export class RecipeRepository {
|
export class RecipeRepository {
|
||||||
constructor(private db: Database) {}
|
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[] {
|
findAll(filters: RecipeFilters = {}): Recipe[] {
|
||||||
const { search, offset = 0, limit = 50 } = filters;
|
const { search, offset = 0, limit = 50 } = filters;
|
||||||
let sql = 'SELECT * FROM recipes';
|
let sql = 'SELECT * FROM recipes';
|
||||||
|
|
@ -38,18 +49,20 @@ export class RecipeRepository {
|
||||||
if (input.ingredients) {
|
if (input.ingredients) {
|
||||||
input.ingredients.forEach((ing, i) => {
|
input.ingredients.forEach((ing, i) => {
|
||||||
this.db.run('INSERT INTO ingredients (recipe_id, position, quantity, unit, item, notes) VALUES (?, ?, ?, ?, ?, ?)',
|
this.db.run('INSERT INTO ingredients (recipe_id, position, quantity, unit, item, notes) VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
[id, i,
|
[
|
||||||
typeof ing.quantity === 'undefined' ? null : ing.quantity,
|
id,
|
||||||
typeof ing.unit === 'undefined' ? null : ing.unit,
|
i,
|
||||||
ing.item,
|
this.toNullableSql(ing.quantity),
|
||||||
typeof ing.notes === 'undefined' ? null : ing.notes
|
this.toNullableSql(ing.unit),
|
||||||
|
this.toRequiredSqlText(ing.item, 'ingredient.item'),
|
||||||
|
this.toNullableSql(ing.notes)
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (input.steps) {
|
if (input.steps) {
|
||||||
input.steps.forEach((step, i) => {
|
input.steps.forEach((step, i) => {
|
||||||
this.db.run('INSERT INTO steps (recipe_id, position, instruction) VALUES (?, ?, ?)',
|
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) {
|
if (input.tagIds && input.tagIds.length > 0) {
|
||||||
|
|
@ -79,18 +92,20 @@ export class RecipeRepository {
|
||||||
this.db.run('DELETE FROM ingredients WHERE recipe_id = ?', [id]);
|
this.db.run('DELETE FROM ingredients WHERE recipe_id = ?', [id]);
|
||||||
input.ingredients.forEach((ing, i) => {
|
input.ingredients.forEach((ing, i) => {
|
||||||
this.db.run('INSERT INTO ingredients (recipe_id, position, quantity, unit, item, notes) VALUES (?, ?, ?, ?, ?, ?)',
|
this.db.run('INSERT INTO ingredients (recipe_id, position, quantity, unit, item, notes) VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
[id, i,
|
[
|
||||||
typeof ing.quantity === 'undefined' ? null : ing.quantity,
|
id,
|
||||||
typeof ing.unit === 'undefined' ? null : ing.unit,
|
i,
|
||||||
ing.item,
|
this.toNullableSql(ing.quantity),
|
||||||
typeof ing.notes === 'undefined' ? null : ing.notes
|
this.toNullableSql(ing.unit),
|
||||||
|
this.toRequiredSqlText(ing.item, 'ingredient.item'),
|
||||||
|
this.toNullableSql(ing.notes)
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (input.steps !== undefined) {
|
if (input.steps !== undefined) {
|
||||||
this.db.run('DELETE FROM steps WHERE recipe_id = ?', [id]);
|
this.db.run('DELETE FROM steps WHERE recipe_id = ?', [id]);
|
||||||
input.steps.forEach((step, i) => {
|
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) {
|
if (input.tagIds !== undefined) {
|
||||||
|
|
@ -146,7 +161,7 @@ export class RecipeRepository {
|
||||||
instruction: r[3] as string
|
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 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 {
|
return {
|
||||||
id,
|
id,
|
||||||
title: map.title as string,
|
title: map.title as string,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue