119 lines
3.1 KiB
TypeScript
119 lines
3.1 KiB
TypeScript
import { action } from '../_generated/server';
|
|
import { v } from 'convex/values';
|
|
|
|
interface OpenFoodFactsProduct {
|
|
product?: {
|
|
product_name?: string;
|
|
product_name_pt?: string;
|
|
generic_name?: string;
|
|
generic_name_pt?: string;
|
|
categories?: string;
|
|
categories_tags?: string[];
|
|
image_url?: string;
|
|
image_front_url?: string;
|
|
image_front_small_url?: string;
|
|
brands?: string;
|
|
quantity?: string;
|
|
packaging?: string;
|
|
};
|
|
status?: number;
|
|
status_verbose?: string;
|
|
}
|
|
|
|
interface ProductInfo {
|
|
nome?: string;
|
|
descricao?: string;
|
|
categoria?: string;
|
|
imagemUrl?: string;
|
|
marca?: string;
|
|
quantidade?: string;
|
|
embalagem?: string;
|
|
}
|
|
|
|
/**
|
|
* Busca informações de produto via API externa (Open Food Facts)
|
|
* Esta é uma funcionalidade opcional que pode ser usada para preencher
|
|
* automaticamente informações de produtos quando disponível.
|
|
*/
|
|
export const buscarInfoProdutoPorCodigoBarras = action({
|
|
args: {
|
|
codigoBarras: v.string()
|
|
},
|
|
handler: async (ctx, args): Promise<ProductInfo | null> => {
|
|
const { codigoBarras } = args;
|
|
|
|
// Validar formato básico de código de barras (EAN-13, UPC, etc.)
|
|
if (!codigoBarras || codigoBarras.length < 8 || codigoBarras.length > 14) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
// Tentar buscar na API Open Food Facts (gratuita, sem autenticação)
|
|
const response = await fetch(
|
|
`https://world.openfoodfacts.org/api/v0/product/${codigoBarras}.json`,
|
|
{
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': 'SGSE-App/1.0 (Almoxarifado)'
|
|
},
|
|
signal: AbortSignal.timeout(5000) // Timeout de 5 segundos
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
return null;
|
|
}
|
|
|
|
const data = (await response.json()) as OpenFoodFactsProduct;
|
|
|
|
if (data.status !== 1 || !data.product) {
|
|
return null;
|
|
}
|
|
|
|
const product = data.product;
|
|
|
|
// Extrair categoria (primeira categoria disponível)
|
|
let categoria: string | undefined;
|
|
if (product.categories_tags && product.categories_tags.length > 0) {
|
|
// Pegar a primeira categoria e limpar tags
|
|
const primeiraCategoria = product.categories_tags[0];
|
|
categoria = primeiraCategoria
|
|
.replace(/^pt:/, '')
|
|
.replace(/^en:/, '')
|
|
.replace(/-/g, ' ')
|
|
.split(' ')
|
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ');
|
|
} else if (product.categories) {
|
|
categoria = product.categories.split(',')[0].trim();
|
|
}
|
|
|
|
const info: ProductInfo = {
|
|
nome: product.product_name_pt || product.product_name || undefined,
|
|
descricao: product.generic_name_pt || product.generic_name || undefined,
|
|
categoria,
|
|
imagemUrl:
|
|
product.image_front_url ||
|
|
product.image_url ||
|
|
product.image_front_small_url ||
|
|
undefined,
|
|
marca: product.brands || undefined,
|
|
quantidade: product.quantity || undefined,
|
|
embalagem: product.packaging || undefined
|
|
};
|
|
|
|
// Retornar apenas se tiver pelo menos nome ou descrição
|
|
if (info.nome || info.descricao) {
|
|
return info;
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
// Log do erro mas não falhar a operação
|
|
console.error('Erro ao buscar informações do produto:', error);
|
|
return null;
|
|
}
|
|
}
|
|
});
|
|
|