Files
picpeak/backend/src/utils/routeHelpers.js
T
Paul Nothaft 0da45e699a feat: Add CSS template system with custom gallery styling support
## Changes

### CSS Template System
- Added CSS class hooks to gallery components for custom template targeting
- Gallery sidebar, header, footer, and photo cards can now be styled via CSS templates
- CSS variables on :root allow themes to override colors, effects, and spacing

### Gallery Component CSS Classes Added
- `.gallery-page` - Main gallery container
- `.gallery-header` - Top header bar
- `.gallery-sidebar` - Filter/download sidebar
- `.gallery-sidebar-header`, `.gallery-sidebar-title`, `.gallery-sidebar-close`
- `.gallery-sidebar-content`, `.gallery-sidebar-section`
- `.gallery-sidebar-search-input`, `.gallery-sidebar-search-icon`
- `.gallery-sidebar-backdrop` - Mobile overlay
- `.gallery-btn`, `.gallery-btn-download` - Sidebar buttons
- `.gallery-footer` - Footer section
- `.photo-card`, `.photo-grid` - Photo display elements

### CSS Templates (Database)
- Elegant Dark (id=1): Dark navy theme with light text and red accents
- Liquid Glass Light (id=2): iOS 26 frosted glass effect with gradient background

### Bug Fixes
- Fixed CSS variables not inheriting (moved from .gallery-page to :root)
- Fixed sidebar position breaking layout (removed position: relative override)
- Fixed Elegant Dark sidebar text visibility (white on white issue)

### Other Changes
- Settings page refactoring and cleanup
- i18n locale updates for new gallery features
- Vite proxy port configuration fix
- Admin auth route improvements
- CSS templates service updates
2026-01-03 08:59:01 +01:00

181 lines
5.2 KiB
JavaScript

/**
* Route helper utilities for standardized request handling.
* Provides async error wrapping, validation, and response formatting.
*/
const { validationResult } = require('express-validator');
const { ValidationError } = require('./errors');
/**
* Wraps an async route handler to catch errors and pass them to the error handler.
* Eliminates the need for try/catch blocks in every route.
*
* @param {Function} fn - Async route handler function
* @returns {Function} Express middleware function
*
* @example
* router.get('/events', handleAsync(async (req, res) => {
* const events = await eventService.getAll();
* res.json(events);
* }));
*/
const handleAsync = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
/**
* Validates the request using express-validator and throws ValidationError if invalid.
* Should be called at the beginning of route handlers after validation middleware.
*
* @param {Request} req - Express request object
* @throws {ValidationError} If validation fails
*
* @example
* router.post('/events', [
* body('name').notEmpty(),
* body('date').isDate()
* ], handleAsync(async (req, res) => {
* validateRequest(req);
* // ... rest of handler
* }));
*/
const validateRequest = (req) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const errorDetails = errors.array().map(err => ({
field: err.path || err.param,
message: err.msg
}));
throw new ValidationError('Validation failed', errorDetails);
}
};
/**
* Sends a standardized success response.
*
* @param {Response} res - Express response object
* @param {*} data - Data to send in the response
* @param {number} [statusCode=200] - HTTP status code
* @param {string} [message] - Optional success message
*
* @example
* successResponse(res, { event }, 201, 'Event created successfully');
*/
const successResponse = (res, data, statusCode = 200, message = null) => {
const response = message ? { message, ...data } : data;
res.status(statusCode).json(response);
};
/**
* Sends a standardized error response.
* Note: Prefer throwing custom errors and letting the error handler format the response.
*
* @param {Response} res - Express response object
* @param {string} message - Error message
* @param {number} [statusCode=500] - HTTP status code
* @param {string} [code] - Optional error code
* @param {*} [details] - Optional additional error details
*
* @example
* errorResponse(res, 'Invalid input', 400, 'VALIDATION_ERROR', { field: 'email' });
*/
const errorResponse = (res, message, statusCode = 500, code = null, details = null) => {
const response = {
error: message,
...(code && { code }),
...(details && { details })
};
res.status(statusCode).json(response);
};
/**
* Creates a route handler with built-in validation.
* Combines handleAsync and validateRequest for cleaner route definitions.
*
* @param {Function} fn - Async route handler function
* @returns {Function} Express middleware function
*
* @example
* router.post('/events', [
* body('name').notEmpty()
* ], withValidation(async (req, res) => {
* const event = await eventService.create(req.body);
* successResponse(res, { event }, 201);
* }));
*/
const withValidation = (fn) => {
return handleAsync(async (req, res, next) => {
validateRequest(req);
return fn(req, res, next);
});
};
/**
* Extracts pagination parameters from query string with defaults.
*
* @param {Request} req - Express request object
* @param {Object} [defaults] - Default values
* @param {number} [defaults.page=1] - Default page number
* @param {number} [defaults.limit=20] - Default items per page
* @param {number} [defaults.maxLimit=100] - Maximum allowed limit
* @returns {{ page: number, limit: number, offset: number }}
*
* @example
* const { page, limit, offset } = getPagination(req);
* const events = await db('events').limit(limit).offset(offset);
*/
const getPagination = (req, defaults = {}) => {
const { page: defaultPage = 1, limit: defaultLimit = 20, maxLimit = 100 } = defaults;
let page = parseInt(req.query.page, 10) || defaultPage;
let limit = parseInt(req.query.limit, 10) || defaultLimit;
// Ensure valid values
page = Math.max(1, page);
limit = Math.min(Math.max(1, limit), maxLimit);
const offset = (page - 1) * limit;
return { page, limit, offset };
};
/**
* Creates a paginated response with metadata.
*
* @param {*} data - Data array
* @param {number} total - Total count of items
* @param {number} page - Current page
* @param {number} limit - Items per page
* @returns {Object} Paginated response object
*
* @example
* const events = await db('events').limit(limit).offset(offset);
* const total = await db('events').count('* as count').first();
* res.json(paginatedResponse(events, total.count, page, limit));
*/
const paginatedResponse = (data, total, page, limit) => {
const totalPages = Math.ceil(total / limit);
return {
data,
pagination: {
page,
limit,
total,
totalPages,
hasMore: page < totalPages
}
};
};
module.exports = {
handleAsync,
validateRequest,
successResponse,
errorResponse,
withValidation,
getPagination,
paginatedResponse
};