Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PORT=5000
MONGO_URI='mongodb://127.0.0.1:27017/huxnStore'
MONGO_URI='mongodb+srv://sam:[email protected]/eStore'
JWT_SECRET=abaya2025
NODE_ENV=development
JWT_SECRET=abac12afsdkjladf
PAYPAL_CLIENT_ID=

52 changes: 52 additions & 0 deletions backend/config/multer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import multer from 'multer';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Ensure uploads directory exists
const uploadsDir = path.join(__dirname, '../uploads');
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
}

// Configure storage
const storage = multer.diskStorage({
destination: function (req, file, cb) {
console.log('Destination directory:', uploadsDir);
cb(null, uploadsDir);
},
filename: function (req, file, cb) {
// Create unique filename with timestamp
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const filename = file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname);
console.log('Generated filename:', filename);
cb(null, filename);
}
});

// File filter to accept only images
const fileFilter = (req, file, cb) => {
console.log('File being processed:', file);
const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
if (allowedTypes.includes(file.mimetype)) {
console.log('File accepted:', file.originalname);
cb(null, true);
} else {
console.log('File rejected:', file.originalname, 'MIME type:', file.mimetype);
cb(new Error('Invalid file type. Only JPEG, PNG, JPG, and WEBP are allowed.'), false);
}
};

// Configure multer
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB limit
}
});

export { upload };
199 changes: 112 additions & 87 deletions backend/controllers/productController.js
Original file line number Diff line number Diff line change
@@ -1,137 +1,156 @@
import asyncHandler from "../middlewares/asyncHandler.js";
import Product from "../models/productModel.js";
import { authenticate, authorizeAdmin } from '../middlewares/authMiddleware.js';
import { upload } from '../config/multer.js';
import path from 'path';

const addProduct = asyncHandler(async (req, res) => {
// Create a new product
const createProduct = asyncHandler(async (req, res) => {
try {
const { name, description, price, category, quantity, brand } = req.fields;

const { name, description, price, category, countInStock, brand } = req.body;
// Validation
switch (true) {
case !name:
return res.json({ error: "Name is required" });
return res.status(400).json({ error: "Name is required" });
case !brand:
return res.json({ error: "Brand is required" });
return res.status(400).json({ error: "Brand is required" });
case !description:
return res.json({ error: "Description is required" });
return res.status(400).json({ error: "Description is required" });
case !price:
return res.json({ error: "Price is required" });
return res.status(400).json({ error: "Price is required" });
case !category:
return res.json({ error: "Category is required" });
case !quantity:
return res.json({ error: "Quantity is required" });
return res.status(400).json({ error: "Category is required" });
case !countInStock:
return res.status(400).json({ error: "Count in stock is required" });
case !req.file:
return res.status(400).json({ error: "Image is required" });
}

const product = new Product({ ...req.fields });
await product.save();
res.json(product);
// Store relative path for the image
const imagePath = path.relative(path.join(process.cwd(), 'backend'), req.file.path);

const product = new Product({
name,
description,
price,
category,
countInStock,
brand,
image: imagePath
});

const createdProduct = await product.save();
res.status(201).json(createdProduct);
} catch (error) {
console.error(error);
res.status(400).json(error.message);
console.error('Error creating product:', error);
res.status(500).json({ error: 'Failed to create product', details: error.message });
}
});

const updateProductDetails = asyncHandler(async (req, res) => {
// Update a product
const updateProduct = asyncHandler(async (req, res) => {
try {
const { name, description, price, category, quantity, brand } = req.fields;

// Validation
switch (true) {
case !name:
return res.json({ error: "Name is required" });
case !brand:
return res.json({ error: "Brand is required" });
case !description:
return res.json({ error: "Description is required" });
case !price:
return res.json({ error: "Price is required" });
case !category:
return res.json({ error: "Category is required" });
case !quantity:
return res.json({ error: "Quantity is required" });
const { name, description, price, category, countInStock, brand } = req.body;

const product = await Product.findById(req.params.id);

if (!product) {
return res.status(404).json({ message: 'Product not found' });
}

const product = await Product.findByIdAndUpdate(
req.params.id,
{ ...req.fields },
{ new: true }
);

await product.save();
product.name = name || product.name;
product.description = description || product.description;
product.price = price || product.price;
product.category = category || product.category;
product.countInStock = countInStock || product.countInStock;
product.brand = brand || product.brand;

if (req.file) {
product.image = path.relative(path.join(process.cwd(), 'backend'), req.file.path);
}

res.json(product);
const updatedProduct = await product.save();
res.json(updatedProduct);
} catch (error) {
console.error(error);
res.status(400).json(error.message);
console.error('Error updating product:', error);
res.status(500).json({ error: 'Failed to update product', details: error.message });
}
});

// Delete a product
const removeProduct = asyncHandler(async (req, res) => {
try {
const product = await Product.findByIdAndDelete(req.params.id);
res.json(product);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
res.json({ message: 'Product removed' });
} catch (error) {
console.error(error);
res.status(500).json({ error: "Server error" });
console.error('Error removing product:', error);
res.status(500).json({ error: 'Failed to remove product', details: error.message });
}
});

// Get all products
const fetchProducts = asyncHandler(async (req, res) => {
try {
const pageSize = 6;
const page = Number(req.query.pageNumber) || 1;

const keyword = req.query.keyword
? {
name: {
$regex: req.query.keyword,
$options: "i",
$options: 'i',
},
}
: {};

const count = await Product.countDocuments({ ...keyword });
const products = await Product.find({ ...keyword }).limit(pageSize);
const products = await Product.find({ ...keyword })
.limit(pageSize)
.skip(pageSize * (page - 1));

res.json({
products,
page: 1,
page,
pages: Math.ceil(count / pageSize),
hasMore: false,
});
} catch (error) {
console.error(error);
res.status(500).json({ error: "Server Error" });
console.error('Error fetching products:', error);
res.status(500).json({ error: 'Failed to fetch products', details: error.message });
}
});

// Get product by ID
const fetchProductById = asyncHandler(async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (product) {
return res.json(product);
res.json(product);
} else {
res.status(404);
throw new Error("Product not found");
throw new Error('Product not found');
}
} catch (error) {
console.error(error);
res.status(404).json({ error: "Product not found" });
console.error('Error fetching product:', error);
res.status(500).json({ error: 'Failed to fetch product', details: error.message });
}
});

// Get all products (admin)
const fetchAllProducts = asyncHandler(async (req, res) => {
try {
const products = await Product.find({})
.populate("category")
.limit(12)
.sort({ createAt: -1 });

const products = await Product.find({});
res.json(products);
} catch (error) {
console.error(error);
res.status(500).json({ error: "Server Error" });
console.error('Error fetching all products:', error);
res.status(500).json({ error: 'Failed to fetch all products', details: error.message });
}
});

// Add product review
const addProductReview = asyncHandler(async (req, res) => {
try {
const { rating, comment } = req.body;
Expand All @@ -144,81 +163,87 @@ const addProductReview = asyncHandler(async (req, res) => {

if (alreadyReviewed) {
res.status(400);
throw new Error("Product already reviewed");
throw new Error('Product already reviewed');
}

const review = {
name: req.user.username,
name: req.user.name,
rating: Number(rating),
comment,
user: req.user._id,
};

product.reviews.push(review);

product.numReviews = product.reviews.length;

product.rating =
product.reviews.reduce((acc, item) => item.rating + acc, 0) /
product.reviews.length;

await product.save();
res.status(201).json({ message: "Review added" });
res.status(201).json({ message: 'Review added' });
} else {
res.status(404);
throw new Error("Product not found");
throw new Error('Product not found');
}
} catch (error) {
console.error(error);
res.status(400).json(error.message);
console.error('Error adding review:', error);
res.status(500).json({ error: 'Failed to add review', details: error.message });
}
});

// Get top rated products
const fetchTopProducts = asyncHandler(async (req, res) => {
try {
const products = await Product.find({}).sort({ rating: -1 }).limit(4);
const products = await Product.find({}).sort({ rating: -1 }).limit(3);
res.json(products);
} catch (error) {
console.error(error);
res.status(400).json(error.message);
console.error('Error fetching top products:', error);
res.status(500).json({ error: 'Failed to fetch top products', details: error.message });
}
});

// Get new products
const fetchNewProducts = asyncHandler(async (req, res) => {
try {
const products = await Product.find().sort({ _id: -1 }).limit(5);
const products = await Product.find().sort({ createdAt: -1 }).limit(5);
res.json(products);
} catch (error) {
console.error(error);
res.status(400).json(error.message);
console.error('Error fetching new products:', error);
res.status(500).json({ error: 'Failed to fetch new products', details: error.message });
}
});

// Filter products
const filterProducts = asyncHandler(async (req, res) => {
try {
const { checked, radio } = req.body;

let args = {};
if (checked.length > 0) args.category = checked;
if (radio.length) args.price = { $gte: radio[0], $lte: radio[1] };
const { category, minPrice, maxPrice, rating } = req.body;
let query = {};

if (category) query.category = category;
if (minPrice || maxPrice) {
query.price = {};
if (minPrice) query.price.$gte = minPrice;
if (maxPrice) query.price.$lte = maxPrice;
}
if (rating) query.rating = { $gte: rating };

const products = await Product.find(args);
const products = await Product.find(query);
res.json(products);
} catch (error) {
console.error(error);
res.status(500).json({ error: "Server Error" });
console.error('Error filtering products:', error);
res.status(500).json({ error: 'Failed to filter products', details: error.message });
}
});

export {
addProduct,
updateProductDetails,
createProduct,
updateProduct,
removeProduct,
fetchProducts,
fetchProductById,
fetchAllProducts,
addProductReview,
fetchTopProducts,
fetchNewProducts,
filterProducts,
filterProducts
};
Loading