Some checks failed
Deploy to Coolify / Code Quality (pull_request) Has been cancelled
Deploy to Coolify / Run Tests (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Development (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Production (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Test (pull_request) Has been cancelled
Pull Request Checks / Validate PR (pull_request) Has been cancelled
- Fetch product data from Open Food Facts API - Cache results in products table - Handle product not found gracefully - CORS enabled for frontend access - Returns cached data for performance Closes #24
141 lines
4.0 KiB
TypeScript
141 lines
4.0 KiB
TypeScript
// Product Lookup Edge Function
|
|
// Fetches product data from Open Food Facts API by barcode
|
|
|
|
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
|
|
const corsHeaders = {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
}
|
|
|
|
interface ProductData {
|
|
barcode: string
|
|
name: string
|
|
brand?: string
|
|
quantity?: string
|
|
image_url?: string
|
|
category?: string
|
|
cached?: boolean
|
|
}
|
|
|
|
serve(async (req) => {
|
|
// Handle CORS preflight
|
|
if (req.method === 'OPTIONS') {
|
|
return new Response('ok', { headers: corsHeaders })
|
|
}
|
|
|
|
try {
|
|
const { barcode } = await req.json()
|
|
|
|
if (!barcode) {
|
|
throw new Error('Barcode is required')
|
|
}
|
|
|
|
// Initialize Supabase client
|
|
const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
const supabase = createClient(supabaseUrl, supabaseKey)
|
|
|
|
// Check cache first (products table can store known products)
|
|
const { data: cachedProduct } = await supabase
|
|
.from('products')
|
|
.select('*')
|
|
.eq('barcode', barcode)
|
|
.single()
|
|
|
|
if (cachedProduct) {
|
|
console.log(`Cache HIT for barcode: ${barcode}`)
|
|
return new Response(
|
|
JSON.stringify({
|
|
barcode: cachedProduct.barcode,
|
|
name: cachedProduct.name,
|
|
brand: cachedProduct.brand,
|
|
quantity: cachedProduct.quantity,
|
|
image_url: cachedProduct.image_url,
|
|
category: cachedProduct.category,
|
|
cached: true,
|
|
} as ProductData),
|
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
)
|
|
}
|
|
|
|
console.log(`Cache MISS for barcode: ${barcode}, fetching from Open Food Facts...`)
|
|
|
|
// Fetch from Open Food Facts
|
|
const offResponse = await fetch(
|
|
`https://world.openfoodfacts.org/api/v2/product/${barcode}.json`,
|
|
{
|
|
headers: {
|
|
'User-Agent': 'Pantry/1.0 (https://github.com/pantry-app/pantry)',
|
|
},
|
|
}
|
|
)
|
|
|
|
if (!offResponse.ok) {
|
|
throw new Error(`Open Food Facts API error: ${offResponse.status}`)
|
|
}
|
|
|
|
const offData = await offResponse.json()
|
|
|
|
if (offData.status !== 1 || !offData.product) {
|
|
// Product not found in Open Food Facts
|
|
return new Response(
|
|
JSON.stringify({
|
|
barcode,
|
|
name: `Unknown Product (${barcode})`,
|
|
cached: false,
|
|
} as ProductData),
|
|
{
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
status: 404
|
|
}
|
|
)
|
|
}
|
|
|
|
const product = offData.product
|
|
|
|
// Extract relevant data
|
|
const productData: ProductData = {
|
|
barcode,
|
|
name: product.product_name || product.generic_name || `Product ${barcode}`,
|
|
brand: product.brands || undefined,
|
|
quantity: product.quantity || undefined,
|
|
image_url: product.image_url || product.image_front_url || undefined,
|
|
category: product.categories || undefined,
|
|
cached: false,
|
|
}
|
|
|
|
// Cache the product in our database (upsert)
|
|
await supabase.from('products').upsert({
|
|
barcode: productData.barcode,
|
|
name: productData.name,
|
|
brand: productData.brand,
|
|
quantity: productData.quantity,
|
|
image_url: productData.image_url,
|
|
category: productData.category,
|
|
}, { onConflict: 'barcode' })
|
|
|
|
console.log(`Successfully fetched and cached product: ${productData.name}`)
|
|
|
|
return new Response(
|
|
JSON.stringify(productData),
|
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
)
|
|
|
|
} catch (error) {
|
|
console.error('Error in product-lookup:', error)
|
|
return new Response(
|
|
JSON.stringify({
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
barcode: null,
|
|
name: null,
|
|
}),
|
|
{
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
status: 500
|
|
}
|
|
)
|
|
}
|
|
})
|