/** * Quote & Invoice System - Main Entry Point * Modularized Backend */ // ── Global timestamp logger – must be first line before any require ── const _ts = () => new Date().toISOString().replace('T', ' ').substring(0, 19); const _origLog = console.log.bind(console); const _origWarn = console.warn.bind(console); const _origError = console.error.bind(console); console.log = (...a) => _origLog(`[${_ts()}]`, ...a); console.warn = (...a) => _origWarn(`[${_ts()}]`, ...a); console.error = (...a) => _origError(`[${_ts()}]`, ...a); const express = require('express'); const path = require('path'); const puppeteer = require('puppeteer'); // Import config const { pool } = require('./config/database'); const { OAuthClient, getOAuthClient, saveTokens } = require('./config/qbo'); // Import routes const customerRoutes = require('./routes/customers'); const quoteRoutes = require('./routes/quotes'); const invoiceRoutes = require('./routes/invoices'); const paymentRoutes = require('./routes/payments'); const qboRoutes = require('./routes/qbo'); const settingsRoutes = require('./routes/settings'); // Import PDF service for browser initialization const { setBrowser } = require('./services/pdf-service'); // Import recurring invoice scheduler const { startRecurringScheduler } = require('./services/recurring-service'); const { startStripePolling } = require('./services/stripe-poll-service'); const app = express(); const PORT = process.env.PORT || 3000; // Global browser instance let browser = null; // Initialize browser on startup async function initBrowser() { if (!browser) { console.log('[BROWSER] Launching persistent browser...'); browser = await puppeteer.launch({ headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-software-rasterizer', '--no-zygote' // '--single-process' WURDE ENTFERNT! ], protocolTimeout: 180000, timeout: 180000 }); console.log('[BROWSER] Browser launched and ready'); // Pass browser to PDF service setBrowser(browser); // Restart browser if it crashes (mit Atempause!) browser.on('disconnected', () => { console.log('[BROWSER] Browser disconnected. Waiting 5 seconds before restarting...'); browser = null; setBrowser(null); // 5 Sekunden warten, bevor ein Neustart versucht wird setTimeout(() => { initBrowser(); }, 5000); }); } return browser; } // Middleware app.use(express.json()); app.use(express.static(path.join(__dirname, '..', 'public'))); // ===================================================== // QBO OAuth Routes — mounted at root level (not under /api/qbo) // These must match the Intuit callback URL configuration // ===================================================== app.get('/auth/qbo', (req, res) => { const client = getOAuthClient(); const authUri = client.authorizeUri({ scope: [OAuthClient.scopes.Accounting], state: 'intuit-qbo-auth' }); console.log('🔗 Redirecting to QBO Authorization:', authUri); res.redirect(authUri); }); app.get('/auth/qbo/callback', async (req, res) => { const client = getOAuthClient(); try { const authResponse = await client.createToken(req.url); console.log('✅ QBO Authorization erfolgreich!'); saveTokens(); res.redirect('/#settings'); } catch (e) { console.error('❌ QBO Authorization fehlgeschlagen:', e); res.status(500).send(`
${e.message || e}
Zurück zur App `); } }); // ===================================================== // API Routes // ===================================================== app.use('/api/customers', customerRoutes); app.use('/api/quotes', quoteRoutes); app.use('/api/invoices', invoiceRoutes); app.use('/api/payments', paymentRoutes); app.use('/api/qbo', qboRoutes); app.use('/api', settingsRoutes); // Start server async function startServer() { await initBrowser(); app.listen(PORT, () => { console.log(`Quote System running on port ${PORT}`); }); // Start recurring invoice scheduler (checks every 24h) startRecurringScheduler(); startStripePolling(); } // Graceful shutdown process.on('SIGTERM', async () => { if (browser) { await browser.close(); } await pool.end(); process.exit(0); }); startServer(); module.exports = app;