fix: prevent double date formatting in email templates
Mirror to GitHub / mirror (push) Successful in 25s
Test and Lint / backend-test (push) Successful in 1m7s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m12s
Version and Release / version-bump (push) Successful in 33s
Version and Release / trigger-drone (push) Successful in 3s

- Remove pre-formatting of dates before passing to email processor
- Let email processor handle all date formatting based on recipient language
- Fix Invalid Date errors by passing raw date values instead of formatted strings
- Remove unused formatDate imports and variables

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
2025-07-16 23:14:32 +02:00
co-authored by Claude
parent 0a691d4251
commit 4e2075c638
8 changed files with 50 additions and 53 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ function validateEnvironment() {
if (name === 'JWT_SECRET' && value) { if (name === 'JWT_SECRET' && value) {
// Check for the insecure default value // Check for the insecure default value
if (value === 'your-secret-key') { if (value === 'your-secret-key') {
errors.push(`CRITICAL: JWT_SECRET is set to the insecure default value. Please set a secure secret key.`); errors.push('CRITICAL: JWT_SECRET is set to the insecure default value. Please set a secure secret key.');
} }
// Check minimum length (should be at least 32 characters for security) // Check minimum length (should be at least 32 characters for security)
+3 -3
View File
@@ -59,9 +59,9 @@ async function initializeDatabase() {
) )
`); `);
await db.raw(`INSERT INTO events_new SELECT * FROM events`); await db.raw('INSERT INTO events_new SELECT * FROM events');
await db.raw(`DROP TABLE events`); await db.raw('DROP TABLE events');
await db.raw(`ALTER TABLE events_new RENAME TO events`); await db.raw('ALTER TABLE events_new RENAME TO events');
} catch (error) { } catch (error) {
// If the migration fails, it might already have been applied // If the migration fails, it might already have been applied
console.log('Color theme migration may have already been applied'); console.log('Color theme migration may have already been applied');
+1 -1
View File
@@ -251,7 +251,7 @@ router.post('/:id/restore', adminAuth, async (req, res) => {
} catch (statError) { } catch (statError) {
console.error(`Failed to stat file: ${actualFilePath}`); console.error(`Failed to stat file: ${actualFilePath}`);
console.error(`Entry name was: ${entry.entryName}`); console.error(`Entry name was: ${entry.entryName}`);
console.error(`Error:`, statError.message); console.error('Error:', statError.message);
// Skip this file if we can't stat it // Skip this file if we can't stat it
continue; continue;
} }
+9 -9
View File
@@ -210,15 +210,15 @@ router.get('/templates', adminAuth, async (req, res) => {
id: template.id, id: template.id,
template_key: template.template_key, template_key: template.template_key,
variables: (() => { variables: (() => {
try { try {
if (!template.variables) return []; if (!template.variables) return [];
if (typeof template.variables === 'object') return template.variables; if (typeof template.variables === 'object') return template.variables;
return JSON.parse(template.variables); return JSON.parse(template.variables);
} catch (e) { } catch (e) {
console.warn('Failed to parse variables for template:', template.template_key, e.message); console.warn('Failed to parse variables for template:', template.template_key, e.message);
return []; return [];
} }
})(), })(),
updated_at: template.updated_at updated_at: template.updated_at
}; };
+9 -12
View File
@@ -10,7 +10,7 @@ const path = require('path');
const { archiveEvent } = require('../services/archiveService'); const { archiveEvent } = require('../services/archiveService');
const { queueEmail } = require('../services/emailProcessor'); const { queueEmail } = require('../services/emailProcessor');
const { escapeLikePattern } = require('../utils/sqlSecurity'); const { escapeLikePattern } = require('../utils/sqlSecurity');
const { formatDate } = require('../utils/dateFormatter'); // formatDate import removed - dates are formatted by email processor
const { validatePasswordInContext, getBcryptRounds } = require('../utils/passwordValidation'); const { validatePasswordInContext, getBcryptRounds } = require('../utils/passwordValidation');
const { formatBoolean } = require('../utils/dbCompat'); const { formatBoolean } = require('../utils/dbCompat');
@@ -135,8 +135,7 @@ router.post('/', adminAuth, [
); );
// Queue creation email // Queue creation email
// Determine language based on email domain // Language detection is handled by email processor
const emailLang = host_email.endsWith('.de') ? 'de' : 'en';
await db('email_queue').insert({ await db('email_queue').insert({
event_id: eventId, event_id: eventId,
@@ -145,10 +144,10 @@ router.post('/', adminAuth, [
email_data: JSON.stringify({ email_data: JSON.stringify({
host_name: host_name, host_name: host_name,
event_name, event_name,
event_date: await formatDate(event_date, emailLang), event_date: event_date, // Pass raw date - will be formatted by email processor
gallery_link: shareLink, gallery_link: shareLink,
gallery_password: password, gallery_password: password,
expiry_date: await formatDate(expires_at, emailLang), expiry_date: expires_at.toISOString(), // Pass ISO string - will be formatted by email processor
welcome_message: welcome_message || '' welcome_message: welcome_message || ''
}), }),
status: 'pending', status: 'pending',
@@ -553,10 +552,10 @@ router.post('/:id/reset-password', adminAuth, async (req, res) => {
await queueEmail(id, event.host_email, 'gallery_created', { await queueEmail(id, event.host_email, 'gallery_created', {
host_name: event.host_email.split('@')[0], host_name: event.host_email.split('@')[0],
event_name: event.event_name, event_name: event.event_name,
event_date: new Date(event.event_date).toLocaleDateString(), event_date: event.event_date, // Pass raw date - will be formatted by email processor
gallery_link: event.share_link, gallery_link: event.share_link,
gallery_password: newPassword, gallery_password: newPassword,
expiry_date: new Date(event.expires_at).toLocaleDateString() expiry_date: event.expires_at // Pass raw date - will be formatted by email processor
}); });
} }
@@ -603,18 +602,16 @@ router.post('/:id/resend-email', adminAuth, async (req, res) => {
galleryPassword = '{{password_security_message}}'; galleryPassword = '{{password_security_message}}';
} }
// Format dates in a neutral format - the email processor will localize them // Dates will be formatted by the email processor based on recipient language
const eventDate = new Date(event.event_date);
const expiryDate = new Date(event.expires_at);
// Queue the email // Queue the email
await queueEmail(id, event.host_email, 'gallery_created', { await queueEmail(id, event.host_email, 'gallery_created', {
host_name: event.host_name || event.host_email.split('@')[0], host_name: event.host_name || event.host_email.split('@')[0],
event_name: event.event_name, event_name: event.event_name,
event_date: eventDate.toISOString().split('T')[0], // YYYY-MM-DD format event_date: event.event_date, // Pass raw date - will be formatted by email processor
gallery_link: event.share_link, gallery_link: event.share_link,
gallery_password: galleryPassword, gallery_password: galleryPassword,
expiry_date: expiryDate.toISOString().split('T')[0], // YYYY-MM-DD format expiry_date: event.expires_at, // Pass raw date - will be formatted by email processor
welcome_message: event.welcome_message || '', welcome_message: event.welcome_message || '',
eventId: id, eventId: id,
isResend: true // Flag to indicate this is a resend isResend: true // Flag to indicate this is a resend
+2 -2
View File
@@ -87,10 +87,10 @@ router.post('/', adminAuth, [
await queueEmail(eventId, host_email, 'gallery_created', { await queueEmail(eventId, host_email, 'gallery_created', {
host_name: host_email.split('@')[0], // Extract name from email host_name: host_email.split('@')[0], // Extract name from email
event_name, event_name,
event_date: new Date(event_date).toLocaleDateString(), event_date: event_date, // Pass raw date - will be formatted by email processor
gallery_link: shareLink, gallery_link: shareLink,
gallery_password: password, gallery_password: password,
expiry_date: expires_at.toLocaleDateString(), expiry_date: expires_at.toISOString(), // Pass ISO string - will be formatted by email processor
welcome_message: welcome_message || '' welcome_message: welcome_message || ''
}); });
+24 -24
View File
@@ -56,30 +56,30 @@ class WatermarkService {
let left, top; let left, top;
switch (position) { switch (position) {
case 'top-left': case 'top-left':
left = padding; left = padding;
top = padding; top = padding;
break; break;
case 'top-right': case 'top-right':
left = imageWidth - watermarkWidth - padding; left = imageWidth - watermarkWidth - padding;
top = padding; top = padding;
break; break;
case 'bottom-left': case 'bottom-left':
left = padding; left = padding;
top = imageHeight - watermarkHeight - padding; top = imageHeight - watermarkHeight - padding;
break; break;
case 'bottom-right': case 'bottom-right':
left = imageWidth - watermarkWidth - padding; left = imageWidth - watermarkWidth - padding;
top = imageHeight - watermarkHeight - padding; top = imageHeight - watermarkHeight - padding;
break; break;
case 'center': case 'center':
left = Math.floor((imageWidth - watermarkWidth) / 2); left = Math.floor((imageWidth - watermarkWidth) / 2);
top = Math.floor((imageHeight - watermarkHeight) / 2); top = Math.floor((imageHeight - watermarkHeight) / 2);
break; break;
default: default:
// Default to bottom-right // Default to bottom-right
left = imageWidth - watermarkWidth - padding; left = imageWidth - watermarkWidth - padding;
top = imageHeight - watermarkHeight - padding; top = imageHeight - watermarkHeight - padding;
} }
return { left: Math.max(0, left), top: Math.max(0, top) }; return { left: Math.max(0, left), top: Math.max(0, top) };
+1 -1
View File
@@ -47,7 +47,7 @@ function escapeLikePattern(input) {
.replace(/\\/g, '\\\\') // Escape backslashes first .replace(/\\/g, '\\\\') // Escape backslashes first
.replace(/%/g, '\\%') // Escape percent signs .replace(/%/g, '\\%') // Escape percent signs
.replace(/_/g, '\\_') // Escape underscores .replace(/_/g, '\\_') // Escape underscores
.replace(/'/g, "''"); // Escape single quotes for safety .replace(/'/g, '\'\''); // Escape single quotes for safety
} }
/** /**