const express = require('express'); const cors = require('cors'); const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const mysql = require('mysql2/promise'); const { corsMiddleware, corsErrorHandler } = require('./middleware/cors'); const { initPool } = require('./utils/db'); const productsRouter = require('./routes/products'); const dashboardRouter = require('./routes/dashboard'); const ordersRouter = require('./routes/orders'); const csvRouter = require('./routes/csv'); const analyticsRouter = require('./routes/analytics'); const purchaseOrdersRouter = require('./routes/purchase-orders'); const configRouter = require('./routes/config'); const metricsRouter = require('./routes/metrics'); const vendorsRouter = require('./routes/vendors'); const categoriesRouter = require('./routes/categories'); // Get the absolute path to the .env file const envPath = path.resolve(process.cwd(), '.env'); console.log('Current working directory:', process.cwd()); console.log('Looking for .env file at:', envPath); console.log('.env file exists:', fs.existsSync(envPath)); try { require('dotenv').config({ path: envPath }); console.log('.env file loaded successfully'); console.log('Environment check:', { NODE_ENV: process.env.NODE_ENV || 'not set', PORT: process.env.PORT || 'not set', DB_HOST: process.env.DB_HOST || 'not set', DB_USER: process.env.DB_USER || 'not set', DB_NAME: process.env.DB_NAME || 'not set', }); } catch (error) { console.error('Error loading .env file:', error); } // Ensure required directories exist ['logs', 'uploads'].forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); const app = express(); // Debug middleware to log request details app.use((req, res, next) => { console.log('Request details:', { method: req.method, url: req.url, origin: req.get('Origin'), headers: req.headers }); next(); }); // Apply CORS middleware first, before any other middleware app.use(corsMiddleware); // Body parser middleware app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Initialize database pool const pool = initPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, waitForConnections: true, connectionLimit: process.env.NODE_ENV === 'production' ? 20 : 10, queueLimit: 0, enableKeepAlive: true, keepAliveInitialDelay: 0 }); // Make pool available to routes app.locals.pool = pool; // Routes app.use('/api/products', productsRouter); app.use('/api/dashboard', dashboardRouter); app.use('/api/orders', ordersRouter); app.use('/api/csv', csvRouter); app.use('/api/analytics', analyticsRouter); app.use('/api/purchase-orders', purchaseOrdersRouter); app.use('/api/config', configRouter); app.use('/api/metrics', metricsRouter); app.use('/api/vendors', vendorsRouter); app.use('/api/categories', categoriesRouter); // Basic health check route app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), environment: process.env.NODE_ENV }); }); // CORS error handler - must be before other error handlers app.use(corsErrorHandler); // Error handling middleware - MUST be after routes and CORS error handler app.use((err, req, res, next) => { console.error(`[${new Date().toISOString()}] Error:`, err); // Send detailed error in development, generic in production const error = process.env.NODE_ENV === 'production' ? 'An internal server error occurred' : err.message || err; res.status(err.status || 500).json({ error }); }); // Handle uncaught exceptions process.on('uncaughtException', (err) => { console.error(`[${new Date().toISOString()}] Uncaught Exception:`, err); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { console.error(`[${new Date().toISOString()}] Unhandled Rejection at:`, promise, 'reason:', reason); }); // Test database connection pool.getConnection() .then(connection => { console.log('[Database] Connected successfully'); connection.release(); }) .catch(err => { console.error('[Database] Error connecting:', err); process.exit(1); }); // Initialize client sets for SSE const importClients = new Set(); const updateClients = new Set(); const resetClients = new Set(); const resetMetricsClients = new Set(); // Helper function to send progress to SSE clients const sendProgressToClients = (clients, data) => { clients.forEach(client => { try { client.write(`data: ${JSON.stringify(data)}\n\n`); } catch (error) { console.error('Error sending SSE update:', error); } }); }; // Setup SSE connection const setupSSE = (req, res) => { const { type } = req.params; // Set headers for SSE res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': req.headers.origin || '*', 'Access-Control-Allow-Credentials': 'true' }); // Send initial message res.write('data: {"status":"connected"}\n\n'); // Add client to appropriate set const clientSet = type === 'import' ? importClients : type === 'update' ? updateClients : type === 'reset' ? resetClients : type === 'reset-metrics' ? resetMetricsClients : null; if (clientSet) { clientSet.add(res); // Remove client when connection closes req.on('close', () => { clientSet.delete(res); }); } }; // Update the status endpoint to include reset-metrics app.get('/csv/status', (req, res) => { res.json({ active: !!currentOperation, type: currentOperation?.type || null, progress: currentOperation ? { status: currentOperation.status, operation: currentOperation.operation, current: currentOperation.current, total: currentOperation.total, percentage: currentOperation.percentage } : null }); }); // Update progress endpoint mapping app.get('/csv/:type/progress', (req, res) => { const { type } = req.params; if (!['import', 'update', 'reset', 'reset-metrics'].includes(type)) { res.status(400).json({ error: 'Invalid operation type' }); return; } setupSSE(req, res); }); // Update the cancel endpoint to handle reset-metrics app.post('/csv/cancel', (req, res) => { const { operation } = req.query; if (!currentOperation) { res.status(400).json({ error: 'No operation in progress' }); return; } if (operation && operation.toLowerCase() !== currentOperation.type) { res.status(400).json({ error: 'Operation type mismatch' }); return; } try { // Handle cancellation based on operation type if (currentOperation.type === 'reset-metrics') { // Reset metrics doesn't need special cleanup currentOperation = null; res.json({ message: 'Reset metrics cancelled' }); } else { // ... existing cancellation logic for other operations ... } } catch (error) { console.error('Error during cancellation:', error); res.status(500).json({ error: 'Failed to cancel operation' }); } }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`[Server] Running in ${process.env.NODE_ENV || 'development'} mode on port ${PORT}`); });