diff --git a/app/services/openfoodfacts.py b/app/services/openfoodfacts.py index e2fd374..409137a 100644 --- a/app/services/openfoodfacts.py +++ b/app/services/openfoodfacts.py @@ -32,22 +32,23 @@ class OpenFoodFactsService: "https://world.openproductsfacts.org/api/v2", ] - async def _lookup_in_database(self, barcode: str, base_url: str) -> Optional[Dict[str, Any]]: - """Try one Open*Facts database. Returns parsed product dict or None.""" + async def _lookup_in_database( + self, barcode: str, base_url: str, client: httpx.AsyncClient + ) -> Optional[Dict[str, Any]]: + """Try one Open*Facts database using an existing client. Returns parsed product dict or None.""" try: - async with httpx.AsyncClient() as client: - response = await client.get( - f"{base_url}/product/{barcode}.json", - headers={"User-Agent": self.USER_AGENT}, - timeout=10.0, - ) - if response.status_code == 404: - return None - response.raise_for_status() - data = response.json() - if data.get("status") != 1: - return None - return self._parse_product_data(data, barcode) + response = await client.get( + f"{base_url}/product/{barcode}.json", + headers={"User-Agent": self.USER_AGENT}, + timeout=10.0, + ) + if response.status_code == 404: + return None + response.raise_for_status() + data = response.json() + if data.get("status") != 1: + return None + return self._parse_product_data(data, barcode) except httpx.HTTPError as e: logger.debug("HTTP error for %s at %s: %s", barcode, base_url, e) return None @@ -59,22 +60,26 @@ class OpenFoodFactsService: """ Look up a product by barcode, trying OFFs then fallback databases. + A single httpx.AsyncClient is created for the whole lookup chain so that + connection pooling and TLS session reuse apply across all database attempts. + Args: barcode: UPC/EAN barcode (8-13 digits) Returns: Dictionary with product information, or None if not found in any database. """ - result = await self._lookup_in_database(barcode, self.BASE_URL) - if result: - return result - - for db_url in self._FALLBACK_DATABASES: - result = await self._lookup_in_database(barcode, db_url) + async with httpx.AsyncClient() as client: + result = await self._lookup_in_database(barcode, self.BASE_URL, client) if result: - logger.info("Barcode %s found in fallback database: %s", barcode, db_url) return result + for db_url in self._FALLBACK_DATABASES: + result = await self._lookup_in_database(barcode, db_url, client) + if result: + logger.info("Barcode %s found in fallback database: %s", barcode, db_url) + return result + logger.info("Barcode %s not found in any Open*Facts database", barcode) return None diff --git a/frontend/src/components/InventoryList.vue b/frontend/src/components/InventoryList.vue index b59fc96..b8108bc 100644 --- a/frontend/src/components/InventoryList.vue +++ b/frontend/src/components/InventoryList.vue @@ -454,7 +454,7 @@ import { storeToRefs } from 'pinia' import { useInventoryStore } from '../stores/inventory' import { useSettingsStore } from '../stores/settings' import { inventoryAPI } from '../services/api' -import type { InventoryItem, BarcodeScanResponse } from '../services/api' +import type { InventoryItem } from '../services/api' import { formatQuantity } from '../utils/units' import EditItemModal from './EditItemModal.vue' import ConfirmDialog from './ConfirmDialog.vue' @@ -716,9 +716,11 @@ async function handleScannerGunInput() { const item = result.results[0] if (item?.added_to_inventory) { + const productName = item.product?.name || 'item' + const productBrand = item.product?.brand ? ` (${item.product.brand})` : '' scannerResults.value.push({ type: 'success', - message: `Added: ${item.product?.name || 'item'} to ${scannerLocation.value}`, + message: `Added: ${productName}${productBrand} to ${scannerLocation.value}`, }) await refreshItems() } else if (item?.needs_manual_entry) { diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index db05fed..e2549a1 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -585,7 +585,8 @@ export interface BuildRequest { export const recipesAPI = { async suggest(req: RecipeRequest): Promise { - const response = await api.post('/recipes/suggest', req) + // Allow up to 120s — cf-orch model cold-start can take 60+ seconds on first request + const response = await api.post('/recipes/suggest', req, { timeout: 120000 }) return response.data }, async getRecipe(id: number): Promise {