fix: Input-Validation auf allen API-Routen vereinheitlichen (Phase 5, Schritt 27)
Alle Routen nutzen jetzt das zentrale Validierungsmodul (validate.js): - Maximale Stringlängen (200 Titel, 5000 Text, 100 Kurztexte) - Enum-Validation für Kategorien, Prioritäten, Meal-Types - Datum/Zeit/DateTime-Format-Prüfung - RRULE-Validation (neue rrule()-Funktion) - Farbwert-Prüfung (#RRGGBB) Betroffene Routen: calendar, notes, contacts, budget, shopping, meals. Tasks-Route um RRULE-Validation ergänzt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+24
-23
@@ -9,9 +9,9 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
const { str, oneOf, date, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT } = require('../middleware/validate');
|
||||
|
||||
const VALID_MEAL_TYPES = ['breakfast', 'lunch', 'dinner', 'snack'];
|
||||
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Hilfsfunktionen
|
||||
@@ -148,20 +148,20 @@ router.get('/', (req, res) => {
|
||||
*/
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const { date, meal_type, title, notes = null, ingredients = [] } = req.body;
|
||||
|
||||
if (!date || !DATE_RE.test(date))
|
||||
return res.status(400).json({ error: 'Gültiges Datum (YYYY-MM-DD) erforderlich', code: 400 });
|
||||
if (!meal_type || !VALID_MEAL_TYPES.includes(meal_type))
|
||||
return res.status(400).json({ error: `meal_type muss einer von: ${VALID_MEAL_TYPES.join(', ')} sein`, code: 400 });
|
||||
if (!title || !title.trim())
|
||||
return res.status(400).json({ error: 'Titel ist erforderlich', code: 400 });
|
||||
const { ingredients = [] } = req.body;
|
||||
const vDate = date(req.body.date, 'Datum', true);
|
||||
const vType = oneOf(req.body.meal_type, VALID_MEAL_TYPES, 'Mahlzeit-Typ');
|
||||
const vTitle = str(req.body.title, 'Titel', { max: MAX_TITLE });
|
||||
const vNotes = str(req.body.notes, 'Notizen', { max: MAX_TEXT, required: false });
|
||||
const errors = collectErrors([vDate, vType, vTitle, vNotes]);
|
||||
if (!req.body.meal_type) errors.push('Mahlzeit-Typ ist erforderlich.');
|
||||
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
|
||||
|
||||
const meal = db.transaction(() => {
|
||||
const result = db.get().prepare(`
|
||||
INSERT INTO meals (date, meal_type, title, notes, created_by)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(date, meal_type, title.trim(), notes || null, req.session.userId);
|
||||
`).run(vDate.value, vType.value, vTitle.value, vNotes.value, req.session.userId);
|
||||
|
||||
const mealId = result.lastInsertRowid;
|
||||
|
||||
@@ -170,9 +170,9 @@ router.post('/', (req, res) => {
|
||||
`);
|
||||
|
||||
for (const ing of ingredients) {
|
||||
if (ing.name && ing.name.trim()) {
|
||||
insertIng.run(mealId, ing.name.trim(), ing.quantity?.trim() || null);
|
||||
}
|
||||
const name = String(ing.name || '').trim().slice(0, MAX_TITLE);
|
||||
const qty = String(ing.quantity || '').trim().slice(0, MAX_SHORT) || null;
|
||||
if (name) insertIng.run(mealId, name, qty);
|
||||
}
|
||||
|
||||
return db.get().prepare(`
|
||||
@@ -207,12 +207,13 @@ router.put('/:id', (req, res) => {
|
||||
const meal = db.get().prepare('SELECT * FROM meals WHERE id = ?').get(id);
|
||||
if (!meal) return res.status(404).json({ error: 'Mahlzeit nicht gefunden', code: 404 });
|
||||
|
||||
const { date, meal_type, title, notes } = req.body;
|
||||
|
||||
if (date !== undefined && !DATE_RE.test(date))
|
||||
return res.status(400).json({ error: 'Ungültiges Datum', code: 400 });
|
||||
if (meal_type !== undefined && !VALID_MEAL_TYPES.includes(meal_type))
|
||||
return res.status(400).json({ error: 'Ungültiger meal_type', code: 400 });
|
||||
const checks = [];
|
||||
if (req.body.date !== undefined) checks.push(date(req.body.date, 'Datum'));
|
||||
if (req.body.meal_type !== undefined) checks.push(oneOf(req.body.meal_type, VALID_MEAL_TYPES, 'Mahlzeit-Typ'));
|
||||
if (req.body.title !== undefined) checks.push(str(req.body.title, 'Titel', { max: MAX_TITLE, required: false }));
|
||||
if (req.body.notes !== undefined) checks.push(str(req.body.notes, 'Notizen', { max: MAX_TEXT, required: false }));
|
||||
const errors = collectErrors(checks);
|
||||
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
|
||||
|
||||
db.get().prepare(`
|
||||
UPDATE meals
|
||||
@@ -222,10 +223,10 @@ router.put('/:id', (req, res) => {
|
||||
notes = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
date ?? null,
|
||||
meal_type ?? null,
|
||||
title?.trim() ?? null,
|
||||
notes !== undefined ? (notes || null) : meal.notes,
|
||||
req.body.date ?? null,
|
||||
req.body.meal_type ?? null,
|
||||
req.body.title?.trim() ?? null,
|
||||
req.body.notes !== undefined ? (req.body.notes || null) : meal.notes,
|
||||
id
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user