const User = require("../models/userModel.js"); const asyncHandler = require("express-async-handler"); const generateToken = require("../utils/generateToken.js"); const jwt = require("jsonwebtoken"); const createUser = async (req, res, next) => { const { name, email, password, dateOfBirth, gender, country } = req.body; console.log('User registration attempt:', { name, email, dateOfBirth, gender, country }); if (!name || !email || !password) { res.status(400); const err = new Error("Please provide name, email and password"); return next(err); } if (password.length < 6) { res.status(400); const err = new Error("Password must be atleast 6 characters"); return next(err); } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { res.status(400); const err = new Error("Invalid email address"); return next(err); } try { const userExists = await User.findOne({ email }); if (userExists) { res.status(400); const err = new Error( "Email is already registered. Please use a different email address" ); return next(err); } const user = await User.create({ name, email, password, dateOfBirth: dateOfBirth || '', gender: gender || '', country: country || '', createdAt: new Date(), }); if (user) { const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET_KEY, { expiresIn: "10d", }); generateToken(res, user._id); res.status(201).json({ _id: user._id, name: user.name, email: user.email, dateOfBirth: user.dateOfBirth, gender: user.gender, country: user.country, token: token, }); } } catch (error) { console.log(error); if (error.code === 11000) { res.status(400); const err = new Error( "Email is already registered. Please use a different email address..." ); return next(err); } res.status(500).json({ error: error.message } || "Internal server error"); } }; const login = asyncHandler(async (req, res) => { const { email, password } = req.body; console.log('Login attempt for:', email); const user = await User.findOne({ email }); if (user && (await user.checkPassword(password))) { console.log('Password check passed for user:', user._id); try { await User.findByIdAndUpdate(user._id, { $set: { isActive: true, lastLogin: new Date() } }); } catch (e) { console.error('Failed to update user active state/lastLogin:', e); } try { const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET_KEY, { expiresIn: "10d", }); console.log('Token generated successfully:', token ? 'YES' : 'NO'); console.log('JWT_SECRET_KEY exists:', !!process.env.JWT_SECRET_KEY); generateToken(res, user._id); const responseData = { _id: user._id, name: user.name, email: user.email, dateOfBirth: user.dateOfBirth, gender: user.gender, country: user.country, token: token, }; console.log('Sending response with token:', !!responseData.token); res.status(200).json(responseData); } catch (tokenError) { console.error('Error generating token:', tokenError); res.status(500).json({ error: 'Failed to generate authentication token' }); } } else { console.log('Login failed for:', email); res.status(400); throw new Error("Invalid email or password"); } }); const logout = asyncHandler(async (req, res) => { const isProduction = process.env.NODE_ENV === 'production'; try { if (req.user?._id) { await User.findByIdAndUpdate(req.user._id, { $set: { isActive: false } }); } } catch (e) { console.error('Failed to mark user inactive on logout:', e); } const cookieOptions = { httpOnly: true, expires: new Date(0), path: '/', sameSite: isProduction ? 'none' : 'lax', secure: isProduction, ...(isProduction && process.env.COOKIE_DOMAIN && { domain: process.env.COOKIE_DOMAIN }) }; res.cookie("token", "", cookieOptions); res.status(200).json({ message: "Logged out.." }); }); const getProfile = asyncHandler(async (req, res) => { const user = { _id: req.user._id, name: req.user.name, email: req.user.email, dateOfBirth: req.user.dateOfBirth, gender: req.user.gender, country: req.user.country, }; res.status(200).json(user); }); const updateProfile = asyncHandler(async (req, res) => { const { password } = req.body; if (password && password.length < 6) { res.status(400); throw new Error("Password must be at least 6 characters"); } const user = await User.findById(req.user._id); if (user) { user.name = req.body.name || user.name; if (password) { user.password = password; } const updatedUser = await user.save(); res.status(200).json({ _id: updatedUser._id, name: updatedUser.name, email: updatedUser.email, }); } else { res.status(404); throw new Error("User not found."); } }); const getWishlist = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const user = await User.findById(req.user._id).populate("wishlist"); res.json({ wishlist: user.wishlist }); }); const createWishlistItem = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { productId } = req.body; if (!productId) { res.status(400); throw new Error("Missing productId in request body"); } const user = await User.findById(req.user._id); if (!user.wishlist.some(item => item.productId && item.productId.toString() === productId.toString())) { const Product = require('../models/productModel'); const product = await Product.findById(productId); if (!product) { res.status(404); throw new Error("Product not found"); } user.wishlist.push({ productId: product._id, name: product.name, price: Number(product.price), category: product.category, seller: product.seller, stock: product.stock, image: product.images && product.images.length > 0 ? product.images[0].image : '', ratings: Number(product.ratings), createdAt: new Date() }); await user.save(); } res.json({ wishlist: user.wishlist }); }); const deleteWishlistItem = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { productId } = req.body; const user = await User.findById(req.user._id); user.wishlist = user.wishlist.filter( (item) => item.productId && item.productId.toString() !== productId.toString() ); await user.save(); res.json({ wishlist: user.wishlist }); }); const updateWishlist = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { wishlistIndex, update } = req.body; const user = await User.findById(req.user._id); if (user.wishlist[wishlistIndex]) { Object.assign(user.wishlist[wishlistIndex], update); await user.save(); res.json({ success: true, wishlist: user.wishlist[wishlistIndex] }); } else { res.status(404); throw new Error("Wishlist item not found"); } }); const getWishlists = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const user = await User.findById(req.user._id).populate("wishlist"); res.json({ wishlist: user.wishlist }); }); const createWishlistItems = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { productIds } = req.body; if (!Array.isArray(productIds) || productIds.length === 0) { res.status(400); throw new Error("Missing productIds array in request body"); } const user = await User.findById(req.user._id); const Product = require('../models/productModel'); for (const productId of productIds) { if (!user.wishlist.some(item => item.productId && item.productId.toString() === productId.toString())) { const product = await Product.findById(productId); if (product) { user.wishlist.push({ productId: product._id, name: product.name, price: Number(product.price), category: product.category, seller: product.seller, stock: product.stock, image: product.images && product.images.length > 0 ? product.images[0].image : '', ratings: Number(product.ratings), createdAt: new Date() }); } } } await user.save(); res.json({ wishlist: user.wishlist }); }); const deleteWishlistItems = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { productIds } = req.body; if (!Array.isArray(productIds) || productIds.length === 0) { res.status(400); throw new Error("Missing productIds array in request body"); } const user = await User.findById(req.user._id); user.wishlist = user.wishlist.filter( (item) => !productIds.includes(item.productId?.toString()) ); await user.save(); res.json({ wishlist: user.wishlist }); }); const updateWishlists = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { updates } = req.body; if (!Array.isArray(updates) || updates.length === 0) { res.status(400); throw new Error("Missing updates array in request body"); } const user = await User.findById(req.user._id); for (const { productId, update } of updates) { const item = user.wishlist.find(item => item.productId && item.productId.toString() === productId.toString()); if (item) { Object.assign(item, update); } } await user.save(); res.json({ wishlist: user.wishlist }); }); const getReviews = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const user = await User.findById(req.user._id).populate("reviews.product"); res.json({ reviews: user.reviews }); }); const addReview = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { productId, rating, comment } = req.body; const user = await User.findById(req.user._id); user.reviews.push({ product: productId, rating, comment }); await user.save(); res.json({ reviews: user.reviews }); }); const deleteReview = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { reviewId } = req.body; const user = await User.findById(req.user._id); user.reviews = user.reviews.filter( (r) => r._id.toString() !== reviewId.toString() ); await user.save(); res.json({ reviews: user.reviews }); }); const getOrders = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const user = await User.findById(req.user._id); res.json({ orders: user.orders }); }); const getOrderById = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { orderId } = req.params; const user = await User.findById(req.user._id); const order = user.orders.find(order => order._id.toString() === orderId || (order.createdAt && order.createdAt.toISOString() === orderId) ); if (order) { res.json({ success: true, order }); } else { res.status(404).json({ success: false, message: "Order not found" }); } }); const createOrderSnapshot = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { order } = req.body; const orderToSave = { ...order, shippingAddress: order.shippingAddress || null, paymentMethod: order.paymentMethod || null, }; const user = await User.findById(req.user._id); user.orders.push(orderToSave); await user.save(); const newOrder = user.orders[user.orders.length - 1]; res.json({ success: true, order: newOrder, orders: user.orders }); }); const deleteOrderSnapshot = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { createdAt } = req.body; const user = await User.findById(req.user._id); const initialLength = user.orders.length; user.orders = user.orders.filter(order => order.createdAt && order.createdAt.toISOString() !== createdAt); if (user.orders.length === initialLength) { return res.status(404).json({ message: "Order not found" }); } await user.save(); res.json({ orders: user.orders }); }); const updateOrderSnapshot = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { createdAt, update } = req.body; const user = await User.findById(req.user._id); const order = user.orders.find(order => order.createdAt && order.createdAt.toISOString() === createdAt); if (order) { Object.assign(order, update); await user.save(); res.json({ order, orders: user.orders }); } else { res.status(404); throw new Error("Order not found in user history"); } }); const getCarts = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const user = await User.findById(req.user._id); res.json({ carts: user.carts }); }); const createCartSnapshot = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { cart } = req.body; if (!cart || !Array.isArray(cart.cartItems) || cart.cartItems.length === 0) { res.status(400); throw new Error("Missing or invalid cart in request body"); } for (const item of cart.cartItems) { if (!item.productId || typeof item.qty !== 'number') { res.status(400); throw new Error("Each cart item must have a productId and qty"); } } const user = await User.findById(req.user._id); user.carts.push(cart); await user.save(); res.json({ success: true, carts: user.carts }); }); const deleteCartSnapshot = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { createdAt } = req.body; const user = await User.findById(req.user._id); const initialLength = user.carts.length; user.carts = user.carts.filter(cart => cart.createdAt && cart.createdAt.toISOString() !== createdAt); if (user.carts.length === initialLength) { return res.status(404).json({ message: "Cart not found" }); } await user.save(); res.json({ carts: user.carts }); }); const updateCartSnapshot = asyncHandler(async (req, res) => { if (!req.user || !req.user._id) { return res.status(401).json({ message: "Not authorized, user not found" }); } const { createdAt, update } = req.body; const user = await User.findById(req.user._id); const cart = user.carts.find(cart => cart.createdAt && cart.createdAt.toISOString() === createdAt); if (cart) { Object.assign(cart, update); await user.save(); res.json({ cart, carts: user.carts }); } else { res.status(404); throw new Error("Cart not found in user history"); } }); module.exports = { createUser, login, logout, getProfile, updateProfile, getWishlist, createWishlistItem, deleteWishlistItem, updateWishlist, getWishlists, createWishlistItems, deleteWishlistItems, updateWishlists, getOrders, getOrderById, createOrderSnapshot, deleteOrderSnapshot, updateOrderSnapshot, getCarts, createCartSnapshot, deleteCartSnapshot, updateCartSnapshot, getReviews, addReview, deleteReview };