0da45e699a
## 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
184 lines
5.2 KiB
JavaScript
184 lines
5.2 KiB
JavaScript
/**
|
|
* Global error handler middleware.
|
|
* Catches all errors and returns standardized responses.
|
|
* Distinguishes between operational errors (expected) and programming errors (bugs).
|
|
*/
|
|
|
|
const logger = require('../utils/logger');
|
|
const { AppError } = require('../utils/errors');
|
|
|
|
/**
|
|
* Determines if an error is operational (expected) or a programming error (bug).
|
|
* Operational errors are expected failures like validation errors, not found, etc.
|
|
* Programming errors are bugs that should be logged and investigated.
|
|
*
|
|
* @param {Error} err - The error to check
|
|
* @returns {boolean} True if operational error
|
|
*/
|
|
const isOperationalError = (err) => {
|
|
return err instanceof AppError && err.isOperational;
|
|
};
|
|
|
|
/**
|
|
* Formats error for development environment (includes stack trace).
|
|
*
|
|
* @param {Error} err - The error object
|
|
* @returns {Object} Formatted error response
|
|
*/
|
|
const formatDevError = (err) => {
|
|
return {
|
|
error: err.message,
|
|
code: err.code || 'INTERNAL_ERROR',
|
|
stack: err.stack,
|
|
...(err.details && { details: err.details }),
|
|
...(err.field && { field: err.field })
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Formats error for production environment (hides sensitive details).
|
|
*
|
|
* @param {Error} err - The error object
|
|
* @param {boolean} isOperational - Whether this is an operational error
|
|
* @returns {Object} Formatted error response
|
|
*/
|
|
const formatProdError = (err, isOperational) => {
|
|
// For operational errors, show the message
|
|
if (isOperational) {
|
|
return {
|
|
error: err.message,
|
|
code: err.code || 'ERROR',
|
|
...(err.details && { details: err.details }),
|
|
...(err.field && { field: err.field })
|
|
};
|
|
}
|
|
|
|
// For programming errors, hide details
|
|
return {
|
|
error: 'An unexpected error occurred',
|
|
code: 'INTERNAL_ERROR'
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Handles specific error types and converts them to AppError format.
|
|
*
|
|
* @param {Error} err - The error to handle
|
|
* @returns {Error} Converted error or original error
|
|
*/
|
|
const handleKnownErrors = (err) => {
|
|
// Handle Knex/Database errors
|
|
if (err.code === 'SQLITE_CONSTRAINT' || err.code === '23505') {
|
|
const { AppError } = require('../utils/errors');
|
|
const error = new AppError('A record with this value already exists', 409, 'DUPLICATE_ENTRY');
|
|
error.isOperational = true;
|
|
return error;
|
|
}
|
|
|
|
// Handle JSON parsing errors
|
|
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
|
|
const { ValidationError } = require('../utils/errors');
|
|
return new ValidationError('Invalid JSON in request body');
|
|
}
|
|
|
|
// Handle multer file upload errors
|
|
if (err.code === 'LIMIT_FILE_SIZE') {
|
|
const { ValidationError } = require('../utils/errors');
|
|
return new ValidationError('File size exceeds the maximum allowed limit');
|
|
}
|
|
|
|
if (err.code === 'LIMIT_UNEXPECTED_FILE') {
|
|
const { ValidationError } = require('../utils/errors');
|
|
return new ValidationError('Unexpected file field');
|
|
}
|
|
|
|
return err;
|
|
};
|
|
|
|
/**
|
|
* Global error handler middleware.
|
|
* Must be registered last, after all routes.
|
|
*
|
|
* @param {Error} err - The error object
|
|
* @param {Request} req - Express request object
|
|
* @param {Response} res - Express response object
|
|
* @param {Function} next - Express next function
|
|
*/
|
|
const errorHandler = (err, req, res, next) => {
|
|
// If headers already sent, delegate to Express default handler
|
|
if (res.headersSent) {
|
|
return next(err);
|
|
}
|
|
|
|
// Convert known error types
|
|
const error = handleKnownErrors(err);
|
|
|
|
// Determine error status code
|
|
const statusCode = error.statusCode || error.status || 500;
|
|
const operational = isOperationalError(error);
|
|
|
|
// Log the error
|
|
const logContext = {
|
|
url: req.originalUrl,
|
|
method: req.method,
|
|
ip: req.ip,
|
|
statusCode,
|
|
errorCode: error.code,
|
|
operational,
|
|
...(req.admin && { adminId: req.admin.id }),
|
|
...(req.gallerySlug && { gallerySlug: req.gallerySlug })
|
|
};
|
|
|
|
if (operational) {
|
|
// Operational errors are expected, log at warn level
|
|
logger.warn('Operational error', {
|
|
...logContext,
|
|
message: error.message
|
|
});
|
|
} else {
|
|
// Programming errors are bugs, log at error level with stack
|
|
logger.error('Unhandled error', {
|
|
...logContext,
|
|
message: error.message,
|
|
stack: error.stack
|
|
});
|
|
}
|
|
|
|
// Format and send response
|
|
const isDev = process.env.NODE_ENV === 'development';
|
|
const response = isDev ? formatDevError(error) : formatProdError(error, operational);
|
|
|
|
res.status(statusCode).json(response);
|
|
};
|
|
|
|
/**
|
|
* 404 handler for undefined routes.
|
|
* Should be registered after all routes but before errorHandler.
|
|
*
|
|
* @param {Request} req - Express request object
|
|
* @param {Response} res - Express response object
|
|
* @param {Function} next - Express next function
|
|
*/
|
|
const notFoundHandler = (req, res, next) => {
|
|
const { NotFoundError } = require('../utils/errors');
|
|
next(new NotFoundError('Route', req.originalUrl));
|
|
};
|
|
|
|
/**
|
|
* Async handler that catches unhandled promise rejections.
|
|
* Use this to wrap async route handlers.
|
|
*
|
|
* @param {Function} fn - Async function to wrap
|
|
* @returns {Function} Wrapped function
|
|
*/
|
|
const asyncHandler = (fn) => (req, res, next) => {
|
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
};
|
|
|
|
module.exports = {
|
|
errorHandler,
|
|
notFoundHandler,
|
|
asyncHandler,
|
|
isOperationalError
|
|
};
|