updateweb

This commit is contained in:
Local Server
2026-01-01 22:24:30 -06:00
parent 017c6376fc
commit 1919f6f8bb
185 changed files with 19860 additions and 17603 deletions

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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