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(); });