ajinkya-mhetre-mern
Version:
A MERN starter with frontend and backend folders
1,295 lines (1,111 loc) • 32 kB
Plain Text
// ===== PROJECT STRUCTURE =====
/*
farm-produce-backend/
├── src/
│ ├── config/
│ │ ├── database.js
│ │ └── cloudinary.js
│ ├── controllers/
│ │ ├── authController.js
│ │ ├── adminController.js
│ │ ├── farmerController.js
│ │ └── customerController.js
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── roleCheck.js
│ │ └── upload.js
│ ├── models/
│ │ ├── User.js
│ │ ├── Product.js
│ │ └── Order.js
│ ├── routes/
│ │ ├── auth.js
│ │ ├── admin.js
│ │ ├── farmer.js
│ │ └── customer.js
│ ├── utils/
│ │ └── helpers.js
│ └── app.js
├── uploads/
├── .env
├── .gitignore
├── package.json
└── server.js
*/
// ===== package.json =====
{
"name": "farm-produce-backend",
"version": "1.0.0",
"description": "Backend for Farm Produce Platform",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.5.0",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"helmet": "^7.0.0",
"express-rate-limit": "^6.10.0",
"cloudinary": "^1.40.0",
"socket.io": "^4.7.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
// ===== .env =====
/*
NODE_ENV=development
PORT=5000
MONGODB_URI=mongodb://localhost:27017/farm-produce
JWT_SECRET=your_jwt_secret_key_here
JWT_EXPIRE=30d
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
*/
// ===== server.js =====
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const dotenv = require('dotenv');
const connectDB = require('./src/config/database');
const app = require('./src/app');
dotenv.config();
// Connect to database
connectDB();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: process.env.CLIENT_URL || "http://localhost:3000",
methods: ["GET", "POST"]
}
});
// Socket.IO for real-time order tracking
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
socket.on('join-room', (userId) => {
socket.join(userId);
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
// Make io accessible to routes
app.set('socketio', io);
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// ===== src/config/database.js =====
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI);
console.log(`MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
console.error('Database connection error:', error);
process.exit(1);
}
};
module.exports = connectDB;
// ===== src/config/cloudinary.js =====
const cloudinary = require('cloudinary').v2;
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
module.exports = cloudinary;
// ===== src/models/User.js =====
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: 6
},
phone: {
type: String,
required: [true, 'Phone number is required']
},
role: {
type: String,
enum: ['farmer', 'customer', 'admin'],
required: true
},
isVerified: {
type: Boolean,
default: function() {
return this.role === 'customer' || this.role === 'admin';
}
},
address: {
street: String,
city: String,
state: String,
zipCode: String
},
farmDetails: {
farmName: String,
farmSize: String,
farmLocation: String,
certifications: [String]
}
}, {
timestamps: true
});
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
// ===== src/models/Product.js =====
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Product name is required'],
trim: true
},
description: {
type: String,
required: [true, 'Description is required']
},
category: {
type: String,
enum: ['fruit', 'vegetable'],
required: [true, 'Category is required']
},
price: {
type: Number,
required: [true, 'Price is required'],
min: 0
},
unit: {
type: String,
enum: ['kg', 'gram', 'piece', 'dozen'],
default: 'kg'
},
availableQuantity: {
type: Number,
required: [true, 'Available quantity is required'],
min: 0
},
images: [{
url: String,
public_id: String
}],
farmer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
isActive: {
type: Boolean,
default: true
},
harvestDate: {
type: Date
},
expiryDate: {
type: Date
}
}, {
timestamps: true
});
productSchema.index({ farmer: 1, category: 1 });
productSchema.index({ name: 'text', description: 'text' });
module.exports = mongoose.model('Product', productSchema);
// ===== src/models/Order.js =====
const mongoose = require('mongoose');
const orderItemSchema = new mongoose.Schema({
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
},
quantity: {
type: Number,
required: true,
min: 1
},
price: {
type: Number,
required: true
},
farmer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
});
const orderSchema = new mongoose.Schema({
customer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
items: [orderItemSchema],
totalAmount: {
type: Number,
required: true
},
status: {
type: String,
enum: ['placed', 'processing', 'dispatched', 'delivered', 'cancelled'],
default: 'placed'
},
shippingAddress: {
street: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
zipCode: { type: String, required: true }
},
paymentStatus: {
type: String,
enum: ['pending', 'paid', 'failed'],
default: 'pending'
},
trackingNumber: String,
statusHistory: [{
status: String,
timestamp: { type: Date, default: Date.now },
note: String
}]
}, {
timestamps: true
});
orderSchema.index({ customer: 1, status: 1 });
orderSchema.index({ 'items.farmer': 1 });
module.exports = mongoose.model('Order', orderSchema);
// ===== src/middleware/auth.js =====
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const auth = async (req, res, next) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ message: 'Access denied. No token provided.' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.id).select('-password');
if (!user) {
return res.status(401).json({ message: 'Invalid token.' });
}
req.user = user;
next();
} catch (error) {
res.status(401).json({ message: 'Invalid token.' });
}
};
module.exports = auth;
// ===== src/middleware/roleCheck.js =====
const checkRole = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
message: 'Access denied. Insufficient permissions.'
});
}
next();
};
};
const checkVerified = (req, res, next) => {
if (req.user.role === 'farmer' && !req.user.isVerified) {
return res.status(403).json({
message: 'Account not verified. Please wait for admin approval.'
});
}
next();
};
module.exports = { checkRole, checkVerified };
// ===== src/middleware/upload.js =====
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`);
}
});
const fileFilter = (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed!'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 } // 5MB limit
});
module.exports = upload;
// ===== src/controllers/authController.js =====
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE
});
};
const register = async (req, res) => {
try {
const { name, email, password, phone, role, address, farmDetails } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: 'User already exists' });
}
const userData = {
name,
email,
password,
phone,
role,
address
};
if (role === 'farmer' && farmDetails) {
userData.farmDetails = farmDetails;
}
const user = await User.create(userData);
const token = generateToken(user._id);
res.status(201).json({
success: true,
message: role === 'farmer'
? 'Registration successful. Account pending verification.'
: 'Registration successful',
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
isVerified: user.isVerified
}
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = generateToken(user._id);
res.json({
success: true,
message: 'Login successful',
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
isVerified: user.isVerified
}
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getProfile = async (req, res) => {
try {
res.json({
success: true,
user: req.user
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
module.exports = { register, login, getProfile };
// ===== src/controllers/adminController.js =====
const User = require('../models/User');
const Product = require('../models/Product');
const Order = require('../models/Order');
const getPendingFarmers = async (req, res) => {
try {
const farmers = await User.find({
role: 'farmer',
isVerified: false
}).select('-password');
res.json({
success: true,
farmers
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const verifyFarmer = async (req, res) => {
try {
const { farmerId } = req.params;
const { action } = req.body; // 'approve' or 'reject'
const farmer = await User.findById(farmerId);
if (!farmer || farmer.role !== 'farmer') {
return res.status(404).json({ message: 'Farmer not found' });
}
if (action === 'approve') {
farmer.isVerified = true;
await farmer.save();
res.json({
success: true,
message: 'Farmer verified successfully'
});
} else if (action === 'reject') {
await User.findByIdAndDelete(farmerId);
res.json({
success: true,
message: 'Farmer application rejected'
});
} else {
res.status(400).json({ message: 'Invalid action' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getDashboardStats = async (req, res) => {
try {
const totalUsers = await User.countDocuments();
const totalFarmers = await User.countDocuments({ role: 'farmer', isVerified: true });
const totalCustomers = await User.countDocuments({ role: 'customer' });
const pendingFarmers = await User.countDocuments({ role: 'farmer', isVerified: false });
const totalProducts = await Product.countDocuments();
const totalOrders = await Order.countDocuments();
res.json({
success: true,
stats: {
totalUsers,
totalFarmers,
totalCustomers,
pendingFarmers,
totalProducts,
totalOrders
}
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getAllUsers = async (req, res) => {
try {
const users = await User.find().select('-password');
res.json({
success: true,
users
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
module.exports = {
getPendingFarmers,
verifyFarmer,
getDashboardStats,
getAllUsers
};
// ===== src/controllers/farmerController.js =====
const Product = require('../models/Product');
const Order = require('../models/Order');
const cloudinary = require('../config/cloudinary');
const fs = require('fs');
const addProduct = async (req, res) => {
try {
const { name, description, category, price, unit, availableQuantity, harvestDate, expiryDate } = req.body;
const productData = {
name,
description,
category,
price: parseFloat(price),
unit,
availableQuantity: parseInt(availableQuantity),
farmer: req.user._id,
harvestDate: harvestDate ? new Date(harvestDate) : undefined,
expiryDate: expiryDate ? new Date(expiryDate) : undefined
};
// Handle image uploads
if (req.files && req.files.length > 0) {
const imageUploadPromises = req.files.map(async (file) => {
const result = await cloudinary.uploader.upload(file.path, {
folder: 'farm-products',
transformation: [
{ width: 800, height: 600, crop: 'fill' }
]
});
// Delete local file after upload
fs.unlinkSync(file.path);
return {
url: result.secure_url,
public_id: result.public_id
};
});
productData.images = await Promise.all(imageUploadPromises);
}
const product = await Product.create(productData);
await product.populate('farmer', 'name email');
res.status(201).json({
success: true,
message: 'Product added successfully',
product
});
} catch (error) {
// Clean up uploaded files if error occurs
if (req.files) {
req.files.forEach(file => {
if (fs.existsSync(file.path)) {
fs.unlinkSync(file.path);
}
});
}
res.status(500).json({ message: error.message });
}
};
const getMyProducts = async (req, res) => {
try {
const products = await Product.find({ farmer: req.user._id })
.populate('farmer', 'name email')
.sort({ createdAt: -1 });
res.json({
success: true,
products
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const updateProduct = async (req, res) => {
try {
const { productId } = req.params;
const updates = req.body;
const product = await Product.findOne({
_id: productId,
farmer: req.user._id
});
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
Object.keys(updates).forEach(key => {
if (updates[key] !== undefined) {
product[key] = updates[key];
}
});
await product.save();
await product.populate('farmer', 'name email');
res.json({
success: true,
message: 'Product updated successfully',
product
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const deleteProduct = async (req, res) => {
try {
const { productId } = req.params;
const product = await Product.findOne({
_id: productId,
farmer: req.user._id
});
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
// Delete images from cloudinary
if (product.images && product.images.length > 0) {
const deletePromises = product.images.map(image =>
cloudinary.uploader.destroy(image.public_id)
);
await Promise.all(deletePromises);
}
await Product.findByIdAndDelete(productId);
res.json({
success: true,
message: 'Product deleted successfully'
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getMyOrders = async (req, res) => {
try {
const orders = await Order.find({ 'items.farmer': req.user._id })
.populate('customer', 'name email phone')
.populate('items.product', 'name images')
.sort({ createdAt: -1 });
res.json({
success: true,
orders
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const updateOrderStatus = async (req, res) => {
try {
const { orderId } = req.params;
const { status, note } = req.body;
const order = await Order.findOne({
_id: orderId,
'items.farmer': req.user._id
});
if (!order) {
return res.status(404).json({ message: 'Order not found' });
}
order.status = status;
order.statusHistory.push({
status,
note: note || `Order ${status}`,
timestamp: new Date()
});
await order.save();
// Emit real-time update
const io = req.app.get('socketio');
io.to(order.customer.toString()).emit('orderStatusUpdate', {
orderId: order._id,
status: order.status,
timestamp: new Date()
});
res.json({
success: true,
message: 'Order status updated successfully',
order
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getDashboardStats = async (req, res) => {
try {
const totalProducts = await Product.countDocuments({ farmer: req.user._id });
const activeProducts = await Product.countDocuments({
farmer: req.user._id,
isActive: true
});
const totalOrders = await Order.countDocuments({ 'items.farmer': req.user._id });
const pendingOrders = await Order.countDocuments({
'items.farmer': req.user._id,
status: { $in: ['placed', 'processing'] }
});
res.json({
success: true,
stats: {
totalProducts,
activeProducts,
totalOrders,
pendingOrders
}
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
module.exports = {
addProduct,
getMyProducts,
updateProduct,
deleteProduct,
getMyOrders,
updateOrderStatus,
getDashboardStats
};
// ===== src/controllers/customerController.js =====
const Product = require('../models/Product');
const Order = require('../models/Order');
const User = require('../models/User');
const getAllProducts = async (req, res) => {
try {
const { category, search, page = 1, limit = 12, sortBy = 'createdAt' } = req.query;
const query = { isActive: true, availableQuantity: { $gt: 0 } };
if (category) {
query.category = category;
}
if (search) {
query.$text = { $search: search };
}
const skip = (page - 1) * limit;
const products = await Product.find(query)
.populate('farmer', 'name farmDetails.farmName')
.sort({ [sortBy]: -1 })
.limit(limit * 1)
.skip(skip);
const total = await Product.countDocuments(query);
res.json({
success: true,
products,
pagination: {
page: parseInt(page),
pages: Math.ceil(total / limit),
total
}
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getProduct = async (req, res) => {
try {
const { productId } = req.params;
const product = await Product.findById(productId)
.populate('farmer', 'name email phone farmDetails');
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
res.json({
success: true,
product
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const placeOrder = async (req, res) => {
try {
const { items, shippingAddress } = req.body;
let totalAmount = 0;
const orderItems = [];
// Validate and calculate order
for (const item of items) {
const product = await Product.findById(item.productId);
if (!product) {
return res.status(404).json({
message: `Product ${item.productId} not found`
});
}
if (product.availableQuantity < item.quantity) {
return res.status(400).json({
message: `Insufficient stock for ${product.name}. Available: ${product.availableQuantity}`
});
}
const itemTotal = product.price * item.quantity;
totalAmount += itemTotal;
orderItems.push({
product: product._id,
quantity: item.quantity,
price: product.price,
farmer: product.farmer
});
// Update product quantity
product.availableQuantity -= item.quantity;
await product.save();
}
const order = await Order.create({
customer: req.user._id,
items: orderItems,
totalAmount,
shippingAddress,
statusHistory: [{
status: 'placed',
timestamp: new Date(),
note: 'Order placed successfully'
}]
});
await order.populate([
{ path: 'customer', select: 'name email' },
{ path: 'items.product', select: 'name images' },
{ path: 'items.farmer', select: 'name' }
]);
res.status(201).json({
success: true,
message: 'Order placed successfully',
order
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getMyOrders = async (req, res) => {
try {
const orders = await Order.find({ customer: req.user._id })
.populate('items.product', 'name images')
.populate('items.farmer', 'name')
.sort({ createdAt: -1 });
res.json({
success: true,
orders
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getOrder = async (req, res) => {
try {
const { orderId } = req.params;
const order = await Order.findOne({
_id: orderId,
customer: req.user._id
})
.populate('items.product', 'name images')
.populate('items.farmer', 'name phone');
if (!order) {
return res.status(404).json({ message: 'Order not found' });
}
res.json({
success: true,
order
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const cancelOrder = async (req, res) => {
try {
const { orderId } = req.params;
const order = await Order.findOne({
_id: orderId,
customer: req.user._id
}).populate('items.product');
if (!order) {
return res.status(404).json({ message: 'Order not found' });
}
if (order.status !== 'placed') {
return res.status(400).json({
message: 'Order cannot be cancelled at this stage'
});
}
// Restore product quantities
for (const item of order.items) {
await Product.findByIdAndUpdate(item.product._id, {
$inc: { availableQuantity: item.quantity }
});
}
order.status = 'cancelled';
order.statusHistory.push({
status: 'cancelled',
timestamp: new Date(),
note: 'Order cancelled by customer'
});
await order.save();
res.json({
success: true,
message: 'Order cancelled successfully'
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
module.exports = {
getAllProducts,
getProduct,
placeOrder,
getMyOrders,
getOrder,
cancelOrder
};
// ===== src/routes/auth.js =====
const express = require('express');
const router = express.Router();
const { register, login, getProfile } = require('../controllers/authController');
const auth = require('../middleware/auth');
router.post('/register', register);
router.post('/login', login);
router.get('/profile', auth, getProfile);
module.exports = router;
// ===== src/routes/admin.js =====
const express = require('express');
const router = express.Router();
const {
getPendingFarmers,
verifyFarmer,
getDashboardStats,
getAllUsers
} = require('../controllers/adminController');
const auth = require('../middleware/auth');
const { checkRole } = require('../middleware/roleCheck');
// Apply middleware to all admin routes
router.use(auth);
router.use(checkRole('admin'));
router.get('/dashboard-stats', getDashboardStats);
router.get('/pending-farmers', getPendingFarmers);
router.put('/verify-farmer/:farmerId', verifyFarmer);
router.get('/users', getAllUsers);
module.exports = router;
// ===== src/routes/farmer.js =====
const express = require('express');
const router = express.Router();
const {
addProduct,
getMyProducts,
updateProduct,
deleteProduct,
getMyOrders,
updateOrderStatus,
getDashboardStats
} = require('../controllers/farmerController');
const auth = require('../middleware/auth');
const { checkRole, checkVerified } = require('../middleware/roleCheck');
const upload = require('../middleware/upload');
// Apply middleware to all farmer routes
router.use(auth);
router.use(checkRole('farmer'));
router.use(checkVerified);
// Dashboard
router.get('/dashboard-stats', getDashboardStats);
// Product management
router.post('/products', upload.array('images', 5), addProduct);
router.get('/products', getMyProducts);
router.put('/products/:productId', updateProduct);
router.delete('/products/:productId', deleteProduct);
// Order management
router.get('/orders', getMyOrders);
router.put('/orders/:orderId/status', updateOrderStatus);
module.exports = router;
// ===== src/routes/customer.js =====
const express = require('express');
const router = express.Router();
const {
getAllProducts,
getProduct,
placeOrder,
getMyOrders,
getOrder,
cancelOrder
} = require('../controllers/customerController');
const auth = require('../middleware/auth');
const { checkRole } = require('../middleware/roleCheck');
// Public routes (no auth required)
router.get('/products', getAllProducts);
router.get('/products/:productId', getProduct);
// Protected customer routes
router.use(auth);
router.use(checkRole('customer'));
router.post('/orders', placeOrder);
router.get('/orders', getMyOrders);
router.get('/orders/:orderId', getOrder);
router.put('/orders/:orderId/cancel', cancelOrder);
module.exports = router;
// ===== src/utils/helpers.js =====
const generateTrackingNumber = () => {
const timestamp = Date.now().toString(36).toUpperCase();
const random = Math.random().toString(36).substr(2, 5).toUpperCase();
return `FP${timestamp}${random}`;
};
const formatPrice = (price) => {
return new Intl.NumberFormat('en-IN', {
style: 'currency',
currency: 'INR'
}).format(price);
};
const calculateDeliveryDate = (days = 3) => {
const date = new Date();
date.setDate(date.getDate() + days);
return date;
};
module.exports = {
generateTrackingNumber,
formatPrice,
calculateDeliveryDate
};
// ===== src/app.js =====
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const path = require('path');
// Import routes
const authRoutes = require('./routes/auth');
const adminRoutes = require('./routes/admin');
const farmerRoutes = require('./routes/farmer');
const customerRoutes = require('./routes/customer');
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.CLIENT_URL || "http://localhost:3000",
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);
// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Static files
app.use('/uploads', express.static(path.join(__dirname, '../uploads')));
// API routes
app.use('/api/auth', authRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/farmer', farmerRoutes);
app.use('/api/customer', customerRoutes);
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
message: 'Route not found'
});
});
// Global error handler
app.use((error, req, res, next) => {
console.error('Error:', error);
if (error.name === 'ValidationError') {
const errors = Object.values(error.errors).map(err => err.message);
return res.status(400).json({
message: 'Validation Error',
errors
});
}
if (error.code === 11000) {
const field = Object.keys(error.keyValue)[0];
return res.status(400).json({
message: `${field} already exists`
});
}
res.status(500).json({
message: process.env.NODE_ENV === 'production'
? 'Internal server error'
: error.message
});
});
module.exports = app;
// ===== .gitignore =====
/*
node_modules/
uploads/
.env
.env.local
.env.production
.DS_Store
*.log
dist/
build/
coverage/
.nyc_output/
*/