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
+204
View File
@@ -0,0 +1,204 @@
/**
* Shared Parser Utilities
* 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 {boolean} [defaultValue=true] - Default if value is undefined/null
* @returns {boolean}
*
* @example
* parseBooleanInput(true) // true
* parseBooleanInput('false') // false
* parseBooleanInput('1') // true
* parseBooleanInput(0) // false
* parseBooleanInput(undefined, false) // false
*/
const parseBooleanInput = (value, defaultValue = true) => {
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.trim().toLowerCase();
if (['false', '0', 'no', 'off', ''].includes(normalized)) {
return false;
}
if (['true', '1', 'yes', 'on'].includes(normalized)) {
return true;
}
}
return defaultValue;
};
/**
* Parse numeric input with validation and bounds
*
* @param {*} value - Input value to parse
* @param {number} defaultValue - Default if invalid
* @param {Object} [options] - Bounds options
* @param {number} [options.min] - Minimum allowed value
* @param {number} [options.max] - Maximum allowed value
* @returns {number}
*
* @example
* parseNumberInput('42', 0) // 42
* parseNumberInput('abc', 10) // 10
* parseNumberInput(5, 0, { min: 10 }) // 10
* parseNumberInput(100, 0, { max: 50 }) // 50
*/
const parseNumberInput = (value, defaultValue, options = {}) => {
const { min, max } = options;
if (value === undefined || value === null || value === '') {
return defaultValue;
}
const parsed = Number(value);
if (!Number.isFinite(parsed)) {
return defaultValue;
}
let result = parsed;
if (min !== undefined && result < min) result = min;
if (max !== undefined && result > max) result = max;
return result;
};
/**
* Parse string input with trimming and null handling
*
* @param {*} value - Input value
* @param {string|null} [defaultValue=null] - Default if empty
* @returns {string|null}
*
* @example
* parseStringInput(' hello ') // 'hello'
* parseStringInput('') // null
* parseStringInput(null, 'default') // 'default'
*/
const parseStringInput = (value, defaultValue = 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
* Returns the parsed value or default if parsing fails
*
* @param {*} value - JSON string or already parsed value
* @param {*} [defaultValue=null] - Default if parsing fails
* @returns {*}
*
* @example
* parseJsonInput('{"a":1}') // { a: 1 }
* parseJsonInput({ a: 1 }) // { a: 1 } (passthrough)
* parseJsonInput('invalid', {}) // {}
*/
const parseJsonInput = (value, defaultValue = null) => {
if (value === undefined || value === null) {
return defaultValue;
}
if (typeof value !== 'string') {
return value; // Already parsed
}
try {
return JSON.parse(value);
} catch {
return defaultValue;
}
};
/**
* Parse email input with validation
*
* @param {*} value - Input value
* @returns {string|null} - Valid email or null
*/
const parseEmailInput = (value) => {
const str = parseStringInput(value);
if (!str) return null;
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(str) ? str.toLowerCase() : null;
};
/**
* Parse date input to ISO string
*
* @param {*} value - Date string, Date object, or timestamp
* @param {string|null} [defaultValue=null] - Default if invalid
* @returns {string|null} - ISO date string (YYYY-MM-DD) or null
*/
const parseDateInput = (value, defaultValue = null) => {
if (value === undefined || value === null || value === '') {
return defaultValue;
}
const date = new Date(value);
if (isNaN(date.getTime())) {
return defaultValue;
}
// Return YYYY-MM-DD format
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
/**
* Parse array input (handles JSON strings and arrays)
*
* @param {*} value - Array or JSON string
* @param {Array} [defaultValue=[]] - Default if invalid
* @returns {Array}
*/
const parseArrayInput = (value, defaultValue = []) => {
if (value === undefined || value === null) {
return defaultValue;
}
if (Array.isArray(value)) {
return value;
}
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : defaultValue;
} catch {
// Try comma-separated
return value.split(',').map(s => s.trim()).filter(Boolean);
}
}
return defaultValue;
};
module.exports = {
parseBooleanInput,
parseNumberInput,
parseStringInput,
parseJsonInput,
parseEmailInput,
parseDateInput,
parseArrayInput
};