| const Admin = require('../models/adminModel'); |
| const User = require('../models/userModel'); |
| const ProductModel = require('../models/productModel'); |
| const asyncHandler = require('express-async-handler'); |
| const jwt = require('jsonwebtoken'); |
| const bcrypt = require('bcryptjs'); |
| const multer = require('multer'); |
| const path = require('path'); |
| const fs = require('fs'); |
| const { getNextImageNumber, isValidImageFile } = require('../utils/imageNumbering'); |
|
|
| const generateAdminToken = (res, adminId) => { |
| const token = jwt.sign({ id: adminId }, process.env.JWT_SECRET_KEY, { |
| expiresIn: '24h', |
| }); |
|
|
| const isProduction = process.env.NODE_ENV === 'production'; |
| |
| res.cookie('adminToken', token, { |
| httpOnly: true, |
| secure: isProduction, |
| sameSite: isProduction ? 'none' : 'lax', |
| maxAge: 24 * 60 * 60 * 1000, |
| ...(isProduction && process.env.COOKIE_DOMAIN && { |
| domain: process.env.COOKIE_DOMAIN |
| }) |
| }); |
|
|
| return token; |
| }; |
|
|
| const adminLogin = asyncHandler(async (req, res) => { |
| const { username, password } = req.body; |
|
|
| if (!username || !password) { |
| res.status(400); |
| throw new Error('Please provide username and password'); |
| } |
|
|
| const admin = await Admin.findOne({ |
| $or: [{ username }, { email: username }] |
| }); |
|
|
| if (!admin) { |
| res.status(401); |
| throw new Error('Invalid credentials'); |
| } |
|
|
| if (!admin.isActive) { |
| res.status(401); |
| throw new Error('Account is deactivated'); |
| } |
|
|
| const isPasswordValid = await admin.checkPassword(password); |
|
|
| if (!isPasswordValid) { |
| res.status(401); |
| throw new Error('Invalid credentials'); |
| } |
|
|
| await Admin.findByIdAndUpdate(admin._id, { lastLogin: new Date() }); |
|
|
| const token = generateAdminToken(res, admin._id); |
|
|
| res.status(200).json({ |
| _id: admin._id, |
| username: admin.username, |
| email: admin.email, |
| firstName: admin.firstName, |
| lastName: admin.lastName, |
| role: admin.role, |
| permissions: admin.permissions, |
| avatar: admin.avatar, |
| token |
| }); |
| }); |
|
|
| const adminLogout = asyncHandler(async (req, res) => { |
| const isProduction = process.env.NODE_ENV === 'production'; |
| |
| res.cookie('adminToken', '', { |
| httpOnly: true, |
| expires: new Date(0), |
| secure: isProduction, |
| sameSite: isProduction ? 'none' : 'lax', |
| ...(isProduction && process.env.COOKIE_DOMAIN && { |
| domain: process.env.COOKIE_DOMAIN |
| }) |
| }); |
|
|
| res.status(200).json({ message: 'Logged out successfully' }); |
| }); |
|
|
| const getAdminProfile = asyncHandler(async (req, res) => { |
| const admin = await Admin.findById(req.admin._id).select('-password'); |
| res.status(200).json(admin); |
| }); |
|
|
| const updateAdminProfile = asyncHandler(async (req, res) => { |
| const { firstName, lastName, email, avatar, preferences } = req.body; |
|
|
| const admin = await Admin.findById(req.admin._id); |
|
|
| if (admin) { |
| admin.firstName = firstName || admin.firstName; |
| admin.lastName = lastName || admin.lastName; |
| admin.email = email || admin.email; |
| admin.avatar = avatar || admin.avatar; |
| admin.preferences = { ...admin.preferences, ...preferences }; |
|
|
| const updatedAdmin = await admin.save(); |
|
|
| res.status(200).json({ |
| _id: updatedAdmin._id, |
| username: updatedAdmin.username, |
| email: updatedAdmin.email, |
| firstName: updatedAdmin.firstName, |
| lastName: updatedAdmin.lastName, |
| role: updatedAdmin.role, |
| permissions: updatedAdmin.permissions, |
| avatar: updatedAdmin.avatar, |
| preferences: updatedAdmin.preferences |
| }); |
| } else { |
| res.status(404); |
| throw new Error('Admin not found'); |
| } |
| }); |
|
|
| const getAllUsers = asyncHandler(async (req, res) => { |
| const page = parseInt(req.query.page) || 1; |
| const limit = parseInt(req.query.limit) || 10; |
| const search = req.query.search || ''; |
| const filter = req.query.filter || 'all'; |
|
|
| const skip = (page - 1) * limit; |
|
|
| const andConditions = []; |
|
|
| if (search) { |
| andConditions.push({ |
| $or: [ |
| { name: { $regex: search, $options: 'i' } }, |
| { email: { $regex: search, $options: 'i' } } |
| ] |
| }); |
| } |
|
|
| if (filter === 'active') { |
| andConditions.push({ |
| $or: [ |
| { isActive: true }, |
| { isActive: { $exists: false } } |
| ] |
| }); |
| } else if (filter === 'inactive') { |
| andConditions.push({ isActive: false }); |
| } |
|
|
| const query = andConditions.length > 0 ? { $and: andConditions } : {}; |
|
|
| const users = await User.find(query) |
| .select('-password') |
| .sort({ createdAt: -1 }) |
| .skip(skip) |
| .limit(limit); |
|
|
| const usersWithDetails = users.map(user => { |
| let recentAddress = '', recentCity = '', recentPhone = ''; |
| if (user.orders && user.orders.length > 0) { |
| |
| const latestOrder = user.orders.reduce((latest, curr) => { |
| return (!latest || (curr.createdAt > latest.createdAt)) ? curr : latest; |
| }, null); |
| if (latestOrder && latestOrder.shippingAddress) { |
| recentAddress = latestOrder.shippingAddress.address || latestOrder.shippingAddress.fullName || ''; |
| recentCity = latestOrder.shippingAddress.city || ''; |
| recentPhone = latestOrder.shippingAddress.phone || ''; |
| } |
| } |
| return { |
| _id: user._id, |
| name: user.name, |
| email: user.email, |
| |
| isActive: typeof user.isActive === 'boolean' ? user.isActive : true, |
| createdAt: user.createdAt, |
| recentAddress, |
| recentCity, |
| recentPhone, |
| country: user.country || '', |
| gender: user.gender || '', |
| dateOfBirth: user.dateOfBirth || '' |
| }; |
| }); |
|
|
| const total = await User.countDocuments(query); |
|
|
| res.status(200).json({ |
| users: usersWithDetails, |
| pagination: { |
| page, |
| limit, |
| total, |
| pages: Math.ceil(total / limit) |
| } |
| }); |
| }); |
|
|
| const getUserById = asyncHandler(async (req, res) => { |
| const user = await User.findById(req.params.id).select('-password'); |
| |
| let recentAddress = '', recentCity = '', recentPhone = ''; |
| if (user && user.orders && user.orders.length > 0) { |
| const latestOrder = user.orders.reduce((latest, curr) => { |
| return (!latest || (curr.createdAt > latest.createdAt)) ? curr : latest; |
| }, null); |
| if (latestOrder && latestOrder.shippingAddress) { |
| recentAddress = latestOrder.shippingAddress.address || latestOrder.shippingAddress.fullName || ''; |
| recentCity = latestOrder.shippingAddress.city || ''; |
| recentPhone = latestOrder.shippingAddress.phone || ''; |
| } |
| } |
|
|
| if (user) { |
| res.status(200).json({ |
| _id: user._id, |
| name: user.name, |
| email: user.email, |
| |
| isActive: typeof user.isActive === 'boolean' ? user.isActive : true, |
| createdAt: user.createdAt, |
| recentAddress, |
| recentCity, |
| recentPhone, |
| country: user.country || '', |
| gender: user.gender || '', |
| dateOfBirth: user.dateOfBirth || '' |
| }); |
| } else { |
| res.status(404); |
| throw new Error('User not found'); |
| } |
| }); |
|
|
| const updateUser = asyncHandler(async (req, res) => { |
| const { name, email, isActive } = req.body; |
|
|
| const user = await User.findById(req.params.id); |
|
|
| if (user) { |
| user.name = name || user.name; |
| user.email = email || user.email; |
| if (typeof isActive === 'boolean') { |
| user.isActive = isActive; |
| } |
|
|
| const updatedUser = await user.save(); |
|
|
| res.status(200).json({ |
| _id: updatedUser._id, |
| name: updatedUser.name, |
| email: updatedUser.email, |
| isActive: updatedUser.isActive |
| }); |
| } else { |
| res.status(404); |
| throw new Error('User not found'); |
| } |
| }); |
|
|
| const deleteUser = asyncHandler(async (req, res) => { |
| const user = await User.findById(req.params.id); |
|
|
| if (user) { |
| await User.findByIdAndDelete(req.params.id); |
| res.status(200).json({ message: 'User deleted successfully' }); |
| } else { |
| res.status(404); |
| throw new Error('User not found'); |
| } |
| }); |
|
|
| const createUser = asyncHandler(async (req, res) => { |
| const { name, email, password, isActive = true } = req.body; |
|
|
| if (!name || !email || !password) { |
| res.status(400); |
| throw new Error('Please provide name, email, and password'); |
| } |
|
|
| const existingUser = await User.findOne({ email }); |
| if (existingUser) { |
| res.status(400); |
| throw new Error('User with this email already exists'); |
| } |
|
|
| const user = await User.create({ |
| name, |
| email, |
| password, |
| isActive, |
| createdAt: new Date(), |
| }); |
|
|
| res.status(201).json({ |
| _id: user._id, |
| name: user.name, |
| email: user.email, |
| isActive: user.isActive, |
| createdAt: user.createdAt |
| }); |
| }); |
|
|
| const getAllProducts = asyncHandler(async (req, res) => { |
| const page = parseInt(req.query.page) || 1; |
| const limit = parseInt(req.query.limit) || 10; |
| const search = req.query.search || ''; |
| const category = req.query.category || ''; |
| const sortBy = req.query.sortBy || 'createdAt'; |
| const sortOrder = req.query.sortOrder || 'desc'; |
|
|
| const skip = (page - 1) * limit; |
|
|
| let query = {}; |
|
|
| if (search) { |
| query.name = { $regex: search, $options: 'i' }; |
| } |
|
|
| if (category) { |
| query.category = category; |
| } |
|
|
| const sortOptions = {}; |
| sortOptions[sortBy] = sortOrder === 'desc' ? -1 : 1; |
|
|
| const products = await ProductModel.find(query) |
| .sort(sortOptions) |
| .skip(skip) |
| .limit(limit); |
|
|
| const total = await ProductModel.countDocuments(query); |
|
|
| res.status(200).json({ |
| products, |
| pagination: { |
| page, |
| limit, |
| total, |
| pages: Math.ceil(total / limit) |
| } |
| }); |
| }); |
|
|
| const createProduct = asyncHandler(async (req, res) => { |
| const { |
| name, |
| price, |
| description, |
| category, |
| seller, |
| stock, |
| images, |
| specifications, |
| warrantyDetails, |
| afterSalesSupport, |
| ratings, |
| numOfReviews, |
| PremiumBadge, |
| reviews |
| } = req.body; |
|
|
| if (!name || !price || !category) { |
| res.status(400); |
| throw new Error('Please provide name, price, and category'); |
| } |
|
|
| await ensureCategoryExists(category); |
|
|
| console.log('Creating product with images:', images); |
|
|
| const product = await ProductModel.create({ |
| name, |
| price, |
| description, |
| category, |
| seller, |
| stock: stock || 0, |
| images: images || [], |
| specifications, |
| warrantyDetails, |
| afterSalesSupport, |
| ratings: ratings , |
| numOfReviews: numOfReviews || 0, |
| PremiumBadge, |
|
|
| reviews: reviews || [] |
| }); |
|
|
| console.log('Product created successfully:', product._id); |
| res.status(201).json(product); |
| }); |
|
|
| const updateProduct = asyncHandler(async (req, res) => { |
| const { |
| name, |
| price, |
| description, |
| category, |
| seller, |
| stock, |
| images, |
| specifications, |
| warrantyDetails, |
| afterSalesSupport, |
| ratings, |
| numOfReviews, |
| PremiumBadge, |
| reviews |
| } = req.body; |
|
|
| const product = await ProductModel.findById(req.params.id); |
|
|
| if (product) { |
| console.log('Updating product with images:', images); |
|
|
| if (category && category !== product.category) { |
| await ensureCategoryExists(category); |
| } |
| |
| product.name = name || product.name; |
| product.price = price || product.price; |
| product.description = description || product.description; |
| product.category = category || product.category; |
| product.seller = seller || product.seller; |
| product.stock = stock !== undefined ? stock : product.stock; |
| product.images = images || product.images; |
| product.specifications = specifications || product.specifications; |
| product.warrantyDetails = warrantyDetails || product.warrantyDetails; |
| product.afterSalesSupport = afterSalesSupport || product.afterSalesSupport; |
| product.ratings = ratings ||product.ratings; |
| product.numOfReviews = numOfReviews !== undefined ? numOfReviews : product.numOfReviews; |
| product.PremiumBadge = PremiumBadge !== undefined ? PremiumBadge : product.PremiumBadge; |
|
|
| product.reviews = reviews || product.reviews; |
|
|
| const updatedProduct = await product.save(); |
| console.log('Product updated successfully:', updatedProduct._id); |
| res.status(200).json(updatedProduct); |
| } else { |
| res.status(404); |
| throw new Error('Product not found'); |
| } |
| }); |
|
|
| const deleteProduct = asyncHandler(async (req, res) => { |
| const product = await ProductModel.findById(req.params.id); |
|
|
| if (product) { |
| await ProductModel.findByIdAndDelete(req.params.id); |
| res.status(200).json({ message: 'Product deleted successfully' }); |
| } else { |
| res.status(404); |
| throw new Error('Product not found'); |
| } |
| }); |
|
|
| const uploadProductImages = asyncHandler(async (req, res) => { |
| try { |
| console.log('Upload request received:', req.files); |
| |
| if (!req.files || req.files.length === 0) { |
| res.status(400); |
| throw new Error('No files uploaded'); |
| } |
|
|
| const uploadedImages = req.files.map(file => { |
| |
| const imagePath = `/images/products/${file.filename}`; |
| console.log('Storing image path in database:', imagePath); |
| return { |
| image: imagePath |
| }; |
| }); |
|
|
| console.log('Uploaded images:', uploadedImages); |
|
|
| res.status(200).json({ |
| success: true, |
| images: uploadedImages, |
| message: `${uploadedImages.length} image(s) uploaded successfully` |
| }); |
| } catch (error) { |
| console.error('Upload error:', error); |
| res.status(500).json({ |
| success: false, |
| message: 'Error uploading images', |
| error: error.message |
| }); |
| } |
| }); |
|
|
| const getDashboardStats = asyncHandler(async (req, res) => { |
| const today = new Date(); |
| const startOfDay = new Date(today.setHours(0, 0, 0, 0)); |
| const startOfWeek = new Date(today.setDate(today.getDate() - today.getDay())); |
| const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); |
| const startOfYear = new Date(today.getFullYear(), 0, 1); |
|
|
| const [ |
| totalUsers, |
| activeUsers, |
| newUsersToday, |
| newUsersThisWeek, |
| newUsersThisMonth, |
| newUsersThisYear, |
| totalProducts, |
| lowStockProducts, |
| outOfStockProducts, |
| highRatedProducts, |
| topProducts |
| ] = await Promise.all([ |
| User.countDocuments(), |
| |
| User.countDocuments({ $or: [{ isActive: true }, { isActive: { $exists: false } }] }), |
| User.countDocuments({ createdAt: { $gte: startOfDay } }), |
| User.countDocuments({ createdAt: { $gte: startOfWeek } }), |
| User.countDocuments({ createdAt: { $gte: startOfMonth } }), |
| User.countDocuments({ createdAt: { $gte: startOfYear } }), |
| ProductModel.countDocuments(), |
| ProductModel.countDocuments({ stock: { $lt: 10, $gt: 0 } }), |
| ProductModel.countDocuments({ stock: 0 }), |
| ProductModel.countDocuments({ ratings: { $gte: 4 } }), |
| ProductModel.aggregate([ |
| { $sort: { ratings: -1, numOfReviews: -1 } }, |
| { $limit: 8 }, |
| { |
| $project: { |
| name: 1, |
| price: 1, |
| ratings: 1, |
| numOfReviews: 1, |
| stock: 1, |
| category: 1, |
| images: 1 |
| } |
| } |
| ]) |
| ]); |
|
|
| const orderStats = await User.aggregate([ |
| { $unwind: '$orders' }, |
| { |
| $group: { |
| _id: null, |
| totalOrders: { $sum: 1 }, |
| totalRevenue: { $sum: '$orders.amount' }, |
| todayOrders: { |
| $sum: { |
| $cond: [ |
| { $gte: ['$orders.createdAt', startOfDay] }, |
| 1, |
| 0 |
| ] |
| } |
| }, |
| todayRevenue: { |
| $sum: { |
| $cond: [ |
| { $gte: ['$orders.createdAt', startOfDay] }, |
| '$orders.amount', |
| 0 |
| ] |
| } |
| }, |
| weekOrders: { |
| $sum: { |
| $cond: [ |
| { $gte: ['$orders.createdAt', startOfWeek] }, |
| 1, |
| 0 |
| ] |
| } |
| }, |
| weekRevenue: { |
| $sum: { |
| $cond: [ |
| { $gte: ['$orders.createdAt', startOfWeek] }, |
| '$orders.amount', |
| 0 |
| ] |
| } |
| }, |
| monthOrders: { |
| $sum: { |
| $cond: [ |
| { $gte: ['$orders.createdAt', startOfMonth] }, |
| 1, |
| 0 |
| ] |
| } |
| }, |
| monthRevenue: { |
| $sum: { |
| $cond: [ |
| { $gte: ['$orders.createdAt', startOfMonth] }, |
| '$orders.amount', |
| 0 |
| ] |
| } |
| }, |
| pendingOrders: { |
| $sum: { |
| $cond: [ |
| { $eq: [{ $toLower: '$orders.status' }, 'placed'] }, |
| 1, |
| 0 |
| ] |
| } |
| }, |
| processingOrders: { |
| $sum: { |
| $cond: [ |
| { $eq: [{ $toLower: '$orders.status' }, 'processing'] }, |
| 1, |
| 0 |
| ] |
| } |
| }, |
| shippedOrders: { |
| $sum: { |
| $cond: [ |
| { $eq: [{ $toLower: '$orders.status' }, 'shipped'] }, |
| 1, |
| 0 |
| ] |
| } |
| }, |
| deliveredOrders: { |
| $sum: { |
| $cond: [ |
| { $eq: [{ $toLower: '$orders.status' }, 'delivered'] }, |
| 1, |
| 0 |
| ] |
| } |
| } |
| } |
| } |
| ]); |
|
|
| const stats = orderStats[0] || { |
| totalOrders: 0, |
| totalRevenue: 0, |
| todayOrders: 0, |
| todayRevenue: 0, |
| weekOrders: 0, |
| weekRevenue: 0, |
| monthOrders: 0, |
| monthRevenue: 0, |
| pendingOrders: 0, |
| processingOrders: 0, |
| shippedOrders: 0, |
| deliveredOrders: 0 |
| }; |
|
|
| const recentOrders = await User.aggregate([ |
| { $unwind: '$orders' }, |
| { $sort: { 'orders.createdAt': -1 } }, |
| { $limit: 10 }, |
| { |
| $project: { |
| orderId: '$orders._id', |
| userId: '$_id', |
| userName: '$name', |
| userEmail: '$email', |
| amount: '$orders.amount', |
| status: '$orders.status', |
| createdAt: '$orders.createdAt', |
| items: '$orders.orderItems' |
| } |
| } |
| ]); |
|
|
| const salesTrends = await User.aggregate([ |
| { $unwind: '$orders' }, |
| { |
| $match: { |
| 'orders.createdAt': { |
| $gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) |
| } |
| } |
| }, |
| { |
| $group: { |
| _id: { |
| $dateToString: { |
| format: '%Y-%m-%d', |
| date: '$orders.createdAt' |
| } |
| }, |
| orders: { $sum: 1 }, |
| revenue: { $sum: '$orders.amount' } |
| } |
| }, |
| { $sort: { _id: 1 } } |
| ]); |
|
|
| const categoryStats = await ProductModel.aggregate([ |
| { $group: { _id: '$category', count: { $sum: 1 } } }, |
| { $sort: { count: -1 } } |
| ]); |
|
|
| res.status(200).json({ |
| users: { |
| total: totalUsers, |
| active: activeUsers, |
| newToday: newUsersToday, |
| newThisWeek: newUsersThisWeek, |
| newThisMonth: newUsersThisMonth, |
| newThisYear: newUsersThisYear, |
| growthRate: totalUsers > 0 ? ((newUsersThisMonth / totalUsers) * 100).toFixed(2) : 0 |
| }, |
| products: { |
| total: totalProducts, |
| lowStock: lowStockProducts, |
| outOfStock: outOfStockProducts, |
| highRated: highRatedProducts, |
| categories: categoryStats |
| }, |
| orders: { |
| total: stats.totalOrders, |
| totalRevenue: stats.totalRevenue, |
| todayOrders: stats.todayOrders, |
| todayRevenue: stats.todayRevenue, |
| weekOrders: stats.weekOrders, |
| weekRevenue: stats.weekRevenue, |
| monthOrders: stats.monthOrders, |
| monthRevenue: stats.monthRevenue, |
| statusBreakdown: { |
| pending: stats.pendingOrders, |
| processing: stats.processingOrders, |
| shipped: stats.shippedOrders, |
| delivered: stats.deliveredOrders |
| }, |
| averageOrderValue: stats.totalOrders > 0 ? (stats.totalRevenue / stats.totalOrders).toFixed(2) : 0 |
| }, |
| topProducts, |
| latestOrders: recentOrders, |
| salesTrends, |
| systemHealth: { |
| database: 'healthy', |
| uptime: process.uptime(), |
| memory: process.memoryUsage(), |
| timestamp: new Date().toISOString() |
| } |
| }); |
| }); |
|
|
| const getAllOrders = asyncHandler(async (req, res) => { |
| const page = parseInt(req.query.page) || 1; |
| const limit = parseInt(req.query.limit) || 10; |
| const status = req.query.status || 'all'; |
| const period = req.query.period || '30d'; |
|
|
| const skip = (page - 1) * limit; |
|
|
| let startDate; |
| const endDate = new Date(); |
| |
| switch (period) { |
| case '7d': |
| startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000); |
| break; |
| case '30d': |
| startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000); |
| break; |
| case '90d': |
| startDate = new Date(endDate.getTime() - 90 * 24 * 60 * 60 * 1000); |
| break; |
| default: |
| startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000); |
| } |
|
|
| const users = await User.find({ |
| 'orders.createdAt': { $gte: startDate, $lte: endDate } |
| }); |
|
|
| let allOrders = []; |
| users.forEach(user => { |
| user.orders.forEach(order => { |
| if (order.createdAt >= startDate && order.createdAt <= endDate) { |
| allOrders.push({ |
| _id: order._id, |
| orderId: order._id, |
| userId: user._id, |
| userName: user.name, |
| userEmail: user.email, |
| amount: order.amount, |
| status: order.status, |
| createdAt: order.createdAt, |
| items: order.orderItems || [], |
| shippingAddress: order.shippingAddress, |
| paymentMethod: order.paymentMethod, |
| estimatedDelivery: order.estimatedDelivery, |
| phone: order.shippingAddress?.phone || '', |
| city: order.shippingAddress?.city || '' |
| }); |
| } |
| }); |
| }); |
|
|
| if (status !== 'all') { |
| allOrders = allOrders.filter(order => order.status === status); |
| } |
|
|
| allOrders.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); |
|
|
| const total = allOrders.length; |
| const orders = allOrders.slice(skip, skip + limit); |
|
|
| res.status(200).json({ |
| orders, |
| pagination: { |
| page, |
| limit, |
| total, |
| pages: Math.ceil(total / limit) |
| } |
| }); |
| }); |
|
|
| const getOrderById = asyncHandler(async (req, res) => { |
| const { id } = req.params; |
| |
| const user = await User.findOne({ 'orders._id': id }); |
| |
| if (!user) { |
| res.status(404); |
| throw new Error('Order not found'); |
| } |
|
|
| const order = user.orders.find(order => order._id.toString() === id); |
| |
| if (order) { |
| res.status(200).json({ |
| order: { |
| ...order.toObject(), |
| userName: user.name, |
| userEmail: user.email, |
| shippingAddress: order.shippingAddress, |
| paymentMethod: order.paymentMethod, |
| estimatedDelivery: order.estimatedDelivery, |
| phone: order.shippingAddress?.phone || '', |
| city: order.shippingAddress?.city || '' |
| } |
| }); |
| } else { |
| res.status(404); |
| throw new Error('Order not found'); |
| } |
| }); |
|
|
| const updateOrder = asyncHandler(async (req, res) => { |
| const { id } = req.params; |
| const { status } = req.body; |
|
|
| const user = await User.findOne({ 'orders._id': id }); |
| |
| if (!user) { |
| res.status(404); |
| throw new Error('Order not found'); |
| } |
|
|
| const orderIndex = user.orders.findIndex(order => order._id.toString() === id); |
| |
| if (orderIndex === -1) { |
| res.status(404); |
| throw new Error('Order not found'); |
| } |
|
|
| user.orders[orderIndex].status = status; |
| await user.save(); |
|
|
| res.status(200).json({ |
| message: 'Order updated successfully', |
| order: user.orders[orderIndex] |
| }); |
| }); |
|
|
| const deleteOrder = asyncHandler(async (req, res) => { |
| const { id } = req.params; |
|
|
| const user = await User.findOne({ 'orders._id': id }); |
| |
| if (!user) { |
| res.status(404); |
| throw new Error('Order not found'); |
| } |
|
|
| user.orders = user.orders.filter(order => order._id.toString() !== id); |
| await user.save(); |
|
|
| res.status(200).json({ message: 'Order deleted successfully' }); |
| }); |
|
|
| const getAnalytics = asyncHandler(async (req, res) => { |
| const { period = '30d' } = req.query; |
| |
| let startDate; |
| const endDate = new Date(); |
| |
| switch (period) { |
| case '7d': |
| startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000); |
| break; |
| case '30d': |
| startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000); |
| break; |
| case '90d': |
| startDate = new Date(endDate.getTime() - 90 * 24 * 60 * 60 * 1000); |
| break; |
| default: |
| startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000); |
| } |
|
|
| const userGrowth = await User.aggregate([ |
| { |
| $match: { |
| createdAt: { $gte: startDate, $lte: endDate } |
| } |
| }, |
| { |
| $group: { |
| _id: { |
| year: { $year: '$createdAt' }, |
| month: { $month: '$createdAt' }, |
| day: { $dayOfMonth: '$createdAt' } |
| }, |
| count: { $sum: 1 } |
| } |
| }, |
| { $sort: { '_id.year': 1, '_id.month': 1, '_id.day': 1 } } |
| ]); |
|
|
| const allUsers = await User.find({ |
| 'orders.createdAt': { $gte: startDate, $lte: endDate } |
| }); |
|
|
| const revenueData = []; |
| const orderData = []; |
| const categoryRevenue = {}; |
| const productPerformance = {}; |
|
|
| allUsers.forEach(user => { |
| user.orders.forEach(order => { |
| if (order.createdAt >= startDate && order.createdAt <= endDate) { |
| const dateKey = order.createdAt.toISOString().split('T')[0]; |
| const orderAmount = order.amount || 0; |
|
|
| const existingRevenue = revenueData.find(item => item.date === dateKey); |
| if (existingRevenue) { |
| existingRevenue.revenue += orderAmount; |
| } else { |
| revenueData.push({ |
| date: dateKey, |
| revenue: orderAmount |
| }); |
| } |
|
|
| const existingOrder = orderData.find(item => item.date === dateKey); |
| if (existingOrder) { |
| existingOrder.orders += 1; |
| } else { |
| orderData.push({ |
| date: dateKey, |
| orders: 1 |
| }); |
| } |
|
|
| order.orderItems?.forEach(item => { |
| if (item.category) { |
| categoryRevenue[item.category] = (categoryRevenue[item.category] || 0) + (item.price * item.qty); |
| } |
| if (item.productId) { |
| productPerformance[item.productId] = (productPerformance[item.productId] || 0) + (item.price * item.qty); |
| } |
| }); |
| } |
| }); |
| }); |
|
|
| revenueData.sort((a, b) => new Date(a.date) - new Date(b.date)); |
| orderData.sort((a, b) => new Date(a.date) - new Date(b.date)); |
|
|
| const topCategories = Object.entries(categoryRevenue) |
| .map(([category, revenue]) => ({ category, revenue })) |
| .sort((a, b) => b.revenue - a.revenue) |
| .slice(0, 10); |
|
|
| const topProductIds = Object.entries(productPerformance) |
| .sort((a, b) => b[1] - a[1]) |
| .slice(0, 10) |
| .map(([productId]) => productId); |
|
|
| const topProducts = await ProductModel.find({ _id: { $in: topProductIds } }) |
| .select('name price category ratings numOfReviews'); |
|
|
| const userEngagement = await User.aggregate([ |
| { |
| $match: { |
| createdAt: { $gte: startDate, $lte: endDate } |
| } |
| }, |
| { |
| $project: { |
| hasOrders: { $gt: [{ $size: '$orders' }, 0] }, |
| orderCount: { $size: '$orders' }, |
| totalSpent: { $sum: '$orders.amount' }, |
| lastOrderDate: { $max: '$orders.createdAt' } |
| } |
| }, |
| { |
| $group: { |
| _id: null, |
| totalUsers: { $sum: 1 }, |
| usersWithOrders: { $sum: { $cond: ['$hasOrders', 1, 0] } }, |
| avgOrdersPerUser: { $avg: '$orderCount' }, |
| avgSpendingPerUser: { $avg: '$totalSpent' } |
| } |
| } |
| ]); |
|
|
| const geographicData = await User.aggregate([ |
| { |
| $match: { |
| createdAt: { $gte: startDate, $lte: endDate } |
| } |
| }, |
| { |
| $group: { |
| _id: '$shippingAddress.state', |
| count: { $sum: 1 } |
| } |
| }, |
| { $sort: { count: -1 } } |
| ]); |
|
|
| const conversionFunnel = { |
| totalVisitors: userEngagement[0]?.totalUsers || 0, |
| registeredUsers: userEngagement[0]?.totalUsers || 0, |
| usersWithOrders: userEngagement[0]?.usersWithOrders || 0, |
| conversionRate: userEngagement[0]?.totalUsers > 0 |
| ? ((userEngagement[0]?.usersWithOrders / userEngagement[0]?.totalUsers) * 100).toFixed(2) |
| : 0 |
| }; |
|
|
| res.status(200).json({ |
| period, |
| userGrowth, |
| revenueData, |
| orderData, |
| topCategories, |
| topProducts, |
| userEngagement: userEngagement[0] || {}, |
| geographicData, |
| conversionFunnel, |
| summary: { |
| totalRevenue: revenueData.reduce((sum, item) => sum + item.revenue, 0), |
| totalOrders: orderData.reduce((sum, item) => sum + item.orders, 0), |
| avgOrderValue: orderData.reduce((sum, item) => sum + item.orders, 0) > 0 |
| ? (revenueData.reduce((sum, item) => sum + item.revenue, 0) / orderData.reduce((sum, item) => sum + item.orders, 0)).toFixed(2) |
| : 0 |
| } |
| }); |
| }); |
|
|
| const getSystemInfo = asyncHandler(async (req, res) => { |
| const systemInfo = { |
| nodeVersion: process.version, |
| platform: process.platform, |
| memory: process.memoryUsage(), |
| uptime: process.uptime(), |
| environment: process.env.NODE_ENV, |
| database: { |
| connected: mongoose.connection.readyState === 1, |
| host: mongoose.connection.host, |
| name: mongoose.connection.name |
| } |
| }; |
|
|
| res.status(200).json(systemInfo); |
| }); |
|
|
| const getAdminActivityLog = asyncHandler(async (req, res) => { |
| const page = parseInt(req.query.page) || 1; |
| const limit = parseInt(req.query.limit) || 50; |
| const adminId = req.query.adminId; |
|
|
| const skip = (page - 1) * limit; |
|
|
| let query = {}; |
| if (adminId) { |
| query._id = adminId; |
| } |
|
|
| const admins = await Admin.find(query) |
| .select('firstName lastName username activityLog') |
| .sort({ 'activityLog.timestamp': -1 }) |
| .skip(skip) |
| .limit(limit); |
|
|
| const activityLogs = []; |
| admins.forEach(admin => { |
| admin.activityLog.forEach(log => { |
| activityLogs.push({ |
| adminName: admin.getFullName(), |
| adminUsername: admin.username, |
| ...log.toObject() |
| }); |
| }); |
| }); |
|
|
| activityLogs.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); |
|
|
| res.status(200).json({ |
| activityLogs: activityLogs.slice(0, limit), |
| pagination: { |
| page, |
| limit, |
| total: activityLogs.length |
| } |
| }); |
| }); |
|
|
| const getAllCategories = asyncHandler(async (req, res) => { |
| const categories = await ProductModel.distinct('category'); |
| |
| res.status(200).json({ |
| success: true, |
| categories: categories.sort() |
| }); |
| }); |
|
|
| const createCategory = asyncHandler(async (req, res) => { |
| const { name } = req.body; |
|
|
| if (!name || name.trim() === '') { |
| res.status(400); |
| throw new Error('Category name is required'); |
| } |
|
|
| const trimmedName = name.trim(); |
|
|
| const existingCategory = await ProductModel.findOne({ category: trimmedName }); |
| if (existingCategory) { |
| res.status(400); |
| throw new Error('Category already exists'); |
| } |
|
|
| res.status(201).json({ |
| success: true, |
| message: 'Category created successfully', |
| category: trimmedName |
| }); |
| }); |
|
|
| const ensureCategoryExists = async (categoryName) => { |
| if (!categoryName || categoryName.trim() === '') { |
| return false; |
| } |
|
|
| const trimmedName = categoryName.trim(); |
|
|
| const existingCategory = await ProductModel.findOne({ category: trimmedName }); |
| if (existingCategory) { |
| return true; |
| } |
|
|
| return true; |
| }; |
|
|
| module.exports = { |
| adminLogin, |
| adminLogout, |
| getAdminProfile, |
| updateAdminProfile, |
| getAllUsers, |
| getUserById, |
| updateUser, |
| deleteUser, |
| createUser, |
| getAllProducts, |
| createProduct, |
| updateProduct, |
| deleteProduct, |
| uploadProductImages, |
| getAllOrders, |
| getOrderById, |
| updateOrder, |
| deleteOrder, |
| getDashboardStats, |
| getAnalytics, |
| getSystemInfo, |
| getAdminActivityLog, |
| getAllCategories, |
| createCategory |
| }; |