refactor: Phase 1 code consolidation and service layer setup

Phase 1.1: Shared parsers utility
- Create backend/src/utils/parsers.js with parseBooleanInput, parseStringInput, etc.
- Create frontend/src/utils/parsers.ts with TypeScript equivalents
- Update routes to import from shared parsers

Phase 1.2: Auth routes consolidation
- Merge auth.js, auth-enhanced.js, auth-enhanced-v2.js into single auth.js
- Add password change and password strength endpoints
- Consolidate middleware (auth.js with token revocation support)
- Update all imports across 14+ route files

Phase 1.3: CreateEvent page consolidation
- Remove duplicate CreateEventPage.tsx (basic version)
- Rename CreateEventPageEnhanced.tsx to CreateEventPage.tsx
- Update exports and imports

Phase 1.4: CMS page consolidation
- Remove duplicate CMSPage.tsx (basic version)
- Rename CMSPageEnhanced.tsx to CMSPage.tsx
- Update exports and imports

Phase 1.5: Multer config factory
- Create backend/src/config/multerConfig.js
- Centralized upload configuration with presets for photos, logos, favicons
- Reusable helpers: createDiskStorage, createFileFilter, uploadTimeoutMiddleware

Phase 2.1: Event service layer
- Create backend/src/services/eventService.js
- Move event business logic out of routes
- Functions: createEvent, getAllEvents, updateEvent, deleteEvent, extendExpiration
This commit is contained in:
Paul Nothaft
2026-01-02 10:12:24 +01:00
parent 77a4bfd499
commit 3424bd22ee
34 changed files with 2450 additions and 3309 deletions
+166
View File
@@ -0,0 +1,166 @@
/**
* Shared Parser Utilities for Frontend
* Pure functions for parsing and transforming input values
*
* @module utils/parsers
*/
/**
* Parse any input value to boolean with configurable default
* Handles: boolean, number, string representations
*
* @param value - Input value to parse
* @param defaultValue - Default if value is undefined/null
* @returns boolean result
*
* @example
* toBoolean(true) // true
* toBoolean('false') // false
* toBoolean('1') // true
* toBoolean(0) // false
* toBoolean(undefined, false) // false
*/
export const toBoolean = (value: unknown, defaultValue = false): boolean => {
if (value === undefined || value === null) {
return defaultValue;
}
if (typeof value === 'boolean') {
return value;
}
if (typeof value === 'number') {
if (Number.isNaN(value)) return defaultValue;
return value !== 0;
}
if (typeof value === 'string') {
const normalized = value.toLowerCase().trim();
if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
if (['false', '0', 'no', 'off', ''].includes(normalized)) return false;
}
return defaultValue;
};
/**
* Parse numeric input with validation and optional bounds
*
* @param value - Input value to parse
* @param defaultValue - Default if invalid
* @param options - Bounds options
* @returns number result
*
* @example
* toNumber('42', 0) // 42
* toNumber('abc', 10) // 10
* toNumber(5, 0, { min: 10 }) // 10
*/
export const toNumber = (
value: unknown,
defaultValue: number,
options?: { min?: number; max?: number }
): number => {
if (value === undefined || value === null || value === '') {
return defaultValue;
}
const parsed = Number(value);
if (!Number.isFinite(parsed)) {
return defaultValue;
}
let result = parsed;
if (options?.min !== undefined && result < options.min) result = options.min;
if (options?.max !== undefined && result > options.max) result = options.max;
return result;
};
/**
* Parse string input with trimming and null handling
*
* @param value - Input value
* @param defaultValue - Default if empty
* @returns string or null
*
* @example
* toString(' hello ') // 'hello'
* toString('') // null
* toString(null, 'default') // 'default'
*/
export const toString = (value: unknown, defaultValue: string | null = null): string | null => {
if (value === undefined || value === null) {
return defaultValue;
}
if (typeof value === 'string') {
const trimmed = value.trim();
return trimmed || defaultValue;
}
return String(value);
};
/**
* Parse JSON string safely
*
* @param value - JSON string or already parsed value
* @param defaultValue - Default if parsing fails
* @returns parsed value or default
*/
export const parseJson = <T>(value: unknown, defaultValue: T): T => {
if (value === undefined || value === null) {
return defaultValue;
}
if (typeof value !== 'string') {
return value as T;
}
try {
return JSON.parse(value) as T;
} catch {
return defaultValue;
}
};
/**
* Parse date input to Date object
*
* @param value - Date string, Date object, or timestamp
* @returns Date object or null if invalid
*/
export const toDate = (value: unknown): Date | null => {
if (value === undefined || value === null || value === '') {
return null;
}
if (value instanceof Date) {
return isNaN(value.getTime()) ? null : value;
}
const date = new Date(value as string | number);
return isNaN(date.getTime()) ? null : date;
};
/**
* Parse array input (handles JSON strings and arrays)
*
* @param value - Array or JSON string
* @param defaultValue - Default if invalid
* @returns array result
*/
export const toArray = <T>(value: unknown, defaultValue: T[] = []): T[] => {
if (value === undefined || value === null) {
return defaultValue;
}
if (Array.isArray(value)) {
return value as T[];
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : defaultValue;
} catch {
// Try comma-separated for string arrays
return value.split(',').map(s => s.trim()).filter(Boolean) as T[];
}
}
return defaultValue;
};
// Re-export with alternative names for backwards compatibility
export const parseBooleanInput = toBoolean;
export const parseNumberInput = toNumber;
export const parseStringInput = toString;