/** * GET /api/data?cid=&cs= * * Verifies the checksum, resolves the org from the contact via the * Primary Form Contact relationship, reads the org's Framework Stage, * and walks past Org Engagement Submission activities for per-field * most-recent prefill. * * Returns FormDataPayload (see types/form.ts). * * STUB MODE: if CiviCRM env vars are unset, returns mock data so the UI * is exercisable without a live CRM. The mock data uses the actual stage * values and a couple of populated example fields so the form looks alive * during development. */ import { NextRequest, NextResponse } from "next/server"; import { civi, verifyChecksum } from "@/lib/civicrm"; import { loadPrefill } from "@/lib/prefill"; import { allFields } from "@/config/form"; import type { FormDataPayload } from "@/types/form"; const STUB_PAYLOAD: FormDataPayload = { orgName: "Sample Co-op (stub)", currentStage: "Organizing", prefill: { stage_0_peer_group_participation: "Peer cohort 4", stage_0_members_current: 87, stage_0_total_members_at_opening: null, stage_0_projected_y1_sales: 2400000, stage_0_projected_y2_sales: 2950000, stage_0_total_cost_of_project: 4200000, stage_1_vision: "A neighborhood-rooted co-op grocery prioritizing local farmers and equity in food access.", stage_1_business_concept: "5,500 sq ft full-service co-op in a transit-adjacent storefront.", }, }; function isStubMode(): boolean { return !( process.env.CIVI_BASE_URL && process.env.CIVI_API_KEY && process.env.CIVI_SITE_KEY ); } export async function GET(req: NextRequest) { const url = new URL(req.url); const cid = url.searchParams.get("cid"); const cs = url.searchParams.get("cs"); if (!cid || !cs) { return NextResponse.json( { error: "Missing cid or cs parameter." }, { status: 400 }, ); } if (isStubMode()) { return NextResponse.json(STUB_PAYLOAD); } // Verify checksum first. const ok = await verifyChecksum(cid, cs); if (!ok) { return NextResponse.json( { error: "Link is invalid or has expired. Please request a fresh one." }, { status: 401 }, ); } // Resolve the org from the contact's Primary Form Contact relationship. // Requires that the relationship exists and is active. We fetch contact_id_b // because the relationship is Individual (A) → Organization (B). const relRes = await civi<{ contact_id_b: number }>("Relationship", "get", { select: ["contact_id_b"], where: [ ["contact_id_a", "=", Number(cid)], ["relationship_type_id:name", "=", "Primary Form Contact of"], ["is_active", "=", true], ], limit: 2, }); const orgs = relRes.values ?? []; if (orgs.length === 0) { return NextResponse.json( { error: "No active Primary Form Contact relationship found for your contact." }, { status: 404 }, ); } if (orgs.length > 1) { return NextResponse.json( { error: "Your contact has multiple active Primary Form Contact relationships; staff must resolve before this link will work." }, { status: 409 }, ); } const orgId = orgs[0].contact_id_b; // Fetch org name + Framework Stage value. const orgRes = await civi<{ id: number; display_name: string; "Food_Co_op_Organizing.Stage": string | null; custom_1?: string | null; }>("Contact", "get", { select: ["id", "display_name", "custom_1", "Food_Co_op_Organizing.Stage"], where: [["id", "=", orgId]], }); const org = orgRes.values?.[0]; if (!org) { return NextResponse.json({ error: "Organization not found." }, { status: 404 }); } const currentStage = org["Food_Co_op_Organizing.Stage"] ?? org.custom_1 ?? ""; // Per-field-most-recent prefill across past submission activities. const { values: prefill } = await loadPrefill(orgId, allFields); const payload: FormDataPayload = { orgName: org.display_name, currentStage: typeof currentStage === "string" ? currentStage : "", prefill, }; return NextResponse.json(payload); }