import { Cache, CacheKey } from '../../lib/cache'; import { product } from '../urls'; import { ProductCategoryResult, ProductResult, SearchParams } from '../../types'; import appFetch from '../../utils/app-fetch'; import { ServerVariables } from '../../utils/server-variables'; import logger from '../../utils/log'; type GetProduct = { pk: number | string; locale?: string; currency?: string; searchParams?: SearchParams; groupProduct?: boolean; headers?: Record; }; const getProductDataHandler = ({ pk, locale, currency, searchParams, groupProduct, headers }: GetProduct) => { return async function () { // Convert pk to number if it's a string and validate const numericPk = typeof pk === 'string' ? parseInt(pk, 10) : pk; if (isNaN(numericPk)) { logger.warn('Invalid product pk provided', { handler: 'getProductDataHandler', pk, numericPk }); return null; } let url = groupProduct ? product.getGroupProductByPk(numericPk) : product.getProductByPk(numericPk); if (searchParams) { url += '?' + Object.keys(searchParams) .map((key) => `${key}=${encodeURIComponent(searchParams[key])}`) .join('&'); } try { const data = await appFetch({ url, locale, currency, init: { headers: { Accept: 'application/json', 'Content-Type': 'application/json', ...(headers ?? {}) } } }); // If product data is not found, return null to trigger 404 if (!data?.product?.pk) { logger.warn('Product not found', { handler: 'getProductDataHandler', pk: numericPk, hasData: !!data, hasProduct: !!data?.product }); return null; } const categoryUrl = product.categoryUrl(data.product.pk); const productCategoryData = await appFetch({ url: categoryUrl, locale, currency, init: { headers: { Accept: 'application/json', 'Content-Type': 'application/json' } } }); const menuItemModel = productCategoryData?.results[0]?.menuitemmodel; if (!menuItemModel) { logger.warn('menuItemModel is undefined, skipping breadcrumbData fetch', { handler: 'getProductDataHandler', pk: numericPk }); return { data, breadcrumbData: undefined }; } const breadcrumbUrl = product.breadcrumbUrl(menuItemModel); const breadcrumbData = await appFetch({ url: breadcrumbUrl, locale, currency, init: { headers: { Accept: 'application/json', 'Content-Type': 'application/json' } } }); return { data, breadcrumbData: breadcrumbData?.menu }; } catch (error) { logger.error('Error in getProductDataHandler', { handler: 'getProductDataHandler', pk: numericPk, error: error.message }); return null; } }; }; export const getProductData = async ({ pk, locale = ServerVariables.locale, currency = ServerVariables.currency, searchParams, groupProduct, headers }: GetProduct) => { // Convert pk to number for cache key if it's a string const numericPkForCache = typeof pk === 'string' ? parseInt(pk, 10) : pk; const result = await Cache.wrap( CacheKey[groupProduct ? 'GroupProduct' : 'Product']( numericPkForCache, searchParams ?? new URLSearchParams() ), locale, getProductDataHandler({ pk, locale, currency, searchParams, groupProduct, headers }), { expire: 300, compressed: true } ); // If product data is not found, throw 404 error if (!result) { const error = new Error('Product not found') as Error & { status: number }; error.status = 404; throw error; } return result; };