Files
acmcc/supabase/functions/reach-connection/index.ts
T
admin abd46bcb2b Hostinger Reach integration UI + ARC Buildium matching, drop Mailchimp
- HostingerReachPage (replaces MailchimpPage): connect Reach via
  reach-connection, per-association segment sync via reach-sync
- ARC Applications: Buildium import review/matching updates
- buildium-import-stage/apply: latest staging + apply changes (already
  deployed to Supabase)
- migrations: hostinger_reach_integration + arc_finalized_lock service
  role (already applied to live DB)
- CI: note that deployment is VPS-side polling (auto-deploy.sh cron)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 23:07:30 -04:00

57 lines
2.6 KiB
TypeScript

// Hostinger Reach — connection test. Validates the stored global API token by listing segments.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
const REACH_BASE = "https://developers.hostinger.com/api/reach/v1";
Deno.serve(async (req) => {
if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
const json = (b: unknown, status = 200) =>
new Response(JSON.stringify(b), { status, headers: { ...corsHeaders, "Content-Type": "application/json" } });
try {
const authHeader = req.headers.get("Authorization");
if (!authHeader) return json({ error: "Unauthorized" }, 401);
const userClient = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{ global: { headers: { Authorization: authHeader } } },
);
const admin = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);
const { data: { user } } = await userClient.auth.getUser();
if (!user) return json({ error: "Unauthorized" }, 401);
const { data: roles } = await admin.from("user_roles").select("role").eq("user_id", user.id);
if (!(roles || []).some((r: any) => r.role === "admin")) return json({ error: "Admin only" }, 403);
// A token can be supplied in the body to test before saving; otherwise use the stored one.
const body = await req.json().catch(() => ({}));
let token: string | null = typeof body.api_token === "string" && body.api_token.trim() ? body.api_token.trim() : null;
if (!token) {
const { data: cfg } = await admin
.from("hostinger_reach_config").select("api_token").order("updated_at", { ascending: false }).limit(1).maybeSingle();
token = cfg?.api_token || null;
}
if (!token) return json({ success: false, error: "No Hostinger Reach API token configured." }, 400);
const res = await fetch(`${REACH_BASE}/segmentation/segments`, {
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
});
const text = await res.text();
if (!res.ok) {
return json({ success: false, error: `Reach API ${res.status}: ${text.slice(0, 300)}` }, 200);
}
let parsed: any = {};
try { parsed = JSON.parse(text); } catch { /* ignore */ }
const list = Array.isArray(parsed) ? parsed : (parsed.data ?? parsed.segments ?? []);
return json({ success: true, segment_count: Array.isArray(list) ? list.length : 0 });
} catch (err) {
return json({ success: false, error: (err as Error).message }, 500);
}
});