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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user