export default { async fetch(request, env) { const url = new URL(request.url); // Decodificação segura contra falha de caracteres latinos (UTF-8 / URI) const profileHeader = request.headers.get("X-User-Profile") || "Anônimo"; let profile = "Anônimo"; try { profile = decodeURIComponent(profileHeader); } catch (e) { profile = profileHeader; } // Configuração dos cabeçalhos globais com suporte estrito a codificação UTF-8 const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, X-User-Profile" }; const jsonHeaders = { "Content-Type": "application/json; charset=utf-8", ...corsHeaders }; if (request.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } // Inicialização semente do catálogo completo e categorias const defaultConfig = { users: ["Otávio", "Camila"], lojas: ["Atacadão", "Tenda", "Pão de Açúcar", "Mercado Local"], categorias: [ "Frios e Laticínios", "Proteínas (Açougue)", "Hortifruti", "Bebidas", "Mercearia e Massas", "Temperos e Condimentos", "Doces e Guloseimas", "Café e Matinal", "Biscoitos e Salgadinhos", "Higiene e Perfumaria", "Limpeza e Utensílios" ], medidas: ["un", "kg", "L", "pc", "gf", "bj"], catalogo: { "Frios e Laticínios": ["Queijo Mussarela", "Presunto Cozido", "Manteiga com Sal", "Requeijão Cremoso", "Iogurte Natural", "Queijo Prato", "Creme de Leite", "Leite Condensado", "Leite Integral", "Salame", "Danone", "Leite em pó"], "Proteínas (Açougue)": ["Carne Moída Patinho", "Filé de Peito de Frango", "Alcatra em Bifes", "Filé de Tilápia", "Ovos Brancos", "Linguiça Toscana", "Sobrecoxa de Frango", "Carne Moída", "Carne", "Sassami de Frango", "Filezinho Sassami", "Linguiça"], "Hortifruti": ["Alface", "Brócolis", "Cebola", "Rúcula", "Uva", "Banana", "Batata", "Kiwi", "Limão", "Maçã", "Cenoura", "Laranja", "Alho"], "Bebidas": ["Refrigerante Cola", "Suco de Uva Integral", "Água Mineral com Gás", "Água Mineral sem Gás", "Cerveja Lata", "Vinho Tinto", "Suco de Laranja", "Bebida 58 e 55", "Refrigerante", "Velho Barreiro", "Vinho"], "Mercearia e Massas": ["Arroz", "Feijão", "Macarrão Espaguete", "Macarrão Parafuso", "Macarrão Artesanal", "Miojo", "Pipoca", "Farofa", "Farinha de Mandioca"], "Temperos e Condimentos": ["Sal Refinado", "Açúcar Refinado", "Azeite de Oliva Extra Virgem", "Óleo de Soja", "Molho de Tomate", "Maionese", "Ketchup", "Mostarda", "Alho Triturado", "Queijo parmesão", "Tempero Completo", "Açafrão", "Champignon", "Chimichurri", "Curry", "Pimenta", "Pimenta Calabresa", "Vinagre"], "Doces e Guloseimas": ["Gelatina", "Chocolate", "Carolina Brigadeiro", "Carolina Limão", "Cookies Goiaba", "Açaí", "Kit Kat", "Paçoca", "Fini", "Doce de Leite"], "Café e Matinal": ["Café", "Filtro de Café", "Baguete", "Pão de Forma", "Pão Francês", "Torrada", "Torrada Tradicional", "Achocolatado", "Cereal"], "Biscoitos e Salgadinhos": ["Biscoito", "Salgadinho", "Bolacha do Gatinho", "Batata Palha", "Amendoim"], "Higiene e Perfumaria": ["Shampoo Neutro", "Condicionador", "Creme Dental", "Escova de Dentes", "Sabonete", "Sabonete líquido", "Creme de cabelo", "Desodorante", "Creme de Barbear", "Fralda", "Absorvente"], "Limpeza e Utensílios": ["Detergente Neutro", "Sabão em Pó", "Amaciante Concentrado", "Desinfetante Perfumado", "Água Sanitária", "Esponja Multiuso", "CIF", "Papel Higiênico Folha Dupla", "Papel Toalha", "Álcool", "Sacolas"] } }; async function getGlobalConfig() { let config = await env.KV.get("config:global"); let parsedConfig = config ? JSON.parse(config) : null; if (!parsedConfig || !parsedConfig.categorias || !parsedConfig.catalogo) { await env.KV.put("config:global", JSON.stringify(defaultConfig)); return defaultConfig; } let updated = false; parsedConfig.categorias.forEach(cat => { if (!parsedConfig.catalogo[cat]) { parsedConfig.catalogo[cat] = defaultConfig.catalogo[cat] || []; updated = true; } }); if (updated) { await env.KV.put("config:global", JSON.stringify(parsedConfig)); } return parsedConfig; } async function getListsMeta() { let lists = await env.KV.get("lists:meta"); if (!lists) { const defaultLists = [ { id: "default_list", nome: "Lista Principal", criadaEm: new Date().toLocaleDateString("pt-BR") } ]; await env.KV.put("lists:meta", JSON.stringify(defaultLists)); return defaultLists; } return JSON.parse(lists); } try { if (url.pathname === "/api/config" && request.method === "GET") { const config = await getGlobalConfig(); return new Response(JSON.stringify(config), { headers: jsonHeaders }); } if (url.pathname === "/api/config" && request.method === "POST") { const newConfig = await request.json(); await env.KV.put("config:global", JSON.stringify(newConfig)); return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } if (url.pathname === "/api/lists" && request.method === "GET") { const lists = await getListsMeta(); return new Response(JSON.stringify({ lists }), { headers: jsonHeaders }); } if (url.pathname === "/api/lists" && request.method === "POST") { const newLists = await request.json(); await env.KV.put("lists:meta", JSON.stringify(newLists)); return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } if (url.pathname === "/api/lista" && request.method === "GET") { const listId = url.searchParams.get("listId") || "default_list"; const rawList = await env.KV.list({ prefix: `list:${listId}:item:` }); const items = rawList.keys.map(k => k.metadata).filter(Boolean); items.sort((a, b) => a.nome.localeCompare(b.nome)); return new Response(JSON.stringify({ items }), { headers: jsonHeaders }); } if (url.pathname === "/api/item" && request.method === "POST") { const payload = await request.json(); const { listId, item } = payload; const key = `list:${listId}:item:${item.id}`; await env.KV.put(key, JSON.stringify(item), { metadata: { id: item.id, nome: item.nome, quantidade: item.quantidade, medida: item.medida, preco: item.preco, categoria: item.categoria, promoMin: item.promoMin || 0, promoPreco: item.promoPreco || 0, carrinho: item.carrinho, nota: item.nota || "", atualizadoPor: profile, timestamp: Date.now() } }); return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } // EXCLUSÃO DE ITEM: Lida com parâmetros recebidos de forma segura via Query String (URL) if (url.pathname === "/api/item" && request.method === "DELETE") { const listId = url.searchParams.get("listId"); const id = url.searchParams.get("id"); if (listId && id) { await env.KV.delete(`list:${listId}:item:${id}`); } return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } // EXCLUSÃO DOS ITENS DA LISTA COMPLETA: Deleta os itens do KV ao remover uma lista do metadado if (url.pathname === "/api/lista" && request.method === "DELETE") { const listId = url.searchParams.get("listId"); if (listId) { const rawList = await env.KV.list({ prefix: `list:${listId}:item:` }); for (const key of rawList.keys) { await env.KV.delete(key.name); } } return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } if (url.pathname === "/api/finalizar" && request.method === "POST") { const { listId, total, itens, resetAction } = await request.json(); const timestamp = Date.now(); const tripId = `history:${timestamp}`; const historicoEntry = { id: tripId, loja: "Mercado", data: new Date().toLocaleDateString("pt-BR"), total: total, itens: itens, finalizadoPor: profile }; await env.KV.put(tripId, JSON.stringify(historicoEntry)); if (resetAction === "clear") { const rawList = await env.KV.list({ prefix: `list:${listId}:item:` }); for (const key of rawList.keys) { await env.KV.delete(key.name); } } else { for (const item of itens) { const key = `list:${listId}:item:${item.id}`; const updatedItem = { ...item, carrinho: false, timestamp: Date.now(), updatedPor: profile }; await env.KV.put(key, JSON.stringify(updatedItem), { metadata: { ...updatedItem } }); } } return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } if (url.pathname === "/api/historico" && request.method === "GET") { const rawHistory = await env.KV.list({ prefix: "history:" }); const historyEntries = []; for (const key of rawHistory.keys) { const val = await env.KV.get(key.name); if (val) historyEntries.push(JSON.parse(val)); } historyEntries.sort((a, b) => b.id.localeCompare(a.id)); return new Response(JSON.stringify({ historyEntries }), { headers: jsonHeaders }); } // EXCLUSÃO DE HISTÓRICO: Lida com parâmetros recebidos de forma segura via Query String (URL) if (url.pathname === "/api/historico" && request.method === "DELETE") { const id = url.searchParams.get("id"); if (id) { await env.KV.delete(id); } return new Response(JSON.stringify({ success: true }), { headers: jsonHeaders }); } return new Response(JSON.stringify({ error: "Endpoint não encontrado" }), { status: 404, headers: jsonHeaders }); } catch (err) { return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: jsonHeaders }); } } };