refactor(backend): standardize error responses, logging, pagination

- errorResponse(res, error, status, publicMessage) in routeHelpers,
  wired into 125 catch blocks across 10 route files; wire format
  ({ error: <string> }) unchanged byte-for-byte
- Replace remaining console.* with logger across src (178 sites);
  3 intentional console sites kept (install boot, unbound .catch ref)
- Adopt getPagination in 6 routes where semantics match exactly
This commit is contained in:
Paul Nothaft
2026-07-03 07:49:45 +02:00
parent d44ead41c5
commit 6eb46d8c31
41 changed files with 372 additions and 482 deletions
+14 -14
View File
@@ -11,14 +11,14 @@ const logger = require('../utils/logger');
function normalizeFiles(files) {
// Handle null, undefined, or falsy values
if (!files) {
console.log('[normalizeFiles] No files provided');
logger.info('[normalizeFiles] No files provided');
return [];
}
// Handle arrays
if (Array.isArray(files)) {
const validFiles = files.filter(Boolean);
console.log(`[normalizeFiles] Normalized ${validFiles.length} files from array`);
logger.info(`[normalizeFiles] Normalized ${validFiles.length} files from array`);
return validFiles;
}
@@ -26,11 +26,11 @@ function normalizeFiles(files) {
try {
if (typeof files === 'object' && typeof files[Symbol.iterator] === 'function') {
const validFiles = Array.from(files).filter(Boolean);
console.log(`[normalizeFiles] Normalized ${validFiles.length} files from iterable`);
logger.info(`[normalizeFiles] Normalized ${validFiles.length} files from iterable`);
return validFiles;
}
} catch (err) {
console.warn('[normalizeFiles] Failed to iterate files object:', err.message);
logger.warn('[normalizeFiles] Failed to iterate files object:', err.message);
}
// Handle plain objects (multer fieldname mapping)
@@ -39,16 +39,16 @@ function normalizeFiles(files) {
const validFiles = Object.values(files)
.flatMap((value) => (Array.isArray(value) ? value : [value]))
.filter(Boolean);
console.log(`[normalizeFiles] Normalized ${validFiles.length} files from object`);
logger.info(`[normalizeFiles] Normalized ${validFiles.length} files from object`);
return validFiles;
} catch (err) {
console.warn('[normalizeFiles] Failed to process files object:', err.message);
logger.warn('[normalizeFiles] Failed to process files object:', err.message);
return [];
}
}
// Unexpected type
console.warn('[normalizeFiles] Unexpected files type:', typeof files);
logger.warn('[normalizeFiles] Unexpected files type:', typeof files);
return [];
}
@@ -115,7 +115,7 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
try {
await fs.access(tempPath);
} catch (accessErr) {
console.error(`Temp file not accessible: ${tempPath}`, {
logger.error(`Temp file not accessible: ${tempPath}`, {
originalname: file?.originalname,
error: accessErr.message
});
@@ -156,7 +156,7 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
};
}
} catch (metadataError) {
console.warn(`Could not extract image dimensions for ${file.originalname}:`, metadataError.message);
logger.warn(`Could not extract image dimensions for ${file.originalname}:`, metadataError.message);
}
}
@@ -167,14 +167,14 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
contentType: file.mimetype,
});
} catch (uploadErr) {
console.error(`Failed to upload ${file.originalname}${finalKey}:`, uploadErr);
logger.error(`Failed to upload ${file.originalname}${finalKey}:`, uploadErr);
throw new Error(`Failed to upload to storage: ${uploadErr.message}`);
} finally {
try {
await fs.unlink(tempPath);
} catch (unlinkErr) {
if (unlinkErr?.code !== 'ENOENT') {
console.warn(`Failed to clean up temp upload ${tempPath}:`, {
logger.warn(`Failed to clean up temp upload ${tempPath}:`, {
error: unlinkErr.message,
code: unlinkErr.code
});
@@ -262,9 +262,9 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
type: photoType
});
console.log(`Successfully processed file ${file.originalname} (ID: ${photoId})`);
logger.info(`Successfully processed file ${file.originalname} (ID: ${photoId})`);
} catch (error) {
console.error(`Error processing file ${file.originalname}:`, {
logger.error(`Error processing file ${file.originalname}:`, {
error: error.message,
stack: error.stack,
originalname: file.originalname,
@@ -277,7 +277,7 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
try {
await trx.rollback();
} catch (rollbackErr) {
console.error('Failed to rollback transaction:', rollbackErr);
logger.error('Failed to rollback transaction:', rollbackErr);
}
}
+3 -2
View File
@@ -1,5 +1,6 @@
const axios = require('axios');
const { db } = require('../database/db');
const logger = require('../utils/logger');
async function verifyRecaptcha(token) {
// Check if reCAPTCHA is enabled
@@ -32,7 +33,7 @@ async function verifyRecaptcha(token) {
// If no secret key configured, fail closed
if (!secretKey) {
console.warn('reCAPTCHA enabled but no secret key configured — blocking request');
logger.warn('reCAPTCHA enabled but no secret key configured — blocking request');
return false;
}
@@ -50,7 +51,7 @@ async function verifyRecaptcha(token) {
return response.data.success === true;
} catch (error) {
console.error('reCAPTCHA verification error:', error);
logger.error('reCAPTCHA verification error:', error);
return false;
}
}
+9 -8
View File
@@ -4,6 +4,7 @@ const { db } = require('../database/db');
const watermarkService = require('./watermarkService');
const path = require('path');
const fs = require('fs').promises;
const logger = require('../utils/logger');
class SecureImageService {
constructor() {
@@ -268,7 +269,7 @@ class SecureImageService {
return buffer;
} catch (error) {
console.error('Error processing protected image:', error);
logger.error('Error processing protected image:', error);
// Return original on error
return await fs.readFile(imagePath);
}
@@ -346,7 +347,7 @@ class SecureImageService {
}
} catch (error) {
console.error('Error logging image access:', error);
logger.error('Error logging image access:', error);
}
}
@@ -386,7 +387,7 @@ class SecureImageService {
}
} catch (error) {
console.error('Error checking for rapid access:', error);
logger.error('Error checking for rapid access:', error);
}
}
@@ -417,7 +418,7 @@ class SecureImageService {
})
});
console.warn(`Suspicious activity flagged: ${reason}`, {
logger.warn(`Suspicious activity flagged: ${reason}`, {
clientFingerprint,
photoId,
details
@@ -432,12 +433,12 @@ class SecureImageService {
.first();
if (parseInt(recentSuspicious.count) >= 3) {
console.warn(`Client fingerprint flagged for blocking: ${clientFingerprint}`);
logger.warn(`Client fingerprint flagged for blocking: ${clientFingerprint}`);
// This would be handled by the middleware's blocking system
}
} catch (error) {
console.error('Error flagging suspicious activity:', error);
logger.error('Error flagging suspicious activity:', error);
}
}
@@ -457,13 +458,13 @@ class SecureImageService {
// Flag if more than 10 accesses to same photo in 5 minutes
if (accessCount > 10) {
console.warn(`Suspicious activity detected: ${accessCount} accesses to photo ${photoId} from ${clientFingerprint}`);
logger.warn(`Suspicious activity detected: ${accessCount} accesses to photo ${photoId} from ${clientFingerprint}`);
return true;
}
return false;
} catch (error) {
console.error('Error detecting suspicious activity:', error);
logger.error('Error detecting suspicious activity:', error);
return false;
}
}
+2 -1
View File
@@ -2,6 +2,7 @@ const { db } = require('../database/db');
const { formatBoolean } = require('../utils/dbCompat');
const { extractShareToken, isPotentialShareToken, buildSharePath } = require('../utils/shareLinkUtils');
const { getFrontendBaseUrl } = require('../utils/frontendUrl');
const logger = require('../utils/logger');
const SETTING_KEY = 'general_short_gallery_urls';
const CACHE_TTL_MS = 60_000;
@@ -59,7 +60,7 @@ const getRawSettingValue = async () => {
const setting = await db('app_settings').where({ setting_key: SETTING_KEY }).first();
return setting?.setting_value ?? null;
} catch (error) {
console.error('Failed to read gallery URL setting:', error.message);
logger.error('Failed to read gallery URL setting:', error.message);
return null;
}
};
+3 -2
View File
@@ -1,4 +1,5 @@
const { db } = require('../database/db');
const logger = require('../utils/logger');
const DEFAULT_MAX_FILES_PER_UPLOAD = 500;
const MAX_ALLOWED_FILES_PER_UPLOAD = 2000;
@@ -87,7 +88,7 @@ const getMaxFilesPerUpload = async () => {
cacheExpiresAt = Date.now() + CACHE_TTL_MS;
return parsedValue;
} catch (error) {
console.error('Failed to read max files per upload setting:', error.message);
logger.error('Failed to read max files per upload setting:', error.message);
cachedValue = DEFAULT_MAX_FILES_PER_UPLOAD;
cacheExpiresAt = Date.now() + CACHE_TTL_MS;
return DEFAULT_MAX_FILES_PER_UPLOAD;
@@ -147,7 +148,7 @@ const getAllowedMimeTypes = async () => {
allowedTypesCacheExpiresAt = Date.now() + CACHE_TTL_MS;
return mimeTypes;
} catch (error) {
console.error('Failed to read allowed file types setting:', error.message);
logger.error('Failed to read allowed file types setting:', error.message);
const fallback = extensionsToMimeTypes(DEFAULT_ALLOWED_FILE_TYPES);
cachedAllowedTypes = fallback;
allowedTypesCacheExpiresAt = Date.now() + CACHE_TTL_MS;
@@ -12,6 +12,7 @@ const { db } = require('../database/db');
const watermarkService = require('./watermarkService');
const { resolvePhotoStorageKey, resolvePhotoFilePath } = require('./photoResolver');
const { withLocalCopy } = require('./imageProcessor');
const logger = require('../utils/logger');
class WatermarkGeneratorService {
constructor() {
@@ -79,7 +80,7 @@ class WatermarkGeneratorService {
return result;
} catch (error) {
console.error(`Error generating watermark for photo ${photoId}:`, error);
logger.error(`Error generating watermark for photo ${photoId}:`, error);
return { success: false, error: error.message };
}
}
@@ -155,7 +156,7 @@ class WatermarkGeneratorService {
return results;
} catch (error) {
console.error(`Error generating watermarks for event ${eventId}:`, error);
logger.error(`Error generating watermarks for event ${eventId}:`, error);
return { ...results, errors: [...results.errors, error.message] };
}
}
@@ -232,7 +233,7 @@ class WatermarkGeneratorService {
return results;
}
console.log(`Starting watermark regeneration for ${photos.length} photos`);
logger.info(`Starting watermark regeneration for ${photos.length} photos`);
// Process in batches
for (let i = 0; i < photos.length; i += this.batchSize) {
@@ -281,11 +282,11 @@ class WatermarkGeneratorService {
}
results.status = 'completed';
console.log(`Watermark regeneration completed: ${results.success}/${results.total} successful`);
logger.info(`Watermark regeneration completed: ${results.success}/${results.total} successful`);
return results;
} catch (error) {
console.error('Error during watermark regeneration:', error);
logger.error('Error during watermark regeneration:', error);
results.status = 'failed';
results.errors.push(error.message);
return results;
@@ -320,10 +321,10 @@ class WatermarkGeneratorService {
watermark_generated_at: null
});
console.log(`Cleared ${photos.length} watermarks`);
logger.info(`Cleared ${photos.length} watermarks`);
return { success: true, cleared: photos.length };
} catch (error) {
console.error('Error clearing watermarks:', error);
logger.error('Error clearing watermarks:', error);
return { success: false, error: error.message };
}
}
@@ -350,7 +351,7 @@ class WatermarkGeneratorService {
return { success: true };
} catch (error) {
console.error(`Error deleting watermark for photo ${photoId}:`, error);
logger.error(`Error deleting watermark for photo ${photoId}:`, error);
return { success: false, error: error.message };
}
}
+6 -5
View File
@@ -3,6 +3,7 @@ const path = require('path');
const fs = require('fs').promises;
const { db } = require('../database/db');
const { getStorage } = require('./storage');
const logger = require('../utils/logger');
class WatermarkService {
constructor() {
@@ -44,7 +45,7 @@ class WatermarkService {
companyName: settingsObj.branding_company_name || 'Photo Gallery'
};
} catch (error) {
console.error('Error fetching watermark settings:', error);
logger.error('Error fetching watermark settings:', error);
return null;
}
}
@@ -138,7 +139,7 @@ class WatermarkService {
watermarkMetadata = { width: targetWidth, height: targetHeight };
} catch (error) {
console.error('Error processing watermark logo:', error);
logger.error('Error processing watermark logo:', error);
watermarkBuffer = null;
}
}
@@ -209,7 +210,7 @@ class WatermarkService {
return watermarkedBuffer;
} catch (error) {
console.error('Error applying watermark:', error);
logger.error('Error applying watermark:', error);
// Return original image on error
return await fs.readFile(imagePath);
}
@@ -287,7 +288,7 @@ class WatermarkService {
error: null
};
} catch (error) {
console.error(`Error generating watermark for photo ${photo.id}:`, error);
logger.error(`Error generating watermark for photo ${photo.id}:`, error);
return {
success: false,
watermarkPath: null,
@@ -308,7 +309,7 @@ class WatermarkService {
await getStorage().delete(watermarkPath);
return true;
} catch (error) {
console.error('Error deleting watermark file:', error);
logger.error('Error deleting watermark file:', error);
return false;
}
}