Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf8df2780e | |||
| 419a283c62 |
Generated
+49
-3
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.52",
|
"version": "1.0.53",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.52",
|
"version": "1.0.53",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"express-rate-limit": "^6.7.0",
|
"express-rate-limit": "^6.7.0",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
"form-data": "^4.0.3",
|
"form-data": "^4.0.3",
|
||||||
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
"i18next": "^25.3.1",
|
"i18next": "^25.3.1",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
@@ -3995,6 +3996,27 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -5985,6 +6007,12 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/node-abi": {
|
||||||
"version": "3.75.0",
|
"version": "3.75.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
|
||||||
@@ -7606,7 +7634,6 @@
|
|||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -8161,6 +8188,19 @@
|
|||||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/undefsafe": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||||
@@ -8412,6 +8452,12 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.52",
|
"version": "1.0.53",
|
||||||
"description": "Backend for PicPeak event photo sharing platform",
|
"description": "Backend for PicPeak event photo sharing platform",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"express-rate-limit": "^6.7.0",
|
"express-rate-limit": "^6.7.0",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
"form-data": "^4.0.3",
|
"form-data": "^4.0.3",
|
||||||
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
"i18next": "^25.3.1",
|
"i18next": "^25.3.1",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
|
|||||||
@@ -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
|
// Archive event
|
||||||
router.post('/:id/archive', adminAuth, async (req, res) => {
|
router.post('/:id/archive', adminAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const nodemailer = require('nodemailer');
|
const nodemailer = require('nodemailer');
|
||||||
|
const Handlebars = require('handlebars');
|
||||||
const { db } = require('../database/db');
|
const { db } = require('../database/db');
|
||||||
const logger = require('../utils/logger');
|
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 frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3005';
|
||||||
const logoFullUrl = logoUrl ? `${apiUrl}${logoUrl}` : `${frontendUrl}/picpeak-logo-transparent.png`;
|
const logoFullUrl = logoUrl ? `${apiUrl}${logoUrl}` : `${frontendUrl}/picpeak-logo-transparent.png`;
|
||||||
|
|
||||||
// Process welcome message section if present
|
// Compile templates with Handlebars
|
||||||
let welcomeMessageSection = '';
|
const subjectTemplate = Handlebars.compile(subject);
|
||||||
if (variables.welcome_message && variables.welcome_message.trim() !== '') {
|
const htmlTemplate = Handlebars.compile(htmlBody);
|
||||||
const welcomeTitle = language === 'de' ? 'Persönliche Nachricht:' : 'Personal Message:';
|
const textTemplate = Handlebars.compile(textBody);
|
||||||
welcomeMessageSection = `
|
|
||||||
<div style="background-color: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
// Process templates with variables
|
||||||
<p style="margin: 0 0 10px 0; font-weight: 600; color: #374151;">${welcomeTitle}</p>
|
subject = subjectTemplate(variables);
|
||||||
<p style="margin: 0; color: #4b5563;">${variables.welcome_message}</p>
|
htmlBody = htmlTemplate(variables);
|
||||||
</div>`;
|
textBody = textTemplate(variables);
|
||||||
}
|
|
||||||
|
|
||||||
// Replace variables
|
|
||||||
Object.entries(variables).forEach(([key, value]) => {
|
|
||||||
const regex = new RegExp(`{{${key}}}`, 'g');
|
|
||||||
subject = subject.replace(regex, value || '');
|
|
||||||
htmlBody = htmlBody.replace(regex, value || '');
|
|
||||||
textBody = textBody.replace(regex, value || '');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace welcome message section placeholder
|
|
||||||
htmlBody = htmlBody.replace(/{{welcome_message_section}}/g, welcomeMessageSection);
|
|
||||||
|
|
||||||
// Wrap HTML body in styled template
|
// Wrap HTML body in styled template
|
||||||
const styledHtmlBody = `
|
const styledHtmlBody = `
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.51",
|
"version": "1.0.53",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.51",
|
"version": "1.0.53",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"@tiptap/extension-link": "^2.25.0",
|
"@tiptap/extension-link": "^2.25.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.51",
|
"version": "1.0.53",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -244,6 +244,9 @@
|
|||||||
"expires": "Läuft ab",
|
"expires": "Läuft ab",
|
||||||
"shareWithGuests": "Teilen Sie diesen Link mit Gästen. Sie benötigen das Passwort, um auf die Galerie zuzugreifen.",
|
"shareWithGuests": "Teilen Sie diesen Link mit Gästen. Sie benötigen das Passwort, um auf die Galerie zuzugreifen.",
|
||||||
"resetGalleryPassword": "Galerie-Passwort zurücksetzen",
|
"resetGalleryPassword": "Galerie-Passwort zurücksetzen",
|
||||||
|
"resendCreationEmail": "Erstellungs-E-Mail erneut senden",
|
||||||
|
"creationEmailResent": "Die Erstellungs-E-Mail wurde zur Warteschlange hinzugefügt",
|
||||||
|
"failedToResendEmail": "Fehler beim erneuten Senden der Erstellungs-E-Mail",
|
||||||
"photoStatistics": "Fotostatistiken",
|
"photoStatistics": "Fotostatistiken",
|
||||||
"managePhotos": "Fotos verwalten",
|
"managePhotos": "Fotos verwalten",
|
||||||
"actions": "Aktionen",
|
"actions": "Aktionen",
|
||||||
|
|||||||
@@ -261,6 +261,9 @@
|
|||||||
"expires": "Expires",
|
"expires": "Expires",
|
||||||
"shareWithGuests": "Share this link with guests. They'll need the password to access the gallery.",
|
"shareWithGuests": "Share this link with guests. They'll need the password to access the gallery.",
|
||||||
"resetGalleryPassword": "Reset Gallery Password",
|
"resetGalleryPassword": "Reset Gallery Password",
|
||||||
|
"resendCreationEmail": "Resend Creation Email",
|
||||||
|
"creationEmailResent": "Creation email has been queued for sending",
|
||||||
|
"failedToResendEmail": "Failed to resend creation email",
|
||||||
"photoStatistics": "Photo Statistics",
|
"photoStatistics": "Photo Statistics",
|
||||||
"totalPhotos": "Total Photos",
|
"totalPhotos": "Total Photos",
|
||||||
"managePhotos": "Manage Photos",
|
"managePhotos": "Manage Photos",
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
CheckCircle,
|
CheckCircle,
|
||||||
Upload,
|
Upload,
|
||||||
Image,
|
Image,
|
||||||
Key
|
Key,
|
||||||
|
Mail
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { parseISO, differenceInDays } from 'date-fns';
|
import { parseISO, differenceInDays } from 'date-fns';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -620,7 +621,7 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{!event.is_archived && (
|
{!event.is_archived && (
|
||||||
<div className="mt-4 pt-4 border-t border-neutral-200">
|
<div className="mt-4 pt-4 border-t border-neutral-200 space-y-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -630,6 +631,22 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{t('events.resetGalleryPassword')}
|
{t('events.resetGalleryPassword')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<Mail className="w-4 h-4" />}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await eventsService.resendCreationEmail(event.id);
|
||||||
|
toast.success(t('events.creationEmailResent'));
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(t('events.failedToResendEmail'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full justify-center"
|
||||||
|
>
|
||||||
|
{t('events.resendCreationEmail')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -118,4 +118,10 @@ export const eventsService = {
|
|||||||
const response = await api.post(`/admin/events/${eventId}/reset-password`, { sendEmail });
|
const response = await api.post(`/admin/events/${eventId}/reset-password`, { sendEmail });
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Resend creation email
|
||||||
|
async resendCreationEmail(eventId: number): Promise<{ success: boolean; message: string }> {
|
||||||
|
const response = await api.post(`/admin/events/${eventId}/resend-email`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user