feat: structure meal planning taxonomy and favorites

This commit is contained in:
OpenClaw Bot
2026-05-12 17:15:31 +02:00
parent cef366cce4
commit 58a76ee02d
9 changed files with 442 additions and 20 deletions
+31 -5
View File
@@ -12,6 +12,22 @@ import { str, num, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT } from '../midd
const log = createLogger('Recipes');
const router = express.Router();
const VALID_MEAL_CATEGORIES = ['meat', 'fish', 'pasta', 'rice', 'vegetarian', 'soup', 'leftovers', 'cozy', 'breakfast', 'snack', 'other'];
const VALID_PROTEINS = ['mixed', 'chicken', 'beef', 'pork', 'fish', 'vegetarian', 'none', 'other'];
const VALID_STYLES = ['family', 'quick', 'cozy', 'grill', 'vegetarian', 'kids', 'leftovers', 'other'];
function normalizeEnum(value, allowed, fallback = null) {
const normalized = String(value || '').trim().toLowerCase();
return allowed.includes(normalized) ? normalized : fallback;
}
function normalizeTags(value) {
const tags = Array.isArray(value)
? value
: String(value || '').split(',');
return tags.map((tag) => String(tag || '').trim().toLowerCase()).filter(Boolean).slice(0, 12);
}
function loadRecipeWithIngredients(id) {
const recipe = db.get().prepare(`
SELECT r.*, u.display_name AS creator_name, u.avatar_color AS creator_color
@@ -75,11 +91,16 @@ router.post('/', (req, res) => {
const errors = collectErrors([vTitle, vNotes, vRecipeUrl]);
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
const mealCategory = normalizeEnum(req.body.meal_category ?? req.body.mealCategory, VALID_MEAL_CATEGORIES, 'other');
const protein = normalizeEnum(req.body.protein, VALID_PROTEINS, 'mixed');
const style = normalizeEnum(req.body.style, VALID_STYLES, 'family');
const tagsJson = JSON.stringify(normalizeTags(req.body.tags ?? req.body.tags_json));
const recipeId = db.transaction(() => {
const result = db.get().prepare(`
INSERT INTO recipes (title, notes, recipe_url, created_by)
VALUES (?, ?, ?, ?)
`).run(vTitle.value, vNotes.value, vRecipeUrl.value, req.session.userId);
INSERT INTO recipes (title, notes, recipe_url, meal_category, protein, style, tags_json, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run(vTitle.value, vNotes.value, vRecipeUrl.value, mealCategory, protein, style, tagsJson, req.session.userId);
const rid = Number(result.lastInsertRowid);
const insertIng = db.get().prepare(`
@@ -122,12 +143,17 @@ router.put('/:id', (req, res) => {
const errors = collectErrors([vTitle, vNotes, vRecipeUrl]);
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
const mealCategory = normalizeEnum(req.body.meal_category ?? req.body.mealCategory, VALID_MEAL_CATEGORIES, existing.meal_category || 'other');
const protein = normalizeEnum(req.body.protein, VALID_PROTEINS, existing.protein || 'mixed');
const style = normalizeEnum(req.body.style, VALID_STYLES, existing.style || 'family');
const tagsJson = JSON.stringify(normalizeTags(req.body.tags ?? req.body.tags_json));
db.transaction(() => {
db.get().prepare(`
UPDATE recipes
SET title = ?, notes = ?, recipe_url = ?
SET title = ?, notes = ?, recipe_url = ?, meal_category = ?, protein = ?, style = ?, tags_json = ?
WHERE id = ?
`).run(vTitle.value, vNotes.value, vRecipeUrl.value, id);
`).run(vTitle.value, vNotes.value, vRecipeUrl.value, mealCategory, protein, style, tagsJson, id);
db.get().prepare('DELETE FROM recipe_ingredients WHERE recipe_id = ?').run(id);