250 lines
7.5 KiB
JavaScript
Executable File
250 lines
7.5 KiB
JavaScript
Executable File
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');
|
|
const testConnectionRouter = require('./routes/test-connection');
|
|
|
|
// 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);
|
|
app.use('/api', testConnectionRouter);
|
|
|
|
// 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}`);
|
|
});
|