## 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
122 lines
2.9 KiB
JavaScript
122 lines
2.9 KiB
JavaScript
/**
|
|
* Custom error classes for standardized error handling across the application.
|
|
* These errors are caught by the global error handler and converted to appropriate HTTP responses.
|
|
*/
|
|
|
|
/**
|
|
* Base class for operational errors (expected errors that can occur during normal operation)
|
|
*/
|
|
class AppError extends Error {
|
|
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
|
|
super(message);
|
|
this.statusCode = statusCode;
|
|
this.code = code;
|
|
this.isOperational = true;
|
|
Error.captureStackTrace(this, this.constructor);
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
error: this.message,
|
|
code: this.code,
|
|
...(process.env.NODE_ENV === 'development' && { stack: this.stack })
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validation error - for invalid input data (400 Bad Request)
|
|
*/
|
|
class ValidationError extends AppError {
|
|
constructor(message = 'Validation failed', details = null) {
|
|
super(message, 400, 'VALIDATION_ERROR');
|
|
this.details = details;
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
...super.toJSON(),
|
|
...(this.details && { details: this.details })
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Not found error - for resources that don't exist (404 Not Found)
|
|
*/
|
|
class NotFoundError extends AppError {
|
|
constructor(resource = 'Resource', identifier = null) {
|
|
const message = identifier
|
|
? `${resource} with identifier '${identifier}' not found`
|
|
: `${resource} not found`;
|
|
super(message, 404, 'NOT_FOUND');
|
|
this.resource = resource;
|
|
this.identifier = identifier;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unauthorized error - for missing or invalid authentication (401 Unauthorized)
|
|
*/
|
|
class UnauthorizedError extends AppError {
|
|
constructor(message = 'Authentication required') {
|
|
super(message, 401, 'UNAUTHORIZED');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forbidden error - for insufficient permissions (403 Forbidden)
|
|
*/
|
|
class ForbiddenError extends AppError {
|
|
constructor(message = 'Access denied') {
|
|
super(message, 403, 'FORBIDDEN');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Conflict error - for resource conflicts (409 Conflict)
|
|
*/
|
|
class ConflictError extends AppError {
|
|
constructor(message = 'Resource conflict', field = null) {
|
|
super(message, 409, 'CONFLICT');
|
|
this.field = field;
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
...super.toJSON(),
|
|
...(this.field && { field: this.field })
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rate limit error - for too many requests (429 Too Many Requests)
|
|
*/
|
|
class RateLimitError extends AppError {
|
|
constructor(message = 'Too many requests', retryAfter = null) {
|
|
super(message, 429, 'RATE_LIMIT_EXCEEDED');
|
|
this.retryAfter = retryAfter;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Service unavailable error - for maintenance mode or service issues (503 Service Unavailable)
|
|
*/
|
|
class ServiceUnavailableError extends AppError {
|
|
constructor(message = 'Service temporarily unavailable') {
|
|
super(message, 503, 'SERVICE_UNAVAILABLE');
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
AppError,
|
|
ValidationError,
|
|
NotFoundError,
|
|
UnauthorizedError,
|
|
ForbiddenError,
|
|
ConflictError,
|
|
RateLimitError,
|
|
ServiceUnavailableError
|
|
};
|