webupdate
This commit is contained in:
@@ -16,6 +16,14 @@ const {
|
||||
sendError,
|
||||
sendNotFound,
|
||||
} = require("../utils/responseHelpers");
|
||||
const {
|
||||
buildProductQuery,
|
||||
buildSingleProductQuery,
|
||||
buildBlogQuery,
|
||||
buildPagesQuery,
|
||||
buildPortfolioQuery,
|
||||
buildCategoriesQuery,
|
||||
} = require("../utils/queryBuilders");
|
||||
const router = express.Router();
|
||||
|
||||
// Apply global optimizations to all routes
|
||||
@@ -23,52 +31,15 @@ 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
|
||||
);
|
||||
const queryText = buildProductQuery({ limit: 100 });
|
||||
const result = await query(queryText);
|
||||
sendSuccess(res, { products: result.rows });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get featured products - Cached for 10 minutes, optimized with index scan
|
||||
@@ -77,19 +48,13 @@ router.get(
|
||||
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]
|
||||
);
|
||||
const queryText = buildProductQuery({
|
||||
where: "p.isactive = true AND p.isfeatured = true",
|
||||
limit,
|
||||
});
|
||||
const result = await query(queryText);
|
||||
sendSuccess(res, { products: result.rows });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get single product by ID or slug - Cached for 15 minutes
|
||||
@@ -97,61 +62,25 @@ 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]
|
||||
);
|
||||
const { text, values } = buildSingleProductQuery(req.params.identifier);
|
||||
const result = await query(text, values);
|
||||
|
||||
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
|
||||
cacheMiddleware(1800000),
|
||||
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`
|
||||
);
|
||||
const result = await query(buildCategoriesQuery());
|
||||
sendSuccess(res, { categories: result.rows.map((row) => row.category) });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get site settings
|
||||
@@ -160,46 +89,39 @@ router.get(
|
||||
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
|
||||
cacheMiddleware(900000),
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
"SELECT * FROM homepagesections ORDER BY displayorder ASC"
|
||||
"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
|
||||
cacheMiddleware(600000),
|
||||
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`
|
||||
);
|
||||
const result = await query(buildPortfolioQuery());
|
||||
sendSuccess(res, { projects: result.rows });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get blog posts - Cached for 5 minutes
|
||||
router.get(
|
||||
"/blog/posts",
|
||||
cacheMiddleware(300000), // 5 minutes cache
|
||||
cacheMiddleware(300000),
|
||||
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`
|
||||
);
|
||||
const result = await query(buildBlogQuery());
|
||||
sendSuccess(res, { posts: result.rows });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get single blog post by slug
|
||||
@@ -208,7 +130,7 @@ router.get(
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
"SELECT * FROM blogposts WHERE slug = $1 AND ispublished = true",
|
||||
[req.params.slug]
|
||||
[req.params.slug],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -216,7 +138,7 @@ router.get(
|
||||
}
|
||||
|
||||
sendSuccess(res, { post: result.rows[0] });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get custom pages - Cached for 10 minutes
|
||||
@@ -224,35 +146,48 @@ 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`
|
||||
);
|
||||
const result = await query(buildPagesQuery());
|
||||
sendSuccess(res, { pages: result.rows });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get single page by slug - Cached for 15 minutes
|
||||
// Get single page by slug - Cache disabled for immediate updates
|
||||
router.get(
|
||||
"/pages/:slug",
|
||||
cacheMiddleware(900000, (req) => `page:${req.params.slug}`),
|
||||
asyncHandler(async (req, res) => {
|
||||
console.log("=== PUBLIC PAGE REQUEST ===");
|
||||
console.log("Requested slug:", req.params.slug);
|
||||
|
||||
// Add no-cache headers
|
||||
res.set({
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
Pragma: "no-cache",
|
||||
Expires: "0",
|
||||
});
|
||||
|
||||
const result = await query(
|
||||
`SELECT id, title, slug, pagecontent as content, metatitle, metadescription
|
||||
`SELECT id, title, slug, pagecontent as content, metatitle, metadescription, pagedata
|
||||
FROM pages
|
||||
WHERE slug = $1 AND isactive = true`,
|
||||
[req.params.slug]
|
||||
[req.params.slug],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
console.log("Page not found for slug:", req.params.slug);
|
||||
return sendNotFound(res, "Page");
|
||||
}
|
||||
|
||||
console.log("=== RETURNING PAGE DATA ===");
|
||||
console.log("Page found, ID:", result.rows[0].id);
|
||||
console.log(
|
||||
"PageData:",
|
||||
result.rows[0].pagedata
|
||||
? JSON.stringify(result.rows[0].pagedata).substring(0, 200) + "..."
|
||||
: "null",
|
||||
);
|
||||
|
||||
sendSuccess(res, { page: result.rows[0] });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Get menu items for frontend navigation - Cached for 30 minutes
|
||||
@@ -261,13 +196,13 @@ router.get(
|
||||
cacheMiddleware(1800000),
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
"SELECT settings FROM site_settings WHERE key = 'menu'"
|
||||
"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
|
||||
@@ -275,11 +210,11 @@ router.get(
|
||||
"/homepage/settings",
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
"SELECT settings FROM site_settings WHERE key = 'homepage'"
|
||||
"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)
|
||||
@@ -287,10 +222,10 @@ 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"
|
||||
"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)
|
||||
@@ -298,7 +233,7 @@ router.get(
|
||||
"/menu",
|
||||
asyncHandler(async (req, res) => {
|
||||
const result = await query(
|
||||
"SELECT settings FROM site_settings WHERE key = 'menu'"
|
||||
"SELECT settings FROM site_settings WHERE key = 'menu'",
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -320,7 +255,7 @@ router.get(
|
||||
// Filter only visible items for public
|
||||
const visibleItems = items.filter((item) => item.visible !== false);
|
||||
sendSuccess(res, { items: visibleItems });
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user