Files
SkyArtShop/backend/routes/public.js

293 lines
8.4 KiB
JavaScript
Raw Normal View History

const express = require("express");
const { query } = require("../config/database");
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 router = express.Router();
2025-12-19 20:44:46 -06:00
const handleDatabaseError = (res, error, context) => {
logger.error(`${context} error:`, error);
sendError(res);
};
// Get all products
2025-12-19 20:44:46 -06:00
router.get(
"/products",
asyncHandler(async (req, res) => {
const result = await query(
2025-12-24 00:13:23 -06:00
`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
FROM products p
LEFT JOIN product_images pi ON pi.product_id = p.id
WHERE p.isactive = true
GROUP BY p.id
ORDER BY p.createdat DESC`
);
2025-12-19 20:44:46 -06:00
sendSuccess(res, { products: result.rows });
})
);
// Get featured products
2025-12-19 20:44:46 -06:00
router.get(
"/products/featured",
asyncHandler(async (req, res) => {
const limit = parseInt(req.query.limit) || 4;
const result = await query(
2025-12-24 00:13:23 -06:00
`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
FROM products p
LEFT JOIN product_images pi ON pi.product_id = p.id
WHERE p.isactive = true AND p.isfeatured = true
GROUP BY p.id
ORDER BY p.createdat DESC
LIMIT $1`,
[limit]
);
2025-12-19 20:44:46 -06:00
sendSuccess(res, { products: result.rows });
})
);
2025-12-24 00:13:23 -06:00
// Get single product by ID or slug
2025-12-19 20:44:46 -06:00
router.get(
2025-12-24 00:13:23 -06:00
"/products/:identifier",
2025-12-19 20:44:46 -06:00
asyncHandler(async (req, res) => {
2025-12-24 00:13:23 -06:00
const { identifier } = req.params;
// Check if identifier is a UUID
const isUUID =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
identifier
);
// Try to find by ID first, then by slug if not UUID
let result;
if (isUUID) {
result = await query(
`SELECT p.*,
json_agg(
json_build_object(
'id', pi.id,
'image_url', pi.image_url,
'color_variant', pi.color_variant,
'alt_text', pi.alt_text,
'display_order', pi.display_order,
'is_primary', pi.is_primary
) ORDER BY pi.display_order, pi.created_at
) FILTER (WHERE pi.id IS NOT NULL) as images
FROM products p
LEFT JOIN product_images pi ON pi.product_id = p.id
WHERE p.id = $1 AND p.isactive = true
GROUP BY p.id`,
[identifier]
);
} else {
// Try both ID and slug for non-UUID identifiers
result = await query(
`SELECT p.*,
json_agg(
json_build_object(
'id', pi.id,
'image_url', pi.image_url,
'color_variant', pi.color_variant,
'alt_text', pi.alt_text,
'display_order', pi.display_order,
'is_primary', pi.is_primary
) ORDER BY pi.display_order, pi.created_at
) FILTER (WHERE pi.id IS NOT NULL) as images
FROM products p
LEFT JOIN product_images pi ON pi.product_id = p.id
WHERE (p.id = $1 OR p.slug = $1) AND p.isactive = true
GROUP BY p.id`,
[identifier]
);
}
2025-12-19 20:44:46 -06:00
if (result.rows.length === 0) {
2025-12-19 20:44:46 -06:00
return sendNotFound(res, "Product");
}
2025-12-19 20:44:46 -06:00
sendSuccess(res, { product: result.rows[0] });
})
);
// Get site settings
2025-12-19 20:44:46 -06:00
router.get(
"/settings",
asyncHandler(async (req, res) => {
const result = await query("SELECT * FROM sitesettings LIMIT 1");
2025-12-19 20:44:46 -06:00
sendSuccess(res, { settings: result.rows[0] || {} });
})
);
// Get homepage sections
2025-12-19 20:44:46 -06:00
router.get(
"/homepage/sections",
asyncHandler(async (req, res) => {
const result = await query(
"SELECT * FROM homepagesections ORDER BY displayorder ASC"
);
2025-12-19 20:44:46 -06:00
sendSuccess(res, { sections: result.rows });
})
);
// Get portfolio projects
2025-12-19 20:44:46 -06:00
router.get(
"/portfolio/projects",
asyncHandler(async (req, res) => {
const result = await query(
2025-12-19 20:44:46 -06:00
`SELECT id, title, description, featuredimage, images, category,
categoryid, isactive, createdat
FROM portfolioprojects WHERE isactive = true
ORDER BY displayorder ASC, createdat DESC`
);
2025-12-19 20:44:46 -06:00
sendSuccess(res, { projects: result.rows });
})
);
// Get blog posts
2025-12-19 20:44:46 -06:00
router.get(
"/blog/posts",
asyncHandler(async (req, res) => {
const result = await query(
2025-12-19 20:44:46 -06:00
`SELECT id, title, slug, excerpt, content, imageurl, ispublished, createdat
FROM blogposts WHERE ispublished = true ORDER BY createdat DESC`
);
2025-12-19 20:44:46 -06:00
sendSuccess(res, { posts: result.rows });
})
);
2025-12-14 01:54:40 -06:00
// Get single blog post by slug
2025-12-19 20:44:46 -06:00
router.get(
"/blog/posts/:slug",
asyncHandler(async (req, res) => {
2025-12-14 01:54:40 -06:00
const result = await query(
"SELECT * FROM blogposts WHERE slug = $1 AND ispublished = true",
[req.params.slug]
);
2025-12-19 20:44:46 -06:00
2025-12-14 01:54:40 -06:00
if (result.rows.length === 0) {
2025-12-19 20:44:46 -06:00
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] });
})
);
2025-12-14 01:54:40 -06:00
// Get custom pages
2025-12-19 20:44:46 -06:00
router.get(
"/pages",
asyncHandler(async (req, res) => {
2025-12-14 01:54:40 -06:00
const result = await query(
2025-12-24 00:13:23 -06:00
`SELECT id, title, slug, pagecontent as content, metatitle, metadescription, isactive, createdat
2025-12-19 20:44:46 -06:00
FROM pages WHERE isactive = true ORDER BY createdat DESC`
2025-12-14 01:54:40 -06:00
);
2025-12-19 20:44:46 -06:00
sendSuccess(res, { pages: result.rows });
})
);
2025-12-14 01:54:40 -06:00
// Get single page by slug
2025-12-19 20:44:46 -06:00
router.get(
"/pages/:slug",
asyncHandler(async (req, res) => {
2025-12-14 01:54:40 -06:00
const result = await query(
2025-12-24 00:13:23 -06:00
"SELECT id, title, slug, pagecontent as content, metatitle, metadescription FROM pages WHERE slug = $1 AND isactive = true",
2025-12-14 01:54:40 -06:00
[req.params.slug]
);
2025-12-19 20:44:46 -06:00
2025-12-14 01:54:40 -06:00
if (result.rows.length === 0) {
2025-12-19 20:44:46 -06:00
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] });
})
);
2025-12-14 01:54:40 -06:00
// Get menu items for frontend navigation
2025-12-19 20:44:46 -06:00
router.get(
"/menu",
asyncHandler(async (req, res) => {
2025-12-14 01:54:40 -06:00
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'menu'"
);
const items =
result.rows.length > 0 ? result.rows[0].settings.items || [] : [];
const visibleItems = items.filter((item) => item.visible !== false);
2025-12-19 20:44:46 -06:00
sendSuccess(res, { items: visibleItems });
})
);
2025-12-14 01:54:40 -06:00
// Get homepage settings for frontend
2025-12-19 20:44:46 -06:00
router.get(
"/homepage/settings",
asyncHandler(async (req, res) => {
2025-12-14 01:54:40 -06:00
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'homepage'"
);
const settings = result.rows.length > 0 ? result.rows[0].settings : {};
2025-12-19 20:44:46 -06:00
sendSuccess(res, { settings });
})
);
2025-12-14 01:54:40 -06:00
2025-12-24 00:13:23 -06:00
// Get all team members (public)
router.get(
"/team-members",
asyncHandler(async (req, res) => {
const result = await query(
"SELECT id, name, position, bio, image_url FROM team_members ORDER BY display_order ASC, created_at DESC"
);
sendSuccess(res, { teamMembers: result.rows });
})
);
// Get menu items (public)
router.get(
"/menu",
asyncHandler(async (req, res) => {
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'menu'"
);
if (result.rows.length === 0) {
return sendSuccess(res, { items: [] });
}
// Parse JSON settings if it's a string
let settings = result.rows[0].settings;
if (typeof settings === "string") {
try {
settings = JSON.parse(settings);
} catch (e) {
logger.error("Failed to parse menu settings:", e);
return sendSuccess(res, { items: [] });
}
}
const items = settings.items || [];
// Filter only visible items for public
const visibleItems = items.filter((item) => item.visible !== false);
sendSuccess(res, { items: visibleItems });
})
);
module.exports = router;