E-Commerce-Module/backend/src/index.js
2025-04-26 23:43:27 -05:00

268 lines
No EOL
8.1 KiB
JavaScript

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');
// 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'); // Add category admin routes
const usersAdminRoutes = require('./routes/userAdmin');
const ordersAdminRoutes = require('./routes/orderAdmin');
const userOrdersRoutes = require('./routes/userOrders');
// 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 });
}
// 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));
// 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')));
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' });
});
// 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.use('/api/admin/users', usersAdminRoutes(pool, query, adminAuthMiddleware(pool, query)));
app.use('/api/admin/orders', ordersAdminRoutes(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/auth', authRoutes(pool, query));
app.use('/api/user/orders', userOrdersRoutes(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))); // Add category admin routes
// 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;