378 lines
10 KiB
JavaScript
378 lines
10 KiB
JavaScript
|
|
const express = require("express");
|
||
|
|
const router = express.Router();
|
||
|
|
const { pool } = require("../config/database");
|
||
|
|
const logger = require("../config/logger");
|
||
|
|
|
||
|
|
// Middleware to check customer auth from session
|
||
|
|
const requireCustomerAuth = (req, res, next) => {
|
||
|
|
if (!req.session || !req.session.customerId) {
|
||
|
|
return res
|
||
|
|
.status(401)
|
||
|
|
.json({ success: false, message: "Please login to continue" });
|
||
|
|
}
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
|
||
|
|
// ===========================
|
||
|
|
// CART ROUTES
|
||
|
|
// ===========================
|
||
|
|
|
||
|
|
// Get cart items
|
||
|
|
router.get("/cart", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const result = await pool.query(
|
||
|
|
`SELECT cc.id, cc.product_id, cc.quantity, cc.variant_color, cc.variant_size, cc.added_at,
|
||
|
|
p.name, p.price, p.imageurl, p.slug
|
||
|
|
FROM customer_cart cc
|
||
|
|
JOIN products p ON p.id = cc.product_id
|
||
|
|
WHERE cc.customer_id = $1
|
||
|
|
ORDER BY cc.added_at DESC`,
|
||
|
|
[req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
const items = result.rows.map((row) => ({
|
||
|
|
id: row.id,
|
||
|
|
productId: row.product_id,
|
||
|
|
name: row.name,
|
||
|
|
price: parseFloat(row.price),
|
||
|
|
image: row.imageurl,
|
||
|
|
slug: row.slug,
|
||
|
|
quantity: row.quantity,
|
||
|
|
variantColor: row.variant_color,
|
||
|
|
variantSize: row.variant_size,
|
||
|
|
addedAt: row.added_at,
|
||
|
|
}));
|
||
|
|
|
||
|
|
const total = items.reduce(
|
||
|
|
(sum, item) => sum + item.price * item.quantity,
|
||
|
|
0
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
success: true,
|
||
|
|
items,
|
||
|
|
itemCount: items.length,
|
||
|
|
total: total.toFixed(2),
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Get cart error:", error);
|
||
|
|
res.status(500).json({ success: false, message: "Failed to get cart" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add to cart
|
||
|
|
router.post("/cart", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const { productId, quantity = 1, variantColor, variantSize } = req.body;
|
||
|
|
|
||
|
|
if (!productId) {
|
||
|
|
return res
|
||
|
|
.status(400)
|
||
|
|
.json({ success: false, message: "Product ID is required" });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if product exists
|
||
|
|
const productCheck = await pool.query(
|
||
|
|
"SELECT id, name FROM products WHERE id = $1",
|
||
|
|
[productId]
|
||
|
|
);
|
||
|
|
if (productCheck.rows.length === 0) {
|
||
|
|
return res
|
||
|
|
.status(404)
|
||
|
|
.json({ success: false, message: "Product not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Insert or update cart item
|
||
|
|
const result = await pool.query(
|
||
|
|
`INSERT INTO customer_cart (customer_id, product_id, quantity, variant_color, variant_size)
|
||
|
|
VALUES ($1, $2, $3, $4, $5)
|
||
|
|
ON CONFLICT (customer_id, product_id, variant_color, variant_size)
|
||
|
|
DO UPDATE SET quantity = customer_cart.quantity + EXCLUDED.quantity, updated_at = CURRENT_TIMESTAMP
|
||
|
|
RETURNING id`,
|
||
|
|
[
|
||
|
|
req.session.customerId,
|
||
|
|
productId,
|
||
|
|
quantity,
|
||
|
|
variantColor || null,
|
||
|
|
variantSize || null,
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
logger.info(
|
||
|
|
`Cart item added for customer ${req.session.customerId}: ${productId}`
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
success: true,
|
||
|
|
message: "Added to cart",
|
||
|
|
cartItemId: result.rows[0].id,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Add to cart error:", error);
|
||
|
|
res.status(500).json({ success: false, message: "Failed to add to cart" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Update cart quantity
|
||
|
|
router.put("/cart/:id", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const { quantity } = req.body;
|
||
|
|
|
||
|
|
if (!quantity || quantity < 1) {
|
||
|
|
return res
|
||
|
|
.status(400)
|
||
|
|
.json({ success: false, message: "Quantity must be at least 1" });
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await pool.query(
|
||
|
|
`UPDATE customer_cart SET quantity = $1, updated_at = CURRENT_TIMESTAMP
|
||
|
|
WHERE id = $2 AND customer_id = $3
|
||
|
|
RETURNING id`,
|
||
|
|
[quantity, req.params.id, req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (result.rows.length === 0) {
|
||
|
|
return res
|
||
|
|
.status(404)
|
||
|
|
.json({ success: false, message: "Cart item not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({ success: true, message: "Cart updated" });
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Update cart error:", error);
|
||
|
|
res.status(500).json({ success: false, message: "Failed to update cart" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Remove from cart
|
||
|
|
router.delete("/cart/:id", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const result = await pool.query(
|
||
|
|
"DELETE FROM customer_cart WHERE id = $1 AND customer_id = $2 RETURNING id",
|
||
|
|
[req.params.id, req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (result.rows.length === 0) {
|
||
|
|
return res
|
||
|
|
.status(404)
|
||
|
|
.json({ success: false, message: "Cart item not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({ success: true, message: "Removed from cart" });
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Remove from cart error:", error);
|
||
|
|
res
|
||
|
|
.status(500)
|
||
|
|
.json({ success: false, message: "Failed to remove from cart" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Clear cart
|
||
|
|
router.delete("/cart", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
await pool.query("DELETE FROM customer_cart WHERE customer_id = $1", [
|
||
|
|
req.session.customerId,
|
||
|
|
]);
|
||
|
|
res.json({ success: true, message: "Cart cleared" });
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Clear cart error:", error);
|
||
|
|
res.status(500).json({ success: false, message: "Failed to clear cart" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// ===========================
|
||
|
|
// WISHLIST ROUTES
|
||
|
|
// ===========================
|
||
|
|
|
||
|
|
// Get wishlist items
|
||
|
|
router.get("/wishlist", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const result = await pool.query(
|
||
|
|
`SELECT cw.id, cw.product_id, cw.added_at,
|
||
|
|
p.name, p.price, p.imageurl, p.slug
|
||
|
|
FROM customer_wishlist cw
|
||
|
|
JOIN products p ON p.id = cw.product_id
|
||
|
|
WHERE cw.customer_id = $1
|
||
|
|
ORDER BY cw.added_at DESC`,
|
||
|
|
[req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
const items = result.rows.map((row) => ({
|
||
|
|
id: row.id,
|
||
|
|
productId: row.product_id,
|
||
|
|
name: row.name,
|
||
|
|
price: parseFloat(row.price),
|
||
|
|
image: row.imageurl,
|
||
|
|
slug: row.slug,
|
||
|
|
addedAt: row.added_at,
|
||
|
|
}));
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
success: true,
|
||
|
|
items,
|
||
|
|
itemCount: items.length,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Get wishlist error:", error);
|
||
|
|
res.status(500).json({ success: false, message: "Failed to get wishlist" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add to wishlist
|
||
|
|
router.post("/wishlist", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const { productId } = req.body;
|
||
|
|
|
||
|
|
if (!productId) {
|
||
|
|
return res
|
||
|
|
.status(400)
|
||
|
|
.json({ success: false, message: "Product ID is required" });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if product exists
|
||
|
|
const productCheck = await pool.query(
|
||
|
|
"SELECT id, name FROM products WHERE id = $1",
|
||
|
|
[productId]
|
||
|
|
);
|
||
|
|
if (productCheck.rows.length === 0) {
|
||
|
|
return res
|
||
|
|
.status(404)
|
||
|
|
.json({ success: false, message: "Product not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Insert wishlist item (ignore if already exists)
|
||
|
|
await pool.query(
|
||
|
|
`INSERT INTO customer_wishlist (customer_id, product_id)
|
||
|
|
VALUES ($1, $2)
|
||
|
|
ON CONFLICT (customer_id, product_id) DO NOTHING`,
|
||
|
|
[req.session.customerId, productId]
|
||
|
|
);
|
||
|
|
|
||
|
|
logger.info(
|
||
|
|
`Wishlist item added for customer ${req.session.customerId}: ${productId}`
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({ success: true, message: "Added to wishlist" });
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Add to wishlist error:", error);
|
||
|
|
res
|
||
|
|
.status(500)
|
||
|
|
.json({ success: false, message: "Failed to add to wishlist" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Remove from wishlist
|
||
|
|
router.delete("/wishlist/:id", requireCustomerAuth, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const result = await pool.query(
|
||
|
|
"DELETE FROM customer_wishlist WHERE id = $1 AND customer_id = $2 RETURNING id",
|
||
|
|
[req.params.id, req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (result.rows.length === 0) {
|
||
|
|
return res
|
||
|
|
.status(404)
|
||
|
|
.json({ success: false, message: "Wishlist item not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({ success: true, message: "Removed from wishlist" });
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Remove from wishlist error:", error);
|
||
|
|
res
|
||
|
|
.status(500)
|
||
|
|
.json({ success: false, message: "Failed to remove from wishlist" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Remove from wishlist by product ID
|
||
|
|
router.delete(
|
||
|
|
"/wishlist/product/:productId",
|
||
|
|
requireCustomerAuth,
|
||
|
|
async (req, res) => {
|
||
|
|
try {
|
||
|
|
await pool.query(
|
||
|
|
"DELETE FROM customer_wishlist WHERE product_id = $1 AND customer_id = $2",
|
||
|
|
[req.params.productId, req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({ success: true, message: "Removed from wishlist" });
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Remove from wishlist error:", error);
|
||
|
|
res
|
||
|
|
.status(500)
|
||
|
|
.json({ success: false, message: "Failed to remove from wishlist" });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// Check if product is in wishlist
|
||
|
|
router.get(
|
||
|
|
"/wishlist/check/:productId",
|
||
|
|
requireCustomerAuth,
|
||
|
|
async (req, res) => {
|
||
|
|
try {
|
||
|
|
const result = await pool.query(
|
||
|
|
"SELECT id FROM customer_wishlist WHERE product_id = $1 AND customer_id = $2",
|
||
|
|
[req.params.productId, req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
success: true,
|
||
|
|
inWishlist: result.rows.length > 0,
|
||
|
|
wishlistItemId: result.rows[0]?.id || null,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Check wishlist error:", error);
|
||
|
|
res
|
||
|
|
.status(500)
|
||
|
|
.json({ success: false, message: "Failed to check wishlist" });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// Get cart count (for navbar badge)
|
||
|
|
router.get("/cart/count", async (req, res) => {
|
||
|
|
try {
|
||
|
|
if (!req.session || !req.session.customerId) {
|
||
|
|
return res.json({ success: true, count: 0 });
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await pool.query(
|
||
|
|
"SELECT COALESCE(SUM(quantity), 0) as count FROM customer_cart WHERE customer_id = $1",
|
||
|
|
[req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
success: true,
|
||
|
|
count: parseInt(result.rows[0].count),
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Get cart count error:", error);
|
||
|
|
res.json({ success: true, count: 0 });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Get wishlist count (for navbar badge)
|
||
|
|
router.get("/wishlist/count", async (req, res) => {
|
||
|
|
try {
|
||
|
|
if (!req.session || !req.session.customerId) {
|
||
|
|
return res.json({ success: true, count: 0 });
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await pool.query(
|
||
|
|
"SELECT COUNT(*) as count FROM customer_wishlist WHERE customer_id = $1",
|
||
|
|
[req.session.customerId]
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
success: true,
|
||
|
|
count: parseInt(result.rows[0].count),
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error("Get wishlist count error:", error);
|
||
|
|
res.json({ success: true, count: 0 });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
module.exports = router;
|