166 lines
5.6 KiB
JavaScript
166 lines
5.6 KiB
JavaScript
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;
|