const express = require('express'); const multer = require('multer'); const path = require('path'); const cors = require('cors'); const morgan = require('morgan'); const config = require('./config'); const { query, pool } = require('./db') const authMiddleware = require('./middleware/auth'); const adminAuthMiddleware = require('./middleware/adminAuth'); const settingsAdminRoutes = require('./routes/settingsAdmin'); const SystemSettings = require('./models/SystemSettings'); const fs = require('fs'); // services const notificationService = require('./services/notificationService'); const emailService = require('./services/emailService'); // routes const stripePaymentRoutes = require('./routes/stripePayment'); const productRoutes = require('./routes/products'); const authRoutes = require('./routes/auth'); const cartRoutes = require('./routes/cart'); const productAdminRoutes = require('./routes/productAdmin'); const categoryAdminRoutes = require('./routes/categoryAdmin'); const usersAdminRoutes = require('./routes/userAdmin'); const ordersAdminRoutes = require('./routes/orderAdmin'); const couponsAdminRoutes = require('./routes/couponAdmin'); const userOrdersRoutes = require('./routes/userOrders'); const shippingRoutes = require('./routes/shipping'); const blogRoutes = require('./routes/blog'); const blogAdminRoutes = require('./routes/blogAdmin'); const blogCommentsAdminRoutes = require('./routes/blogCommentsAdmin'); const productReviewsRoutes = require('./routes/productReviews'); const productReviewsAdminRoutes = require('./routes/productReviewsAdmin'); const emailTemplatesAdminRoutes = require('./routes/emailTemplatesAdmin'); const publicSettingsRoutes = require('./routes/publicSettings'); const mailingListRoutes = require('./routes/mailingListAdmin'); const emailCampaignListRoutes = require('./routes/emailCampaignsAdmin'); const subscribersAdminRoutes = require('./routes/subscribersAdmin'); const subscribersRoutes = require('./routes/subscribers'); // Create Express app const app = express(); const port = config.port || 4000; // Ensure uploads directories exist const uploadsDir = path.join(__dirname, '../public/uploads'); const productImagesDir = path.join(uploadsDir, 'products'); if (!fs.existsSync(uploadsDir)) { fs.mkdirSync(uploadsDir, { recursive: true }); } if (!fs.existsSync(productImagesDir)) { fs.mkdirSync(productImagesDir, { recursive: true }); } const blogImagesDir = path.join(uploadsDir, 'blog'); if (!fs.existsSync(blogImagesDir)) { fs.mkdirSync(blogImagesDir, { recursive: true }); } // Configure storage const storage = multer.diskStorage({ destination: (req, file, cb) => { // Determine destination based on upload type if (req.originalUrl.includes('/product')) { cb(null, productImagesDir); } else { cb(null, uploadsDir); } }, filename: (req, file, cb) => { // Create unique filename with original extension const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); const fileExt = path.extname(file.originalname); const safeName = path.basename(file.originalname, fileExt) .toLowerCase() .replace(/[^a-z0-9]/g, '-'); cb(null, `${safeName}-${uniqueSuffix}${fileExt}`); } }); // File filter to only allow images const fileFilter = (req, file, cb) => { // Accept only image files if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('Only image files are allowed!'), false); } }; // Create the multer instance const upload = multer({ storage, fileFilter, limits: { fileSize: 5 * 1024 * 1024 // 5MB limit } }); pool.connect() .then(async () => { console.log('Connected to PostgreSQL database'); // Load settings from database try { const settings = await SystemSettings.getAllSettings(pool, query); if (settings) { // Update config with database settings config.updateFromDatabase(settings); console.log('Loaded settings from database'); } } catch (error) { console.error('Error loading settings from database:', error); } }) .catch(err => console.error('Database connection error:', err)) .finally((() => { let timeInterval = 60 * 1000; if (config.environment === 'prod') { console.log('Setting up scheduled tasks for production environment, 10 mins'); timeInterval = 10 * 60 * 1000; }else { console.log('Setting up scheduled tasks for development environment, 2 mins'); timeInterval = 2 * 60 * 1000; } // Process stock notifications every X minutes setInterval(async () => { try { console.log('Processing low stock notifications...'); const processedCount = await notificationService.processLowStockNotifications(pool, query); console.log(`Processed ${processedCount} low stock notifications`); } catch (error) { console.error('Error processing low stock notifications:', error); } }, timeInterval); })); // Handle SSL proxy headers app.use((req, res, next) => { // Trust X-Forwarded-Proto header from Cloudflare if (req.headers['x-forwarded-proto'] === 'https') { req.secure = true; } next(); }); app.set('trust proxy', true); // Middleware app.use(cors({ origin: '*', credentials: true })); app.use('/api/payment', stripePaymentRoutes(pool, query, authMiddleware(pool, query))); app.use(express.json()); app.use(morgan('dev')); // Serve static files - serve the entire public directory app.use(express.static(path.join(__dirname, '../public'))); // More specific static file serving for images app.use('/images', express.static(path.join(__dirname, '../public/uploads'))); app.use('/uploads', express.static(path.join(__dirname, '../public/uploads'))); app.use('/api/uploads', express.static(path.join(__dirname, '../public/uploads'))); app.use('/api/images', express.static(path.join(__dirname, '../public/uploads'))); app.use('/uploads/blog', express.static(path.join(__dirname, '../public/uploads/blog'))); app.use('/api/uploads/blog', express.static(path.join(__dirname, '../public/uploads/blog'))); if (!fs.existsSync(path.join(__dirname, '../public/uploads'))) { fs.mkdirSync(path.join(__dirname, '../public/uploads'), { recursive: true }); } if (!fs.existsSync(path.join(__dirname, '../public/uploads/products'))) { fs.mkdirSync(path.join(__dirname, '../public/uploads/products'), { recursive: true }); } // For direct access to product images app.use('/products/images', express.static(path.join(__dirname, '../public/uploads/products'))); app.use('/api/products/images', express.static(path.join(__dirname, '../public/uploads/products'))); // Health check route app.get('/health', (req, res) => { res.status(200).json({ status: 'ok', message: 'API is running' }); }); app.use('/api/settings', publicSettingsRoutes(pool, query)); // Upload endpoints // Public upload endpoint (basic) app.post('/api/image/upload', upload.single('image'), (req, res) => { console.log('/api/image/upload'); if (!req.file) { return res.status(400).json({ error: true, message: 'No image file provided' }); } res.json({ success: true, imagePath: `/uploads/${req.file.filename}` }); }); app.get('/api/public-file/:filename', (req, res) => { const { filename } = req.params; // Prevent path traversal attacks if (filename.includes('..') || filename.includes('/')) { return res.status(400).json({ error: true, message: 'Invalid filename' }); } // Serve files from public uploads folder res.sendFile(path.join(__dirname, '../public/uploads', filename)); }); app.use('/api/product-reviews', productReviewsRoutes(pool, query, authMiddleware(pool, query))); app.use('/api/admin/product-reviews', productReviewsAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/users', usersAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/coupons', couponsAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/subscribers', subscribersAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/orders', ordersAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/blog', blogAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/blog-comments', blogCommentsAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/email-templates', emailTemplatesAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/mailing-lists', mailingListRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/email-campaigns', emailCampaignListRoutes(pool, query, adminAuthMiddleware(pool, query))); // Admin-only product image upload app.post('/api/image/product', adminAuthMiddleware(pool, query), upload.single('image'), (req, res) => { console.log('/api/image/product', req.file); if (!req.file) { return res.status(400).json({ error: true, message: 'No image file provided' }); } // Get the relative path to the image const imagePath = `/uploads/products/${req.file.filename}`; res.json({ success: true, imagePath, filename: req.file.filename }); }); // Upload multiple product images (admin only) app.post('/api/image/products', adminAuthMiddleware(pool, query), upload.array('images', 10), (req, res) => { console.log('/api/image/products', req.files); if (!req.files || req.files.length === 0) { return res.status(400).json({ error: true, message: 'No image files provided' }); } // Get the relative paths to the images const imagePaths = req.files.map(file => ({ imagePath: `/uploads/products/${file.filename}`, filename: file.filename })); res.json({ success: true, images: imagePaths }); }); // Delete product image (admin only) app.delete('/api/image/product/:filename', adminAuthMiddleware(pool, query), (req, res) => { try { const { filename } = req.params; // Prevent path traversal attacks if (filename.includes('..') || filename.includes('/')) { return res.status(400).json({ error: true, message: 'Invalid filename' }); } const filePath = path.join(__dirname, '../public/uploads/products', filename); // Check if file exists if (!fs.existsSync(filePath)) { return res.status(404).json({ error: true, message: 'Image not found' }); } // Delete the file fs.unlinkSync(filePath); res.json({ success: true, message: 'Image deleted successfully' }); } catch (error) { console.error('Error deleting image:', error); res.status(500).json({ error: true, message: 'Failed to delete image' }); } }); // Use routes app.use('/api/admin/settings', settingsAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/products', productRoutes(pool, query)); app.use('/api/subscribers', subscribersRoutes(pool, query)); app.use('/api/auth', authRoutes(pool, query)); app.use('/api/user/orders', userOrdersRoutes(pool, query, authMiddleware(pool, query))); app.use('/api/blog', blogRoutes(pool, query, authMiddleware(pool, query))); app.use('/api/cart', cartRoutes(pool, query, authMiddleware(pool, query))); app.use('/api/admin/products', productAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/admin/categories', categoryAdminRoutes(pool, query, adminAuthMiddleware(pool, query))); app.use('/api/shipping', shippingRoutes(pool, query, authMiddleware(pool, query))); // Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: true, message: err.message || 'An unexpected error occurred' }); }); // Start server app.listen(port, () => { console.log(`Server running on port ${port} in ${config.environment} environment`); }); module.exports = app;