255 lines
8.5 KiB
JavaScript
255 lines
8.5 KiB
JavaScript
import { getUserFromToken, hasAnyRole } from '../../../../../utils/auth.js'
|
|
import { readSubscribers, writeSubscribers } from '../../../../../utils/newsletter.js'
|
|
import { randomUUID } from 'crypto'
|
|
import nodemailer from 'nodemailer'
|
|
import crypto from 'crypto'
|
|
import fs from 'fs/promises'
|
|
import path from 'path'
|
|
|
|
const getDataPath = (filename) => {
|
|
const cwd = process.cwd()
|
|
if (cwd.endsWith('.output')) {
|
|
return path.join(cwd, '../server/data', filename)
|
|
}
|
|
return path.join(cwd, 'server/data', filename)
|
|
}
|
|
|
|
const NEWSLETTER_GROUPS_FILE = getDataPath('newsletter-groups.json')
|
|
|
|
async function readGroups() {
|
|
try {
|
|
const data = await fs.readFile(NEWSLETTER_GROUPS_FILE, 'utf-8')
|
|
return JSON.parse(data)
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
return []
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
// Authentifizierung prüfen
|
|
const token = getCookie(event, 'auth_token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
|
|
|
|
if (!token) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
statusMessage: 'Nicht authentifiziert'
|
|
})
|
|
}
|
|
|
|
const user = await getUserFromToken(token)
|
|
if (!user || !hasAnyRole(user, 'admin', 'vorstand', 'newsletter')) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: 'Keine Berechtigung'
|
|
})
|
|
}
|
|
|
|
const groupId = getRouterParam(event, 'id')
|
|
const body = await readBody(event)
|
|
const { email, name, customMessage } = body
|
|
|
|
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Ungültige E-Mail-Adresse'
|
|
})
|
|
}
|
|
|
|
// Prüfe ob Gruppe existiert und vom Typ 'subscription' ist
|
|
const groups = await readGroups()
|
|
const group = groups.find(g => g.id === groupId)
|
|
|
|
if (!group) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: 'Newsletter-Gruppe nicht gefunden'
|
|
})
|
|
}
|
|
|
|
if (group.type !== 'subscription') {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Diese Funktion ist nur für Abonnenten-Newsletter verfügbar'
|
|
})
|
|
}
|
|
|
|
const subscribers = await readSubscribers()
|
|
const emailLower = email.toLowerCase()
|
|
|
|
// Prüfe ob bereits für diese Gruppe angemeldet
|
|
const existing = subscribers.find(s => {
|
|
const sEmail = (s.email || '').toLowerCase()
|
|
return sEmail === emailLower && s.groupIds && s.groupIds.includes(groupId)
|
|
})
|
|
|
|
if (existing) {
|
|
if (existing.confirmed) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: 'Diese E-Mail-Adresse ist bereits für diesen Newsletter angemeldet'
|
|
})
|
|
} else {
|
|
// Bestätigungsmail erneut senden mit individueller Nachricht
|
|
await sendConfirmationEmail(existing.email, existing.name || name, existing.confirmationToken, group.name, customMessage, user.name)
|
|
return {
|
|
success: true,
|
|
message: 'Eine Bestätigungsmail wurde an die E-Mail-Adresse gesendet'
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prüfe ob E-Mail bereits existiert (für andere Gruppe oder ohne Gruppe)
|
|
const existingEmail = subscribers.find(s => (s.email || '').toLowerCase() === emailLower)
|
|
|
|
if (existingEmail) {
|
|
// Bestehender Subscriber - Gruppe hinzufügen
|
|
if (!existingEmail.groupIds) {
|
|
existingEmail.groupIds = []
|
|
}
|
|
|
|
if (existingEmail.groupIds.includes(groupId)) {
|
|
// Bereits für diese Gruppe angemeldet
|
|
if (existingEmail.confirmed) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: 'Diese E-Mail-Adresse ist bereits für diesen Newsletter angemeldet'
|
|
})
|
|
} else {
|
|
// Bestätigungsmail erneut senden mit individueller Nachricht
|
|
await sendConfirmationEmail(existingEmail.email, existingEmail.name || name, existingEmail.confirmationToken, group.name, customMessage, user.name)
|
|
return {
|
|
success: true,
|
|
message: 'Eine Bestätigungsmail wurde an die E-Mail-Adresse gesendet'
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gruppe hinzufügen
|
|
existingEmail.groupIds.push(groupId)
|
|
if (!existingEmail.confirmed) {
|
|
// Neuer Bestätigungstoken für alle Gruppen
|
|
existingEmail.confirmationToken = crypto.randomBytes(32).toString('hex')
|
|
}
|
|
existingEmail.name = name || existingEmail.name || ''
|
|
|
|
await writeSubscribers(subscribers)
|
|
|
|
if (existingEmail.confirmed) {
|
|
// Bereits bestätigt - sofort aktiviert
|
|
return {
|
|
success: true,
|
|
message: `Empfänger wurde erfolgreich für den Newsletter "${group.name}" hinzugefügt`
|
|
}
|
|
} else {
|
|
// Bestätigungsmail senden mit individueller Nachricht
|
|
await sendConfirmationEmail(existingEmail.email, existingEmail.name, existingEmail.confirmationToken, group.name, customMessage, user.name)
|
|
return {
|
|
success: true,
|
|
message: 'Eine Bestätigungsmail wurde an die E-Mail-Adresse gesendet'
|
|
}
|
|
}
|
|
}
|
|
|
|
// Neuer Abonnent
|
|
const confirmationToken = crypto.randomBytes(32).toString('hex')
|
|
const unsubscribeToken = crypto.randomBytes(32).toString('hex')
|
|
const newSubscriber = {
|
|
id: randomUUID(),
|
|
email: emailLower,
|
|
name: name || '',
|
|
groupIds: [groupId],
|
|
confirmed: false,
|
|
confirmationToken,
|
|
unsubscribeToken,
|
|
subscribedAt: new Date().toISOString(),
|
|
confirmedAt: null,
|
|
unsubscribedAt: null
|
|
}
|
|
|
|
subscribers.push(newSubscriber)
|
|
await writeSubscribers(subscribers)
|
|
|
|
// Bestätigungsmail senden mit individueller Nachricht
|
|
await sendConfirmationEmail(email, name, confirmationToken, group.name, customMessage, user.name)
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Eine Bestätigungsmail wurde an die E-Mail-Adresse gesendet'
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Hinzufügen des Empfängers:', error)
|
|
if (error.statusCode) {
|
|
throw error
|
|
}
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Fehler beim Hinzufügen des Empfängers'
|
|
})
|
|
}
|
|
})
|
|
|
|
async function sendConfirmationEmail(email, name, token, groupName, customMessage = null, inviterName = null) {
|
|
const smtpUser = process.env.SMTP_USER
|
|
const smtpPass = process.env.SMTP_PASS
|
|
|
|
if (!smtpUser || !smtpPass) {
|
|
console.warn('SMTP-Credentials fehlen! Bestätigungsmail kann nicht gesendet werden.')
|
|
return
|
|
}
|
|
|
|
const baseUrl = process.env.NUXT_PUBLIC_BASE_URL || 'http://localhost:3100'
|
|
const confirmationUrl = `${baseUrl}/newsletter/confirm?token=${token}`
|
|
|
|
const transporter = nodemailer.createTransport({
|
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
|
port: process.env.SMTP_PORT || 587,
|
|
secure: false,
|
|
auth: {
|
|
user: smtpUser,
|
|
pass: smtpPass
|
|
}
|
|
})
|
|
|
|
// Individuelle Nachricht einbauen, falls vorhanden
|
|
const customMessageHtml = customMessage
|
|
? `<div style="background-color: #f3f4f6; padding: 15px; border-left: 4px solid #dc2626; margin: 20px 0;">
|
|
<p style="margin: 0; color: #374151; font-style: italic;">${customMessage.replace(/\n/g, '<br>')}</p>
|
|
</div>`
|
|
: ''
|
|
|
|
const inviterText = inviterName
|
|
? `<p style="margin-top: 20px; color: #666; font-size: 14px;">Sie wurden von ${inviterName} zum Newsletter eingeladen.</p>`
|
|
: ''
|
|
|
|
await transporter.sendMail({
|
|
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
|
to: email,
|
|
subject: `Newsletter-Anmeldung bestätigen - ${groupName} - Harheimer TC`,
|
|
html: `
|
|
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
|
<h2 style="color: #dc2626;">Newsletter-Anmeldung bestätigen</h2>
|
|
<p>Hallo ${name || 'Liebe/r Abonnent/in'},</p>
|
|
${inviterText}
|
|
<p>vielen Dank für Ihre Anmeldung zum Newsletter "${groupName}" des Harheimer TC!</p>
|
|
${customMessageHtml}
|
|
<p>Bitte bestätigen Sie Ihre Anmeldung, indem Sie auf den folgenden Link klicken:</p>
|
|
<p style="margin: 30px 0;">
|
|
<a href="${confirmationUrl}" style="display: inline-block; padding: 12px 24px; background-color: #dc2626; color: white; text-decoration: none; border-radius: 5px;">
|
|
Newsletter-Anmeldung bestätigen
|
|
</a>
|
|
</p>
|
|
<p>Falls Sie sich nicht angemeldet haben, können Sie diese E-Mail ignorieren.</p>
|
|
<p style="margin-top: 30px; color: #666; font-size: 12px;">
|
|
Mit sportlichen Grüßen,<br>
|
|
Ihr Harheimer Tischtennis-Club 1954 e.V.
|
|
</p>
|
|
</div>
|
|
`
|
|
})
|
|
}
|
|
|