const express = require("express"); const { query, batchQuery } = require("../config/database"); const logger = require("../config/logger"); const { asyncHandler } = require("../middleware/errorHandler"); const { cacheMiddleware, cache } = require("../middleware/cache"); const { addCacheHeaders, fieldFilter, paginate, trackResponseTime, generateETag, optimizeJSON, } = require("../middleware/apiOptimization"); const { sendSuccess, sendError, sendNotFound, } = require("../utils/responseHelpers"); const router = express.Router(); // Apply global optimizations to all routes router.use(trackResponseTime); router.use(fieldFilter); router.use(optimizeJSON); // Reusable query fragments const PRODUCT_FIELDS = ` 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 `; const PRODUCT_IMAGE_AGG = ` 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 `; const handleDatabaseError = (res, error, context) => { logger.error(`${context} error:`, error); sendError(res); }; // Get all products - Cached for 5 minutes, optimized with index hints router.get( "/products", cacheMiddleware(300000), asyncHandler(async (req, res) => { const result = await query( `SELECT ${PRODUCT_FIELDS}, ${PRODUCT_IMAGE_AGG} 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 LIMIT 100` // Prevent full table scan ); sendSuccess(res, { products: result.rows }); }) ); // Get featured products - Cached for 10 minutes, optimized with index scan router.get( "/products/featured", cacheMiddleware(600000, (req) => `featured:${req.query.limit || 4}`), asyncHandler(async (req, res) => { const limit = Math.min(parseInt(req.query.limit) || 4, 20); const result = await query( `SELECT p.id, p.name, p.slug, p.shortdescription, p.price, p.category, p.stockquantity, ${PRODUCT_IMAGE_AGG} 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] ); sendSuccess(res, { products: result.rows }); }) ); // Get single product by ID or slug - Cached for 15 minutes router.get( "/products/:identifier", cacheMiddleware(900000, (req) => `product:${req.params.identifier}`), asyncHandler(async (req, res) => { const { identifier } = req.params; // Optimized UUID check const isUUID = identifier.length === 36 && identifier.indexOf("-") === 8; // Single optimized query for both cases const whereClause = isUUID ? "p.id = $1" : "(p.id = $1 OR p.slug = $1)"; const result = await query( `SELECT p.*, 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, 'display_order', pi.display_order, '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 ${whereClause} AND p.isactive = true GROUP BY p.id LIMIT 1`, [identifier] ); if (result.rows.length === 0) { return sendNotFound(res, "Product"); } sendSuccess(res, { product: result.rows[0] }); }) ); // 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", asyncHandler(async (req, res) => { const result = await query("SELECT * FROM sitesettings LIMIT 1"); sendSuccess(res, { settings: result.rows[0] || {} }); }) ); // 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" ); sendSuccess(res, { sections: result.rows }); }) ); // 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, categoryid, isactive, createdat FROM portfolioprojects WHERE isactive = true ORDER BY displayorder ASC, createdat DESC` ); sendSuccess(res, { projects: result.rows }); }) ); // 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 FROM blogposts WHERE ispublished = true ORDER BY createdat DESC` ); sendSuccess(res, { posts: result.rows }); }) ); // Get single blog post by slug router.get( "/blog/posts/:slug", asyncHandler(async (req, res) => { const result = await query( "SELECT * FROM blogposts WHERE slug = $1 AND ispublished = true", [req.params.slug] ); if (result.rows.length === 0) { return sendNotFound(res, "Blog post"); } sendSuccess(res, { post: result.rows[0] }); }) ); // Get custom pages - Cached for 10 minutes router.get( "/pages", cacheMiddleware(600000), asyncHandler(async (req, res) => { const result = await query( `SELECT id, title, slug, pagecontent as content, metatitle, metadescription, isactive, createdat FROM pages WHERE isactive = true ORDER BY createdat DESC` ); sendSuccess(res, { pages: result.rows }); }) ); // Get single page by slug - Cached for 15 minutes router.get( "/pages/:slug", cacheMiddleware(900000, (req) => `page:${req.params.slug}`), asyncHandler(async (req, res) => { const result = await query( `SELECT id, title, slug, pagecontent as content, metatitle, metadescription FROM pages WHERE slug = $1 AND isactive = true`, [req.params.slug] ); if (result.rows.length === 0) { return sendNotFound(res, "Page"); } sendSuccess(res, { page: result.rows[0] }); }) ); // Get menu items for frontend navigation - Cached for 30 minutes router.get( "/menu", cacheMiddleware(1800000), 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 || [] : []; const visibleItems = items.filter((item) => item.visible !== false); sendSuccess(res, { items: visibleItems }); }) ); // Get homepage settings for frontend router.get( "/homepage/settings", asyncHandler(async (req, res) => { const result = await query( "SELECT settings FROM site_settings WHERE key = 'homepage'" ); const settings = result.rows.length > 0 ? result.rows[0].settings : {}; sendSuccess(res, { settings }); }) ); // 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;