Initial commit

This commit is contained in:
2026-09-16 17:36:46 +05:30
commit c9f429d123
117 changed files with 14145 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
const User = require('../models/User');
const { generateToken } = require('../utils/jwt');
const { successResponse, errorResponse } = require('../utils/apiResponse');
// @desc Register new user
// @route POST /api/auth/register
// @access Public
const registerUser = async (req, res, next) => {
try {
const { name, email, password, phone } = req.body;
const userExists = await User.findOne({ email: email.toLowerCase() });
if (userExists) {
return errorResponse(res, 'User already exists with this email', [], 400);
}
const user = await User.create({
name,
email,
password,
phone: phone || ''
});
const token = generateToken(user._id, user.role);
return successResponse(res, 'Registration successful', {
_id: user._id,
name: user.name,
email: user.email,
role: user.role,
token
}, 201);
} catch (error) {
next(error);
}
};
// @desc Login user & get token
// @route POST /api/auth/login
// @access Public
const loginUser = async (req, res, next) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return errorResponse(res, 'Please provide email and password', [], 400);
}
const user = await User.findOne({ email: email.toLowerCase() }).select('+password');
if (!user || !(await user.matchPassword(password))) {
return errorResponse(res, 'Invalid credentials', [], 401);
}
const token = generateToken(user._id, user.role);
return successResponse(res, 'Login successful', {
_id: user._id,
name: user.name,
email: user.email,
role: user.role,
avatar: user.avatar,
addresses: user.addresses,
token
});
} catch (error) {
next(error);
}
};
// @desc Get user profile
// @route GET /api/auth/profile
// @access Private
const getUserProfile = async (req, res, next) => {
try {
const user = await User.findById(req.user._id);
if (!user) {
return errorResponse(res, 'User not found', [], 404);
}
return successResponse(res, 'User profile retrieved', user);
} catch (error) {
next(error);
}
};
// @desc Update user profile & addresses
// @route PUT /api/auth/profile
// @access Private
const updateUserProfile = async (req, res, next) => {
try {
const user = await User.findById(req.user._id);
if (!user) {
return errorResponse(res, 'User not found', [], 404);
}
user.name = req.body.name || user.name;
user.phone = req.body.phone !== undefined ? req.body.phone : user.phone;
if (req.body.addresses) {
user.addresses = req.body.addresses;
}
if (req.body.password) {
user.password = req.body.password;
}
const updatedUser = await user.save();
const token = generateToken(updatedUser._id, updatedUser.role);
return successResponse(res, 'Profile updated successfully', {
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
role: updatedUser.role,
phone: updatedUser.phone,
addresses: updatedUser.addresses,
token
});
} catch (error) {
next(error);
}
};
module.exports = {
registerUser,
loginUser,
getUserProfile,
updateUserProfile
};

View File

@@ -0,0 +1,44 @@
const Banner = require('../models/Banner');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getBanners = async (req, res, next) => {
try {
const banners = await Banner.find({ isActive: true }).sort({ displayOrder: 1 });
return successResponse(res, 'Hero banners retrieved', banners);
} catch (error) {
next(error);
}
};
const createBanner = async (req, res, next) => {
try {
const banner = new Banner(req.body);
const saved = await banner.save();
return successResponse(res, 'Banner created', saved, 201);
} catch (error) {
next(error);
}
};
const updateBanner = async (req, res, next) => {
try {
const banner = await Banner.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!banner) return errorResponse(res, 'Banner not found', [], 404);
return successResponse(res, 'Banner updated', banner);
} catch (error) {
next(error);
}
};
const deleteBanner = async (req, res, next) => {
try {
const banner = await Banner.findByIdAndDelete(req.params.id);
if (!banner) return errorResponse(res, 'Banner not found', [], 404);
return successResponse(res, 'Banner deleted');
} catch (error) {
next(error);
}
};
module.exports = { getBanners, createBanner, updateBanner, deleteBanner };

View File

@@ -0,0 +1,44 @@
const Brand = require('../models/Brand');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getBrands = async (req, res, next) => {
try {
const brands = await Brand.find({ isActive: true }).sort({ name: 1 });
return successResponse(res, 'Brands retrieved', brands);
} catch (error) {
next(error);
}
};
const createBrand = async (req, res, next) => {
try {
const brand = new Brand(req.body);
const saved = await brand.save();
return successResponse(res, 'Brand created', saved, 201);
} catch (error) {
next(error);
}
};
const updateBrand = async (req, res, next) => {
try {
const brand = await Brand.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!brand) return errorResponse(res, 'Brand not found', [], 404);
return successResponse(res, 'Brand updated', brand);
} catch (error) {
next(error);
}
};
const deleteBrand = async (req, res, next) => {
try {
const brand = await Brand.findByIdAndDelete(req.params.id);
if (!brand) return errorResponse(res, 'Brand not found', [], 404);
return successResponse(res, 'Brand deleted');
} catch (error) {
next(error);
}
};
module.exports = { getBrands, createBrand, updateBrand, deleteBrand };

View File

@@ -0,0 +1,150 @@
const Cart = require('../models/Cart');
const Product = require('../models/Product');
const Coupon = require('../models/Coupon');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getCart = async (req, res, next) => {
try {
const userId = req.user ? req.user._id : null;
const sessionId = req.headers['x-session-id'] || null;
let cart = null;
if (userId) {
cart = await Cart.findOne({ user: userId }).populate('items.product');
} else if (sessionId) {
cart = await Cart.findOne({ sessionId }).populate('items.product');
}
if (!cart) {
cart = { items: [], couponCode: '', discountAmount: 0 };
}
return successResponse(res, 'Cart retrieved', cart);
} catch (error) {
next(error);
}
};
const addToCart = async (req, res, next) => {
try {
const { productId, quantity = 1, variantColor = '', variantFinish = '' } = req.body;
const userId = req.user ? req.user._id : null;
const sessionId = req.headers['x-session-id'] || 'guest-' + Date.now();
const product = await Product.findById(productId);
if (!product) {
return errorResponse(res, 'Product not found', [], 404);
}
// Inventory check
if (product.stock < quantity) {
return errorResponse(res, `Only ${product.stock} items remaining in stock`, [], 400);
}
let cart;
if (userId) {
cart = await Cart.findOne({ user: userId });
} else {
cart = await Cart.findOne({ sessionId });
}
const price = product.salePrice > 0 ? product.salePrice : product.price;
if (!cart) {
cart = new Cart({
user: userId,
sessionId,
items: [{ product: productId, quantity, variantColor, variantFinish, price }]
});
} else {
const existingItemIndex = cart.items.findIndex(
item => item.product.toString() === productId && item.variantColor === variantColor
);
if (existingItemIndex > -1) {
cart.items[existingItemIndex].quantity += Number(quantity);
} else {
cart.items.push({ product: productId, quantity, variantColor, variantFinish, price });
}
}
await cart.save();
const updatedCart = await Cart.findById(cart._id).populate('items.product');
return successResponse(res, 'Product added to cart', updatedCart);
} catch (error) {
next(error);
}
};
const updateCartItem = async (req, res, next) => {
try {
const { itemId, quantity } = req.body;
const userId = req.user ? req.user._id : null;
const sessionId = req.headers['x-session-id'];
const cart = userId
? await Cart.findOne({ user: userId })
: await Cart.findOne({ sessionId });
if (!cart) return errorResponse(res, 'Cart not found', [], 404);
const item = cart.items.id(itemId);
if (!item) return errorResponse(res, 'Item not found in cart', [], 404);
if (quantity <= 0) {
cart.items.pull(itemId);
} else {
item.quantity = quantity;
}
await cart.save();
const updatedCart = await Cart.findById(cart._id).populate('items.product');
return successResponse(res, 'Cart updated', updatedCart);
} catch (error) {
next(error);
}
};
const removeCartItem = async (req, res, next) => {
try {
const userId = req.user ? req.user._id : null;
const sessionId = req.headers['x-session-id'];
const cart = userId
? await Cart.findOne({ user: userId })
: await Cart.findOne({ sessionId });
if (cart) {
cart.items = cart.items.filter(item => item._id.toString() !== req.params.itemId);
await cart.save();
}
const updatedCart = cart ? await Cart.findById(cart._id).populate('items.product') : { items: [] };
return successResponse(res, 'Item removed from cart', updatedCart);
} catch (error) {
next(error);
}
};
const applyCoupon = async (req, res, next) => {
try {
const { code } = req.body;
const coupon = await Coupon.findOne({ code: code.toUpperCase(), isActive: true });
if (!coupon || coupon.endDate < new Date()) {
return errorResponse(res, 'Invalid or expired coupon code', [], 400);
}
return successResponse(res, 'Coupon applied successfully', {
code: coupon.code,
discountType: coupon.discountType,
discountValue: coupon.discountValue,
minPurchaseAmount: coupon.minPurchaseAmount
});
} catch (error) {
next(error);
}
};
module.exports = { getCart, addToCart, updateCartItem, removeCartItem, applyCoupon };

View File

@@ -0,0 +1,44 @@
const Category = require('../models/Category');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getCategories = async (req, res, next) => {
try {
const categories = await Category.find({ isActive: true }).sort({ displayOrder: 1 });
return successResponse(res, 'Categories retrieved', categories);
} catch (error) {
next(error);
}
};
const createCategory = async (req, res, next) => {
try {
const category = new Category(req.body);
const saved = await category.save();
return successResponse(res, 'Category created', saved, 201);
} catch (error) {
next(error);
}
};
const updateCategory = async (req, res, next) => {
try {
const category = await Category.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!category) return errorResponse(res, 'Category not found', [], 404);
return successResponse(res, 'Category updated', category);
} catch (error) {
next(error);
}
};
const deleteCategory = async (req, res, next) => {
try {
const category = await Category.findByIdAndDelete(req.params.id);
if (!category) return errorResponse(res, 'Category not found', [], 404);
return successResponse(res, 'Category deleted');
} catch (error) {
next(error);
}
};
module.exports = { getCategories, createCategory, updateCategory, deleteCategory };

View File

@@ -0,0 +1,34 @@
const ContactMessage = require('../models/ContactMessage');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const submitContactForm = async (req, res, next) => {
try {
const { name, email, phone, subject, message } = req.body;
const msg = await ContactMessage.create({ name, email, phone, subject, message });
return successResponse(res, 'Thank you! Your message has been received.', msg, 201);
} catch (error) {
next(error);
}
};
const getContactMessages = async (req, res, next) => {
try {
const messages = await ContactMessage.find({}).sort({ createdAt: -1 });
return successResponse(res, 'Contact messages retrieved', messages);
} catch (error) {
next(error);
}
};
const updateMessageStatus = async (req, res, next) => {
try {
const msg = await ContactMessage.findByIdAndUpdate(req.params.id, { status: req.body.status }, { new: true });
if (!msg) return errorResponse(res, 'Message not found', [], 404);
return successResponse(res, 'Message status updated', msg);
} catch (error) {
next(error);
}
};
module.exports = { submitContactForm, getContactMessages, updateMessageStatus };

View File

@@ -0,0 +1,34 @@
const Coupon = require('../models/Coupon');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getCoupons = async (req, res, next) => {
try {
const coupons = await Coupon.find({}).sort({ createdAt: -1 });
return successResponse(res, 'Coupons retrieved', coupons);
} catch (error) {
next(error);
}
};
const createCoupon = async (req, res, next) => {
try {
const coupon = new Coupon(req.body);
const saved = await coupon.save();
return successResponse(res, 'Coupon created', saved, 201);
} catch (error) {
next(error);
}
};
const deleteCoupon = async (req, res, next) => {
try {
const coupon = await Coupon.findByIdAndDelete(req.params.id);
if (!coupon) return errorResponse(res, 'Coupon not found', [], 404);
return successResponse(res, 'Coupon deleted');
} catch (error) {
next(error);
}
};
module.exports = { getCoupons, createCoupon, deleteCoupon };

View File

@@ -0,0 +1,24 @@
const EmailTemplate = require('../models/EmailTemplate');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getEmailTemplates = async (req, res, next) => {
try {
const templates = await EmailTemplate.find({});
return successResponse(res, 'Email templates retrieved', templates);
} catch (error) {
next(error);
}
};
const updateEmailTemplate = async (req, res, next) => {
try {
const template = await EmailTemplate.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!template) return errorResponse(res, 'Template not found', [], 404);
return successResponse(res, 'Email template updated', template);
} catch (error) {
next(error);
}
};
module.exports = { getEmailTemplates, updateEmailTemplate };

View File

@@ -0,0 +1,85 @@
const Product = require('../models/Product');
const InventoryHistory = require('../models/InventoryHistory');
const { successResponse, errorResponse } = require('../utils/apiResponse');
// @desc Get stock overview & alerts (Admin)
// @route GET /api/inventory/overview
// @access Private/Admin
const getStockOverview = async (req, res, next) => {
try {
const products = await Product.find({})
.select('name sku stock lowStockThreshold price category brand images')
.populate('category', 'name')
.populate('brand', 'name');
const lowStock = products.filter(p => p.stock > 0 && p.stock <= p.lowStockThreshold);
const outOfStock = products.filter(p => p.stock === 0);
return successResponse(res, 'Stock overview retrieved', {
totalProducts: products.length,
lowStockCount: lowStock.length,
outOfStockCount: outOfStock.length,
lowStock,
outOfStock,
products
});
} catch (error) {
next(error);
}
};
// @desc Adjust inventory stock level (Admin)
// @route POST /api/inventory/adjust
// @access Private/Admin
const adjustStock = async (req, res, next) => {
try {
const { productId, newStock, reason, changeType = 'Manual Adjustment' } = req.body;
const product = await Product.findById(productId);
if (!product) {
return errorResponse(res, 'Product not found', [], 404);
}
const previousStock = product.stock;
const quantityChanged = Number(newStock) - previousStock;
product.stock = Number(newStock);
await product.save();
// Log history
const history = await InventoryHistory.create({
product: product._id,
sku: product.sku,
changeType,
quantityChanged,
previousStock,
newStock: product.stock,
reason: reason || 'Manual admin inventory adjustment',
performedBy: req.user._id
});
return successResponse(res, 'Stock adjusted successfully', { product, history });
} catch (error) {
next(error);
}
};
// @desc Get complete inventory change audit log (Admin)
// @route GET /api/inventory/history
// @access Private/Admin
const getInventoryHistory = async (req, res, next) => {
try {
const history = await InventoryHistory.find({})
.populate('product', 'name images')
.populate('performedBy', 'name email')
.sort({ createdAt: -1 })
.limit(100);
return successResponse(res, 'Inventory history audit log retrieved', history);
} catch (error) {
next(error);
}
};
module.exports = { getStockOverview, adjustStock, getInventoryHistory };

View File

@@ -0,0 +1,212 @@
const Order = require('../models/Order');
const Product = require('../models/Product');
const InventoryHistory = require('../models/InventoryHistory');
const Cart = require('../models/Cart');
const { sendEmail } = require('../utils/emailSender');
const { successResponse, errorResponse } = require('../utils/apiResponse');
// @desc Create new order
// @route POST /api/orders
// @access Private
const createOrder = async (req, res, next) => {
try {
const {
orderItems,
shippingAddress,
paymentMethod,
itemsPrice,
taxPrice,
shippingPrice,
discountAmount,
totalAmount
} = req.body;
if (!orderItems || orderItems.length === 0) {
return errorResponse(res, 'No order items provided', [], 400);
}
// 1. Check stock and reduce inventory
for (const item of orderItems) {
const product = await Product.findById(item.product);
if (!product) {
return errorResponse(res, `Product ${item.name} not found`, [], 400);
}
if (product.stock < item.quantity) {
return errorResponse(res, `Insufficient stock for product ${product.name}`, [], 400);
}
// Record stock deduction
const oldStock = product.stock;
product.stock -= item.quantity;
product.salesCount += item.quantity;
await product.save();
await InventoryHistory.create({
product: product._id,
sku: product.sku,
changeType: 'Order Placed',
quantityChanged: -item.quantity,
previousStock: oldStock,
newStock: product.stock,
reason: `Order placed by user ${req.user.name}`,
performedBy: req.user._id
});
}
// 2. Generate unique order number
const orderNumber = `GTR-${Math.floor(100000 + Math.random() * 900000)}`;
// 3. Save Order
const order = new Order({
orderNumber,
user: req.user._id,
orderItems,
shippingAddress,
paymentMethod,
itemsPrice,
taxPrice,
shippingPrice,
discountAmount,
totalAmount,
isPaid: paymentMethod === 'Stripe', // Auto mark paid for simulated stripe
paidAt: paymentMethod === 'Stripe' ? Date.now() : null,
orderStatus: paymentMethod === 'Stripe' ? 'Payment Confirmed' : 'Pending',
statusHistory: [
{
status: paymentMethod === 'Stripe' ? 'Payment Confirmed' : 'Pending',
note: `Order created via ${paymentMethod}`
}
]
});
const createdOrder = await order.save();
// 4. Clear user cart
await Cart.findOneAndDelete({ user: req.user._id });
// 5. Send order confirmation email
sendEmail({
templateName: 'Order Confirmation',
to: req.user.email,
variables: {
customerName: req.user.name,
orderId: createdOrder.orderNumber,
orderTotal: `$${createdOrder.totalAmount.toFixed(2)}`
}
});
return successResponse(res, 'Order created successfully', createdOrder, 201);
} catch (error) {
next(error);
}
};
// @desc Get logged in user orders
// @route GET /api/orders/my-orders
// @access Private
const getMyOrders = async (req, res, next) => {
try {
const orders = await Order.find({ user: req.user._id }).sort({ createdAt: -1 });
return successResponse(res, 'User orders retrieved', orders);
} catch (error) {
next(error);
}
};
// @desc Get order by ID
// @route GET /api/orders/:id
// @access Private
const getOrderById = async (req, res, next) => {
try {
const order = await Order.findById(req.params.id).populate('user', 'name email phone');
if (!order) {
return errorResponse(res, 'Order not found', [], 404);
}
// Check authorization: customer can only view own order unless Admin
if (order.user._id.toString() !== req.user._id.toString() && req.user.role !== 'Admin') {
return errorResponse(res, 'Not authorized to view this order', [], 403);
}
return successResponse(res, 'Order retrieved', order);
} catch (error) {
next(error);
}
};
// @desc Get all orders (Admin)
// @route GET /api/orders
// @access Private/Admin
const getAllOrders = async (req, res, next) => {
try {
const { status } = req.query;
const filter = {};
if (status) filter.orderStatus = status;
const orders = await Order.find(filter)
.populate('user', 'name email')
.sort({ createdAt: -1 });
return successResponse(res, 'All orders retrieved', orders);
} catch (error) {
next(error);
}
};
// @desc Update order status & shipping info (Admin)
// @route PUT /api/orders/:id/status
// @access Private/Admin
const updateOrderStatus = async (req, res, next) => {
try {
const { orderStatus, trackingNumber, shippingProvider, note } = req.body;
const order = await Order.findById(req.params.id).populate('user', 'name email');
if (!order) {
return errorResponse(res, 'Order not found', [], 404);
}
if (orderStatus) {
order.orderStatus = orderStatus;
order.statusHistory.push({
status: orderStatus,
note: note || `Status updated to ${orderStatus}`
});
}
if (trackingNumber) order.trackingNumber = trackingNumber;
if (shippingProvider) order.shippingProvider = shippingProvider;
if (orderStatus === 'Delivered') {
order.isDelivered = true;
order.deliveredAt = Date.now();
}
const updatedOrder = await order.save();
// Trigger status change email
if (['Shipped', 'Delivered', 'Cancelled'].includes(orderStatus)) {
sendEmail({
templateName: orderStatus === 'Shipped' ? 'Order Shipped' : 'Order Confirmation',
to: order.user.email,
variables: {
customerName: order.user.name,
orderId: order.orderNumber,
trackingNumber: order.trackingNumber || 'N/A'
}
});
}
return successResponse(res, 'Order status updated', updatedOrder);
} catch (error) {
next(error);
}
};
module.exports = {
createOrder,
getMyOrders,
getOrderById,
getAllOrders,
updateOrderStatus
};

View File

@@ -0,0 +1,235 @@
const Product = require('../models/Product');
const Category = require('../models/Category');
const Brand = require('../models/Brand');
const { successResponse, errorResponse } = require('../utils/apiResponse');
// @desc Get all products with filtering, search, sorting & pagination
// @route GET /api/products
// @access Public
const getProducts = async (req, res, next) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 12;
const skip = (page - 1) * limit;
const query = { isActive: true };
// Search query
if (req.query.search) {
const searchRegex = new RegExp(req.query.search, 'i');
query.$or = [
{ name: searchRegex },
{ description: searchRegex },
{ tags: searchRegex },
{ sku: searchRegex }
];
}
// Category filter
if (req.query.category) {
const cat = await Category.findOne({ slug: req.query.category });
if (cat) query.category = cat._id;
}
// Brand filter
if (req.query.brand) {
const brand = await Brand.findOne({ slug: req.query.brand });
if (brand) query.brand = brand._id;
}
// Price range filter
if (req.query.minPrice || req.query.maxPrice) {
query.price = {};
if (req.query.minPrice) query.price.$gte = Number(req.query.minPrice);
if (req.query.maxPrice) query.price.$lte = Number(req.query.maxPrice);
}
// Guitar specific filters
if (req.query.guitarType) {
query['specs.guitarType'] = req.query.guitarType;
}
if (req.query.bodyShape) {
query['specs.bodyShape'] = req.query.bodyShape;
}
if (req.query.handOrientation) {
query['specs.handOrientation'] = req.query.handOrientation;
}
if (req.query.numberOfStrings) {
query['specs.numberOfStrings'] = Number(req.query.numberOfStrings);
}
if (req.query.isFeatured) {
query.isFeatured = true;
}
if (req.query.isNewArrival) {
query.isNewArrival = true;
}
if (req.query.isBestSeller) {
query.isBestSeller = true;
}
// Sorting
let sortOption = { createdAt: -1 };
if (req.query.sort === 'price_asc') sortOption = { price: 1 };
if (req.query.sort === 'price_desc') sortOption = { price: -1 };
if (req.query.sort === 'rating') sortOption = { rating: -1 };
if (req.query.sort === 'bestselling') sortOption = { salesCount: -1 };
const total = await Product.countDocuments(query);
const products = await Product.find(query)
.populate('category', 'name slug')
.populate('brand', 'name slug logo')
.sort(sortOption)
.skip(skip)
.limit(limit);
return successResponse(res, 'Products retrieved successfully', {
products,
pagination: {
total,
page,
pages: Math.ceil(total / limit),
limit
}
});
} catch (error) {
next(error);
}
};
// @desc Guitar Finder Recommendation Tool
// @route POST /api/products/guitar-finder
// @access Public
const getGuitarRecommendations = async (req, res, next) => {
try {
const { skillLevel, acousticElectric, musicStyle, maxBudget, handOrientation } = req.body;
const query = { isActive: true };
if (acousticElectric && acousticElectric !== 'Any') {
query['specs.guitarType'] = acousticElectric;
}
if (maxBudget) {
query.price = { $lte: Number(maxBudget) };
}
if (handOrientation) {
query['specs.handOrientation'] = handOrientation;
}
const recommendations = await Product.find(query)
.populate('category', 'name slug')
.populate('brand', 'name slug logo')
.sort({ rating: -1, salesCount: -1 })
.limit(6);
return successResponse(res, 'Guitar recommendations generated', recommendations);
} catch (error) {
next(error);
}
};
// @desc Get single product by slug
// @route GET /api/products/:slug
// @access Public
const getProductBySlug = async (req, res, next) => {
try {
const product = await Product.findOne({ slug: req.params.slug })
.populate('category', 'name slug')
.populate('brand', 'name slug logo description');
if (!product) {
return errorResponse(res, 'Product not found', [], 404);
}
// Related products (same category)
const relatedProducts = await Product.find({
category: product.category._id,
_id: { $ne: product._id },
isActive: true
})
.limit(4)
.select('name slug price salePrice images rating brand');
return successResponse(res, 'Product retrieved successfully', {
product,
relatedProducts
});
} catch (error) {
next(error);
}
};
// @desc Compare products by array of IDs
// @route POST /api/products/compare
// @access Public
const compareProducts = async (req, res, next) => {
try {
const { productIds } = req.body;
if (!productIds || !Array.isArray(productIds)) {
return errorResponse(res, 'Please provide an array of product IDs', [], 400);
}
const products = await Product.find({ _id: { $in: productIds } })
.populate('brand', 'name logo')
.populate('category', 'name');
return successResponse(res, 'Comparison data retrieved', products);
} catch (error) {
next(error);
}
};
// @desc Create new product (Admin)
// @route POST /api/products
// @access Private/Admin
const createProduct = async (req, res, next) => {
try {
const product = new Product(req.body);
const createdProduct = await product.save();
return successResponse(res, 'Product created successfully', createdProduct, 201);
} catch (error) {
next(error);
}
};
// @desc Update product (Admin)
// @route PUT /api/products/:id
// @access Private/Admin
const updateProduct = async (req, res, next) => {
try {
const product = await Product.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!product) {
return errorResponse(res, 'Product not found', [], 404);
}
return successResponse(res, 'Product updated successfully', product);
} catch (error) {
next(error);
}
};
// @desc Delete product (Admin)
// @route DELETE /api/products/:id
// @access Private/Admin
const deleteProduct = async (req, res, next) => {
try {
const product = await Product.findByIdAndDelete(req.params.id);
if (!product) {
return errorResponse(res, 'Product not found', [], 404);
}
return successResponse(res, 'Product deleted successfully');
} catch (error) {
next(error);
}
};
module.exports = {
getProducts,
getGuitarRecommendations,
getProductBySlug,
compareProducts,
createProduct,
updateProduct,
deleteProduct
};

View File

@@ -0,0 +1,50 @@
const Review = require('../models/Review');
const Product = require('../models/Product');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getProductReviews = async (req, res, next) => {
try {
const reviews = await Review.find({ product: req.params.productId, isApproved: true })
.populate('user', 'name avatar')
.sort({ createdAt: -1 });
return successResponse(res, 'Reviews retrieved', reviews);
} catch (error) {
next(error);
}
};
const createReview = async (req, res, next) => {
try {
const { productId, rating, title, comment } = req.body;
const existing = await Review.findOne({ user: req.user._id, product: productId });
if (existing) {
return errorResponse(res, 'You have already reviewed this product', [], 400);
}
const review = await Review.create({
user: req.user._id,
product: productId,
rating: Number(rating),
title,
comment
});
// Update Product average rating
const reviews = await Review.find({ product: productId, isApproved: true });
const avgRating = reviews.reduce((acc, item) => item.rating + acc, 0) / reviews.length;
await Product.findByIdAndUpdate(productId, {
rating: Number(avgRating.toFixed(1)),
numReviews: reviews.length
});
return successResponse(res, 'Review submitted successfully', review, 201);
} catch (error) {
next(error);
}
};
module.exports = { getProductReviews, createReview };

View File

@@ -0,0 +1,32 @@
const WebsiteSettings = require('../models/WebsiteSettings');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getSettings = async (req, res, next) => {
try {
let settings = await WebsiteSettings.findOne({});
if (!settings) {
settings = await WebsiteSettings.create({});
}
return successResponse(res, 'Website settings retrieved', settings);
} catch (error) {
next(error);
}
};
const updateSettings = async (req, res, next) => {
try {
let settings = await WebsiteSettings.findOne({});
if (!settings) {
settings = new WebsiteSettings(req.body);
} else {
Object.assign(settings, req.body);
}
await settings.save();
return successResponse(res, 'Website settings updated successfully', settings);
} catch (error) {
next(error);
}
};
module.exports = { getSettings, updateSettings };

View File

@@ -0,0 +1,33 @@
const SocialMedia = require('../models/SocialMedia');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getSocialMedia = async (req, res, next) => {
try {
const socialLinks = await SocialMedia.find({ isEnabled: true });
return successResponse(res, 'Social media links retrieved', socialLinks);
} catch (error) {
next(error);
}
};
const updateSocialMedia = async (req, res, next) => {
try {
const { links } = req.body; // Array of { platform, url, isEnabled }
if (Array.isArray(links)) {
for (const link of links) {
await SocialMedia.findOneAndUpdate(
{ platform: link.platform },
{ url: link.url, isEnabled: link.isEnabled },
{ upsert: true, new: true }
);
}
}
const updated = await SocialMedia.find({});
return successResponse(res, 'Social media accounts updated', updated);
} catch (error) {
next(error);
}
};
module.exports = { getSocialMedia, updateSocialMedia };

View File

@@ -0,0 +1,41 @@
const Wishlist = require('../models/Wishlist');
const { successResponse, errorResponse } = require('../utils/apiResponse');
const getWishlist = async (req, res, next) => {
try {
let wishlist = await Wishlist.findOne({ user: req.user._id }).populate('products');
if (!wishlist) {
wishlist = await Wishlist.create({ user: req.user._id, products: [] });
}
return successResponse(res, 'Wishlist retrieved', wishlist);
} catch (error) {
next(error);
}
};
const toggleWishlist = async (req, res, next) => {
try {
const { productId } = req.body;
let wishlist = await Wishlist.findOne({ user: req.user._id });
if (!wishlist) {
wishlist = new Wishlist({ user: req.user._id, products: [productId] });
} else {
const exists = wishlist.products.includes(productId);
if (exists) {
wishlist.products.pull(productId);
} else {
wishlist.products.push(productId);
}
}
await wishlist.save();
const updated = await Wishlist.findById(wishlist._id).populate('products');
return successResponse(res, 'Wishlist updated', updated);
} catch (error) {
next(error);
}
};
module.exports = { getWishlist, toggleWishlist };