import axios from 'axios'; import redisClient from './redis-client.js'; const OFF_API_BASE = 'https://world.openfoodfacts.org'; const CACHE_TTL = 3600; export function transformOFFProduct(offProduct) { if (!offProduct || !offProduct._id || !offProduct.product_name) { return null; } const categoriesTags = offProduct.categories_tags || []; let category = 'baby_food'; for (const tag of categoriesTags) { const lower = tag.toLowerCase(); if (lower.includes('baby-milk') || lower.includes('infant-milk')) { category = 'baby_milk'; break; } if (lower.includes('baby-cereal') || lower.includes('infant-cereal')) { category = 'baby_cereal'; break; } } return { id: offProduct._id, source: 'openfoodfacts', name: offProduct.product_name, brand: offProduct.brands || '', category, image_url: offProduct.image_url || null, nutriscore: offProduct.nutriscore_grade || null, ingredients: offProduct.ingredients_text || null, nova_group: offProduct.nova_group || null, eco_score: offProduct.ecoscore_grade || null, }; } export async function searchBabyProducts(query) { if (!query || query.trim().length < 2) { return []; } const searchTerm = query.trim().toLowerCase(); const cacheKey = `off:baby:${searchTerm}`; try { const cachedData = await redisClient.get(cacheKey); if (cachedData) { console.log(`Cache hit for OFF search: ${searchTerm}`); return JSON.parse(cachedData); } console.log(`Fetching from OFF API: ${searchTerm}`); const response = await axios.get(`${OFF_API_BASE}/cgi/search.pl`, { params: { search_terms: searchTerm, json: true, fields: 'product_name,brands,image_url,nutriscore_grade,ingredients_text,nova_group,ecoscore_grade,categories_tags', page_size: 20, action: 'process', }, timeout: 10000, }); if (response.data && response.data.products) { const products = response.data.products .map(transformOFFProduct) .filter(Boolean); try { await redisClient.setEx(cacheKey, CACHE_TTL, JSON.stringify(products)); console.log(`Cached ${products.length} OFF products for: ${searchTerm}`); } catch (cacheErr) { // cache write failed, still return the data } return products; } return []; } catch (error) { console.error('Error searching baby products from OFF:', error.message); return []; } } export async function getBabyProductDetails(barcode) { if (!barcode) { return null; } const cacheKey = `off:product:${barcode}`; try { const cachedData = await redisClient.get(cacheKey); if (cachedData) { console.log(`Cache hit for OFF product: ${barcode}`); return JSON.parse(cachedData); } console.log(`Fetching OFF product details: ${barcode}`); const response = await axios.get(`${OFF_API_BASE}/api/v2/product/${barcode}.json`, { timeout: 10000, }); if (response.data && response.data.product) { const product = transformOFFProduct(response.data.product); if (product) { try { await redisClient.setEx(cacheKey, CACHE_TTL, JSON.stringify(product)); console.log(`Cached OFF product: ${barcode}`); } catch (cacheErr) { // cache write failed, still return the data } return product; } } return null; } catch (error) { console.error(`Error fetching OFF product ${barcode}:`, error.message); return null; } } export async function clearCache(pattern = 'off:*') { try { const keys = await redisClient.keys(pattern); if (keys.length > 0) { await redisClient.del(keys); console.log(`Cleared ${keys.length} OFF cache entries`); return keys.length; } return 0; } catch (error) { console.error('Error clearing OFF cache:', error); return 0; } }