fix(backend): use v2 structured search API for OFF
- Switch from /cgi/search.pl (503 errors) to /api/v2/search with brand filter - Add required User-Agent header per OFF documentation - Add OFF_USER_AGENT env var for customization - Update .env.example with User-Agent documentation - Retry logic continues to work with new endpoint
This commit is contained in:
@@ -1,15 +1,25 @@
|
||||
import axios from 'axios';
|
||||
import redisClient from './redis-client.js';
|
||||
|
||||
// OFF API v3 (recommended) with fallback to v2 for search
|
||||
const OFF_API_BASE = 'https://world.openfoodfacts.org';
|
||||
const CACHE_TTL = 3600;
|
||||
const MAX_RETRIES = 2;
|
||||
const RETRY_DELAY_MS = 1000;
|
||||
|
||||
// Open Food Facts authentication (required for reliable access)
|
||||
// User-Agent is REQUIRED per OFF docs: AppName/Version (ContactEmail)
|
||||
const OFF_USER_AGENT = process.env.OFF_USER_AGENT || 'FarmaFinder/1.0 (https://github.com/farmafinder)';
|
||||
|
||||
// Open Food Facts authentication (optional, for write operations)
|
||||
const OFF_USERNAME = process.env.OFF_USERNAME;
|
||||
const OFF_PASSWORD = process.env.OFF_PASSWORD;
|
||||
|
||||
function getOffHeaders() {
|
||||
return {
|
||||
'User-Agent': OFF_USER_AGENT,
|
||||
};
|
||||
}
|
||||
|
||||
function getOffAuth() {
|
||||
if (OFF_USERNAME && OFF_PASSWORD) {
|
||||
return {
|
||||
@@ -74,18 +84,18 @@ export async function searchBabyProducts(query) {
|
||||
|
||||
console.log(`Fetching from OFF API: ${searchTerm}`);
|
||||
const auth = getOffAuth();
|
||||
const headers = getOffHeaders();
|
||||
|
||||
let lastError;
|
||||
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const response = await axios.get(`${OFF_API_BASE}/cgi/search.pl`, {
|
||||
// Use v2 structured search with brand filter (more reliable than /cgi/search.pl)
|
||||
const response = await axios.get(`${OFF_API_BASE}/api/v2/search`, {
|
||||
params: {
|
||||
search_terms: searchTerm,
|
||||
json: true,
|
||||
fields: 'product_name,brands,image_url,nutriscore_grade,ingredients_text,nova_group,ecoscore_grade,categories_tags',
|
||||
brands_tags: searchTerm,
|
||||
page_size: 20,
|
||||
action: 'process',
|
||||
},
|
||||
headers,
|
||||
timeout: 10000,
|
||||
validateStatus: (status) => status === 200,
|
||||
...(auth && { auth }),
|
||||
@@ -150,7 +160,10 @@ export async function getBabyProductDetails(barcode) {
|
||||
|
||||
console.log(`Fetching OFF product details: ${barcode}`);
|
||||
const auth = getOffAuth();
|
||||
const response = await axios.get(`${OFF_API_BASE}/api/v2/product/${barcode}.json`, {
|
||||
const headers = getOffHeaders();
|
||||
// Use v3 API (recommended) for product details
|
||||
const response = await axios.get(`${OFF_API_BASE}/api/v3/product/${barcode}.json`, {
|
||||
headers,
|
||||
timeout: 10000,
|
||||
validateStatus: (status) => status === 200,
|
||||
...(auth && { auth }),
|
||||
|
||||
Reference in New Issue
Block a user