diff --git a/backend/package-lock.json b/backend/package-lock.json index 94e8b07..05d8442 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,6 +19,7 @@ "express-rate-limit": "^6.7.0", "express-validator": "^7.0.1", "form-data": "^4.0.3", + "handlebars": "^4.7.8", "helmet": "^7.0.0", "i18next": "^25.3.1", "i18next-browser-languagedetector": "^8.2.0", @@ -3995,6 +3996,27 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5985,6 +6007,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/node-abi": { "version": "3.75.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", @@ -7606,7 +7634,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -8161,6 +8188,19 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -8412,6 +8452,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 599e089..8956c31 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,6 +23,7 @@ "express-rate-limit": "^6.7.0", "express-validator": "^7.0.1", "form-data": "^4.0.3", + "handlebars": "^4.7.8", "helmet": "^7.0.0", "i18next": "^25.3.1", "i18next-browser-languagedetector": "^8.2.0", diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index 8d41ad5..94036da 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -564,6 +564,57 @@ router.post('/:id/reset-password', adminAuth, async (req, res) => { } }); +// Resend creation email +router.post('/:id/resend-email', adminAuth, async (req, res) => { + try { + const { id } = req.params; + + // Get event details + const event = await db('events') + .where('id', id) + .first(); + + if (!event) { + return res.status(404).json({ error: 'Event not found' }); + } + + // Format dates properly for the email + const eventDate = new Date(event.event_date); + const expiryDate = new Date(event.expires_at); + + // Queue the email + const { queueEmail } = require('../services/emailProcessor'); + await queueEmail(id, event.host_email, 'gallery_created', { + host_name: event.host_name || event.host_email.split('@')[0], + event_name: event.event_name, + event_date: eventDate.toLocaleDateString('de-DE'), // Using German date format + gallery_link: event.share_link, + gallery_password: '(Aus Sicherheitsgründen nicht angezeigt)', // Security: don't show password in resent emails + expiry_date: expiryDate.toLocaleDateString('de-DE'), + welcome_message: event.welcome_message || '', + eventId: id + }); + + // Log the activity + await db('activity_logs').insert({ + event_id: id, + ip_address: req.ip, + user_agent: req.get('user-agent'), + action_type: 'email_resent', + action_details: JSON.stringify({ email_type: 'gallery_created' }), + timestamp: new Date() + }); + + res.json({ + success: true, + message: 'Creation email has been queued for sending' + }); + } catch (error) { + console.error('Error resending creation email:', error); + res.status(500).json({ error: 'Failed to resend creation email' }); + } +}); + // Archive event router.post('/:id/archive', adminAuth, async (req, res) => { try { diff --git a/backend/src/services/emailProcessor.js b/backend/src/services/emailProcessor.js index 21a17d2..f6d7526 100644 --- a/backend/src/services/emailProcessor.js +++ b/backend/src/services/emailProcessor.js @@ -1,4 +1,5 @@ const nodemailer = require('nodemailer'); +const Handlebars = require('handlebars'); const { db } = require('../database/db'); const logger = require('../utils/logger'); @@ -138,27 +139,15 @@ async function processTemplate(template, variables, language = 'en') { const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3005'; const logoFullUrl = logoUrl ? `${apiUrl}${logoUrl}` : `${frontendUrl}/picpeak-logo-transparent.png`; - // Process welcome message section if present - let welcomeMessageSection = ''; - if (variables.welcome_message && variables.welcome_message.trim() !== '') { - const welcomeTitle = language === 'de' ? 'Persönliche Nachricht:' : 'Personal Message:'; - welcomeMessageSection = ` -
${welcomeTitle}
-${variables.welcome_message}
-