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:
@@ -9,6 +9,7 @@ const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { validateFileType } = require('../utils/fileSecurityUtils');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* Get the storage path from environment or default
|
||||
@@ -220,14 +221,14 @@ const createCustomUploader = (config) => {
|
||||
const uploadTimeoutMiddleware = (timeout = 300000) => {
|
||||
return (req, res, next) => {
|
||||
req.setTimeout(timeout, () => {
|
||||
console.error('Upload request timed out');
|
||||
logger.error('Upload request timed out');
|
||||
if (!res.headersSent) {
|
||||
res.status(408).json({ error: 'Upload request timed out' });
|
||||
}
|
||||
});
|
||||
|
||||
res.setTimeout(timeout, () => {
|
||||
console.error('Upload response timed out');
|
||||
logger.error('Upload response timed out');
|
||||
});
|
||||
|
||||
next();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { db } = require('../database/db');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Cache maintenance mode status to avoid DB queries on every request
|
||||
let maintenanceMode = false;
|
||||
@@ -26,7 +27,7 @@ async function queryWithRetry(queryFn, retries = MAX_RETRIES) {
|
||||
error.code === 'ECONNRESET';
|
||||
|
||||
if (isConnectionError) {
|
||||
console.warn(`Database connection error, retrying in ${RETRY_DELAY}ms... (attempt ${i + 1}/${retries})`);
|
||||
logger.warn(`Database connection error, retrying in ${RETRY_DELAY}ms... (attempt ${i + 1}/${retries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
||||
} else {
|
||||
throw error; // Don't retry non-connection errors
|
||||
@@ -56,7 +57,7 @@ async function checkMaintenanceMode() {
|
||||
|
||||
return maintenanceMode;
|
||||
} catch (error) {
|
||||
console.error('Error checking maintenance mode after retries:', error.message);
|
||||
logger.error('Error checking maintenance mode after retries:', error.message);
|
||||
// Return cached value or false if no cache
|
||||
return maintenanceMode;
|
||||
}
|
||||
@@ -102,7 +103,7 @@ async function maintenanceMiddleware(req, res, next) {
|
||||
}
|
||||
} catch (error) {
|
||||
// If we can't check maintenance mode, allow the request to proceed
|
||||
console.error('Failed to check maintenance mode, allowing request:', error.message);
|
||||
logger.error('Failed to check maintenance mode, allowing request:', error.message);
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
@@ -333,7 +333,7 @@ class SecureImageMiddleware {
|
||||
await db('security_logs').insert(logData).catch(console.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error logging security event:', error);
|
||||
logger.error('Error logging security event:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ class SecureImageMiddleware {
|
||||
perHour: config.perHour || 500
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting rate limit settings:', error);
|
||||
logger.error('Error getting rate limit settings:', error);
|
||||
return { perMinute: 30, per5Minutes: 100, perHour: 500 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const { safePathJoin, isPathSafe } = require('../utils/fileSecurityUtils');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* Create a secure static file serving middleware that prevents path traversal attacks
|
||||
@@ -17,7 +18,7 @@ function secureStatic(basePath, options = {}) {
|
||||
|
||||
// Validate the path doesn't contain dangerous patterns
|
||||
if (!isPathSafe(requestedPath)) {
|
||||
console.warn(`Potential path traversal attempt blocked: ${requestedPath}`);
|
||||
logger.warn(`Potential path traversal attempt blocked: ${requestedPath}`);
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ function secureStatic(basePath, options = {}) {
|
||||
return staticMiddleware(req, res, next);
|
||||
} catch (error) {
|
||||
// Path traversal detected
|
||||
console.error(`Path traversal blocked: ${requestedPath}`, error.message);
|
||||
logger.error(`Path traversal blocked: ${requestedPath}`, error.message);
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { db } = require('../database/db');
|
||||
const { getAdminTokenFromRequest } = require('../utils/tokenUtils');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// In-memory session tracking (in production, use Redis)
|
||||
const sessions = new Map();
|
||||
@@ -69,7 +70,7 @@ async function getSessionTimeout() {
|
||||
} catch (error) {
|
||||
// Only log if it's not a connection error (to avoid spam)
|
||||
if (error.code !== 'ECONNRESET' && !error.message?.includes('Connection terminated')) {
|
||||
console.error('Error getting session timeout:', error.message);
|
||||
logger.error('Error getting session timeout:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ const { requirePermission } = require('../middleware/permissions');
|
||||
const archiver = require('archiver');
|
||||
const StreamZip = require('node-stream-zip');
|
||||
const { requireEventOwnership } = require('../middleware/ownership');
|
||||
const logger = require('../utils/logger');
|
||||
const { getPagination } = require('../utils/routeHelpers');
|
||||
const router = express.Router();
|
||||
|
||||
// Get all archived events
|
||||
router.get('/', adminAuth, requirePermission('archives.view'), async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 20;
|
||||
const offset = (page - 1) * limit;
|
||||
const { page, limit, offset } = getPagination(req);
|
||||
|
||||
// Get total count
|
||||
const totalCount = await db('events')
|
||||
@@ -48,7 +48,7 @@ router.get('/', adminAuth, requirePermission('archives.view'), async (req, res)
|
||||
const stats = await fs.stat(fullArchivePath);
|
||||
archiveFileSize = stats.size;
|
||||
} catch (error) {
|
||||
console.error(`Archive file not found: ${archive.archive_path}`);
|
||||
logger.error(`Archive file not found: ${archive.archive_path}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ router.get('/', adminAuth, requirePermission('archives.view'), async (req, res)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Archives list error:', error);
|
||||
logger.error('Archives list error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch archives' });
|
||||
}
|
||||
});
|
||||
@@ -113,7 +113,7 @@ router.get('/:id', adminAuth, requirePermission('archives.view'), requireEventOw
|
||||
path: archive.archive_path
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Archive file not found:', error);
|
||||
logger.error('Archive file not found:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ router.get('/:id', adminAuth, requirePermission('archives.view'), requireEventOw
|
||||
archiveFile: archiveFileInfo
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Archive details error:', error);
|
||||
logger.error('Archive details error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch archive details' });
|
||||
}
|
||||
});
|
||||
@@ -179,9 +179,9 @@ router.post('/:id/restore', adminAuth, requirePermission('archives.restore'), re
|
||||
await fs.mkdir(eventDir, { recursive: true });
|
||||
|
||||
// Log ZIP contents for debugging
|
||||
console.log(`Extracting archive to: ${eventDir}`);
|
||||
logger.info(`Extracting archive to: ${eventDir}`);
|
||||
const entries = Object.values(await zip.entries());
|
||||
console.log(`Archive contains ${entries.length} entries`);
|
||||
logger.info(`Archive contains ${entries.length} entries`);
|
||||
|
||||
// Stream-extract everything to disk
|
||||
await zip.extract(null, eventDir);
|
||||
@@ -203,12 +203,12 @@ router.post('/:id/restore', adminAuth, requirePermission('archives.restore'), re
|
||||
if (m && m.filename) manifestByFilename.set(m.filename, m);
|
||||
}
|
||||
}
|
||||
console.log(`Loaded photos manifest: ${manifestByFilename.size} entries`);
|
||||
logger.info(`Loaded photos manifest: ${manifestByFilename.size} entries`);
|
||||
} catch (e) {
|
||||
if (e.code !== 'ENOENT') {
|
||||
console.warn('Photos manifest present but unreadable; falling back to filenames', e.message);
|
||||
logger.warn('Photos manifest present but unreadable; falling back to filenames', e.message);
|
||||
} else {
|
||||
console.log('No photos manifest in archive (older archive); original_filename falls back to filename');
|
||||
logger.info('No photos manifest in archive (older archive); original_filename falls back to filename');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,9 +286,9 @@ router.post('/:id/restore', adminAuth, requirePermission('archives.restore'), re
|
||||
});
|
||||
}
|
||||
} catch (statError) {
|
||||
console.error(`Failed to stat file: ${actualFilePath}`);
|
||||
console.error(`Entry name was: ${entry.name}`);
|
||||
console.error('Error:', statError.message);
|
||||
logger.error(`Failed to stat file: ${actualFilePath}`);
|
||||
logger.error(`Entry name was: ${entry.name}`);
|
||||
logger.error('Error:', statError.message);
|
||||
// Skip this file if we can't stat it
|
||||
continue;
|
||||
}
|
||||
@@ -301,7 +301,7 @@ router.post('/:id/restore', adminAuth, requirePermission('archives.restore'), re
|
||||
}
|
||||
|
||||
} catch (extractError) {
|
||||
console.error('Archive extraction error:', extractError);
|
||||
logger.error('Archive extraction error:', extractError);
|
||||
return res.status(500).json({ error: 'Failed to extract archive: ' + extractError.message });
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ router.post('/:id/restore', adminAuth, requirePermission('archives.restore'), re
|
||||
|
||||
res.json({ message: 'Archive restored successfully' });
|
||||
} catch (error) {
|
||||
console.error('Archive restore error:', error);
|
||||
logger.error('Archive restore error:', error);
|
||||
res.status(500).json({ error: 'Failed to restore archive' });
|
||||
}
|
||||
});
|
||||
@@ -380,7 +380,7 @@ router.get('/:id/download', adminAuth, requirePermission('archives.download'), r
|
||||
metadata: JSON.stringify({ event_name: archive.event_name })
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Archive download error:', error);
|
||||
logger.error('Archive download error:', error);
|
||||
res.status(500).json({ error: 'Failed to download archive' });
|
||||
}
|
||||
});
|
||||
@@ -404,7 +404,7 @@ router.delete('/:id', adminAuth, requirePermission('archives.delete'), requireEv
|
||||
const fullArchivePath = path.join(storagePath, archive.archive_path);
|
||||
await fs.unlink(fullArchivePath);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete archive file:', error);
|
||||
logger.error('Failed to delete archive file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ router.delete('/:id', adminAuth, requirePermission('archives.delete'), requireEv
|
||||
|
||||
res.json({ message: 'Archive deleted permanently' });
|
||||
} catch (error) {
|
||||
console.error('Archive delete error:', error);
|
||||
logger.error('Archive delete error:', error);
|
||||
res.status(500).json({ error: 'Failed to delete archive' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const { triggerManualBackup, getBackupStatus, cleanupOldBackupRuns, getBackupManifest, validateBackupManifest } = require('../services/backupService');
|
||||
const logger = require('../utils/logger');
|
||||
const { errorResponse, getPagination } = require('../utils/routeHelpers');
|
||||
const { formatBytes } = require('../utils/formatBytes');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
@@ -31,8 +32,7 @@ router.get('/config', adminAuth, requirePermission('backup.view'), async (req, r
|
||||
|
||||
res.json(config);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get backup configuration:', error);
|
||||
res.status(500).json({ error: 'Failed to get backup configuration' });
|
||||
errorResponse(res, error, 500, 'Failed to get backup configuration');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -93,21 +93,19 @@ router.put('/config', adminAuth, requirePermission('backup.create'), async (req,
|
||||
|
||||
res.json({ success: true, message: 'Backup configuration updated' });
|
||||
} catch (error) {
|
||||
logger.error('Failed to update backup configuration:', error);
|
||||
res.status(500).json({ error: 'Failed to update backup configuration' });
|
||||
errorResponse(res, error, 500, 'Failed to update backup configuration');
|
||||
}
|
||||
});
|
||||
|
||||
// Get backup status and history
|
||||
router.get('/status', adminAuth, requirePermission('backup.view'), async (req, res) => {
|
||||
try {
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const { limit } = getPagination(req, { limit: 10 });
|
||||
const status = await getBackupStatus(limit);
|
||||
|
||||
res.json(status);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get backup status:', error);
|
||||
res.status(500).json({ error: 'Failed to get backup status' });
|
||||
errorResponse(res, error, 500, 'Failed to get backup status');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -127,8 +125,7 @@ router.post('/run', adminAuth, requirePermission('backup.create'), async (req, r
|
||||
|
||||
res.json({ success: true, message: 'Backup started' });
|
||||
} catch (error) {
|
||||
logger.error('Failed to trigger manual backup:', error);
|
||||
res.status(500).json({ error: 'Failed to trigger backup' });
|
||||
errorResponse(res, error, 500, 'Failed to trigger backup');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -156,8 +153,7 @@ router.get('/runs/:id', adminAuth, requirePermission('backup.view'), async (req,
|
||||
|
||||
res.json(run);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get backup run details:', error);
|
||||
res.status(500).json({ error: 'Failed to get backup run details' });
|
||||
errorResponse(res, error, 500, 'Failed to get backup run details');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -191,8 +187,7 @@ router.get('/files', adminAuth, requirePermission('backup.view'), async (req, re
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to get backup file states:', error);
|
||||
res.status(500).json({ error: 'Failed to get file states' });
|
||||
errorResponse(res, error, 500, 'Failed to get file states');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -205,8 +200,7 @@ router.delete('/cleanup', adminAuth, requirePermission('backup.delete'), async (
|
||||
|
||||
res.json({ success: true, message: `Cleaned up backup runs older than ${days} days` });
|
||||
} catch (error) {
|
||||
logger.error('Failed to cleanup old backup runs:', error);
|
||||
res.status(500).json({ error: 'Failed to cleanup backup runs' });
|
||||
errorResponse(res, error, 500, 'Failed to cleanup backup runs');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -337,8 +331,7 @@ router.post('/test-connection', adminAuth, requirePermission('backup.create'), a
|
||||
res.status(400).json({ error: 'Invalid destination type' });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to test backup connection:', error);
|
||||
res.status(500).json({ error: 'Failed to test connection' });
|
||||
errorResponse(res, error, 500, 'Failed to test connection');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -381,8 +374,7 @@ router.post('/manifest/validate', adminAuth, requirePermission('backup.view'), a
|
||||
manifestPath
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to validate manifest:', error);
|
||||
res.status(500).json({ error: 'Failed to validate manifest' });
|
||||
errorResponse(res, error, 500, 'Failed to validate manifest');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -494,8 +486,7 @@ router.post('/manifests/validate', adminAuth, requirePermission('backup.view'),
|
||||
manifestPath
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to validate manifest:', error);
|
||||
res.status(500).json({ error: 'Failed to validate manifest' });
|
||||
errorResponse(res, error, 500, 'Failed to validate manifest');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -526,8 +517,7 @@ router.get('/s3/buckets', adminAuth, requirePermission('backup.view'), async (re
|
||||
owner: result.Owner || null
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to list S3 buckets:', error);
|
||||
res.status(500).json({ error: 'Failed to list S3 buckets' });
|
||||
errorResponse(res, error, 500, 'Failed to list S3 buckets');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -563,8 +553,7 @@ router.get('/s3/files', adminAuth, requirePermission('backup.view'), async (req,
|
||||
prefix: prefix
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to list S3 files:', error);
|
||||
res.status(500).json({ error: 'Failed to list S3 files' });
|
||||
errorResponse(res, error, 500, 'Failed to list S3 files');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -625,8 +614,7 @@ router.delete('/s3/cleanup', adminAuth, requirePermission('backup.delete'), asyn
|
||||
message: `Cleaned up ${deletedCount} S3 backup files older than ${retentionDays} days`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to cleanup S3 backups:', error);
|
||||
res.status(500).json({ error: 'Failed to cleanup S3 backups' });
|
||||
errorResponse(res, error, 500, 'Failed to cleanup S3 backups');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -677,8 +665,7 @@ router.post('/s3/test-upload', adminAuth, requirePermission('backup.create'), as
|
||||
message: 'S3 upload test completed successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('S3 upload test failed:', error);
|
||||
res.status(500).json({ error: 'S3 upload test failed' });
|
||||
errorResponse(res, error, 500, 'S3 upload test failed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -765,8 +752,7 @@ router.get('/download/:backupId', adminAuth, requirePermission('backup.view'), a
|
||||
return res.status(400).json({ error: 'Unknown backup type' });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to download backup:', error);
|
||||
res.status(500).json({ error: 'Failed to download backup' });
|
||||
errorResponse(res, error, 500, 'Failed to download backup');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -838,8 +824,7 @@ router.get('/checksums', adminAuth, requirePermission('backup.view'), async (req
|
||||
path: targetPath || '/'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to get file checksums:', error);
|
||||
res.status(500).json({ error: 'Failed to get file checksums' });
|
||||
errorResponse(res, error, 500, 'Failed to get file checksums');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -941,8 +926,7 @@ router.post('/estimate', adminAuth, requirePermission('backup.view'), async (req
|
||||
warnings: totalSize > 10 * 1024 * 1024 * 1024 ? ['Backup size exceeds 10GB, may take significant time'] : []
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to estimate backup size:', error);
|
||||
res.status(500).json({ error: 'Failed to estimate backup size' });
|
||||
errorResponse(res, error, 500, 'Failed to estimate backup size');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const { db, logActivity } = require('../database/db');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const { validateFileType } = require('../utils/fileSecurityUtils');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '../../../storage');
|
||||
@@ -43,7 +44,7 @@ router.get('/pages', adminAuth, requirePermission('cms.view'), async (req, res)
|
||||
const pages = await db('cms_pages').select('*').orderBy('slug', 'asc');
|
||||
res.json(pages);
|
||||
} catch (error) {
|
||||
console.error('Error fetching CMS pages:', error);
|
||||
logger.error('Error fetching CMS pages:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch pages' });
|
||||
}
|
||||
});
|
||||
@@ -60,7 +61,7 @@ router.get('/pages/:slug', adminAuth, requirePermission('cms.view'), async (req,
|
||||
|
||||
res.json(page);
|
||||
} catch (error) {
|
||||
console.error('Error fetching CMS page:', error);
|
||||
logger.error('Error fetching CMS page:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch page' });
|
||||
}
|
||||
});
|
||||
@@ -144,7 +145,7 @@ router.put('/pages/:slug', adminAuth, requirePermission('cms.edit'), [
|
||||
|
||||
res.json(updated);
|
||||
} catch (error) {
|
||||
console.error('Error updating CMS page:', error);
|
||||
logger.error('Error updating CMS page:', error);
|
||||
res.status(500).json({ error: 'Failed to update page' });
|
||||
}
|
||||
});
|
||||
@@ -184,7 +185,7 @@ router.post(
|
||||
|
||||
res.json({ logo_url: logoUrl });
|
||||
} catch (error) {
|
||||
console.error('Error uploading CMS page logo:', error);
|
||||
logger.error('Error uploading CMS page logo:', error);
|
||||
res.status(500).json({ error: 'Failed to upload logo' });
|
||||
}
|
||||
}
|
||||
@@ -208,7 +209,7 @@ router.delete(
|
||||
|
||||
res.json({ logo_url: null });
|
||||
} catch (error) {
|
||||
console.error('Error clearing CMS page logo:', error);
|
||||
logger.error('Error clearing CMS page logo:', error);
|
||||
res.status(500).json({ error: 'Failed to clear logo' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const { db, logActivity } = require('../database/db');
|
||||
const { formatBoolean } = require('../utils/dbCompat');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
// Get all global categories
|
||||
@@ -15,7 +16,7 @@ router.get('/global', adminAuth, requirePermission('settings.view'), async (req,
|
||||
|
||||
res.json(categories);
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
logger.error('Error fetching categories:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch categories' });
|
||||
}
|
||||
});
|
||||
@@ -35,7 +36,7 @@ router.get('/event/:eventId', adminAuth, requirePermission('settings.view'), asy
|
||||
|
||||
res.json(categories);
|
||||
} catch (error) {
|
||||
console.error('Error fetching event categories:', error);
|
||||
logger.error('Error fetching event categories:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch categories' });
|
||||
}
|
||||
});
|
||||
@@ -101,7 +102,7 @@ router.post('/', adminAuth, requirePermission('settings.edit'), [
|
||||
|
||||
res.json(category);
|
||||
} catch (error) {
|
||||
console.error('Error creating category:', error);
|
||||
logger.error('Error creating category:', error);
|
||||
res.status(500).json({ error: 'Failed to create category' });
|
||||
}
|
||||
});
|
||||
@@ -165,7 +166,7 @@ router.put('/:id', adminAuth, requirePermission('settings.edit'), [
|
||||
|
||||
res.json(updated);
|
||||
} catch (error) {
|
||||
console.error('Error updating category:', error);
|
||||
logger.error('Error updating category:', error);
|
||||
res.status(500).json({ error: 'Failed to update category' });
|
||||
}
|
||||
});
|
||||
@@ -214,7 +215,7 @@ router.put('/:id/hero', adminAuth, requirePermission('settings.edit'), [
|
||||
|
||||
res.json(updated);
|
||||
} catch (error) {
|
||||
console.error('Error updating category hero:', error);
|
||||
logger.error('Error updating category hero:', error);
|
||||
res.status(500).json({ error: 'Failed to update category hero' });
|
||||
}
|
||||
});
|
||||
@@ -248,7 +249,7 @@ router.delete('/:id', adminAuth, requirePermission('settings.edit'), async (req,
|
||||
|
||||
res.json({ message: 'Category deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting category:', error);
|
||||
logger.error('Error deleting category:', error);
|
||||
res.status(500).json({ error: 'Failed to delete category' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const { sanitizeCSS, validateCSS, MAX_CSS_SIZE } = require('../utils/cssSanitizer');
|
||||
const { DEFAULT_CSS_TEMPLATE } = require('../../migrations/core/052_add_css_templates');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* GET /admin/css-templates
|
||||
@@ -23,7 +24,7 @@ router.get('/', adminAuth, requirePermission('branding.view'), async (req, res)
|
||||
);
|
||||
res.json({ success: true, templates });
|
||||
} catch (error) {
|
||||
console.error('Get CSS templates error:', error);
|
||||
logger.error('Get CSS templates error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch templates' });
|
||||
}
|
||||
});
|
||||
@@ -42,7 +43,7 @@ router.get('/enabled', adminAuth, requirePermission('branding.view'), async (req
|
||||
);
|
||||
res.json({ success: true, templates });
|
||||
} catch (error) {
|
||||
console.error('Get enabled templates error:', error);
|
||||
logger.error('Get enabled templates error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch templates' });
|
||||
}
|
||||
});
|
||||
@@ -73,7 +74,7 @@ router.get('/:slotNumber', adminAuth, requirePermission('branding.view'), [
|
||||
|
||||
res.json({ success: true, template });
|
||||
} catch (error) {
|
||||
console.error('Get template error:', error);
|
||||
logger.error('Get template error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch template' });
|
||||
}
|
||||
});
|
||||
@@ -150,7 +151,7 @@ router.put('/:slotNumber', adminAuth, requirePermission('branding.edit'), [
|
||||
sanitization_warnings: warnings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Update template error:', error);
|
||||
logger.error('Update template error:', error);
|
||||
res.status(500).json({ error: 'Failed to update template' });
|
||||
}
|
||||
});
|
||||
@@ -187,7 +188,7 @@ router.post('/:slotNumber/reset', adminAuth, requirePermission('branding.edit'),
|
||||
|
||||
res.json({ success: true, template });
|
||||
} catch (error) {
|
||||
console.error('Reset template error:', error);
|
||||
logger.error('Reset template error:', error);
|
||||
res.status(500).json({ error: 'Failed to reset template' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ const { sanitizeDays, addDateRangeCondition } = require('../utils/sqlSecurity');
|
||||
const { formatBoolean } = require('../utils/dbCompat');
|
||||
const { resolveAdapter } = require('../services/trackers');
|
||||
const logger = require('../utils/logger');
|
||||
const { errorResponse, getPagination } = require('../utils/routeHelpers');
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
@@ -126,16 +127,15 @@ router.get('/stats', adminAuth, requirePermission('analytics.view'), async (req,
|
||||
totalEvents: totalEvents.count || 0
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard stats error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch dashboard statistics' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch dashboard statistics');
|
||||
}
|
||||
});
|
||||
|
||||
// Get recent activity
|
||||
router.get('/activity', adminAuth, requirePermission('analytics.view'), async (req, res) => {
|
||||
try {
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
|
||||
const { limit } = getPagination(req, { limit: 10 });
|
||||
|
||||
const activities = await db('activity_logs')
|
||||
.select('activity_logs.*', 'events.event_name')
|
||||
.leftJoin('events', 'activity_logs.event_id', 'events.id')
|
||||
@@ -155,7 +155,7 @@ router.get('/activity', adminAuth, requirePermission('analytics.view'), async (r
|
||||
if (typeof activity.metadata === 'object') return activity.metadata;
|
||||
return JSON.parse(activity.metadata);
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse metadata for activity:', activity.id, e.message);
|
||||
logger.warn('Failed to parse metadata for activity:', activity.id, e.message);
|
||||
return {};
|
||||
}
|
||||
})(),
|
||||
@@ -164,8 +164,7 @@ router.get('/activity', adminAuth, requirePermission('analytics.view'), async (r
|
||||
|
||||
res.json(formattedActivities);
|
||||
} catch (error) {
|
||||
console.error('Activity log error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch activity log' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch activity log');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -233,7 +232,7 @@ router.get('/health', adminAuth, requirePermission('settings.view'), async (req,
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Health check error:', error);
|
||||
logger.error('Health check error:', error);
|
||||
res.status(500).json({
|
||||
overall: 'error',
|
||||
error: 'Failed to check system health'
|
||||
@@ -400,8 +399,7 @@ router.get('/analytics', adminAuth, requirePermission('analytics.view'), async (
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Analytics error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch analytics data' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch analytics data');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -586,8 +584,7 @@ router.get('/crm-stats', adminAuth, async (req, res) => {
|
||||
generatedAt: new Date().toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
require('../utils/logger').error('CRM stats error:', error);
|
||||
res.status(500).json({ error: 'Failed to load CRM stats' });
|
||||
errorResponse(res, error, 500, 'Failed to load CRM stats');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ const { requirePermission } = require('../middleware/permissions');
|
||||
const { databaseBackupService } = require('../services/databaseBackup');
|
||||
const { db } = require('../database/db');
|
||||
const logger = require('../utils/logger');
|
||||
const { getPagination } = require('../utils/routeHelpers');
|
||||
|
||||
// All routes require admin authentication
|
||||
router.use(adminAuth);
|
||||
@@ -154,10 +155,8 @@ router.get('/progress', requirePermission('backup.view'), async (req, res) => {
|
||||
*/
|
||||
router.get('/history', requirePermission('backup.view'), async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 20;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { page, limit, offset } = getPagination(req);
|
||||
|
||||
const [backups, totalCount] = await Promise.all([
|
||||
db('database_backup_runs')
|
||||
.orderBy('started_at', 'desc')
|
||||
|
||||
@@ -5,6 +5,8 @@ const { db, logActivity } = require('../database/db');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const { wrapEmailHtml, processEmailQueue } = require('../services/emailProcessor');
|
||||
const { errorResponse } = require('../utils/routeHelpers');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
// Get email configuration
|
||||
@@ -31,8 +33,7 @@ router.get('/config', adminAuth, requirePermission('email.view'), async (req, re
|
||||
smtp_pass: config.smtp_pass ? '********' : ''
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Email config fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch email configuration' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch email configuration');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -113,8 +114,7 @@ router.post('/config', [
|
||||
|
||||
res.json({ message: 'Email configuration updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Email config update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update email configuration' });
|
||||
errorResponse(res, error, 500, 'Failed to update email configuration');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -131,8 +131,7 @@ router.get('/incoming-config', adminAuth, requirePermission('email.view'), async
|
||||
imap_folder: c?.imap_folder || 'INBOX',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Incoming mail config fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch incoming mail configuration' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch incoming mail configuration');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,8 +167,7 @@ router.post('/incoming-config', [
|
||||
await logActivity('incoming_mail_config_updated', { imap_host }, null, { type: 'admin', id: req.admin.id, name: req.admin.username });
|
||||
res.json({ message: 'Incoming mail configuration updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Incoming mail config update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update incoming mail configuration' });
|
||||
errorResponse(res, error, 500, 'Failed to update incoming mail configuration');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -192,7 +190,7 @@ router.post('/incoming-config/folders', adminAuth, requirePermission('email.view
|
||||
);
|
||||
res.json({ folders });
|
||||
} catch (error) {
|
||||
console.error('IMAP folder detection error:', error);
|
||||
logger.error('IMAP folder detection error:', error);
|
||||
res.status(422).json({ error: `Could not connect to the mailbox (${error.message}). Check host, port (IMAP is usually 993) and credentials.` });
|
||||
}
|
||||
});
|
||||
@@ -217,7 +215,7 @@ router.post('/incoming-config/test', adminAuth, requirePermission('email.view'),
|
||||
}
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('IMAP connection test error:', error);
|
||||
logger.error('IMAP connection test error:', error);
|
||||
res.status(422).json({ error: `Could not connect to the mailbox (${error.message}). Check host, port (IMAP is usually 993), credentials and folder.` });
|
||||
}
|
||||
});
|
||||
@@ -239,7 +237,7 @@ router.post('/incoming-config/roundtrip', adminAuth, requirePermission('email.se
|
||||
return res.status(result.reason === 'not_received' ? 504 : 400)
|
||||
.json({ error: map[result.reason] || 'Round-trip test failed.', sent: !!result.sent, recipient: result.recipient });
|
||||
} catch (error) {
|
||||
console.error('Round-trip test error:', error);
|
||||
logger.error('Round-trip test error:', error);
|
||||
res.status(422).json({ error: `Round-trip test failed (${error.message}) — check both SMTP and IMAP settings.` });
|
||||
}
|
||||
});
|
||||
@@ -253,7 +251,7 @@ router.post('/incoming-config/poll', adminAuth, requirePermission('email.view'),
|
||||
const result = await emailIntakeService.pollOnce();
|
||||
res.json(result); // { processed } or { skipped: 'disabled'|'unconfigured'|'busy' }
|
||||
} catch (error) {
|
||||
console.error('Manual poll error:', error);
|
||||
logger.error('Manual poll error:', error);
|
||||
res.status(422).json({ error: `Mailbox poll failed (${error.message}).` });
|
||||
}
|
||||
});
|
||||
@@ -268,8 +266,7 @@ router.get('/received', adminAuth, requirePermission('email.view'), async (req,
|
||||
const items = await base.clone().orderBy('received_at', 'desc').limit(pageSize).offset((page - 1) * pageSize);
|
||||
res.json({ items, pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize) } });
|
||||
} catch (error) {
|
||||
console.error('Received emails fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch received emails' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch received emails');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -322,7 +319,7 @@ router.post('/test', adminAuth, requirePermission('email.send'), async (req, res
|
||||
debug: process.env.NODE_ENV === 'development'
|
||||
};
|
||||
|
||||
console.log('Creating email transporter with config:', {
|
||||
logger.info('Creating email transporter with config:', {
|
||||
host: transportConfig.host,
|
||||
port: transportConfig.port,
|
||||
secure: transportConfig.secure,
|
||||
@@ -356,8 +353,8 @@ router.post('/test', adminAuth, requirePermission('email.send'), async (req, res
|
||||
|
||||
res.json({ message: 'Test email sent successfully' });
|
||||
} catch (error) {
|
||||
console.error('Test email error:', error);
|
||||
console.error('Error stack:', error.stack);
|
||||
logger.error('Test email error:', error);
|
||||
logger.error('Error stack:', error.stack);
|
||||
|
||||
// Provide more specific error messages with translation keys
|
||||
let errorMessage = 'Error sending email';
|
||||
@@ -428,7 +425,7 @@ router.post('/flush-queue', adminAuth, requirePermission('email.send'), async (r
|
||||
} catch (_) { /* activity logging is best-effort */ }
|
||||
res.json({ message: 'Email queue flushed', ...summary });
|
||||
} catch (error) {
|
||||
console.error('Flush email queue error:', error);
|
||||
logger.error('Flush email queue error:', error);
|
||||
res.status(500).json({ error: 'Failed to flush email queue', details: error.message });
|
||||
}
|
||||
});
|
||||
@@ -516,7 +513,7 @@ router.get('/queue', adminAuth, requirePermission('email.view'), [
|
||||
pagination: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) || 1 },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('List email queue error:', error);
|
||||
logger.error('List email queue error:', error);
|
||||
res.status(500).json({ error: 'Failed to load email queue', details: error.message });
|
||||
}
|
||||
});
|
||||
@@ -528,7 +525,7 @@ function parseVariables(template) {
|
||||
if (typeof template.variables === 'object') return template.variables;
|
||||
return JSON.parse(template.variables);
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse variables for template:', template.template_key, e.message);
|
||||
logger.warn('Failed to parse variables for template:', template.template_key, e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -611,8 +608,7 @@ router.get('/templates', adminAuth, requirePermission('email.view'), async (req,
|
||||
|
||||
res.json(formattedTemplates);
|
||||
} catch (error) {
|
||||
console.error('Email templates fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch email templates' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch email templates');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -641,8 +637,7 @@ router.get('/templates/:key', adminAuth, requirePermission('email.view'), async
|
||||
updated_at: template.updated_at,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Email template fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch email template' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch email template');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -730,8 +725,7 @@ router.put('/templates/:key', [
|
||||
|
||||
res.json({ message: 'Email template updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Email template update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update email template' });
|
||||
errorResponse(res, error, 500, 'Failed to update email template');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -818,8 +812,7 @@ router.post('/templates', [
|
||||
|
||||
return res.status(201).json({ template_key: templateKey, id: templateId });
|
||||
} catch (error) {
|
||||
console.error('Email template create error:', error);
|
||||
return res.status(500).json({ error: 'Failed to create email template' });
|
||||
return errorResponse(res, error, 500, 'Failed to create email template');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -897,8 +890,7 @@ router.post('/templates/:key/preview', adminAuth, requirePermission('email.view'
|
||||
language
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Email template preview error:', error);
|
||||
res.status(500).json({ error: 'Failed to preview email template' });
|
||||
errorResponse(res, error, 500, 'Failed to preview email template');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ const { body, validationResult } = require('express-validator');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const eventRenameService = require('../services/eventRenameService');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
@@ -50,7 +51,7 @@ router.post('/:eventId/rename', adminAuth, requirePermission('events.edit'), [
|
||||
data: result.data
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error renaming event:', error);
|
||||
logger.error('Error renaming event:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to rename event' });
|
||||
}
|
||||
});
|
||||
@@ -81,7 +82,7 @@ router.post('/:eventId/validate-rename', adminAuth, requirePermission('events.ed
|
||||
|
||||
res.json(validation);
|
||||
} catch (error) {
|
||||
console.error('Error validating rename:', error);
|
||||
logger.error('Error validating rename:', error);
|
||||
res.status(500).json({ valid: false, error: 'Validation failed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ const { escapeLikePattern } = require('../utils/sqlSecurity');
|
||||
// formatDate import removed - dates are formatted by email processor
|
||||
const { validatePasswordInContext, getBcryptRounds } = require('../utils/passwordValidation');
|
||||
const logger = require('../utils/logger');
|
||||
const { errorResponse } = require('../utils/routeHelpers');
|
||||
const { buildShareLinkVariants } = require('../services/shareLinkService');
|
||||
const { parseBooleanInput, parseStringInput } = require('../utils/parsers');
|
||||
const eventTypeService = require('../services/eventTypeService');
|
||||
@@ -441,7 +442,7 @@ router.post('/', adminAuth, requirePermission('events.create'), [
|
||||
logger.debug('Create event request body', { body: req.body });
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
console.error('Validation errors:', errors.array());
|
||||
logger.error('Validation errors:', errors.array());
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
@@ -923,8 +924,7 @@ router.post('/', adminAuth, requirePermission('events.create'), [
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating event:', error);
|
||||
res.status(500).json({ error: 'Failed to create event' });
|
||||
errorResponse(res, error, 500, 'Failed to create event');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1022,8 +1022,7 @@ router.get('/', adminAuth, requirePermission('events.view'), async (req, res) =>
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching events:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch events' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch events');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1105,8 +1104,7 @@ router.get('/:id', adminAuth, requirePermission('events.view'), async (req, res)
|
||||
})),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error fetching event:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch event details' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch event details');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1264,8 +1262,7 @@ router.post('/:id/publish', adminAuth, requirePermission('events.edit'), require
|
||||
|
||||
res.json({ message: 'Event published successfully', is_draft: false });
|
||||
} catch (error) {
|
||||
logger.error('Error publishing event:', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to publish event' });
|
||||
errorResponse(res, error, 500, 'Failed to publish event');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1451,8 +1448,7 @@ router.post('/:id/duplicate', adminAuth, requirePermission('events.create'), req
|
||||
is_draft: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error duplicating event:', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to duplicate event' });
|
||||
errorResponse(res, error, 500, 'Failed to duplicate event');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1831,8 +1827,7 @@ router.put('/:id', adminAuth, requirePermission('events.edit'), requireEventOwne
|
||||
|
||||
res.json({ message: 'Event updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error updating event:', error);
|
||||
res.status(500).json({ error: 'Failed to update event' });
|
||||
errorResponse(res, error, 500, 'Failed to update event');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1891,8 +1886,7 @@ router.post('/:id/toggle-status', adminAuth, requirePermission('events.edit'), r
|
||||
is_active: newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error toggling event status:', error);
|
||||
res.status(500).json({ error: 'Failed to toggle event status' });
|
||||
errorResponse(res, error, 500, 'Failed to toggle event status');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1958,8 +1952,7 @@ router.post('/:id/slideshow/generate', adminAuth, requirePermission('events.edit
|
||||
slideshow_url: await buildSlideshowUrl(event.slug, token)
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error generating slideshow link', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to generate slideshow link' });
|
||||
errorResponse(res, error, 500, 'Failed to generate slideshow link');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1984,8 +1977,7 @@ router.post('/:id/slideshow/disable', adminAuth, requirePermission('events.edit'
|
||||
|
||||
res.json({ show_share_token: null });
|
||||
} catch (error) {
|
||||
logger.error('Error disabling slideshow link', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to disable slideshow link' });
|
||||
errorResponse(res, error, 500, 'Failed to disable slideshow link');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2036,8 +2028,7 @@ router.patch('/:id/slideshow', adminAuth, requirePermission('events.edit'), requ
|
||||
show_colorfilter: updates.show_colorfilter ?? event.show_colorfilter ?? 'none'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error updating slideshow settings', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to update slideshow settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update slideshow settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2125,8 +2116,7 @@ router.post('/:id/reset-password', adminAuth, requirePermission('events.edit'),
|
||||
emailSent: sendEmail
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error resetting password:', error);
|
||||
res.status(500).json({ error: 'Failed to reset password' });
|
||||
errorResponse(res, error, 500, 'Failed to reset password');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2202,7 +2192,7 @@ router.post('/:id/resend-email', adminAuth, requirePermission('events.edit'), re
|
||||
name: req.admin.username
|
||||
});
|
||||
} catch (logError) {
|
||||
console.error('Warning: Failed to log activity:', logError);
|
||||
logger.error('Warning: Failed to log activity:', logError);
|
||||
// Don't fail the request if activity logging fails
|
||||
}
|
||||
|
||||
@@ -2211,9 +2201,8 @@ router.post('/:id/resend-email', adminAuth, requirePermission('events.edit'), re
|
||||
message: 'Creation email has been queued for sending'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error resending creation email:', error);
|
||||
console.error('Stack trace:', error.stack);
|
||||
res.status(500).json({ error: 'Failed to resend creation email' });
|
||||
logger.error('Error resending creation email:', error);
|
||||
errorResponse(res, error, 500, 'Failed to resend creation email');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2243,8 +2232,7 @@ router.post('/:id/archive', adminAuth, requirePermission('events.archive'), requ
|
||||
|
||||
res.json({ message: 'Event archived successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error archiving event:', error);
|
||||
res.status(500).json({ error: 'Failed to archive event' });
|
||||
errorResponse(res, error, 500, 'Failed to archive event');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2297,7 +2285,7 @@ router.post('/bulk-archive', adminAuth, requirePermission('events.archive'), [
|
||||
name: event.event_name
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to archive event ${event.id}:`, error);
|
||||
logger.error(`Failed to archive event ${event.id}:`, error);
|
||||
results.failed.push({
|
||||
id: event.id,
|
||||
name: event.event_name,
|
||||
@@ -2322,8 +2310,7 @@ router.post('/bulk-archive', adminAuth, requirePermission('events.archive'), [
|
||||
results
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in bulk archive:', error);
|
||||
res.status(500).json({ error: 'Failed to perform bulk archive' });
|
||||
errorResponse(res, error, 500, 'Failed to perform bulk archive');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2391,8 +2378,7 @@ router.post('/bulk-delete', adminAuth, requirePermission('events.delete'), [
|
||||
results
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error in bulk delete', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to perform bulk delete' });
|
||||
errorResponse(res, error, 500, 'Failed to perform bulk delete');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2446,8 +2432,7 @@ router.post('/:id/logo', adminAuth, requirePermission('events.edit'), requireEve
|
||||
hero_logo_url: logoUrl
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error uploading event logo:', { error: error.message, eventId: req.params.id });
|
||||
res.status(500).json({ error: 'Failed to upload event logo' });
|
||||
errorResponse(res, error, 500, 'Failed to upload event logo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2490,8 +2475,7 @@ router.delete('/:id/logo', adminAuth, requirePermission('events.edit'), requireE
|
||||
|
||||
res.json({ message: 'Event logo removed successfully' });
|
||||
} catch (error) {
|
||||
logger.error('Error deleting event logo:', { error: error.message, eventId: req.params.id });
|
||||
res.status(500).json({ error: 'Failed to delete event logo' });
|
||||
errorResponse(res, error, 500, 'Failed to delete event logo');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ const { requirePermission } = require('../middleware/permissions');
|
||||
const { requireEventOwnership } = require('../middleware/ownership');
|
||||
const feedbackService = require('../services/feedbackService');
|
||||
const logger = require('../utils/logger');
|
||||
const { errorResponse } = require('../utils/routeHelpers');
|
||||
|
||||
const FRONTEND_URL = process.env.FRONTEND_URL || '';
|
||||
|
||||
@@ -94,8 +95,7 @@ router.get(
|
||||
|
||||
res.json({ guests });
|
||||
} catch (error) {
|
||||
logger.error('Error listing guests:', error);
|
||||
res.status(500).json({ error: 'Failed to list guests' });
|
||||
errorResponse(res, error, 500, 'Failed to list guests');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -144,8 +144,7 @@ router.get(
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching aggregate view:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch aggregate view' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch aggregate view');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -196,8 +195,7 @@ router.get(
|
||||
|
||||
res.json({ invites });
|
||||
} catch (error) {
|
||||
logger.error('Error listing invites:', error);
|
||||
res.status(500).json({ error: 'Failed to list invites' });
|
||||
errorResponse(res, error, 500, 'Failed to list invites');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -266,8 +264,7 @@ router.post(
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error creating invite:', error);
|
||||
res.status(500).json({ error: 'Failed to create invite' });
|
||||
errorResponse(res, error, 500, 'Failed to create invite');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -302,8 +299,7 @@ router.delete(
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Error revoking invite:', error);
|
||||
res.status(500).json({ error: 'Failed to revoke invite' });
|
||||
errorResponse(res, error, 500, 'Failed to revoke invite');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -457,8 +453,7 @@ router.get(
|
||||
selections,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching guest detail:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch guest detail' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch guest detail');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -509,8 +504,7 @@ router.get(
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
return res.send(selections.map((s) => s.original_filename || s.filename).join('\n'));
|
||||
} catch (error) {
|
||||
logger.error('Error exporting guest:', error);
|
||||
res.status(500).json({ error: 'Failed to export guest' });
|
||||
errorResponse(res, error, 500, 'Failed to export guest');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -548,8 +542,7 @@ router.delete(
|
||||
|
||||
res.json({ success: true, ...result });
|
||||
} catch (error) {
|
||||
logger.error('Error deleting guest:', error);
|
||||
res.status(500).json({ error: 'Failed to delete guest' });
|
||||
errorResponse(res, error, 500, 'Failed to delete guest');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -600,8 +593,7 @@ router.post(
|
||||
|
||||
res.json({ success: true, ...result });
|
||||
} catch (error) {
|
||||
logger.error('Error merging guests:', error);
|
||||
res.status(500).json({ error: 'Failed to merge guests' });
|
||||
errorResponse(res, error, 500, 'Failed to merge guests');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require('express');
|
||||
const { db, logActivity } = require('../database/db');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
// Get notifications (unread activity logs)
|
||||
@@ -39,7 +40,7 @@ router.get('/', adminAuth, requirePermission('settings.view'), async (req, res)
|
||||
if (typeof notification.metadata === 'object') return notification.metadata;
|
||||
return JSON.parse(notification.metadata);
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse metadata for notification:', notification.id, e.message);
|
||||
logger.warn('Failed to parse metadata for notification:', notification.id, e.message);
|
||||
return {};
|
||||
}
|
||||
})(),
|
||||
@@ -59,7 +60,7 @@ router.get('/', adminAuth, requirePermission('settings.view'), async (req, res)
|
||||
unreadCount: unreadCount.count || 0
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Notifications fetch error:', error);
|
||||
logger.error('Notifications fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch notifications' });
|
||||
}
|
||||
});
|
||||
@@ -77,7 +78,7 @@ router.put('/:id/read', adminAuth, requirePermission('settings.edit'), async (re
|
||||
|
||||
res.json({ message: 'Notification marked as read' });
|
||||
} catch (error) {
|
||||
console.error('Mark notification read error:', error);
|
||||
logger.error('Mark notification read error:', error);
|
||||
res.status(500).json({ error: 'Failed to mark notification as read' });
|
||||
}
|
||||
});
|
||||
@@ -93,7 +94,7 @@ router.put('/read-all', adminAuth, requirePermission('settings.edit'), async (re
|
||||
|
||||
res.json({ message: 'All notifications marked as read' });
|
||||
} catch (error) {
|
||||
console.error('Mark all notifications read error:', error);
|
||||
logger.error('Mark all notifications read error:', error);
|
||||
res.status(500).json({ error: 'Failed to mark all notifications as read' });
|
||||
}
|
||||
});
|
||||
@@ -112,7 +113,7 @@ router.delete('/clear-all', adminAuth, requirePermission('settings.edit'), async
|
||||
const deletedCount = await db('activity_logs').delete();
|
||||
res.json({ message: 'All notifications cleared', deletedCount });
|
||||
} catch (error) {
|
||||
console.error('Clear notifications error:', error);
|
||||
logger.error('Clear notifications error:', error);
|
||||
res.status(500).json({ error: 'Failed to clear notifications' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,7 +11,9 @@ const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const { requireEventOwnership } = require('../middleware/ownership');
|
||||
const { PhotoFilterBuilder } = require('../utils/photoFilterBuilder');
|
||||
const { getPagination } = require('../utils/routeHelpers');
|
||||
const { PhotoExportService } = require('../services/photoExportService');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const exportService = new PhotoExportService();
|
||||
|
||||
@@ -66,8 +68,7 @@ router.get('/:eventId/filtered', adminAuth, requirePermission('photos.view'), re
|
||||
|
||||
const sort = req.query.sort || 'date';
|
||||
const order = req.query.order || 'desc';
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 50;
|
||||
const { page, limit } = getPagination(req, { limit: 50 });
|
||||
|
||||
// Build filtered query
|
||||
const filterBuilder = new PhotoFilterBuilder(
|
||||
@@ -124,7 +125,7 @@ router.get('/:eventId/filtered', adminAuth, requirePermission('photos.view'), re
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Filter photos error:', error);
|
||||
logger.error('Filter photos error:', error);
|
||||
res.status(500).json({ error: 'Failed to filter photos' });
|
||||
}
|
||||
});
|
||||
@@ -146,7 +147,7 @@ router.get('/:eventId/filter-summary', adminAuth, requirePermission('photos.view
|
||||
data: summary
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Filter summary error:', error);
|
||||
logger.error('Filter summary error:', error);
|
||||
res.status(500).json({ error: 'Failed to get filter summary' });
|
||||
}
|
||||
});
|
||||
@@ -206,7 +207,7 @@ router.post('/:eventId/export', adminAuth, requirePermission('photos.download'),
|
||||
res.send(result.content);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Export photos error:', error);
|
||||
logger.error('Export photos error:', error);
|
||||
res.status(500).json({ error: error.message || 'Failed to export photos' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -22,6 +22,8 @@ const downloadZipService = require('../services/downloadZipService');
|
||||
const { findReplacementCandidate, replacePhoto } = require('../services/photoReplacementService');
|
||||
const { requireEventOwnership } = require('../middleware/ownership');
|
||||
const { getStorage } = require('../services/storage');
|
||||
const { errorResponse } = require('../utils/routeHelpers');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
// Get storage path from environment or default
|
||||
@@ -31,7 +33,7 @@ const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '.
|
||||
// IMPORTANT: Using synchronous functions to prevent file corruption
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
console.log('Multer destination called for file:', file.originalname);
|
||||
logger.info('Multer destination called for file:', file.originalname);
|
||||
const { eventId } = req.params;
|
||||
|
||||
// We'll validate the event exists in the route handler
|
||||
@@ -40,7 +42,7 @@ const storage = multer.diskStorage({
|
||||
|
||||
// Create directory synchronously
|
||||
require('fs').mkdirSync(tempPath, { recursive: true });
|
||||
console.log('Temp destination path:', tempPath);
|
||||
logger.info('Temp destination path:', tempPath);
|
||||
|
||||
// Store temp path for cleanup
|
||||
req.tempUploadPath = tempPath;
|
||||
@@ -48,10 +50,10 @@ const storage = multer.diskStorage({
|
||||
cb(null, tempPath);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
console.log('Multer filename called for file:', file.originalname);
|
||||
logger.info('Multer filename called for file:', file.originalname);
|
||||
// Use a simple temporary filename
|
||||
const tempName = `temp_${Date.now()}_${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
|
||||
console.log('Temp filename:', tempName);
|
||||
logger.info('Temp filename:', tempName);
|
||||
cb(null, tempName);
|
||||
}
|
||||
});
|
||||
@@ -89,7 +91,7 @@ const resolveAllowedTypes = async (req, res, next) => {
|
||||
try {
|
||||
req.allowedMimeTypes = await getAllowedMimeTypes();
|
||||
} catch (error) {
|
||||
console.error('Failed to resolve allowed MIME types:', error);
|
||||
logger.error('Failed to resolve allowed MIME types:', error);
|
||||
req.allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
|
||||
}
|
||||
next();
|
||||
@@ -111,7 +113,7 @@ const uploadTimeout = (timeout = 300000) => { // 5 minutes default
|
||||
return (req, res, next) => {
|
||||
// Set timeout for the request
|
||||
req.setTimeout(timeout, () => {
|
||||
console.error('Upload request timed out');
|
||||
logger.error('Upload request timed out');
|
||||
if (!res.headersSent) {
|
||||
res.status(408).json({ error: 'Upload request timed out' });
|
||||
}
|
||||
@@ -119,7 +121,7 @@ const uploadTimeout = (timeout = 300000) => { // 5 minutes default
|
||||
|
||||
// Set response timeout as well
|
||||
res.setTimeout(timeout, () => {
|
||||
console.error('Upload response timed out');
|
||||
logger.error('Upload response timed out');
|
||||
});
|
||||
|
||||
next();
|
||||
@@ -133,13 +135,12 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), r
|
||||
try {
|
||||
maxFilesPerUpload = await getMaxFilesPerUpload();
|
||||
} catch (error) {
|
||||
console.error('Failed to resolve max files per upload:', error);
|
||||
return res.status(500).json({ error: 'Unable to determine upload limits' });
|
||||
return errorResponse(res, error, 500, 'Unable to determine upload limits');
|
||||
}
|
||||
|
||||
upload.array('photos', maxFilesPerUpload)(req, res, (err) => {
|
||||
if (err) {
|
||||
console.error('Multer error:', err);
|
||||
logger.error('Multer error:', err);
|
||||
if (err instanceof multer.MulterError) {
|
||||
if (err.code === 'LIMIT_FILE_SIZE') {
|
||||
return res.status(400).json({ error: 'File too large. Maximum size is 10GB per file.' });
|
||||
@@ -166,7 +167,7 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), r
|
||||
try {
|
||||
await fs.rm(req.tempUploadPath, { recursive: true, force: true });
|
||||
} catch (e) {
|
||||
console.error('Failed to clean up temp upload directory:', e);
|
||||
logger.error('Failed to clean up temp upload directory:', e);
|
||||
}
|
||||
};
|
||||
res.on('finish', cleanupTempDir);
|
||||
@@ -177,16 +178,16 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), r
|
||||
const { category_id, replace_by_name } = req.body;
|
||||
const replaceByName = replace_by_name === 'true' || replace_by_name === true;
|
||||
|
||||
console.log('Upload request received for event:', eventId);
|
||||
console.log('Body:', req.body);
|
||||
console.log('Files:', req.files ? req.files.length : 'none');
|
||||
console.log('File details:', req.files?.map(f => ({ name: f.originalname, size: f.size, mimetype: f.mimetype })));
|
||||
console.log('Category ID received:', category_id);
|
||||
logger.info('Upload request received for event:', eventId);
|
||||
logger.info('Body:', req.body);
|
||||
logger.info('Files:', req.files ? req.files.length : 'none');
|
||||
logger.info('File details:', req.files?.map(f => ({ name: f.originalname, size: f.size, mimetype: f.mimetype })));
|
||||
logger.info('Category ID received:', category_id);
|
||||
|
||||
// Verify event exists and admin has access
|
||||
const event = await db('events').where({ id: eventId }).first();
|
||||
if (!event) {
|
||||
console.error('Event not found:', eventId);
|
||||
logger.error('Event not found:', eventId);
|
||||
return res.status(404).json({ error: 'Event not found' });
|
||||
}
|
||||
|
||||
@@ -213,8 +214,8 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), r
|
||||
}
|
||||
|
||||
if (!req.files || req.files.length === 0) {
|
||||
console.error('No files in request. req.files:', req.files);
|
||||
console.error('Request body keys:', Object.keys(req.body));
|
||||
logger.error('No files in request. req.files:', req.files);
|
||||
logger.error('Request body keys:', Object.keys(req.body));
|
||||
return res.status(400).json({ error: 'No files uploaded' });
|
||||
}
|
||||
|
||||
@@ -389,7 +390,7 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), r
|
||||
category_id: parsedCategoryId,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error queuing file ${file.originalname}:`, err);
|
||||
logger.error(`Error queuing file ${file.originalname}:`, err);
|
||||
errors.push({ filename: file.originalname, error: err.message });
|
||||
}
|
||||
}
|
||||
@@ -451,10 +452,9 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), r
|
||||
// 202 Accepted — files stored, processing happens in background.
|
||||
res.status(202).json(response);
|
||||
} catch (error) {
|
||||
console.error('Error uploading photos:', error);
|
||||
// Temp directory cleanup is handled by the response finish/close
|
||||
// listeners above, regardless of which exit path fires.
|
||||
res.status(500).json({ error: 'Failed to upload photos' });
|
||||
errorResponse(res, error, 500, 'Failed to upload photos');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -523,8 +523,7 @@ router.get(
|
||||
...summariseUpload(group.photos),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error reading upload status:', error);
|
||||
res.status(500).json({ error: 'Failed to read upload status' });
|
||||
errorResponse(res, error, 500, 'Failed to read upload status');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -575,7 +574,7 @@ router.get(
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Upload stream poll error:', e);
|
||||
logger.error('Upload stream poll error:', e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -622,8 +621,7 @@ router.post(
|
||||
});
|
||||
res.json({ id: photo.id, status: 'pending' });
|
||||
} catch (error) {
|
||||
console.error('Error retrying photo processing:', error);
|
||||
res.status(500).json({ error: 'Failed to retry photo processing' });
|
||||
errorResponse(res, error, 500, 'Failed to retry photo processing');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -651,7 +649,7 @@ router.delete('/:eventId/photos/:photoId', adminAuth, requirePermission('photos.
|
||||
const originalKey = resolvePhotoStorageKey(event, photo);
|
||||
if (originalKey) await storage.delete(originalKey);
|
||||
} catch (error) {
|
||||
console.error('Error deleting photo file:', error);
|
||||
logger.error('Error deleting photo file:', error);
|
||||
}
|
||||
|
||||
// photo.thumbnail_path is stored as the canonical storage key
|
||||
@@ -660,7 +658,7 @@ router.delete('/:eventId/photos/:photoId', adminAuth, requirePermission('photos.
|
||||
try {
|
||||
await storage.delete(photo.thumbnail_path);
|
||||
} catch (error) {
|
||||
console.error('Error deleting thumbnail:', error);
|
||||
logger.error('Error deleting thumbnail:', error);
|
||||
}
|
||||
}
|
||||
if (photo.hero_path) {
|
||||
@@ -700,8 +698,7 @@ router.delete('/:eventId/photos/:photoId', adminAuth, requirePermission('photos.
|
||||
downloadZipService.invalidate(parseInt(eventId));
|
||||
res.json({ message: 'Photo deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting photo:', error);
|
||||
res.status(500).json({ error: 'Failed to delete photo' });
|
||||
errorResponse(res, error, 500, 'Failed to delete photo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -763,8 +760,7 @@ router.patch('/:eventId/photos/:photoId', adminAuth, requirePermission('photos.e
|
||||
photo: updatedPhoto
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating photo:', error);
|
||||
res.status(500).json({ error: 'Failed to update photo' });
|
||||
errorResponse(res, error, 500, 'Failed to update photo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -797,7 +793,7 @@ router.post('/:eventId/photos/bulk-delete', adminAuth, requirePermission('photos
|
||||
const originalKey = resolvePhotoStorageKey(event, photo);
|
||||
if (originalKey) await storage.delete(originalKey);
|
||||
} catch (error) {
|
||||
console.error('Error deleting photo file:', error);
|
||||
logger.error('Error deleting photo file:', error);
|
||||
}
|
||||
|
||||
if (photo.thumbnail_path) {
|
||||
@@ -842,8 +838,7 @@ router.post('/:eventId/photos/bulk-delete', adminAuth, requirePermission('photos
|
||||
downloadZipService.invalidate(parseInt(eventId));
|
||||
res.json({ message: `${photos.length} photos deleted successfully` });
|
||||
} catch (error) {
|
||||
console.error('Error bulk deleting photos:', error);
|
||||
res.status(500).json({ error: 'Failed to delete photos' });
|
||||
errorResponse(res, error, 500, 'Failed to delete photos');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -905,8 +900,7 @@ router.post('/:eventId/photos/bulk-update', adminAuth, requirePermission('photos
|
||||
|
||||
res.json({ message: `${photoIds.length} photos updated successfully` });
|
||||
} catch (error) {
|
||||
console.error('Error bulk updating photos:', error);
|
||||
res.status(500).json({ error: 'Failed to update photos' });
|
||||
errorResponse(res, error, 500, 'Failed to update photos');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -961,8 +955,7 @@ router.get('/:eventId/photos/:photoId/download', adminAuth, requirePermission('p
|
||||
});
|
||||
res.sendFile(filePath);
|
||||
} catch (error) {
|
||||
console.error('Error downloading photo:', error);
|
||||
res.status(500).json({ error: 'Failed to download photo' });
|
||||
errorResponse(res, error, 500, 'Failed to download photo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1096,8 +1089,7 @@ router.get('/:eventId/photos', adminAuth, requirePermission('photos.view'), requ
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching photos:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch photos' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch photos');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1153,8 +1145,7 @@ router.get('/:eventId/photo/:photoId', adminAuth, requirePermission('photos.view
|
||||
}
|
||||
res.sendFile(path.resolve(filePath));
|
||||
} catch (error) {
|
||||
console.error('Error serving photo:', error);
|
||||
res.status(500).json({ error: 'Failed to serve photo' });
|
||||
errorResponse(res, error, 500, 'Failed to serve photo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1168,7 +1159,7 @@ router.get('/:eventId/thumbnail/:photoId', adminAuth, requirePermission('photos.
|
||||
.first();
|
||||
|
||||
if (!photo) {
|
||||
console.error(`Photo not found: ${photoId}, event ${eventId}`);
|
||||
logger.error(`Photo not found: ${photoId}, event ${eventId}`);
|
||||
return res.status(404).json({ error: 'Photo not found' });
|
||||
}
|
||||
|
||||
@@ -1192,7 +1183,7 @@ router.get('/:eventId/thumbnail/:photoId', adminAuth, requirePermission('photos.
|
||||
const thumbnailPath = await ensureThumbnail(photo);
|
||||
|
||||
if (!thumbnailPath) {
|
||||
console.error(`Failed to generate thumbnail for photo ${photoId}`);
|
||||
logger.error(`Failed to generate thumbnail for photo ${photoId}`);
|
||||
return res.status(404).json({ error: 'Thumbnail generation failed' });
|
||||
}
|
||||
|
||||
@@ -1209,10 +1200,9 @@ router.get('/:eventId/thumbnail/:photoId', adminAuth, requirePermission('photos.
|
||||
const stream = await storage.get(thumbnailPath);
|
||||
stream.pipe(res);
|
||||
} catch (error) {
|
||||
console.error('Error serving thumbnail:', error);
|
||||
console.error('Photo ID:', req.params.photoId);
|
||||
console.error('Event ID:', req.params.eventId);
|
||||
res.status(500).json({ error: 'Failed to serve thumbnail' });
|
||||
logger.error('Error serving thumbnail:', error);
|
||||
logger.error('Photo ID:', req.params.photoId);
|
||||
errorResponse(res, error, 500, 'Failed to serve thumbnail');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1232,8 +1222,7 @@ router.get('/:eventId/debug', adminAuth, requirePermission('photos.view'), requi
|
||||
storagePath: getStoragePath()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching admin photo debug data:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch photo debug data' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch photo debug data');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1275,8 +1264,7 @@ router.post('/:eventId/chunked-upload/init', adminAuth, requirePermission('photo
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error initializing chunked upload:', error);
|
||||
res.status(500).json({ error: 'Failed to initialize upload' });
|
||||
errorResponse(res, error, 500, 'Failed to initialize upload');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1296,7 +1284,7 @@ router.post('/:eventId/chunked-upload/:uploadId/chunk/:chunkIndex', adminAuth, r
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error uploading chunk:', error);
|
||||
logger.error('Error uploading chunk:', error);
|
||||
res.status(500).json({ error: error.message || 'Failed to upload chunk' });
|
||||
}
|
||||
});
|
||||
@@ -1329,7 +1317,7 @@ router.post('/:eventId/chunked-upload/:uploadId/complete', adminAuth, requirePer
|
||||
try {
|
||||
await fs.rm(mergedFile.tempDir, { recursive: true, force: true });
|
||||
} catch (cleanupErr) {
|
||||
console.warn('Failed to clean up temp directory:', cleanupErr.message);
|
||||
logger.warn('Failed to clean up temp directory:', cleanupErr.message);
|
||||
}
|
||||
|
||||
res.json({
|
||||
@@ -1338,7 +1326,7 @@ router.post('/:eventId/chunked-upload/:uploadId/complete', adminAuth, requirePer
|
||||
photos: uploadedPhotos
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error completing chunked upload:', error);
|
||||
logger.error('Error completing chunked upload:', error);
|
||||
res.status(500).json({ error: error.message || 'Failed to complete upload' });
|
||||
}
|
||||
});
|
||||
@@ -1356,8 +1344,7 @@ router.get('/:eventId/chunked-upload/:uploadId/status', adminAuth, requirePermis
|
||||
|
||||
res.json(status);
|
||||
} catch (error) {
|
||||
console.error('Error getting upload status:', error);
|
||||
res.status(500).json({ error: 'Failed to get upload status' });
|
||||
errorResponse(res, error, 500, 'Failed to get upload status');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1370,8 +1357,7 @@ router.delete('/:eventId/chunked-upload/:uploadId', adminAuth, requirePermission
|
||||
|
||||
res.json({ success: true, message: 'Upload aborted' });
|
||||
} catch (error) {
|
||||
console.error('Error aborting upload:', error);
|
||||
res.status(500).json({ error: 'Failed to abort upload' });
|
||||
errorResponse(res, error, 500, 'Failed to abort upload');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ const { adminAuth } = require('../middleware/auth');
|
||||
const { requirePermission } = require('../middleware/permissions');
|
||||
const { body, query, validationResult } = require('express-validator');
|
||||
const logger = require('../utils/logger');
|
||||
const { getPagination } = require('../utils/routeHelpers');
|
||||
const { db } = require('../database/db');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
@@ -48,7 +49,7 @@ function transformS3Config(body) {
|
||||
*/
|
||||
router.get('/status', requirePermission('backup.view'), async (req, res) => {
|
||||
try {
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
const { limit } = getPagination(req, { limit: 10 });
|
||||
const history = await restoreService.getRestoreHistory(limit);
|
||||
|
||||
const status = {
|
||||
|
||||
@@ -22,6 +22,8 @@ const { sanitizeCss } = require('../utils/cssSanitizer');
|
||||
const { upsertAppSetting } = require('../utils/appSettings');
|
||||
const { clearShareLinkSettingsCache } = require('../services/shareLinkService');
|
||||
const { resetSecurityConfigCache } = require('../utils/authSecurity');
|
||||
const { errorResponse } = require('../utils/routeHelpers');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
const { clearMaxFilesPerUploadCache, MAX_ALLOWED_FILES_PER_UPLOAD } = require('../services/uploadSettings');
|
||||
const watermarkService = require('../services/watermarkService');
|
||||
@@ -158,8 +160,7 @@ router.get('/', adminAuth, requirePermission('settings.view'), async (req, res)
|
||||
|
||||
res.json(settingsObject);
|
||||
} catch (error) {
|
||||
console.error('Settings fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch settings' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -200,8 +201,7 @@ router.get('/customer-surface', adminAuth, requirePermission('settings.view'), a
|
||||
|
||||
res.json(settings);
|
||||
} catch (error) {
|
||||
console.error('Customer surface settings fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch customer surface settings' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch customer surface settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -231,8 +231,7 @@ router.put('/customer-surface', adminAuth, requirePermission('settings.edit'), a
|
||||
|
||||
res.json({ message: 'Customer surface settings updated', updated: updates.map((u) => u.setting_key) });
|
||||
} catch (error) {
|
||||
console.error('Customer surface settings save error:', error);
|
||||
res.status(500).json({ error: 'Failed to save customer surface settings' });
|
||||
errorResponse(res, error, 500, 'Failed to save customer surface settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -295,8 +294,7 @@ router.put('/accounting', adminAuth, requirePermission('settings.edit'), async (
|
||||
}
|
||||
res.json({ message: 'Accounting settings updated', updated: updates.map((u) => u.setting_key) });
|
||||
} catch (error) {
|
||||
console.error('Accounting settings save error:', error);
|
||||
res.status(500).json({ error: 'Failed to save accounting settings' });
|
||||
errorResponse(res, error, 500, 'Failed to save accounting settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -360,8 +358,7 @@ router.put('/slideshow', adminAuth, requirePermission('settings.edit'), async (r
|
||||
require('../utils/slideshowGlobals').invalidateSlideshowGlobals();
|
||||
res.json({ message: 'Slideshow settings updated', updated: updates.map((u) => u.setting_key) });
|
||||
} catch (error) {
|
||||
console.error('Slideshow settings save error:', error);
|
||||
res.status(500).json({ error: 'Failed to save slideshow settings' });
|
||||
errorResponse(res, error, 500, 'Failed to save slideshow settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -413,8 +410,7 @@ router.get('/:type', adminAuth, requirePermission('settings.view'), async (req,
|
||||
|
||||
res.json(settingsObject);
|
||||
} catch (error) {
|
||||
console.error('Settings fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch settings' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -434,8 +430,7 @@ router.get('/password/complexity', adminAuth, requirePermission('settings.view')
|
||||
config
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Password complexity settings fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch password complexity settings' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch password complexity settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -577,9 +572,9 @@ router.put('/branding', adminAuth, requirePermission('settings.edit'), async (re
|
||||
const faviconPath = path.join(getStoragePath(), relativePath);
|
||||
try {
|
||||
await fs.unlink(faviconPath);
|
||||
console.log('Deleted favicon file:', faviconPath);
|
||||
logger.info('Deleted favicon file:', faviconPath);
|
||||
} catch (err) {
|
||||
console.error('Error deleting favicon file:', err);
|
||||
logger.error('Error deleting favicon file:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,9 +603,9 @@ router.put('/branding', adminAuth, requirePermission('settings.edit'), async (re
|
||||
const logoPath = path.join(getStoragePath(), relativePath);
|
||||
try {
|
||||
await fs.unlink(logoPath);
|
||||
console.log('Deleted logo file:', logoPath);
|
||||
logger.info('Deleted logo file:', logoPath);
|
||||
} catch (err) {
|
||||
console.error('Error deleting logo file:', err);
|
||||
logger.error('Error deleting logo file:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -656,20 +651,20 @@ router.put('/branding', adminAuth, requirePermission('settings.edit'), async (re
|
||||
|
||||
if (currentSettings && currentSettings.enabled) {
|
||||
// Start background regeneration of all watermarks
|
||||
console.log('Watermark settings changed, starting background regeneration');
|
||||
logger.info('Watermark settings changed, starting background regeneration');
|
||||
watermarkGeneratorService.regenerateAll()
|
||||
.then(result => {
|
||||
console.log(`Watermark regeneration completed: ${result.success}/${result.total} successful`);
|
||||
logger.info(`Watermark regeneration completed: ${result.success}/${result.total} successful`);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Watermark regeneration failed:', err);
|
||||
logger.error('Watermark regeneration failed:', err);
|
||||
});
|
||||
watermarkRegenerationStarted = true;
|
||||
} else {
|
||||
// Watermarking was disabled, clear all pre-generated watermarks
|
||||
console.log('Watermarking disabled, clearing pre-generated watermarks');
|
||||
logger.info('Watermarking disabled, clearing pre-generated watermarks');
|
||||
watermarkGeneratorService.clearAllWatermarks()
|
||||
.catch(err => console.error('Failed to clear watermarks:', err));
|
||||
.catch(err => logger.error('Failed to clear watermarks:', err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,8 +673,7 @@ router.put('/branding', adminAuth, requirePermission('settings.edit'), async (re
|
||||
watermarkRegenerationStarted
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Branding update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update branding settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update branding settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -711,7 +705,7 @@ router.post('/logo', adminAuth, requirePermission('settings.edit'), upload.singl
|
||||
}
|
||||
await fs.unlink(oldPath);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete old logo:', error);
|
||||
logger.error('Failed to delete old logo:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,8 +745,7 @@ router.post('/logo', adminAuth, requirePermission('settings.edit'), upload.singl
|
||||
logoUrl: publicPath
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Logo upload error:', error);
|
||||
res.status(500).json({ error: 'Failed to upload logo' });
|
||||
errorResponse(res, error, 500, 'Failed to upload logo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -772,7 +765,7 @@ router.delete('/logo', adminAuth, requirePermission('settings.edit'), async (req
|
||||
if (p.startsWith('"')) p = JSON.parse(p);
|
||||
await fs.unlink(p);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete logo file:', error);
|
||||
logger.error('Failed to delete logo file:', error);
|
||||
}
|
||||
}
|
||||
await db('app_settings')
|
||||
@@ -781,8 +774,7 @@ router.delete('/logo', adminAuth, requirePermission('settings.edit'), async (req
|
||||
|
||||
res.json({ message: 'Logo removed' });
|
||||
} catch (error) {
|
||||
console.error('Logo delete error:', error);
|
||||
res.status(500).json({ error: 'Failed to remove logo' });
|
||||
errorResponse(res, error, 500, 'Failed to remove logo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -812,7 +804,7 @@ router.post('/branding/watermark-logo', adminAuth, requirePermission('settings.e
|
||||
try {
|
||||
await fs.unlink(oldPath);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete old watermark logo:', error);
|
||||
logger.error('Failed to delete old watermark logo:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -854,13 +846,13 @@ router.post('/branding/watermark-logo', adminAuth, requirePermission('settings.e
|
||||
let watermarkRegenerationStarted = false;
|
||||
|
||||
if (currentSettings && currentSettings.enabled) {
|
||||
console.log('Watermark logo changed, starting background regeneration');
|
||||
logger.info('Watermark logo changed, starting background regeneration');
|
||||
watermarkGeneratorService.regenerateAll()
|
||||
.then(result => {
|
||||
console.log(`Watermark regeneration completed: ${result.success}/${result.total} successful`);
|
||||
logger.info(`Watermark regeneration completed: ${result.success}/${result.total} successful`);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Watermark regeneration failed:', err);
|
||||
logger.error('Watermark regeneration failed:', err);
|
||||
});
|
||||
watermarkRegenerationStarted = true;
|
||||
}
|
||||
@@ -871,8 +863,7 @@ router.post('/branding/watermark-logo', adminAuth, requirePermission('settings.e
|
||||
watermarkRegenerationStarted
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Watermark logo upload error:', error);
|
||||
res.status(500).json({ error: 'Failed to upload watermark logo' });
|
||||
errorResponse(res, error, 500, 'Failed to upload watermark logo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -908,8 +899,7 @@ router.put('/theme', adminAuth, requirePermission('settings.edit'), async (req,
|
||||
|
||||
res.json({ message: 'Theme settings updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Theme update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update theme settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update theme settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1005,7 +995,7 @@ router.put('/general', adminAuth, requirePermission('settings.edit'), async (req
|
||||
require('../services/downloadFilenameService').clearCache();
|
||||
require('../services/downloadZipService').invalidateAll();
|
||||
} catch (e) {
|
||||
console.warn('Failed to invalidate download caches after filename setting change:', e.message);
|
||||
logger.warn('Failed to invalidate download caches after filename setting change:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1020,8 +1010,7 @@ router.put('/general', adminAuth, requirePermission('settings.edit'), async (req
|
||||
|
||||
res.json({ message: 'General settings updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('General settings update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update general settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update general settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1059,8 +1048,7 @@ router.put('/security', adminAuth, requirePermission('settings.edit'), async (re
|
||||
|
||||
res.json({ message: 'Security settings updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Security settings update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update security settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update security settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1115,8 +1103,7 @@ router.put('/analytics', adminAuth, requirePermission('settings.edit'), async (r
|
||||
|
||||
res.json({ message: 'Analytics settings updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Analytics settings update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update analytics settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update analytics settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1179,8 +1166,7 @@ router.put('/seo', adminAuth, requirePermission('settings.edit'), async (req, re
|
||||
|
||||
res.json({ message: 'SEO settings updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('SEO settings update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update SEO settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update SEO settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1216,7 +1202,7 @@ router.get('/storage/info', adminAuth, requirePermission('settings.view'), async
|
||||
const stats = await fs.stat(fullArchivePath);
|
||||
archiveStorage += stats.size;
|
||||
} catch (error) {
|
||||
console.error('Archive file not found:', archive.archive_path, error.message);
|
||||
logger.error('Archive file not found:', archive.archive_path, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1234,7 +1220,7 @@ router.get('/storage/info', adminAuth, requirePermission('settings.view'), async
|
||||
rawDiskFree = Number(diskStats.bsize) * Number(diskStats.bfree);
|
||||
rawDiskAvailable = Number(diskStats.bsize) * Number(diskStats.bavail);
|
||||
} catch (diskError) {
|
||||
console.error('Disk stats error:', diskError.message);
|
||||
logger.error('Disk stats error:', diskError.message);
|
||||
}
|
||||
|
||||
const clampDiskValue = (value) => {
|
||||
@@ -1333,7 +1319,7 @@ router.get('/storage/info', adminAuth, requirePermission('settings.view'), async
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Storage settings read error:', error.message);
|
||||
logger.error('Storage settings read error:', error.message);
|
||||
}
|
||||
|
||||
const capacityOverrideEnv = parseEnvOverride('STORAGE_CAPACITY_OVERRIDE_BYTES', 'STORAGE_CAPACITY_OVERRIDE_GB');
|
||||
@@ -1407,8 +1393,7 @@ router.get('/storage/info', adminAuth, requirePermission('settings.view'), async
|
||||
disk_override_source: overrideSource
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Storage info error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch storage information' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch storage information');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1445,8 +1430,7 @@ router.post('/favicon', adminAuth, requirePermission('settings.edit'), faviconUp
|
||||
|
||||
res.json({ faviconUrl });
|
||||
} catch (error) {
|
||||
console.error('Error uploading favicon:', error);
|
||||
res.status(500).json({ error: 'Failed to upload favicon' });
|
||||
errorResponse(res, error, 500, 'Failed to upload favicon');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1509,8 +1493,7 @@ router.put('/security/rate-limit', adminAuth, requirePermission('settings.edit')
|
||||
|
||||
res.json({ message: 'Rate limit settings updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Rate limit settings update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update rate limit settings' });
|
||||
errorResponse(res, error, 500, 'Failed to update rate limit settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1530,8 +1513,7 @@ router.get('/public-site/default', adminAuth, requirePermission('settings.view')
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load public site defaults:', error);
|
||||
res.status(500).json({ error: 'Failed to load defaults' });
|
||||
errorResponse(res, error, 500, 'Failed to load defaults');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1584,8 +1566,7 @@ router.post('/public-site/reset', adminAuth, requirePermission('settings.edit'),
|
||||
branding: defaults.branding
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to reset public site template:', error);
|
||||
res.status(500).json({ error: 'Failed to reset template' });
|
||||
errorResponse(res, error, 500, 'Failed to reset template');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ router.get('/version', adminAuth, requirePermission('settings.view'), async (req
|
||||
const packageJson = JSON.parse(packageContent);
|
||||
backendVersion = packageJson.version || '1.0.0';
|
||||
} catch (err) {
|
||||
console.error('Could not read package.json:', err);
|
||||
logger.error('Could not read package.json:', err);
|
||||
}
|
||||
|
||||
const channel = getCurrentChannel(backendVersion);
|
||||
@@ -42,7 +42,7 @@ router.get('/version', adminAuth, requirePermission('settings.view'), async (req
|
||||
channel: channel
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching version:', error);
|
||||
logger.error('Error fetching version:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch version information' });
|
||||
}
|
||||
});
|
||||
@@ -231,7 +231,7 @@ router.get('/status', adminAuth, requirePermission('settings.view'), async (req,
|
||||
`, [dbName]);
|
||||
dbSize = result.rows[0]?.size || 0;
|
||||
} catch (error) {
|
||||
console.error('Error getting PostgreSQL database size:', error);
|
||||
logger.error('Error getting PostgreSQL database size:', error);
|
||||
}
|
||||
} else {
|
||||
// SQLite - check file size
|
||||
@@ -240,7 +240,7 @@ router.get('/status', adminAuth, requirePermission('settings.view'), async (req,
|
||||
const stats = await fs.stat(dbPath);
|
||||
dbSize = stats.size;
|
||||
} catch (error) {
|
||||
console.error('Error getting SQLite database size:', error);
|
||||
logger.error('Error getting SQLite database size:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ router.get('/status', adminAuth, requirePermission('settings.view'), async (req,
|
||||
const stats = await fs.stat(fullArchivePath);
|
||||
archiveStorage += stats.size;
|
||||
} catch (error) {
|
||||
console.error('Archive file not found:', archive.archive_path);
|
||||
logger.error('Archive file not found:', archive.archive_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,7 +345,7 @@ router.get('/status', adminAuth, requirePermission('settings.view'), async (req,
|
||||
|
||||
res.json(status);
|
||||
} catch (error) {
|
||||
console.error('Error fetching system status:', error);
|
||||
logger.error('Error fetching system status:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch system status' });
|
||||
}
|
||||
});
|
||||
@@ -407,7 +407,7 @@ router.get('/database', adminAuth, requirePermission('settings.view'), async (re
|
||||
timestamp: new Date()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching database info:', error);
|
||||
logger.error('Error fetching database info:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch database information' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ const {
|
||||
const { endSession } = require('../middleware/sessionTimeout');
|
||||
const { revokeToken } = require('../utils/tokenRevocation');
|
||||
const logger = require('../utils/logger');
|
||||
const { errorResponse } = require('../utils/routeHelpers');
|
||||
const {
|
||||
setAdminAuthCookie,
|
||||
clearAdminAuthCookie,
|
||||
@@ -132,8 +133,7 @@ router.post('/admin/login', [
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Login error:', error);
|
||||
res.status(500).json({ error: 'Login failed' });
|
||||
errorResponse(res, error, 500, 'Login failed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -175,8 +175,7 @@ router.post('/logout', async (req, res) => {
|
||||
|
||||
res.json({ message: 'Logged out successfully' });
|
||||
} catch (error) {
|
||||
logger.error('Logout error:', error);
|
||||
res.status(500).json({ error: 'Logout failed' });
|
||||
errorResponse(res, error, 500, 'Logout failed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -288,8 +287,7 @@ router.post('/gallery/verify', [
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Gallery verification error:', error);
|
||||
res.status(500).json({ error: 'Verification failed' });
|
||||
errorResponse(res, error, 500, 'Verification failed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -364,8 +362,7 @@ router.post('/gallery/:slug/client-login', [
|
||||
accessLevel: 'client'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Client login error:', error);
|
||||
res.status(500).json({ error: 'Authentication failed' });
|
||||
errorResponse(res, error, 500, 'Authentication failed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -451,8 +448,7 @@ router.post('/gallery/share-login', [
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Share link authentication error:', error);
|
||||
res.status(500).json({ error: 'Share link login failed' });
|
||||
errorResponse(res, error, 500, 'Share link login failed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -467,8 +463,7 @@ router.post('/gallery/logout', async (req, res) => {
|
||||
clearGalleryAuthCookies(res, slug);
|
||||
res.json({ message: 'Logged out successfully' });
|
||||
} catch (error) {
|
||||
logger.error('Gallery logout error:', error);
|
||||
res.status(500).json({ error: 'Logout failed' });
|
||||
errorResponse(res, error, 500, 'Logout failed');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -682,8 +677,7 @@ router.post('/admin/change-password', [
|
||||
score: passwordValidation.score
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Password change error:', error);
|
||||
res.status(500).json({ error: 'Failed to change password' });
|
||||
errorResponse(res, error, 500, 'Failed to change password');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ const { db, logActivity } = require('../database/db');
|
||||
const { formatBoolean } = require('../utils/dbCompat');
|
||||
const { getBcryptRounds } = require('../utils/passwordValidation');
|
||||
const logger = require('../utils/logger');
|
||||
const { errorResponse } = require('../utils/routeHelpers');
|
||||
const { getClientIp } = require('../utils/requestIp');
|
||||
const { customerAuth } = require('../middleware/customerAuth');
|
||||
const { setGalleryAuthCookies } = require('../utils/tokenUtils');
|
||||
@@ -117,8 +118,7 @@ router.get('/events', customerAuth, async (req, res) => {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Customer event list error:', error);
|
||||
res.status(500).json({ error: 'Failed to load events' });
|
||||
errorResponse(res, error, 500, 'Failed to load events');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -225,8 +225,7 @@ router.get('/events/:slug/access-token', [
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Customer access-token exchange error:', error);
|
||||
res.status(500).json({ error: 'Failed to issue access token' });
|
||||
errorResponse(res, error, 500, 'Failed to issue access token');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -248,8 +247,7 @@ router.get('/profile', customerAuth, async (req, res) => {
|
||||
}
|
||||
res.json({ profile: shapeProfile(row) });
|
||||
} catch (error) {
|
||||
logger.error('Customer profile read error:', error);
|
||||
res.status(500).json({ error: 'Failed to load profile' });
|
||||
errorResponse(res, error, 500, 'Failed to load profile');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -316,8 +314,7 @@ router.put('/profile', [
|
||||
|
||||
res.json({ profile: shapeProfile(row) });
|
||||
} catch (error) {
|
||||
logger.error('Customer profile update error:', error);
|
||||
res.status(500).json({ error: 'Failed to update profile' });
|
||||
errorResponse(res, error, 500, 'Failed to update profile');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -376,8 +373,7 @@ router.post('/profile/password', [
|
||||
|
||||
res.json({ message: 'Password updated' });
|
||||
} catch (error) {
|
||||
logger.error('Customer password change error:', error);
|
||||
res.status(500).json({ error: 'Failed to change password' });
|
||||
errorResponse(res, error, 500, 'Failed to change password');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -457,8 +453,7 @@ router.get('/quotes', customerAuth, async (req, res) => {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Customer quotes list error:', error);
|
||||
res.status(500).json({ error: 'Failed to load quotes' });
|
||||
errorResponse(res, error, 500, 'Failed to load quotes');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -545,8 +540,7 @@ router.get('/invoices', customerAuth, async (req, res) => {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Customer invoice list error:', error);
|
||||
res.status(500).json({ error: 'Failed to load invoices' });
|
||||
errorResponse(res, error, 500, 'Failed to load invoices');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -582,8 +576,7 @@ router.get('/quotes/:id/pdf', customerAuth, async (req, res) => {
|
||||
res.set('Content-Disposition', `inline; filename="${filename}"`);
|
||||
res.send(buf);
|
||||
} catch (error) {
|
||||
logger.error('Customer quote PDF error:', error);
|
||||
res.status(500).json({ error: 'Failed to render quote PDF' });
|
||||
errorResponse(res, error, 500, 'Failed to render quote PDF');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -614,8 +607,7 @@ router.get('/invoices/:id/pdf', customerAuth, async (req, res) => {
|
||||
res.set('Content-Disposition', `inline; filename="${filename}"`);
|
||||
res.send(buf);
|
||||
} catch (error) {
|
||||
logger.error('Customer invoice PDF error:', error);
|
||||
res.status(500).json({ error: 'Failed to render invoice PDF' });
|
||||
errorResponse(res, error, 500, 'Failed to render invoice PDF');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -679,8 +671,7 @@ router.get('/contracts', customerAuth, async (req, res) => {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Customer contracts list error:', error);
|
||||
res.status(500).json({ error: 'Failed to load contracts' });
|
||||
errorResponse(res, error, 500, 'Failed to load contracts');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -717,8 +708,7 @@ router.get('/contracts/:id/pdf', customerAuth, async (req, res) => {
|
||||
res.set('Content-Disposition', `inline; filename="${path.basename(filePath)}"`);
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
} catch (error) {
|
||||
logger.error('Customer contract PDF error:', error);
|
||||
res.status(500).json({ error: 'Failed to render contract PDF' });
|
||||
errorResponse(res, error, 500, 'Failed to render contract PDF');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ const { buildShareLinkVariants } = require('../services/shareLinkService');
|
||||
const { parseBooleanInput, parseStringInput } = require('../utils/parsers');
|
||||
const eventTypeService = require('../services/eventTypeService');
|
||||
const { IDENTITY_PRESERVING_NORMALIZE_EMAIL } = require('../utils/emailNormalization');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Use parseStringInput from shared parsers for customer data extraction
|
||||
const getCustomerNameFromPayload = (payload = {}) => parseStringInput(payload.customer_name);
|
||||
@@ -235,7 +236,7 @@ router.post('/', adminAuth, [
|
||||
});
|
||||
}
|
||||
} catch (waError) {
|
||||
console.warn('Failed to queue WhatsApp notification on create', waError.message);
|
||||
logger.warn('Failed to queue WhatsApp notification on create', waError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +272,7 @@ router.post('/', adminAuth, [
|
||||
customer_email: customerEmail
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
res.status(500).json({ error: 'Failed to create event' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ const secureImageService = require('../services/secureImageService');
|
||||
const logger = require('../utils/logger');
|
||||
const { resolvePhotoFilePath } = require('../services/photoResolver');
|
||||
const { getEventShareToken, resolveShareIdentifier, buildShareLinkVariants } = require('../services/shareLinkService');
|
||||
const { handleAsync } = require('../utils/routeHelpers');
|
||||
const { handleAsync, errorResponse } = require('../utils/routeHelpers');
|
||||
const { NotFoundError } = require('../utils/errors');
|
||||
const { ensureThumbnail, ensureHeroImage, ensurePreviewImage, withLocalCopy } = require('../services/imageProcessor');
|
||||
const downloadZipService = require('../services/downloadZipService');
|
||||
@@ -213,8 +213,7 @@ router.get('/:slug/info', async (req, res) => {
|
||||
promo_markdown: event.promo_markdown || null
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching gallery info:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch gallery info' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch gallery info');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -739,8 +738,7 @@ router.get('/:slug/photos', verifyGalleryAccess, resolveGuest, async (req, res)
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching photos:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch photos' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch photos');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -772,8 +770,7 @@ router.patch('/:slug/photos/:photoId/visibility', verifyGalleryAccess, async (re
|
||||
|
||||
res.json({ message: 'Photo visibility updated', visibility });
|
||||
} catch (error) {
|
||||
logger.error('Error updating photo visibility:', error);
|
||||
res.status(500).json({ error: 'Failed to update photo visibility' });
|
||||
errorResponse(res, error, 500, 'Failed to update photo visibility');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -801,8 +798,7 @@ router.patch('/:slug/photos/visibility/bulk', verifyGalleryAccess, async (req, r
|
||||
|
||||
res.json({ message: `${count} photos updated`, visibility });
|
||||
} catch (error) {
|
||||
logger.error('Error bulk updating photo visibility:', error);
|
||||
res.status(500).json({ error: 'Failed to update photo visibility' });
|
||||
errorResponse(res, error, 500, 'Failed to update photo visibility');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -916,13 +912,7 @@ router.get('/:slug/download/:photoId', verifyGalleryAccess, denySlideshowToken,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Unexpected error processing gallery download', {
|
||||
slug: req.params.slug,
|
||||
photoId: req.params.photoId,
|
||||
eventId: req.event?.id,
|
||||
error: error.message,
|
||||
});
|
||||
res.status(500).json({ error: 'Failed to download photo' });
|
||||
errorResponse(res, error, 500, 'Failed to download photo');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1093,12 +1083,7 @@ router.get('/:slug/download-all', verifyGalleryAccess, denySlideshowToken, async
|
||||
action: 'download_all'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error creating bulk gallery download', {
|
||||
slug: req.params.slug,
|
||||
eventId: req.event?.id,
|
||||
error: error.message,
|
||||
});
|
||||
res.status(500).json({ error: 'Failed to create download archive' });
|
||||
errorResponse(res, error, 500, 'Failed to create download archive');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1215,12 +1200,7 @@ router.post('/:slug/download-selected', verifyGalleryAccess, denySlideshowToken,
|
||||
action: 'download_selected'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error in download-selected:', {
|
||||
slug: req.params.slug,
|
||||
eventId: req.event?.id,
|
||||
error: error.message,
|
||||
});
|
||||
res.status(500).json({ error: 'Failed to download selected photos' });
|
||||
errorResponse(res, error, 500, 'Failed to download selected photos');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1452,13 +1432,7 @@ router.get('/:slug/photo/:photoId',
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error serving photo:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
photoId: req.params.photoId,
|
||||
eventId: req.event?.id
|
||||
});
|
||||
res.status(500).json({ error: 'Failed to serve photo' });
|
||||
errorResponse(res, error, 500, 'Failed to serve photo');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1549,12 +1523,7 @@ router.get('/:slug/thumbnail/:photoId',
|
||||
stream.pipe(res);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error serving thumbnail:', {
|
||||
error: error.message,
|
||||
photoId: req.params.photoId,
|
||||
eventId: req.event?.id
|
||||
});
|
||||
res.status(500).json({ error: 'Failed to serve thumbnail' });
|
||||
errorResponse(res, error, 500, 'Failed to serve thumbnail');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1763,8 +1732,7 @@ router.get('/:slug/feedback-settings', verifyGalleryAccess, async (req, res) =>
|
||||
identity_mode: settings.identity_mode || 'simple'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching feedback settings:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch feedback settings' });
|
||||
errorResponse(res, error, 500, 'Failed to fetch feedback settings');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1826,8 +1794,7 @@ router.post('/:eventId/upload', verifyGalleryAccess, denySlideshowToken, async (
|
||||
fs.mkdirSync(tempUploadDir, { recursive: true, mode: 0o755 });
|
||||
logger.info('Created temp upload directory:', tempUploadDir);
|
||||
} catch (mkdirErr) {
|
||||
logger.error('Failed to create temp upload directory:', mkdirErr);
|
||||
return res.status(500).json({ error: 'Server configuration error: unable to create upload directory' });
|
||||
return errorResponse(res, mkdirErr, 500, 'Server configuration error: unable to create upload directory');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1877,7 +1844,7 @@ router.post('/:eventId/upload', verifyGalleryAccess, denySlideshowToken, async (
|
||||
// Handle upload
|
||||
upload(req, res, async (err) => {
|
||||
if (err) {
|
||||
console.error('Upload error:', err);
|
||||
logger.error('Upload error:', err);
|
||||
return res.status(400).json({ error: err.message });
|
||||
}
|
||||
|
||||
@@ -1911,13 +1878,11 @@ router.post('/:eventId/upload', verifyGalleryAccess, denySlideshowToken, async (
|
||||
errors: result.errors.length > 0 ? result.errors : undefined,
|
||||
});
|
||||
} catch (processError) {
|
||||
console.error('Photo processing error:', processError);
|
||||
res.status(500).json({ error: 'Failed to process photos' });
|
||||
errorResponse(res, processError, 500, 'Failed to process photos');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Upload route error:', error);
|
||||
res.status(500).json({ error: 'Failed to upload photos' });
|
||||
errorResponse(res, error, 500, 'Failed to upload photos');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1955,7 +1920,7 @@ router.get('/:slug/css-template', async (req, res) => {
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600'); // 1 hour cache
|
||||
res.send(template.css_content);
|
||||
} catch (error) {
|
||||
console.error('Get CSS template error:', error);
|
||||
logger.error('Get CSS template error:', error);
|
||||
res.status(500).send('/* Error loading template */');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ const { getStorage } = require('../services/storage');
|
||||
const { resolvePhotoStorageKey, resolvePhotoFilePath } = require('../services/photoResolver');
|
||||
const { withLocalCopy } = require('../services/imageProcessor');
|
||||
const crypto = require('crypto');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -164,7 +165,7 @@ router.get('/:slug/photo/:photoId/view', verifyGalleryAccess, async (req, res) =
|
||||
res.send(finalImage);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error serving protected image:', error);
|
||||
logger.error('Error serving protected image:', error);
|
||||
res.status(500).json({ error: 'Failed to serve image' });
|
||||
}
|
||||
});
|
||||
@@ -208,7 +209,7 @@ router.post('/:slug/photo/:photoId/generate-secure-token', verifyGalleryAccess,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating secure token:', error);
|
||||
logger.error('Error generating secure token:', error);
|
||||
res.status(500).json({ error: 'Failed to generate token' });
|
||||
}
|
||||
});
|
||||
@@ -242,7 +243,7 @@ router.post('/:slug/photo/:photoId/generate-url', verifyGalleryAccess, async (re
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating signed URL:', error);
|
||||
logger.error('Error generating signed URL:', error);
|
||||
res.status(500).json({ error: 'Failed to generate URL' });
|
||||
}
|
||||
});
|
||||
@@ -304,7 +305,7 @@ router.get('/:slug/photo/:photoId/signed/:token', async (req, res) => {
|
||||
res.send(imageBuffer);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error serving signed image:', error);
|
||||
logger.error('Error serving signed image:', error);
|
||||
res.status(500).json({ error: 'Failed to serve image' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const { db } = require('../database/db');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
// Get public CMS page
|
||||
@@ -41,7 +42,7 @@ router.get('/pages/:slug', async (req, res) => {
|
||||
updated_at: page.updated_at
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching public CMS page:', error);
|
||||
logger.error('Error fetching public CMS page:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch page' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const { db, withRetry } = require('../database/db');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
// Get public settings (branding and theme)
|
||||
@@ -205,7 +206,7 @@ router.get('/', async (req, res) => {
|
||||
|
||||
res.json(publicSettings);
|
||||
} catch (error) {
|
||||
console.error('Public settings fetch error:', error);
|
||||
logger.error('Public settings fetch error:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch settings' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -412,7 +412,7 @@ async function getRecentAccessStats() {
|
||||
return acc;
|
||||
}, {});
|
||||
} catch (error) {
|
||||
console.error('Error getting recent access stats:', error);
|
||||
logger.error('Error getting recent access stats:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -440,7 +440,7 @@ async function getSuspiciousActivityStats() {
|
||||
uniqueIPs: parseInt(uniqueIPs.count)
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting suspicious activity stats:', error);
|
||||
logger.error('Error getting suspicious activity stats:', error);
|
||||
return { suspiciousEvents: 0, uniqueIPs: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { db } = require('../database/db');
|
||||
const logger = require('./logger');
|
||||
|
||||
// Default date format settings
|
||||
const DEFAULT_FORMAT = {
|
||||
@@ -19,7 +20,7 @@ async function formatDate(date, language = 'en') {
|
||||
try {
|
||||
dateConfig = JSON.parse(setting.setting_value);
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse date format setting:', e.message);
|
||||
logger.warn('Failed to parse date format setting:', e.message);
|
||||
dateConfig = DEFAULT_FORMAT;
|
||||
}
|
||||
} else {
|
||||
@@ -46,7 +47,7 @@ async function formatDate(date, language = 'en') {
|
||||
|
||||
// Check if date is valid
|
||||
if (isNaN(dateObj.getTime())) {
|
||||
console.error('Invalid date provided to formatDate:', date);
|
||||
logger.error('Invalid date provided to formatDate:', date);
|
||||
throw new Error('Invalid date');
|
||||
}
|
||||
|
||||
@@ -91,7 +92,7 @@ async function formatDate(date, language = 'en') {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error formatting date:', error);
|
||||
logger.error('Error formatting date:', error);
|
||||
// Fallback to basic formatting
|
||||
return date instanceof Date ? date.toLocaleDateString() : new Date(date).toLocaleDateString();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
// Note: Requiring db here creates circular dependency
|
||||
// db should be passed as parameter or required where needed
|
||||
const logger = require('./logger');
|
||||
|
||||
/**
|
||||
* Get database client type
|
||||
@@ -91,7 +92,7 @@ async function getDatabaseSize(db, dbName) {
|
||||
const stats = await fs.stat(dbPath);
|
||||
return stats.size;
|
||||
} catch (error) {
|
||||
console.error('Error getting SQLite database size:', error);
|
||||
logger.error('Error getting SQLite database size:', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const logger = require('./logger');
|
||||
|
||||
/**
|
||||
* Secure file security utilities to prevent path traversal and validate file types
|
||||
@@ -174,7 +175,7 @@ async function validateFileContent(filePath, expectedMimeType) {
|
||||
return true;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error validating file content:', error);
|
||||
logger.error('Error validating file content:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -239,7 +240,7 @@ function createFileUploadValidator(options = {}) {
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (err) {
|
||||
console.error('Error removing invalid file:', err);
|
||||
logger.error('Error removing invalid file:', err);
|
||||
}
|
||||
return res.status(400).json({
|
||||
error: `File content does not match declared type: ${file.originalname}`
|
||||
@@ -250,7 +251,7 @@ function createFileUploadValidator(options = {}) {
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('File validation error:', error);
|
||||
logger.error('File validation error:', error);
|
||||
res.status(500).json({ error: 'File validation failed' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
const { validationResult } = require('express-validator');
|
||||
const { ValidationError } = require('./errors');
|
||||
const logger = require('./logger');
|
||||
|
||||
/**
|
||||
* Wraps an async route handler to catch errors and pass them to the error handler.
|
||||
@@ -69,25 +70,26 @@ const successResponse = (res, data, statusCode = 200, message = null) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a standardized error response.
|
||||
* Note: Prefer throwing custom errors and letting the error handler format the response.
|
||||
* Logs an error and sends a standardized error response of shape `{ error: <string> }`.
|
||||
*
|
||||
* @param {Response} res - Express response object
|
||||
* @param {string} message - Error message
|
||||
* @param {Error|*} error - The caught error (logged, never sent to the client)
|
||||
* @param {number} [statusCode=500] - HTTP status code
|
||||
* @param {string} [code] - Optional error code
|
||||
* @param {*} [details] - Optional additional error details
|
||||
* @param {string} [publicMessage] - Message sent to the client; falls back to the error's message
|
||||
*
|
||||
* @example
|
||||
* errorResponse(res, 'Invalid input', 400, 'VALIDATION_ERROR', { field: 'email' });
|
||||
* } catch (error) {
|
||||
* errorResponse(res, error, 500, 'Failed to fetch events');
|
||||
* }
|
||||
*/
|
||||
const errorResponse = (res, message, statusCode = 500, code = null, details = null) => {
|
||||
const response = {
|
||||
error: message,
|
||||
...(code && { code }),
|
||||
...(details && { details })
|
||||
};
|
||||
res.status(statusCode).json(response);
|
||||
const errorResponse = (res, error, statusCode = 500, publicMessage) => {
|
||||
const message = publicMessage || (error instanceof Error ? error.message : String(error));
|
||||
const route = res.req ? `${res.req.method} ${res.req.originalUrl}` : null;
|
||||
logger.error(route ? `${route} - ${message}` : message, {
|
||||
error: error instanceof Error ? error.message : error,
|
||||
stack: error instanceof Error ? error.stack : undefined
|
||||
});
|
||||
res.status(statusCode).json({ error: message });
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user