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:
ulsklyc
2026-03-26 00:23:57 +01:00
parent f507ef8488
commit a787667dcb
8 changed files with 193 additions and 140 deletions
+60 -1
View File
@@ -10,6 +10,15 @@
const MAX_TITLE = 200;
const MAX_TEXT = 5000;
const MAX_SHORT = 100;
const MAX_RRULE = 300;
// Regex-Muster
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
const TIME_RE = /^\d{2}:\d{2}$/;
const DATETIME_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?Z?)?$/;
const COLOR_RE = /^#[0-9A-Fa-f]{6}$/;
const MONTH_RE = /^\d{4}-\d{2}$/;
const RRULE_RE = /^(FREQ=(DAILY|WEEKLY|MONTHLY)(;INTERVAL=\d{1,2})?(;BYDAY=[A-Z,]{2,}(,[A-Z]{2})*)?(;UNTIL=\d{8}(T\d{6}Z)?)?)?$/;
/**
* Bereinigt und validiert einen Pflicht-String.
@@ -103,4 +112,54 @@ function collectErrors(results) {
return results.map((r) => r.error).filter(Boolean);
}
module.exports = { str, oneOf, date, time, num, color, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT };
/**
* Validiert ein Datetime-Format YYYY-MM-DD oder YYYY-MM-DDTHH:MM[:SS][Z].
*/
function datetime(val, field, required = false) {
if (!val) {
if (required) return { value: null, error: `${field} ist erforderlich.` };
return { value: null, error: null };
}
if (!DATETIME_RE.test(String(val)))
return { value: null, error: `${field} muss im Format YYYY-MM-DD oder YYYY-MM-DDTHH:MM sein.` };
return { value: String(val), error: null };
}
/**
* Validiert ein Monatsformat YYYY-MM.
*/
function month(val, field) {
if (!val) return { value: null, error: null };
if (!MONTH_RE.test(String(val)))
return { value: null, error: `${field} muss im Format YYYY-MM sein.` };
return { value: String(val), error: null };
}
/**
* Validiert eine optionale RRULE.
*/
function rrule(val, field) {
if (!val) return { value: null, error: null };
const s = String(val).trim();
if (s.length > MAX_RRULE)
return { value: null, error: `${field} darf maximal ${MAX_RRULE} Zeichen haben.` };
// Grundlegende Struktur: KEY=VALUE;KEY=VALUE
if (!/^FREQ=(DAILY|WEEKLY|MONTHLY)/.test(s))
return { value: null, error: `${field}: Ungültige Wiederholungsregel.` };
return { value: s, error: null };
}
/**
* Validiert eine ganzzahlige ID (positiv).
*/
function id(val, field) {
const n = parseInt(val, 10);
if (!n || n < 1) return { value: null, error: `${field} muss eine positive Zahl sein.` };
return { value: n, error: null };
}
module.exports = {
str, oneOf, date, time, datetime, month, num, color, rrule, id, collectErrors,
MAX_TITLE, MAX_TEXT, MAX_SHORT, MAX_RRULE,
DATE_RE, TIME_RE, DATETIME_RE, COLOR_RE, MONTH_RE,
};