updateweb
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
const express = require("express");
|
||||
const { query } = require("../config/database");
|
||||
const { requireAuth } = require("../middleware/auth");
|
||||
const { cache } = require("../middleware/cache");
|
||||
const {
|
||||
invalidateProductCache,
|
||||
invalidateBlogCache,
|
||||
invalidatePortfolioCache,
|
||||
invalidateHomepageCache,
|
||||
} = require("../utils/cacheInvalidation");
|
||||
const logger = require("../config/logger");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const {
|
||||
@@ -252,6 +259,10 @@ router.put(
|
||||
"/products/:id",
|
||||
requireAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
console.log("=== UPDATE PRODUCT API CALLED ===");
|
||||
console.log("Product ID:", req.params.id);
|
||||
console.log("Request body:", JSON.stringify(req.body, null, 2));
|
||||
|
||||
const {
|
||||
name,
|
||||
shortdescription,
|
||||
@@ -269,6 +280,8 @@ router.put(
|
||||
images,
|
||||
} = req.body;
|
||||
|
||||
console.log("Images to save:", images);
|
||||
|
||||
// Generate slug if name is provided
|
||||
const slug = name ? generateSlug(name) : null;
|
||||
|
||||
@@ -344,16 +357,27 @@ router.put(
|
||||
return sendNotFound(res, "Product");
|
||||
}
|
||||
|
||||
console.log("Product updated in database:", result.rows[0].id);
|
||||
|
||||
// Update images if provided
|
||||
if (images && Array.isArray(images)) {
|
||||
console.log("Updating images, count:", images.length);
|
||||
|
||||
// Delete existing images for this product
|
||||
await query("DELETE FROM product_images WHERE product_id = $1", [
|
||||
req.params.id,
|
||||
]);
|
||||
const deleteResult = await query(
|
||||
"DELETE FROM product_images WHERE product_id = $1",
|
||||
[req.params.id]
|
||||
);
|
||||
console.log("Deleted existing images, count:", deleteResult.rowCount);
|
||||
|
||||
// Insert new images
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const img = images[i];
|
||||
console.log(
|
||||
`Inserting image ${i + 1}/${images.length}:`,
|
||||
img.image_url
|
||||
);
|
||||
|
||||
await query(
|
||||
`INSERT INTO product_images (
|
||||
product_id, image_url, color_variant, color_code, alt_text, display_order, is_primary, variant_price, variant_stock
|
||||
@@ -371,6 +395,9 @@ router.put(
|
||||
]
|
||||
);
|
||||
}
|
||||
console.log("All images inserted successfully");
|
||||
} else {
|
||||
console.log("No images to update");
|
||||
}
|
||||
|
||||
// Fetch complete product with images
|
||||
@@ -396,6 +423,12 @@ router.put(
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
console.log("Final product with images:", completeProduct.rows[0]);
|
||||
console.log("=== PRODUCT UPDATE COMPLETE ===");
|
||||
|
||||
// Invalidate product cache
|
||||
invalidateProductCache();
|
||||
|
||||
sendSuccess(res, {
|
||||
product: completeProduct.rows[0],
|
||||
message: "Product updated successfully",
|
||||
@@ -655,10 +688,15 @@ router.post(
|
||||
ispublished,
|
||||
pagedata,
|
||||
} = req.body;
|
||||
|
||||
// Generate readable ID from slug
|
||||
const pageId = `page-${slug}`;
|
||||
|
||||
const result = await query(
|
||||
`INSERT INTO pages (title, slug, content, pagecontent, metatitle, metadescription, ispublished, isactive, pagedata, createdat)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW()) RETURNING *`,
|
||||
`INSERT INTO pages (id, title, slug, content, pagecontent, metatitle, metadescription, ispublished, isactive, pagedata, createdat)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW()) RETURNING *`,
|
||||
[
|
||||
pageId,
|
||||
title,
|
||||
slug,
|
||||
content,
|
||||
|
||||
@@ -1,611 +0,0 @@
|
||||
const express = require("express");
|
||||
const { query } = require("../config/database");
|
||||
const { requireAuth } = require("../middleware/auth");
|
||||
const logger = require("../config/logger");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const router = express.Router();
|
||||
|
||||
// Dashboard stats API
|
||||
router.get("/dashboard/stats", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const productsCount = await query("SELECT COUNT(*) FROM products");
|
||||
const projectsCount = await query("SELECT COUNT(*) FROM portfolioprojects");
|
||||
const blogCount = await query("SELECT COUNT(*) FROM blogposts");
|
||||
const pagesCount = await query("SELECT COUNT(*) FROM pages");
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
stats: {
|
||||
products: parseInt(productsCount.rows[0].count),
|
||||
projects: parseInt(projectsCount.rows[0].count),
|
||||
blog: parseInt(blogCount.rows[0].count),
|
||||
pages: parseInt(pagesCount.rows[0].count),
|
||||
},
|
||||
user: {
|
||||
name: req.session.name,
|
||||
email: req.session.email,
|
||||
role: req.session.role,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Dashboard error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Products API
|
||||
router.get("/products", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT id, name, price, stockquantity, isactive, createdat FROM products ORDER BY createdat DESC"
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
products: result.rows,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Products error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio Projects API
|
||||
router.get("/portfolio/projects", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT id, title, description, imageurl, categoryid, createdat FROM portfolioprojects ORDER BY createdat DESC"
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
projects: result.rows,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Portfolio error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Blog Posts API
|
||||
router.get("/blog", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT id, title, slug, excerpt, ispublished, createdat FROM blogposts ORDER BY createdat DESC"
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
posts: result.rows,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Blog error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Pages API
|
||||
router.get("/pages", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT id, title, slug, ispublished, createdat FROM pages ORDER BY createdat DESC"
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
pages: result.rows,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Pages error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Get single product
|
||||
router.get("/products/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query("SELECT * FROM products WHERE id = $1", [
|
||||
req.params.id,
|
||||
]);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Product not found" });
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
product: result.rows[0],
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Product error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Create product
|
||||
router.post("/products", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
price,
|
||||
stockquantity,
|
||||
category,
|
||||
isactive,
|
||||
isbestseller,
|
||||
} = req.body;
|
||||
|
||||
const result = await query(
|
||||
`INSERT INTO products (name, description, price, stockquantity, category, isactive, isbestseller, createdat)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
|
||||
RETURNING *`,
|
||||
[
|
||||
name,
|
||||
description,
|
||||
price,
|
||||
stockquantity || 0,
|
||||
category,
|
||||
isactive !== false,
|
||||
isbestseller || false,
|
||||
]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
product: result.rows[0],
|
||||
message: "Product created successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Create product error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Update product
|
||||
router.put("/products/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
price,
|
||||
stockquantity,
|
||||
category,
|
||||
isactive,
|
||||
isbestseller,
|
||||
} = req.body;
|
||||
|
||||
const result = await query(
|
||||
`UPDATE products
|
||||
SET name = $1, description = $2, price = $3, stockquantity = $4,
|
||||
category = $5, isactive = $6, isbestseller = $7, updatedat = NOW()
|
||||
WHERE id = $8
|
||||
RETURNING *`,
|
||||
[
|
||||
name,
|
||||
description,
|
||||
price,
|
||||
stockquantity || 0,
|
||||
category,
|
||||
isactive !== false,
|
||||
isbestseller || false,
|
||||
req.params.id,
|
||||
]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Product not found" });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
product: result.rows[0],
|
||||
message: "Product updated successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Update product error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete product
|
||||
router.delete("/products/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"DELETE FROM products WHERE id = $1 RETURNING id",
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Product not found" });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Product deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Delete product error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio Project CRUD
|
||||
router.get("/portfolio/projects/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT * FROM portfolioprojects WHERE id = $1",
|
||||
[req.params.id]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Project not found" });
|
||||
}
|
||||
res.json({ success: true, project: result.rows[0] });
|
||||
} catch (error) {
|
||||
logger.error("Portfolio project error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/portfolio/projects", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { title, description, category, isactive } = req.body;
|
||||
const result = await query(
|
||||
`INSERT INTO portfolioprojects (title, description, category, isactive, createdat)
|
||||
VALUES ($1, $2, $3, $4, NOW()) RETURNING *`,
|
||||
[title, description, category, isactive !== false]
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
project: result.rows[0],
|
||||
message: "Project created successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Create portfolio project error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/portfolio/projects/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { title, description, category, isactive } = req.body;
|
||||
const result = await query(
|
||||
`UPDATE portfolioprojects
|
||||
SET title = $1, description = $2, category = $3, isactive = $4, updatedat = NOW()
|
||||
WHERE id = $5 RETURNING *`,
|
||||
[title, description, category, isactive !== false, req.params.id]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Project not found" });
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
project: result.rows[0],
|
||||
message: "Project updated successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Update portfolio project error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/portfolio/projects/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"DELETE FROM portfolioprojects WHERE id = $1 RETURNING id",
|
||||
[req.params.id]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Project not found" });
|
||||
}
|
||||
res.json({ success: true, message: "Project deleted successfully" });
|
||||
} catch (error) {
|
||||
logger.error("Delete portfolio project error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Blog Post CRUD
|
||||
router.get("/blog/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query("SELECT * FROM blogposts WHERE id = $1", [
|
||||
req.params.id,
|
||||
]);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Blog post not found" });
|
||||
}
|
||||
res.json({ success: true, post: result.rows[0] });
|
||||
} catch (error) {
|
||||
logger.error("Blog post error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/blog", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
title,
|
||||
slug,
|
||||
excerpt,
|
||||
content,
|
||||
metatitle,
|
||||
metadescription,
|
||||
ispublished,
|
||||
} = req.body;
|
||||
const result = await query(
|
||||
`INSERT INTO blogposts (title, slug, excerpt, content, metatitle, metadescription, ispublished, createdat)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW()) RETURNING *`,
|
||||
[
|
||||
title,
|
||||
slug,
|
||||
excerpt,
|
||||
content,
|
||||
metatitle,
|
||||
metadescription,
|
||||
ispublished || false,
|
||||
]
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
post: result.rows[0],
|
||||
message: "Blog post created successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Create blog post error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/blog/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
title,
|
||||
slug,
|
||||
excerpt,
|
||||
content,
|
||||
metatitle,
|
||||
metadescription,
|
||||
ispublished,
|
||||
} = req.body;
|
||||
const result = await query(
|
||||
`UPDATE blogposts
|
||||
SET title = $1, slug = $2, excerpt = $3, content = $4, metatitle = $5,
|
||||
metadescription = $6, ispublished = $7, updatedat = NOW()
|
||||
WHERE id = $8 RETURNING *`,
|
||||
[
|
||||
title,
|
||||
slug,
|
||||
excerpt,
|
||||
content,
|
||||
metatitle,
|
||||
metadescription,
|
||||
ispublished || false,
|
||||
req.params.id,
|
||||
]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Blog post not found" });
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
post: result.rows[0],
|
||||
message: "Blog post updated successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Update blog post error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/blog/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"DELETE FROM blogposts WHERE id = $1 RETURNING id",
|
||||
[req.params.id]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Blog post not found" });
|
||||
}
|
||||
res.json({ success: true, message: "Blog post deleted successfully" });
|
||||
} catch (error) {
|
||||
logger.error("Delete blog post error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Custom Pages CRUD
|
||||
router.get("/pages/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query("SELECT * FROM pages WHERE id = $1", [
|
||||
req.params.id,
|
||||
]);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Page not found" });
|
||||
}
|
||||
res.json({ success: true, page: result.rows[0] });
|
||||
} catch (error) {
|
||||
logger.error("Page error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/pages", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { title, slug, content, metatitle, metadescription, ispublished } =
|
||||
req.body;
|
||||
const result = await query(
|
||||
`INSERT INTO pages (title, slug, content, metatitle, metadescription, ispublished, createdat)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, NOW()) RETURNING *`,
|
||||
[title, slug, content, metatitle, metadescription, ispublished !== false]
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
page: result.rows[0],
|
||||
message: "Page created successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Create page error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/pages/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { title, slug, content, metatitle, metadescription, ispublished } =
|
||||
req.body;
|
||||
const result = await query(
|
||||
`UPDATE pages
|
||||
SET title = $1, slug = $2, content = $3, metatitle = $4,
|
||||
metadescription = $5, ispublished = $6, updatedat = NOW()
|
||||
WHERE id = $7 RETURNING *`,
|
||||
[
|
||||
title,
|
||||
slug,
|
||||
content,
|
||||
metatitle,
|
||||
metadescription,
|
||||
ispublished !== false,
|
||||
req.params.id,
|
||||
]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Page not found" });
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
page: result.rows[0],
|
||||
message: "Page updated successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Update page error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/pages/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query("DELETE FROM pages WHERE id = $1 RETURNING id", [
|
||||
req.params.id,
|
||||
]);
|
||||
if (result.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Page not found" });
|
||||
}
|
||||
res.json({ success: true, message: "Page deleted successfully" });
|
||||
} catch (error) {
|
||||
logger.error("Delete page error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Homepage Settings
|
||||
router.get("/homepage/settings", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT settings FROM site_settings WHERE key = 'homepage'"
|
||||
);
|
||||
const settings = result.rows.length > 0 ? result.rows[0].settings : {};
|
||||
res.json({ success: true, settings });
|
||||
} catch (error) {
|
||||
logger.error("Homepage settings error:", error);
|
||||
res.json({ success: true, settings: {} });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/homepage/settings", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = req.body;
|
||||
await query(
|
||||
`INSERT INTO site_settings (key, settings, updatedat)
|
||||
VALUES ('homepage', $1, NOW())
|
||||
ON CONFLICT (key) DO UPDATE SET settings = $1, updatedat = NOW()`,
|
||||
[JSON.stringify(settings)]
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Homepage settings saved successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Save homepage settings error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// General Settings
|
||||
router.get("/settings", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT settings FROM site_settings WHERE key = 'general'"
|
||||
);
|
||||
const settings = result.rows.length > 0 ? result.rows[0].settings : {};
|
||||
res.json({ success: true, settings });
|
||||
} catch (error) {
|
||||
logger.error("Settings error:", error);
|
||||
res.json({ success: true, settings: {} });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/settings", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = req.body;
|
||||
await query(
|
||||
`INSERT INTO site_settings (key, settings, updatedat)
|
||||
VALUES ('general', $1, NOW())
|
||||
ON CONFLICT (key) DO UPDATE SET settings = $1, updatedat = NOW()`,
|
||||
[JSON.stringify(settings)]
|
||||
);
|
||||
res.json({ success: true, message: "Settings saved successfully" });
|
||||
} catch (error) {
|
||||
logger.error("Save settings error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Menu Management
|
||||
router.get("/menu", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT settings FROM site_settings WHERE key = 'menu'"
|
||||
);
|
||||
const items =
|
||||
result.rows.length > 0 ? result.rows[0].settings.items || [] : [];
|
||||
res.json({ success: true, items });
|
||||
} catch (error) {
|
||||
logger.error("Menu error:", error);
|
||||
res.json({ success: true, items: [] });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/menu", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { items } = req.body;
|
||||
await query(
|
||||
`INSERT INTO site_settings (key, settings, updatedat)
|
||||
VALUES ('menu', $1, NOW())
|
||||
ON CONFLICT (key) DO UPDATE SET settings = $1, updatedat = NOW()`,
|
||||
[JSON.stringify({ items })]
|
||||
);
|
||||
res.json({ success: true, message: "Menu saved successfully" });
|
||||
} catch (error) {
|
||||
logger.error("Save menu error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -2,6 +2,7 @@ const express = require("express");
|
||||
const { query } = require("../config/database");
|
||||
const logger = require("../config/logger");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const { cacheMiddleware, cache } = require("../middleware/cache");
|
||||
const {
|
||||
sendSuccess,
|
||||
sendError,
|
||||
@@ -14,23 +15,30 @@ const handleDatabaseError = (res, error, context) => {
|
||||
sendError(res);
|
||||
};
|
||||
|
||||
// Get all products
|
||||
// Get all products - Cached for 5 minutes
|
||||
router.get(
|
||||
"/products",
|
||||
cacheMiddleware(300000), // 5 minutes cache
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
`SELECT p.id, p.name, p.slug, p.shortdescription, p.description, p.price,
|
||||
p.category, p.stockquantity, p.sku, p.weight, p.dimensions,
|
||||
p.material, p.isfeatured, p.isbestseller, p.createdat,
|
||||
json_agg(
|
||||
json_build_object(
|
||||
'id', pi.id,
|
||||
'image_url', pi.image_url,
|
||||
'color_variant', pi.color_variant,
|
||||
'alt_text', pi.alt_text,
|
||||
'is_primary', pi.is_primary
|
||||
) ORDER BY pi.display_order, pi.created_at
|
||||
) FILTER (WHERE pi.id IS NOT NULL) as images
|
||||
COALESCE(
|
||||
json_agg(
|
||||
json_build_object(
|
||||
'id', pi.id,
|
||||
'image_url', pi.image_url,
|
||||
'color_variant', pi.color_variant,
|
||||
'color_code', pi.color_code,
|
||||
'alt_text', pi.alt_text,
|
||||
'is_primary', pi.is_primary,
|
||||
'variant_price', pi.variant_price,
|
||||
'variant_stock', pi.variant_stock
|
||||
) ORDER BY pi.display_order, pi.created_at
|
||||
) FILTER (WHERE pi.id IS NOT NULL),
|
||||
'[]'::json
|
||||
) as images
|
||||
FROM products p
|
||||
LEFT JOIN product_images pi ON pi.product_id = p.id
|
||||
WHERE p.isactive = true
|
||||
@@ -41,20 +49,27 @@ router.get(
|
||||
})
|
||||
);
|
||||
|
||||
// Get featured products
|
||||
// Get featured products - Cached for 10 minutes
|
||||
router.get(
|
||||
"/products/featured",
|
||||
cacheMiddleware(600000, (req) => `featured:${req.query.limit || 4}`), // 10 minutes cache
|
||||
asyncHandler(async (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 4;
|
||||
const limit = Math.min(parseInt(req.query.limit) || 4, 20); // Max 20 items
|
||||
const result = await query(
|
||||
`SELECT p.id, p.name, p.slug, p.shortdescription, p.price, p.category,
|
||||
json_agg(
|
||||
json_build_object(
|
||||
'image_url', pi.image_url,
|
||||
'color_variant', pi.color_variant,
|
||||
'alt_text', pi.alt_text
|
||||
) ORDER BY pi.display_order, pi.created_at
|
||||
) FILTER (WHERE pi.id IS NOT NULL) as images
|
||||
`SELECT p.id, p.name, p.slug, p.shortdescription, p.price, p.category, p.stockquantity,
|
||||
COALESCE(
|
||||
json_agg(
|
||||
json_build_object(
|
||||
'image_url', pi.image_url,
|
||||
'color_variant', pi.color_variant,
|
||||
'color_code', pi.color_code,
|
||||
'alt_text', pi.alt_text,
|
||||
'variant_price', pi.variant_price,
|
||||
'variant_stock', pi.variant_stock
|
||||
) ORDER BY pi.display_order, pi.created_at
|
||||
) FILTER (WHERE pi.id IS NOT NULL),
|
||||
'[]'::json
|
||||
) as images
|
||||
FROM products p
|
||||
LEFT JOIN product_images pi ON pi.product_id = p.id
|
||||
WHERE p.isactive = true AND p.isfeatured = true
|
||||
@@ -89,9 +104,12 @@ router.get(
|
||||
'id', pi.id,
|
||||
'image_url', pi.image_url,
|
||||
'color_variant', pi.color_variant,
|
||||
'color_code', pi.color_code,
|
||||
'alt_text', pi.alt_text,
|
||||
'display_order', pi.display_order,
|
||||
'is_primary', pi.is_primary
|
||||
'is_primary', pi.is_primary,
|
||||
'variant_price', pi.variant_price,
|
||||
'variant_stock', pi.variant_stock
|
||||
) ORDER BY pi.display_order, pi.created_at
|
||||
) FILTER (WHERE pi.id IS NOT NULL) as images
|
||||
FROM products p
|
||||
@@ -109,9 +127,12 @@ router.get(
|
||||
'id', pi.id,
|
||||
'image_url', pi.image_url,
|
||||
'color_variant', pi.color_variant,
|
||||
'color_code', pi.color_code,
|
||||
'alt_text', pi.alt_text,
|
||||
'display_order', pi.display_order,
|
||||
'is_primary', pi.is_primary
|
||||
'is_primary', pi.is_primary,
|
||||
'variant_price', pi.variant_price,
|
||||
'variant_stock', pi.variant_stock
|
||||
) ORDER BY pi.display_order, pi.created_at
|
||||
) FILTER (WHERE pi.id IS NOT NULL) as images
|
||||
FROM products p
|
||||
@@ -130,6 +151,21 @@ router.get(
|
||||
})
|
||||
);
|
||||
|
||||
// Get all product categories - Cached for 30 minutes
|
||||
router.get(
|
||||
"/categories",
|
||||
cacheMiddleware(1800000), // 30 minutes cache
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
`SELECT DISTINCT category
|
||||
FROM products
|
||||
WHERE isactive = true AND category IS NOT NULL AND category != ''
|
||||
ORDER BY category ASC`
|
||||
);
|
||||
sendSuccess(res, { categories: result.rows.map((row) => row.category) });
|
||||
})
|
||||
);
|
||||
|
||||
// Get site settings
|
||||
router.get(
|
||||
"/settings",
|
||||
@@ -139,9 +175,10 @@ router.get(
|
||||
})
|
||||
);
|
||||
|
||||
// Get homepage sections
|
||||
// Get homepage sections - Cached for 15 minutes
|
||||
router.get(
|
||||
"/homepage/sections",
|
||||
cacheMiddleware(900000), // 15 minutes cache
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
"SELECT * FROM homepagesections ORDER BY displayorder ASC"
|
||||
@@ -149,10 +186,10 @@ router.get(
|
||||
sendSuccess(res, { sections: result.rows });
|
||||
})
|
||||
);
|
||||
|
||||
// Get portfolio projects
|
||||
// Get portfolio projects - Cached for 10 minutes
|
||||
router.get(
|
||||
"/portfolio/projects",
|
||||
cacheMiddleware(600000), // 10 minutes cache
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
`SELECT id, title, description, featuredimage, images, category,
|
||||
@@ -164,9 +201,10 @@ router.get(
|
||||
})
|
||||
);
|
||||
|
||||
// Get blog posts
|
||||
// Get blog posts - Cached for 5 minutes
|
||||
router.get(
|
||||
"/blog/posts",
|
||||
cacheMiddleware(300000), // 5 minutes cache
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
`SELECT id, title, slug, excerpt, content, imageurl, ispublished, createdat
|
||||
|
||||
@@ -54,62 +54,96 @@ router.get("/roles", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get single user by ID
|
||||
router.get("/:id", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const result = await query(
|
||||
`
|
||||
SELECT
|
||||
u.id, u.username, u.email, u.name, u.role, u.isactive,
|
||||
u.last_login, u.createdat, u.passwordneverexpires, u.role_id
|
||||
FROM adminusers u
|
||||
WHERE u.id = $1
|
||||
`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "User not found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: result.rows[0],
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Get user error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Create new user
|
||||
router.post("/", async (req, res) => {
|
||||
try {
|
||||
const { username, email, password, role_id, password_never_expires } =
|
||||
const { name, username, email, password, role, passwordneverexpires } =
|
||||
req.body;
|
||||
|
||||
// Validate required fields
|
||||
if (!username || !email || !password || !role_id) {
|
||||
if (!username || !email || !password || !role) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Username, email, password, and role are required",
|
||||
message: "Name, username, email, password, and role are required",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
const existing = await query("SELECT id FROM adminusers WHERE email = $1", [
|
||||
email,
|
||||
]);
|
||||
const existing = await query(
|
||||
"SELECT id FROM adminusers WHERE email = $1 OR username = $2",
|
||||
[email, username]
|
||||
);
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "User with this email already exists",
|
||||
message: "User with this email or username already exists",
|
||||
});
|
||||
}
|
||||
|
||||
// Hash password
|
||||
// Hash password with bcrypt (10 rounds)
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Calculate password expiry (90 days from now if not never expires)
|
||||
let passwordExpiresAt = null;
|
||||
if (!password_never_expires) {
|
||||
if (!passwordneverexpires) {
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setDate(expiryDate.getDate() + 90);
|
||||
passwordExpiresAt = expiryDate.toISOString();
|
||||
}
|
||||
|
||||
// Insert new user
|
||||
// Insert new user with both role and name fields
|
||||
const result = await query(
|
||||
`
|
||||
INSERT INTO adminusers (
|
||||
id, username, email, passwordhash, role_id,
|
||||
password_never_expires, password_expires_at,
|
||||
isactive, created_by, createdat, last_password_change
|
||||
id, name, username, email, passwordhash, role,
|
||||
passwordneverexpires, password_expires_at,
|
||||
isactive, created_by, createdat, lastpasswordchange
|
||||
) VALUES (
|
||||
'user-' || gen_random_uuid()::text,
|
||||
$1, $2, $3, $4, $5, $6, true, $7, NOW(), NOW()
|
||||
$1, $2, $3, $4, $5, $6, $7, true, $8, NOW(), NOW()
|
||||
)
|
||||
RETURNING id, username, email, role_id, isactive, createdat
|
||||
RETURNING id, name, username, email, role, isactive, createdat, passwordneverexpires
|
||||
`,
|
||||
[
|
||||
name || username,
|
||||
username,
|
||||
email,
|
||||
hashedPassword,
|
||||
role_id,
|
||||
password_never_expires || false,
|
||||
role,
|
||||
passwordneverexpires || false,
|
||||
passwordExpiresAt,
|
||||
req.session.user.email,
|
||||
]
|
||||
@@ -130,14 +164,25 @@ router.post("/", async (req, res) => {
|
||||
router.put("/:id", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { username, email, role_id, isactive, password_never_expires } =
|
||||
req.body;
|
||||
const {
|
||||
name,
|
||||
username,
|
||||
email,
|
||||
role,
|
||||
isactive,
|
||||
passwordneverexpires,
|
||||
password,
|
||||
} = req.body;
|
||||
|
||||
// Build update query dynamically
|
||||
const updates = [];
|
||||
const values = [];
|
||||
let paramCount = 1;
|
||||
|
||||
if (name !== undefined) {
|
||||
updates.push(`name = $${paramCount++}`);
|
||||
values.push(name);
|
||||
}
|
||||
if (username !== undefined) {
|
||||
updates.push(`username = $${paramCount++}`);
|
||||
values.push(username);
|
||||
@@ -146,25 +191,39 @@ router.put("/:id", async (req, res) => {
|
||||
updates.push(`email = $${paramCount++}`);
|
||||
values.push(email);
|
||||
}
|
||||
if (role_id !== undefined) {
|
||||
updates.push(`role_id = $${paramCount++}`);
|
||||
values.push(role_id);
|
||||
if (role !== undefined) {
|
||||
updates.push(`role = $${paramCount++}`);
|
||||
values.push(role);
|
||||
}
|
||||
if (isactive !== undefined) {
|
||||
updates.push(`isactive = $${paramCount++}`);
|
||||
values.push(isactive);
|
||||
}
|
||||
if (password_never_expires !== undefined) {
|
||||
updates.push(`password_never_expires = $${paramCount++}`);
|
||||
values.push(password_never_expires);
|
||||
if (passwordneverexpires !== undefined) {
|
||||
updates.push(`passwordneverexpires = $${paramCount++}`);
|
||||
values.push(passwordneverexpires);
|
||||
|
||||
// If setting to never expire, clear expiry date
|
||||
if (password_never_expires) {
|
||||
if (passwordneverexpires) {
|
||||
updates.push(`password_expires_at = NULL`);
|
||||
}
|
||||
}
|
||||
|
||||
updates.push(`updated_at = NOW()`);
|
||||
// Handle password update if provided
|
||||
if (password !== undefined && password !== "") {
|
||||
if (password.length < 8) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Password must be at least 8 characters long",
|
||||
});
|
||||
}
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
updates.push(`passwordhash = $${paramCount++}`);
|
||||
values.push(hashedPassword);
|
||||
updates.push(`lastpasswordchange = NOW()`);
|
||||
}
|
||||
|
||||
updates.push(`updatedat = NOW()`);
|
||||
values.push(id);
|
||||
|
||||
const result = await query(
|
||||
@@ -172,7 +231,7 @@ router.put("/:id", async (req, res) => {
|
||||
UPDATE adminusers
|
||||
SET ${updates.join(", ")}
|
||||
WHERE id = $${paramCount}
|
||||
RETURNING id, username, email, role_id, isactive, password_never_expires
|
||||
RETURNING id, name, username, email, role, isactive, passwordneverexpires
|
||||
`,
|
||||
values
|
||||
);
|
||||
@@ -195,6 +254,66 @@ router.put("/:id", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Change user password (PUT endpoint for password modal)
|
||||
router.put("/:id/password", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { password } = req.body;
|
||||
|
||||
if (!password || password.length < 8) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Password must be at least 8 characters long",
|
||||
});
|
||||
}
|
||||
|
||||
// Hash new password with bcrypt (10 rounds)
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Get user's password expiry setting
|
||||
const userResult = await query(
|
||||
"SELECT passwordneverexpires FROM adminusers WHERE id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "User not found",
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate new expiry date (90 days from now if not never expires)
|
||||
let passwordExpiresAt = null;
|
||||
if (!userResult.rows[0].passwordneverexpires) {
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setDate(expiryDate.getDate() + 90);
|
||||
passwordExpiresAt = expiryDate.toISOString();
|
||||
}
|
||||
|
||||
// Update password
|
||||
await query(
|
||||
`
|
||||
UPDATE adminusers
|
||||
SET passwordhash = $1,
|
||||
password_expires_at = $2,
|
||||
lastpasswordchange = NOW(),
|
||||
updatedat = NOW()
|
||||
WHERE id = $3
|
||||
`,
|
||||
[hashedPassword, passwordExpiresAt, id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Password changed successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Change password error:", error);
|
||||
res.status(500).json({ success: false, message: "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Reset user password
|
||||
router.post("/:id/reset-password", async (req, res) => {
|
||||
try {
|
||||
@@ -208,7 +327,7 @@ router.post("/:id/reset-password", async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
// Hash new password with bcrypt (10 rounds)
|
||||
const hashedPassword = await bcrypt.hash(new_password, 10);
|
||||
|
||||
// Get user's password expiry setting
|
||||
|
||||
Reference in New Issue
Block a user