import { getClientInvitationStateFromOrderStatus, hashInvitationToken, isActiveInvitationState, } from "../_shared/delivery-invitations.ts"; import { createServiceClient } from "../_shared/chatbot.ts"; const normalizeOrderItems = ( items: unknown, ): Array<{ name: string; quantity?: string }> => { if (!Array.isArray(items)) { return []; } return items .map((item) => { if (typeof item === "string") { const [namePart, quantityPart] = item.split("|").map((part) => part.trim()); const name = namePart || item.trim(); if (!name) { return null; } return quantityPart ? { name, quantity: quantityPart } : { name }; } if (item && typeof item === "object") { const typedItem = item as { name?: unknown; quantity?: unknown; label?: unknown }; const name = typeof typedItem.name === "string" ? typedItem.name.trim() : typeof typedItem.label === "string" ? typedItem.label.trim() : ""; const quantity = typeof typedItem.quantity === "string" ? typedItem.quantity.trim() : typeof typedItem.quantity === "number" ? String(typedItem.quantity) : ""; if (!name && !quantity) { return null; } return quantity ? { name: name || "Позиция", quantity } : { name: name || "Позиция" }; } return null; }) .filter((item): item is { name: string; quantity?: string } => Boolean(item)); }; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; Deno.serve(async (request) => { if (request.method === "OPTIONS") { return new Response("ok", { headers: corsHeaders }); } if (!["GET", "POST"].includes(request.method)) { return new Response(JSON.stringify({ error: "Method not allowed" }), { status: 405, headers: { ...corsHeaders, "Content-Type": "application/json", }, }); } try { const url = new URL(request.url); const token = request.method === "POST" ? ((await request.json()) as { token?: string })?.token || "" : url.searchParams.get("token") || ""; if (!token) { return new Response(JSON.stringify({ error: "token is required" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json", }, }); } const tokenHash = await hashInvitationToken(token); const supabase = createServiceClient(); const { data: invitation, error: invitationError } = await supabase .from("delivery_invitations") .select("*") .eq("token_hash", tokenHash) .single(); if (invitationError) { if (invitationError.code === "PGRST116") { return new Response( JSON.stringify({ ok: false, error: "Invitation not found" }), { status: 404, headers: { ...corsHeaders, "Content-Type": "application/json", }, }, ); } throw invitationError; } const { data: order, error: orderError } = await supabase .from("orders") .select("id, order_number, status, delivery_agreement_status, customer") .eq("id", invitation.order_id) .single(); if (orderError) { throw orderError; } const publicState = getClientInvitationStateFromOrderStatus(order.status); if (isActiveInvitationState(publicState) && !invitation.opened_at) { await supabase .from("delivery_invitations") .update({ opened_at: new Date().toISOString(), }) .eq("id", invitation.id); } return new Response( JSON.stringify({ ok: true, invitation: { orderId: invitation.order_id, state: publicState, token: token, orderNumber: order.order_number, customerName: order.customer?.name || invitation.customer_name || null, customerPhone: order.customer?.phone || invitation.customer_phone || null, orderItems: normalizeOrderItems(order.customer?.items), availableSlots: invitation.available_slots || [], deliveryDate: invitation.delivery_date || null, deliveryTime: invitation.delivery_time || null, orderStatus: order.status, deliveryAgreementStatus: order.delivery_agreement_status, }, }), { headers: { ...corsHeaders, "Content-Type": "application/json", }, }, ); } catch (error) { return new Response( JSON.stringify({ ok: false, error: error instanceof Error ? error.message : "Unexpected error", }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json", }, }, ); } });