Vertragsverwaltung_Plusfit24/routes/mailing.js
2026-03-28 09:14:41 +00:00

166 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const router = express.Router();
const db = require('../config/database');
const mailer = require('../config/mailer');
const { requireAdmin } = require('../middleware/auth');
// ============================================
// GET /admin/mailing Übersicht
// ============================================
router.get('/', requireAdmin, async (req, res) => {
try {
const [members] = await db.query(`
SELECT m.id, m.first_name, m.last_name, m.email, m.status,
t.name as tariff_name
FROM memberships m
LEFT JOIN tariffs t ON m.tariff_id = t.id
WHERE m.status = 'active'
ORDER BY m.last_name ASC
`);
const [log] = await db.query(`
SELECT e.id, e.membership_id, e.type, e.recipient, e.subject, e.status, e.sent_at,
m.first_name, m.last_name
FROM email_log e
LEFT JOIN memberships m ON e.membership_id = m.id
ORDER BY e.sent_at DESC
LIMIT 50
`);
res.render('admin/mailing', {
admin: req.session.adminUser,
members,
log,
success: req.query.success || null,
error: req.query.error || null
});
} catch (err) {
console.error(err);
res.redirect('/admin?error=' + encodeURIComponent('Fehler im Mailing: ' + err.message));
}
});
// ============================================
// POST /admin/mailing/send-all An alle aktiven
// ============================================
router.post('/send-all', requireAdmin, async (req, res) => {
const { subject, body, include_name } = req.body;
if (!subject || !body) return res.redirect('/admin/mailing?error=Betreff+und+Text+erforderlich');
try {
const [members] = await db.query(
"SELECT * FROM memberships WHERE status = 'active'"
);
let sent = 0, failed = 0;
for (const member of members) {
const personalBody = include_name
? `Hallo ${member.first_name} ${member.last_name},\n\n${body}`
: body;
const html = bodyToHtml(personalBody, subject);
try {
await mailer.sendMail({
from: process.env.MAIL_FROM,
to: member.email,
subject: subject,
html: html
});
await db.query(
'INSERT INTO email_log (membership_id, type, recipient, subject, status) VALUES (?,?,?,?,?)',
[member.id, 'bulk', member.email, subject, 'sent']
);
sent++;
} catch (err) {
await db.query(
'INSERT INTO email_log (membership_id, type, recipient, subject, status) VALUES (?,?,?,?,?)',
[member.id, 'bulk', member.email, subject, 'failed']
);
failed++;
}
}
res.redirect(`/admin/mailing?success=${sent}+E-Mails+gesendet${failed > 0 ? '+(' + failed + '+Fehler)' : ''}`);
} catch (err) {
console.error(err);
res.redirect('/admin/mailing?error=' + encodeURIComponent(err.message));
}
});
// ============================================
// POST /admin/mailing/send-one An einzelnes Mitglied
// ============================================
router.post('/send-one', requireAdmin, async (req, res) => {
const { membership_id, subject, body } = req.body;
const backUrl = req.headers.referer || '/admin/mailing';
if (!membership_id || !subject || !body) {
return res.redirect(backUrl + '?error=Alle+Felder+erforderlich');
}
try {
const [rows] = await db.query(
'SELECT * FROM memberships WHERE id = ?', [membership_id]
);
if (rows.length === 0) return res.redirect(backUrl + '?error=Mitglied+nicht+gefunden');
const member = rows[0];
const html = bodyToHtml(body, subject);
await mailer.sendMail({
from: process.env.MAIL_FROM,
to: member.email,
subject: subject,
html: html
});
await db.query(
'INSERT INTO email_log (membership_id, type, recipient, subject, status) VALUES (?,?,?,?,?)',
[member.id, 'direct', member.email, subject, 'sent']
);
res.redirect(backUrl + '?success=E-Mail+an+' + encodeURIComponent(member.email) + '+gesendet');
} catch (err) {
console.error(err);
await db.query(
'INSERT INTO email_log (membership_id, type, recipient, subject, status) VALUES (?,?,?,?,?)',
[membership_id, 'direct', '', subject, 'failed']
).catch(() => {});
res.redirect(backUrl + '?error=E-Mail+Fehler:+' + encodeURIComponent(err.message));
}
});
// ============================================
// HTML Template
// ============================================
function bodyToHtml(text, subject) {
const paragraphs = text.split('\n')
.map(l => l.trim())
.map(l => l ? `<p style="margin:0 0 12px;color:#374151;line-height:1.6">${l}</p>` : '<br>')
.join('');
return `<!DOCTYPE html>
<html lang="de">
<head><meta charset="UTF-8"></head>
<body style="font-family:Outfit,Arial,sans-serif;background:#f8f9ff;margin:0;padding:20px">
<div style="max-width:600px;margin:0 auto;background:white;border-radius:16px;overflow:hidden;box-shadow:0 2px 16px rgba(0,0,0,0.08)">
<div style="background:#2d2dcc;padding:28px 32px">
<h1 style="color:white;margin:0;font-size:1.6rem">Plusfit<span style="color:#a5b4fc">24</span></h1>
</div>
<div style="padding:32px">
<h2 style="margin:0 0 20px;color:#1a1a2e;font-size:1.2rem">${subject}</h2>
${paragraphs}
<hr style="border:none;border-top:1px solid #e2e4ed;margin:24px 0">
<p style="font-size:0.82rem;color:#9ca3af;margin:0">
PlusFit24 UG · Moosleiten 12 · 84089 Aiglsbach<br>
Diese E-Mail wurde über das PlusFit24 Verwaltungssystem gesendet.
</p>
</div>
</div>
</body></html>`;
}
module.exports = router;