E-Commerce-Module/backend/src/routes/productAdmin.js

488 lines
No EOL
14 KiB
JavaScript

const express = require('express');
const { v4: uuidv4 } = require('uuid');
const router = express.Router();
module.exports = (pool, query, authMiddleware) => {
// Apply authentication middleware to all routes
router.use(authMiddleware);
// Create a new product with multiple images
router.post('/', async (req, res, next) => {
try {
const {
name,
description,
categoryName,
price,
stockQuantity,
weightGrams,
lengthCm,
widthCm,
heightCm,
origin,
age,
materialType,
color,
images,
tags
} = req.body;
// Validate required fields
if (!name || !description || !categoryName || !price || !stockQuantity) {
return res.status(400).json({
error: true,
message: 'Required fields missing: name, description, categoryName, price, and stockQuantity are mandatory'
});
}
// Begin transaction
const client = await pool.connect();
try {
await client.query('BEGIN');
// Get category ID by name
const categoryResult = await client.query(
'SELECT id FROM product_categories WHERE name = $1',
[categoryName]
);
if (categoryResult.rows.length === 0) {
return res.status(404).json({
error: true,
message: `Category "${categoryName}" not found`
});
}
const categoryId = categoryResult.rows[0].id;
// Create product
const productId = uuidv4();
await client.query(
`INSERT INTO products (
id, name, description, category_id, price, stock_quantity,
weight_grams, length_cm, width_cm, height_cm,
origin, age, material_type, color
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`,
[
productId, name, description, categoryId, price, stockQuantity,
weightGrams || null, lengthCm || null, widthCm || null, heightCm || null,
origin || null, age || null, materialType || null, color || null
]
);
// Add images if provided
if (images && images.length > 0) {
for (let i = 0; i < images.length; i++) {
const { path, isPrimary = (i === 0) } = images[i];
await client.query(
'INSERT INTO product_images (product_id, image_path, display_order, is_primary) VALUES ($1, $2, $3, $4)',
[productId, path, i, isPrimary]
);
}
}
// Add tags if provided
if (tags && tags.length > 0) {
for (const tagName of tags) {
// Get tag ID
let tagResult = await client.query(
'SELECT id FROM tags WHERE name = $1',
[tagName]
);
let tagId;
// If tag doesn't exist, create it
if (tagResult.rows.length === 0) {
const newTagResult = await client.query(
'INSERT INTO tags (name) VALUES ($1) RETURNING id',
[tagName]
);
tagId = newTagResult.rows[0].id;
} else {
tagId = tagResult.rows[0].id;
}
// Add tag to product
await client.query(
'INSERT INTO product_tags (product_id, tag_id) VALUES ($1, $2)',
[productId, tagId]
);
}
}
await client.query('COMMIT');
// Get complete product with images and tags
const productQuery = `
SELECT p.*,
pc.name as category_name,
ARRAY_AGG(DISTINCT t.name) FILTER (WHERE t.name IS NOT NULL) AS tags,
json_agg(json_build_object(
'id', pi.id,
'path', pi.image_path,
'isPrimary', pi.is_primary,
'displayOrder', pi.display_order
)) FILTER (WHERE pi.id IS NOT NULL) AS images
FROM products p
JOIN product_categories pc ON p.category_id = pc.id
LEFT JOIN product_tags pt ON p.id = pt.product_id
LEFT JOIN tags t ON pt.tag_id = t.id
LEFT JOIN product_images pi ON p.id = pi.product_id
WHERE p.id = $1
GROUP BY p.id, pc.name
`;
const product = await query(productQuery, [productId]);
res.status(201).json({
message: 'Product created successfully',
product: product.rows[0]
});
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
} catch (error) {
next(error);
}
});
router.post('/:id/stock-notification', async (req, res, next) => {
try {
const { id } = req.params;
const { enabled, email, threshold } = req.body;
// Check if user is admin
if (!req.user.is_admin) {
return res.status(403).json({
error: true,
message: 'Admin access required'
});
}
// Check if product exists
const productCheck = await query(
'SELECT * FROM products WHERE id = $1',
[id]
);
if (productCheck.rows.length === 0) {
return res.status(404).json({
error: true,
message: 'Product not found'
});
}
// Store notification settings as JSONB
const notificationSettings = {
enabled,
email: email || null,
threshold: threshold || 0
};
// Update product with notification settings
const result = await query(
`UPDATE products
SET stock_notification = $1
WHERE id = $2
RETURNING *`,
[JSON.stringify(notificationSettings), id]
);
res.json({
message: 'Stock notification settings updated successfully',
product: result.rows[0]
});
} catch (error) {
next(error);
}
});
// Update an existing product
router.put('/:id', async (req, res, next) => {
try {
const { id } = req.params;
const {
name,
description,
categoryName,
price,
stockQuantity,
weightGrams,
lengthCm,
widthCm,
heightCm,
origin,
age,
materialType,
color,
images,
tags
} = req.body;
// Begin transaction
const client = await pool.connect();
try {
await client.query('BEGIN');
// Check if product exists
const productCheck = await client.query(
'SELECT * FROM products WHERE id = $1',
[id]
);
if (productCheck.rows.length === 0) {
return res.status(404).json({
error: true,
message: 'Product not found'
});
}
// If category is changing, get the new category ID
let categoryId = productCheck.rows[0].category_id;
if (categoryName) {
const categoryResult = await client.query(
'SELECT id FROM product_categories WHERE name = $1',
[categoryName]
);
if (categoryResult.rows.length === 0) {
return res.status(404).json({
error: true,
message: `Category "${categoryName}" not found`
});
}
categoryId = categoryResult.rows[0].id;
}
// Update product
const updateFields = [];
const updateValues = [];
let valueIndex = 1;
if (name) {
updateFields.push(`name = $${valueIndex}`);
updateValues.push(name);
valueIndex++;
}
if (description) {
updateFields.push(`description = $${valueIndex}`);
updateValues.push(description);
valueIndex++;
}
if (categoryName) {
updateFields.push(`category_id = $${valueIndex}`);
updateValues.push(categoryId);
valueIndex++;
}
if (price) {
updateFields.push(`price = $${valueIndex}`);
updateValues.push(price);
valueIndex++;
}
if (stockQuantity !== undefined) {
updateFields.push(`stock_quantity = $${valueIndex}`);
updateValues.push(stockQuantity);
valueIndex++;
}
if (weightGrams !== undefined) {
updateFields.push(`weight_grams = $${valueIndex}`);
updateValues.push(weightGrams);
valueIndex++;
}
if (lengthCm !== undefined) {
updateFields.push(`length_cm = $${valueIndex}`);
updateValues.push(lengthCm);
valueIndex++;
}
if (widthCm !== undefined) {
updateFields.push(`width_cm = $${valueIndex}`);
updateValues.push(widthCm);
valueIndex++;
}
if (heightCm !== undefined) {
updateFields.push(`height_cm = $${valueIndex}`);
updateValues.push(heightCm);
valueIndex++;
}
if (origin !== undefined) {
updateFields.push(`origin = $${valueIndex}`);
updateValues.push(origin);
valueIndex++;
}
if (age !== undefined) {
updateFields.push(`age = $${valueIndex}`);
updateValues.push(age);
valueIndex++;
}
if (materialType !== undefined) {
updateFields.push(`material_type = $${valueIndex}`);
updateValues.push(materialType);
valueIndex++;
}
if (color !== undefined) {
updateFields.push(`color = $${valueIndex}`);
updateValues.push(color);
valueIndex++;
}
if (updateFields.length > 0) {
const updateQuery = `
UPDATE products
SET ${updateFields.join(', ')}
WHERE id = $${valueIndex}
`;
updateValues.push(id);
await client.query(updateQuery, updateValues);
}
// Update images if provided
if (images) {
// Remove existing images
await client.query(
'DELETE FROM product_images WHERE product_id = $1',
[id]
);
// Add new images
for (let i = 0; i < images.length; i++) {
const { path, isPrimary = (i === 0) } = images[i];
await client.query(
'INSERT INTO product_images (product_id, image_path, display_order, is_primary) VALUES ($1, $2, $3, $4)',
[id, path, i, isPrimary]
);
}
}
// Update tags if provided
if (tags) {
// Remove existing tags
await client.query(
'DELETE FROM product_tags WHERE product_id = $1',
[id]
);
// Add new tags
for (const tagName of tags) {
// Get tag ID
let tagResult = await client.query(
'SELECT id FROM tags WHERE name = $1',
[tagName]
);
let tagId;
// If tag doesn't exist, create it
if (tagResult.rows.length === 0) {
const newTagResult = await client.query(
'INSERT INTO tags (name) VALUES ($1) RETURNING id',
[tagName]
);
tagId = newTagResult.rows[0].id;
} else {
tagId = tagResult.rows[0].id;
}
// Add tag to product
await client.query(
'INSERT INTO product_tags (product_id, tag_id) VALUES ($1, $2)',
[id, tagId]
);
}
}
await client.query('COMMIT');
// Get updated product
const productQuery = `
SELECT p.*,
pc.name as category_name,
ARRAY_AGG(DISTINCT t.name) FILTER (WHERE t.name IS NOT NULL) AS tags,
json_agg(json_build_object(
'id', pi.id,
'path', pi.image_path,
'isPrimary', pi.is_primary,
'displayOrder', pi.display_order
)) FILTER (WHERE pi.id IS NOT NULL) AS images
FROM products p
JOIN product_categories pc ON p.category_id = pc.id
LEFT JOIN product_tags pt ON p.id = pt.product_id
LEFT JOIN tags t ON pt.tag_id = t.id
LEFT JOIN product_images pi ON p.id = pi.product_id
WHERE p.id = $1
GROUP BY p.id, pc.name
`;
const product = await query(productQuery, [id]);
res.json({
message: 'Product updated successfully',
product: product.rows[0]
});
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
} catch (error) {
next(error);
}
});
// Delete a product
router.delete('/:id', async (req, res, next) => {
try {
const { id } = req.params;
// Check if product exists
const productCheck = await query(
'SELECT * FROM products WHERE id = $1',
[id]
);
if (productCheck.rows.length === 0) {
return res.status(404).json({
error: true,
message: 'Product not found'
});
}
// Delete product (cascade will handle related records)
await query(
'DELETE FROM products WHERE id = $1',
[id]
);
res.json({
message: 'Product deleted successfully'
});
} catch (error) {
next(error);
}
});
return router;
};