105 lines
2.5 KiB
TypeScript
105 lines
2.5 KiB
TypeScript
import { createServiceClient } from "../_shared/security.ts";
|
|
import {
|
|
getCorsHeaders,
|
|
jsonResponse,
|
|
preflightResponse,
|
|
readJsonBody,
|
|
} from "../_shared/security.ts";
|
|
|
|
const MAX_BODY_BYTES = 4 * 1024;
|
|
|
|
type UnsubscribeBody = {
|
|
endpoint?: string;
|
|
phone_normalized?: 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<UnsubscribeBody>(request, {
|
|
maxBytes: MAX_BODY_BYTES,
|
|
});
|
|
|
|
const endpoint = String(body.endpoint || "").trim();
|
|
const phoneNormalized = String(body.phone_normalized || "").trim();
|
|
|
|
if (!endpoint) {
|
|
return jsonResponse(
|
|
{ ok: false, error: "endpoint is required" },
|
|
400,
|
|
corsHeaders,
|
|
);
|
|
}
|
|
if (!phoneNormalized) {
|
|
return jsonResponse(
|
|
{ ok: false, error: "phone_normalized is required" },
|
|
400,
|
|
corsHeaders,
|
|
);
|
|
}
|
|
|
|
const supabase = createServiceClient();
|
|
const now = new Date().toISOString();
|
|
|
|
const { data, error } = await supabase
|
|
.from("push_subscriptions")
|
|
.update({
|
|
is_active: false,
|
|
updated_at: now,
|
|
})
|
|
.eq("endpoint", endpoint)
|
|
.eq("phone_normalized", phoneNormalized)
|
|
.select("id, phone_normalized, endpoint, is_active, updated_at");
|
|
|
|
if (error) {
|
|
console.error("push-unsubscribe update error:", error);
|
|
return jsonResponse(
|
|
{ ok: false, error: "Database error", detail: error.message },
|
|
500,
|
|
corsHeaders,
|
|
);
|
|
}
|
|
|
|
const deactivatedCount = data?.length || 0;
|
|
|
|
return jsonResponse(
|
|
{
|
|
ok: true,
|
|
deactivated: deactivatedCount,
|
|
subscriptions: 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,
|
|
);
|
|
}
|
|
}); |