import { createServiceClient } from "../_shared/security.ts"; import { getCorsHeaders, jsonResponse, preflightResponse, readJsonBody, } from "../_shared/security.ts"; const MAX_BODY_BYTES = 8 * 1024; type SubscribeBody = { phone_normalized?: string; endpoint?: string; p256dh?: string; auth?: string; user_agent?: string; }; Deno.serve(async (request) => { if (request.method === "OPTIONS") { return preflightResponse(request, "public"); } if (request.method !== "POST") { return jsonResponse({ ok: false, error: "Method not allowed" }, 405); } const corsHeaders = getCorsHeaders(request, "public"); if (!corsHeaders) { return jsonResponse({ ok: false, error: "Origin not allowed" }, 403); } try { const { body } = await readJsonBody(request, { maxBytes: MAX_BODY_BYTES, }); const phoneNormalized = String(body.phone_normalized || "").trim(); const endpoint = String(body.endpoint || "").trim(); const p256dh = String(body.p256dh || "").trim(); const auth = String(body.auth || "").trim(); const userAgent = String(body.user_agent || "").trim() || null; if (!phoneNormalized) { return jsonResponse( { ok: false, error: "phone_normalized is required" }, 400, corsHeaders, ); } if (!endpoint) { return jsonResponse( { ok: false, error: "endpoint is required" }, 400, corsHeaders, ); } if (!p256dh) { return jsonResponse( { ok: false, error: "p256dh is required" }, 400, corsHeaders, ); } if (!auth) { return jsonResponse( { ok: false, error: "auth is required" }, 400, corsHeaders, ); } const supabase = createServiceClient(); const now = new Date().toISOString(); // Upsert on (phone_normalized, endpoint) — unique constraint idx_push_subs_phone_endpoint const { data, error } = await supabase .from("push_subscriptions") .upsert( { phone_normalized: phoneNormalized, endpoint, p256dh, auth, user_agent: userAgent, is_active: true, updated_at: now, last_seen_at: now, }, { onConflict: "phone_normalized,endpoint" }, ) .select("id, phone_normalized, endpoint, is_active, updated_at") .single(); if (error) { console.error("push-subscribe upsert error:", error); return jsonResponse( { ok: false, error: "Database error", detail: error.message }, 500, corsHeaders, ); } return jsonResponse( { ok: true, subscription: data }, 200, corsHeaders, ); } catch (error) { if (error instanceof Error && "status" in error) { const httpError = error as { status: number; message: string }; return jsonResponse( { ok: false, error: httpError.message }, httpError.status, corsHeaders, ); } return jsonResponse( { ok: false, error: error instanceof Error ? error.message : "Unexpected error", }, 500, corsHeaders, ); } });