Add unified product search routes (CIMA OTC + Open Food Facts)

- GET /api/products/search?q={query} - parallel search across both sources
- GET /api/products/:source/:id - product detail by source (cima/openfoodfacts)
- Uses Promise.allSettled for fault-tolerant parallel search
- Added imports for searchOTC and OFF service functions
This commit is contained in:
Antoni Nuñez Romeu
2026-07-13 15:23:43 +02:00
parent f2a5dc4ca3
commit 229e1d8106
+50 -1
View File
@@ -19,7 +19,8 @@ import pinoHttp from 'pino-http';
import multer from 'multer'; import multer from 'multer';
import { createWorker } from 'tesseract.js'; import { createWorker } from 'tesseract.js';
import { BarcodeDetector } from 'barcode-detector'; import { BarcodeDetector } from 'barcode-detector';
import { searchMedicines, getMedicineDetails } from './cima-service.js'; import { searchMedicines, getMedicineDetails, searchOTC } from './cima-service.js';
import { searchBabyProducts, getBabyProductDetails } from './off-service.js';
import { runFarmaciaWebhookImport, DEFAULT_FARMACIAS_WEBHOOK, importPharmaciesFromRows } from './farmacias-webhook-import.js'; import { runFarmaciaWebhookImport, DEFAULT_FARMACIAS_WEBHOOK, importPharmaciesFromRows } from './farmacias-webhook-import.js';
import { fetchPharmaciesExternal } from '../API/index.js'; import { fetchPharmaciesExternal } from '../API/index.js';
@@ -598,6 +599,54 @@ app.get('/api/medicines/:medicineId', async (req, res) => {
} }
}); });
// ========== UNIFIED PRODUCT SEARCH ==========
app.get('/api/products/search', async (req, res) => {
try {
const { q } = req.query;
if (!q || q.trim().length < 2) {
return res.json({ results: [], total: 0 });
}
const searchTerm = q.trim();
const [cimaResults, offResults] = await Promise.allSettled([
searchOTC(searchTerm),
searchBabyProducts(searchTerm)
]);
const cimaProducts = cimaResults.status === 'fulfilled' ? cimaResults.value : [];
const offProducts = offResults.status === 'fulfilled' ? offResults.value : [];
const allProducts = [...cimaProducts, ...offProducts]
.sort((a, b) => a.name.localeCompare(b.name));
res.json({
results: allProducts,
total: allProducts.length,
sources: { cima: cimaProducts.length, openfoodfacts: offProducts.length }
});
} catch (err) {
console.error('[Products] Search error:', err);
res.status(500).json({ error: 'Error searching products' });
}
});
app.get('/api/products/:source/:id', async (req, res) => {
try {
const { source, id } = req.params;
if (source === 'cima') {
const product = await getMedicineDetails(id);
if (!product) return res.status(404).json({ error: 'Product not found' });
return res.json({ ...product, source: 'cima', category: 'otc' });
}
if (source === 'openfoodfacts') {
const product = await getBabyProductDetails(id);
if (!product) return res.status(404).json({ error: 'Product not found' });
return res.json(product);
}
res.status(400).json({ error: 'Invalid source' });
} catch (err) {
console.error('[Products] Detail error:', err);
res.status(500).json({ error: 'Error fetching product details' });
}
});
// ========== AUTHENTICATION MIDDLEWARE ========== // ========== AUTHENTICATION MIDDLEWARE ==========
// Middleware to check if user is authenticated // Middleware to check if user is authenticated