176 lines
6.8 KiB
JavaScript
176 lines
6.8 KiB
JavaScript
require('dotenv').config();
|
||
const express = require('express');
|
||
const session = require('express-session');
|
||
const path = require('path');
|
||
const bcrypt = require('bcryptjs');
|
||
const db = require('./config/database');
|
||
|
||
const app = express();
|
||
|
||
// View Engine
|
||
app.set('view engine', 'ejs');
|
||
app.set('views', path.join(__dirname, 'views'));
|
||
|
||
// Static Files
|
||
app.use(express.static(path.join(__dirname, 'public')));
|
||
|
||
// Body Parser
|
||
app.use(express.json());
|
||
app.use(express.urlencoded({ extended: true }));
|
||
|
||
// Session
|
||
app.use(session({
|
||
secret: process.env.SESSION_SECRET || 'plusfit24-secret',
|
||
resave: false,
|
||
saveUninitialized: false,
|
||
cookie: {
|
||
secure: false, // auf true setzen wenn HTTPS direkt (nicht via Proxy)
|
||
maxAge: 24 * 60 * 60 * 1000 // 24 Stunden
|
||
}
|
||
}));
|
||
|
||
// Routen
|
||
const indexRouter = require('./routes/index');
|
||
const adminRouter = require('./routes/admin');
|
||
const apiRouter = require('./routes/api');
|
||
const billingRouter = require('./routes/billing');
|
||
const financeRouter = require('./routes/finance');
|
||
const renewalRouter = require('./routes/renewal');
|
||
const contractsRouter = require('./routes/contracts');
|
||
const mailingRouter = require('./routes/mailing');
|
||
const cron = require('node-cron');
|
||
|
||
app.use('/', indexRouter);
|
||
app.use('/admin', adminRouter);
|
||
app.use('/api', apiRouter);
|
||
app.use('/admin/billing', billingRouter);
|
||
app.use('/admin/finance', financeRouter);
|
||
app.use('/', renewalRouter);
|
||
app.use('/admin/contracts', contractsRouter);
|
||
app.use('/admin/mailing', mailingRouter);
|
||
|
||
// 404 Handler
|
||
app.use((req, res) => {
|
||
res.status(404).render('error', { message: 'Seite nicht gefunden' });
|
||
});
|
||
|
||
// Fehler Handler
|
||
app.use((err, req, res, next) => {
|
||
console.error(err.stack);
|
||
res.status(500).render('error', { message: 'Ein Fehler ist aufgetreten' });
|
||
});
|
||
|
||
// Admin Account beim Start erstellen falls keiner existiert
|
||
async function initAdmin() {
|
||
try {
|
||
const [rows] = await db.query('SELECT COUNT(*) as count FROM admins');
|
||
if (rows[0].count === 0) {
|
||
const hash = await bcrypt.hash(process.env.ADMIN_PASSWORD || 'Admin1234!', 12);
|
||
await db.query(
|
||
'INSERT INTO admins (username, password_hash) VALUES (?, ?)',
|
||
[process.env.ADMIN_USER || 'admin', hash]
|
||
);
|
||
console.log('✅ Admin Account erstellt:', process.env.ADMIN_USER || 'admin');
|
||
}
|
||
} catch (err) {
|
||
console.error('❌ Fehler beim Erstellen des Admin Accounts:', err.message);
|
||
}
|
||
}
|
||
|
||
// Verlängerungs-E-Mails: täglich um 08:00 Uhr prüfen (60 Tage vorher)
|
||
cron.schedule('0 8 * * *', async () => {
|
||
console.log('⏰ Prüfe auslaufende Verträge (60 Tage)...');
|
||
try {
|
||
const [expiring] = await db.query(`
|
||
SELECT m.* FROM memberships m
|
||
WHERE m.status = 'active'
|
||
AND m.effective_end BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 61 DAY)
|
||
AND m.id NOT IN (
|
||
SELECT membership_id FROM renewal_requests
|
||
WHERE status IN ('pending','completed')
|
||
AND sent_at >= DATE_SUB(NOW(), INTERVAL 70 DAY)
|
||
)
|
||
`);
|
||
if (expiring.length > 0) {
|
||
const { renewalEmailHtml } = require('./routes/renewal');
|
||
const mailer = require('./config/mailer');
|
||
const crypto = require('crypto');
|
||
const [tariffs] = await db.query('SELECT * FROM tariffs WHERE active = 1 ORDER BY price_monthly ASC');
|
||
for (const member of expiring) {
|
||
try {
|
||
const token = crypto.randomBytes(32).toString('hex');
|
||
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
||
await db.query(
|
||
'INSERT INTO renewal_requests (membership_id, token, expires_at) VALUES (?,?,?)',
|
||
[member.id, token, expiresAt]
|
||
);
|
||
const baseUrl = process.env.APP_URL || 'https://plusfit24.software-joksch.com';
|
||
const link = baseUrl + '/renew/' + token;
|
||
await mailer.sendMail({
|
||
from: process.env.MAIL_FROM,
|
||
to: member.email,
|
||
subject: `Deine PlusFit24 Mitgliedschaft läuft bald ab`,
|
||
html: renewalEmailHtml(member, tariffs, link, member.effective_end || member.contract_end)
|
||
});
|
||
await db.query(
|
||
'INSERT INTO email_log (membership_id, type, recipient, subject, status) VALUES (?,?,?,?,?)',
|
||
[member.id, 'renewal_auto', member.email, 'Mitgliedschaft läuft ab', 'sent']
|
||
);
|
||
console.log('📧 Verlängerungs-E-Mail gesendet an:', member.email);
|
||
} catch (err) {
|
||
console.error('❌ E-Mail Fehler für', member.email, ':', err.message);
|
||
}
|
||
}
|
||
} else {
|
||
console.log('✅ Keine auslaufenden Verträge heute.');
|
||
}
|
||
} catch (err) {
|
||
console.error('❌ Cron Fehler:', err.message);
|
||
}
|
||
});
|
||
|
||
// Auto-Abrechnungslauf jeden 1. des Monats um 06:00 Uhr
|
||
cron.schedule('0 6 1 * *', async () => {
|
||
const { currentPeriod } = require('./routes/billing');
|
||
const period = currentPeriod();
|
||
console.log(`⏰ Auto-Abrechnungslauf gestartet für ${period}`);
|
||
try {
|
||
const [existing] = await db.query('SELECT COUNT(*) as c FROM invoices WHERE period = ?', [period]);
|
||
if (existing[0].c > 0) {
|
||
console.log(`⏭ Abrechnungslauf für ${period} bereits vorhanden – übersprungen`);
|
||
return;
|
||
}
|
||
const [members] = await db.query(`
|
||
SELECT m.*, t.price_monthly FROM memberships m
|
||
JOIN tariffs t ON m.tariff_id = t.id
|
||
WHERE m.status IN ('active','paused')
|
||
AND m.contract_start <= LAST_DAY(STR_TO_DATE(CONCAT(?, '-01'), '%Y-%m-%d'))
|
||
AND (m.contract_end IS NULL OR m.contract_end >= STR_TO_DATE(CONCAT(?, '-01'), '%Y-%m-%d'))
|
||
`, [period, period]);
|
||
const [runResult] = await db.query(
|
||
'INSERT INTO billing_runs (run_date, period, created_by) VALUES (CURDATE(), ?, ?)',
|
||
[period, 'system-auto']
|
||
);
|
||
let total = 0, count = 0;
|
||
for (const m of members) {
|
||
const firstPeriod = m.first_payment_date ? m.first_payment_date.toISOString().substring(0,7) : null;
|
||
const amount = firstPeriod === period && m.first_payment_amt ? parseFloat(m.first_payment_amt) : parseFloat(m.price_monthly);
|
||
await db.query(
|
||
'INSERT IGNORE INTO invoices (billing_run_id, membership_id, period, amount, description, iban, account_holder, bank_name) VALUES (?,?,?,?,?,?,?,?)',
|
||
[runResult.insertId, m.id, period, amount, `Mitgliedsbeitrag ${period}`, m.iban||'', m.account_holder||'', m.bank_name||'']
|
||
);
|
||
total += amount; count++;
|
||
}
|
||
await db.query('UPDATE billing_runs SET total_amount=?, invoice_count=? WHERE id=?', [total, count, runResult.insertId]);
|
||
console.log(`✅ Auto-Abrechnungslauf abgeschlossen: ${count} Rechnungen, ${total.toFixed(2)} €`);
|
||
} catch (err) {
|
||
console.error('❌ Auto-Abrechnungslauf Fehler:', err.message);
|
||
}
|
||
});
|
||
|
||
const PORT = process.env.PORT || 3100;
|
||
app.listen(PORT, async () => {
|
||
console.log(`🚀 PlusFit24 Server läuft auf Port ${PORT}`);
|
||
await initAdmin();
|
||
});
|