2025-12-13 22:34:11 -06:00
|
|
|
const express = require("express");
|
|
|
|
|
const { query } = require("../config/database");
|
|
|
|
|
const { requireAuth } = require("../middleware/auth");
|
2025-12-19 20:44:46 -06:00
|
|
|
const logger = require("../config/logger");
|
|
|
|
|
const { asyncHandler } = require("../middleware/errorHandler");
|
|
|
|
|
const { sendSuccess, sendError, sendNotFound } = require("../utils/responseHelpers");
|
|
|
|
|
const { getById, deleteById, countRecords } = require("../utils/queryHelpers");
|
|
|
|
|
const { HTTP_STATUS } = require("../config/constants");
|
2025-12-13 17:53:34 -06:00
|
|
|
const router = express.Router();
|
|
|
|
|
|
2025-12-13 22:34:11 -06:00
|
|
|
// Dashboard stats API
|
2025-12-19 20:44:46 -06:00
|
|
|
router.get("/dashboard/stats", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const [productsCount, projectsCount, blogCount, pagesCount] = await Promise.all([
|
|
|
|
|
countRecords("products"),
|
|
|
|
|
countRecords("portfolioprojects"),
|
|
|
|
|
countRecords("blogposts"),
|
|
|
|
|
countRecords("pages"),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
stats: {
|
|
|
|
|
products: productsCount,
|
|
|
|
|
projects: projectsCount,
|
|
|
|
|
blog: blogCount,
|
|
|
|
|
pages: pagesCount,
|
|
|
|
|
},
|
|
|
|
|
user: {
|
|
|
|
|
name: req.session.name,
|
|
|
|
|
email: req.session.email,
|
|
|
|
|
role: req.session.role,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Generic CRUD factory function
|
|
|
|
|
const createCRUDRoutes = (config) => {
|
|
|
|
|
const { table, resourceName, listFields = "*", requiresAuth = true } = config;
|
|
|
|
|
const auth = requiresAuth ? requireAuth : (req, res, next) => next();
|
|
|
|
|
|
|
|
|
|
// List all
|
|
|
|
|
router.get(`/${resourceName}`, auth, asyncHandler(async (req, res) => {
|
2025-12-13 17:53:34 -06:00
|
|
|
const result = await query(
|
2025-12-19 20:44:46 -06:00
|
|
|
`SELECT ${listFields} FROM ${table} ORDER BY createdat DESC`
|
2025-12-13 17:53:34 -06:00
|
|
|
);
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { [resourceName]: result.rows });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Get by ID
|
|
|
|
|
router.get(`/${resourceName}/:id`, auth, asyncHandler(async (req, res) => {
|
|
|
|
|
const item = await getById(table, req.params.id);
|
|
|
|
|
if (!item) {
|
|
|
|
|
return sendNotFound(res, resourceName);
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
const responseKey = resourceName.slice(0, -1); // Remove 's' for singular
|
|
|
|
|
sendSuccess(res, { [responseKey]: item });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Delete
|
|
|
|
|
router.delete(`/${resourceName}/:id`, auth, asyncHandler(async (req, res) => {
|
|
|
|
|
const deleted = await deleteById(table, req.params.id);
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
return sendNotFound(res, resourceName);
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { message: `${resourceName} deleted successfully` });
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Products CRUD
|
|
|
|
|
router.get("/products", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const result = await query(
|
|
|
|
|
"SELECT id, name, price, stockquantity, isactive, createdat FROM products ORDER BY createdat DESC"
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, { products: result.rows });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.get("/products/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const product = await getById("products", req.params.id);
|
|
|
|
|
if (!product) {
|
|
|
|
|
return sendNotFound(res, "Product");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { product });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.post("/products", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
product: result.rows[0],
|
|
|
|
|
message: "Product created successfully",
|
|
|
|
|
}, HTTP_STATUS.CREATED);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.put("/products/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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 sendNotFound(res, "Product");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
|
|
|
|
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, {
|
|
|
|
|
product: result.rows[0],
|
|
|
|
|
message: "Product updated successfully",
|
|
|
|
|
});
|
|
|
|
|
}));
|
2025-12-14 01:54:40 -06:00
|
|
|
|
2025-12-19 20:44:46 -06:00
|
|
|
router.delete("/products/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const deleted = await deleteById("products", req.params.id);
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
return sendNotFound(res, "Product");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { message: "Product deleted successfully" });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Portfolio Projects CRUD
|
|
|
|
|
router.get("/portfolio/projects", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const result = await query(
|
|
|
|
|
"SELECT id, title, description, imageurl, categoryid, createdat FROM portfolioprojects ORDER BY createdat DESC"
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, { projects: result.rows });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.get("/portfolio/projects/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const project = await getById("portfolioprojects", req.params.id);
|
|
|
|
|
if (!project) {
|
|
|
|
|
return sendNotFound(res, "Project");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { project });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.post("/portfolio/projects", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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]
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
project: result.rows[0],
|
|
|
|
|
message: "Project created successfully",
|
|
|
|
|
}, HTTP_STATUS.CREATED);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.put("/portfolio/projects/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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 sendNotFound(res, "Project");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
project: result.rows[0],
|
|
|
|
|
message: "Project updated successfully",
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.delete("/portfolio/projects/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const deleted = await deleteById("portfolioprojects", req.params.id);
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
return sendNotFound(res, "Project");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { message: "Project deleted successfully" });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Blog Posts CRUD
|
|
|
|
|
router.get("/blog", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const result = await query(
|
|
|
|
|
"SELECT id, title, slug, excerpt, ispublished, createdat FROM blogposts ORDER BY createdat DESC"
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, { posts: result.rows });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.get("/blog/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const post = await getById("blogposts", req.params.id);
|
|
|
|
|
if (!post) {
|
|
|
|
|
return sendNotFound(res, "Blog post");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { post });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.post("/blog", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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]
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
post: result.rows[0],
|
|
|
|
|
message: "Blog post created successfully",
|
|
|
|
|
}, HTTP_STATUS.CREATED);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.put("/blog/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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 sendNotFound(res, "Blog post");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
post: result.rows[0],
|
|
|
|
|
message: "Blog post updated successfully",
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.delete("/blog/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const deleted = await deleteById("blogposts", req.params.id);
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
return sendNotFound(res, "Blog post");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { message: "Blog post deleted successfully" });
|
|
|
|
|
}));
|
2025-12-14 01:54:40 -06:00
|
|
|
|
|
|
|
|
// Custom Pages CRUD
|
2025-12-19 20:44:46 -06:00
|
|
|
router.get("/pages", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const result = await query(
|
|
|
|
|
"SELECT id, title, slug, ispublished, createdat FROM pages ORDER BY createdat DESC"
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, { pages: result.rows });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.get("/pages/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const page = await getById("pages", req.params.id);
|
|
|
|
|
if (!page) {
|
|
|
|
|
return sendNotFound(res, "Page");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { page });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.post("/pages", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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]
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
page: result.rows[0],
|
|
|
|
|
message: "Page created successfully",
|
|
|
|
|
}, HTTP_STATUS.CREATED);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.put("/pages/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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 sendNotFound(res, "Page");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
|
|
|
|
|
sendSuccess(res, {
|
|
|
|
|
page: result.rows[0],
|
|
|
|
|
message: "Page updated successfully",
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.delete("/pages/:id", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const deleted = await deleteById("pages", req.params.id);
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
return sendNotFound(res, "Page");
|
2025-12-14 01:54:40 -06:00
|
|
|
}
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { message: "Page deleted successfully" });
|
|
|
|
|
}));
|
2025-12-14 01:54:40 -06:00
|
|
|
|
2025-12-19 20:44:46 -06:00
|
|
|
// Settings Management
|
|
|
|
|
const settingsHandler = (key) => ({
|
|
|
|
|
get: asyncHandler(async (req, res) => {
|
2025-12-14 01:54:40 -06:00
|
|
|
const result = await query(
|
2025-12-19 20:44:46 -06:00
|
|
|
"SELECT settings FROM site_settings WHERE key = $1",
|
|
|
|
|
[key]
|
2025-12-14 01:54:40 -06:00
|
|
|
);
|
|
|
|
|
const settings = result.rows.length > 0 ? result.rows[0].settings : {};
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { settings });
|
|
|
|
|
}),
|
|
|
|
|
post: asyncHandler(async (req, res) => {
|
2025-12-14 01:54:40 -06:00
|
|
|
const settings = req.body;
|
|
|
|
|
await query(
|
|
|
|
|
`INSERT INTO site_settings (key, settings, updatedat)
|
2025-12-19 20:44:46 -06:00
|
|
|
VALUES ($1, $2, NOW())
|
|
|
|
|
ON CONFLICT (key) DO UPDATE SET settings = $2, updatedat = NOW()`,
|
|
|
|
|
[key, JSON.stringify(settings)]
|
2025-12-14 01:54:40 -06:00
|
|
|
);
|
2025-12-19 20:44:46 -06:00
|
|
|
sendSuccess(res, { message: `${key} settings saved successfully` });
|
|
|
|
|
}),
|
2025-12-14 01:54:40 -06:00
|
|
|
});
|
|
|
|
|
|
2025-12-19 20:44:46 -06:00
|
|
|
// Homepage Settings
|
|
|
|
|
const homepageSettings = settingsHandler("homepage");
|
|
|
|
|
router.get("/homepage/settings", requireAuth, homepageSettings.get);
|
|
|
|
|
router.post("/homepage/settings", requireAuth, homepageSettings.post);
|
2025-12-14 01:54:40 -06:00
|
|
|
|
2025-12-19 20:44:46 -06:00
|
|
|
// General Settings
|
|
|
|
|
const generalSettings = settingsHandler("general");
|
|
|
|
|
router.get("/settings", requireAuth, generalSettings.get);
|
|
|
|
|
router.post("/settings", requireAuth, generalSettings.post);
|
2025-12-14 01:54:40 -06:00
|
|
|
|
|
|
|
|
// Menu Management
|
2025-12-19 20:44:46 -06:00
|
|
|
router.get("/menu", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
const result = await query(
|
|
|
|
|
"SELECT settings FROM site_settings WHERE key = 'menu'"
|
|
|
|
|
);
|
|
|
|
|
const items = result.rows.length > 0 ? result.rows[0].settings.items || [] : [];
|
|
|
|
|
sendSuccess(res, { items });
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
router.post("/menu", requireAuth, asyncHandler(async (req, res) => {
|
|
|
|
|
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 })]
|
|
|
|
|
);
|
|
|
|
|
sendSuccess(res, { message: "Menu saved successfully" });
|
|
|
|
|
}));
|
2025-12-14 01:54:40 -06:00
|
|
|
|
2025-12-13 17:53:34 -06:00
|
|
|
module.exports = router;
|