Skip to main content

Edge Functions

All edge functions live under supabase/functions/<name>/index.ts and are deployed via the Supabase CLI. They share two helpers under supabase/functions/_shared/:

  • _shared/auth.tsrequireAuth(req) for bearer-JWT validation + admin client.
  • _shared/supabase.tscreateServiceRoleClient() and createUserClient(authorization).

1. get-permissions-snapshot

Path: supabase/functions/get-permissions-snapshot/index.ts

Method: GET. Bearer-token auth.

Query params:

  • team_id (optional)
  • campaign_id (requires team_id)

Response:

{
teamAccess: boolean;
campaignAccess?: boolean;
permissionKeys?: string[];
}

Always returns Cache-Control: no-store. Caching is the caller's responsibility (see Permissions Snapshot).

Behavior:

  • No team_id → just answers "does the user have any active team membership?".
  • With team_id:
    • Reads team_users joined to roles.scope.
    • If roles.scope === "inherit_team", the user is a team admin → permissions come from team_p_sets (team-wide bundles).
    • Otherwise, permissions come from role_p_sets for the user's role.
    • All bundles are flattened to perm keys via permission_set_keys.perm_id.
  • With campaign_id:
    • Allowed campaigns = union of campaign_user_roles_teamcampaign_user_team_sets (∪ campaign_team for team admins).
    • Permissions:
      • Team admin → campaign_team_sets[team, campaign].
      • Otherwise → role_p_sets[campaign_user_roles_team[user, team, campaign].role]campaign_user_team_sets[user, team, campaign].
    • Returned permissionKeys is the union of team- and campaign-level keys.

This function inlines logic that previously lived in the SQL helpers get_user_permissions, get_campaign_user_permissions, and get_user_combined_permissions — those helpers can be retired.

2. send-email

Path: supabase/functions/send-email/index.ts

Method: POST. Bearer-token auth via requireAuth.

Request body:

{
to: string;
subject: string;
content: string; // plain text fallback / required
html?: string; // optional HTML body
}

Behavior:

  • Validates JWT via requireAuth.
  • Reads SMTP env: SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SENDER_EMAIL, SMTP_SENDER_NAME.
  • Sends via denomailer (SMTPClient).
  • Returns { success, message } on success; { error, details } otherwise.

Caller: App/src/lib/email/send-invite-email.ts:

const { error } = await supabase.functions.invoke("send-email", {
body: { to, subject, html, content: html },
});

HTML templates are assembled in App/src/lib/email/invitation-email-templates.ts.

3. generate-chart-data

Path: supabase/functions/generate-chart-data/index.ts

Method: POST. The chart id comes from the URL path (last segment).

Behavior:

  • Loads charts.id == :id via the service-role client.
  • Builds a ChartConfig (source, groupBy, metrics, filters, campaign, team).
  • Runs generateChartData(config, supabaseAdmin):
    • from(source).select("*") filtered by team / campaign / global filters (eq, neq, gt(e), lt(e), in, containsilike).
    • Groups by groupBy (comma-separated). Tokens like createdAt:month / :date / :year bucket date columns into YYYY-MM / YYYY-MM-DD / YYYY keys.
    • Computes per-metric aggregations (count, sum, avg, min, max) with optional per-metric segment filters.
  • Persists data + lastModifiedAt on the chart row.

Sister Next.js route: App/src/app/api/charts/[id]/route.ts is a near-identical Node implementation used from the dashboard builder. The edge function is preferred when invoked from another edge function (see refresh-all-charts).

4. refresh-all-charts

Path: supabase/functions/refresh-all-charts/index.ts

Method: POST. Service-role.

Behavior:

  • Selects id, dashboard for every row in charts.
  • Fans out a parallel POST to generate-chart-data/<id> for each chart (using the project anon key + Authorization header).
  • Waits with Promise.allSettled.
  • For successful charts, updates the dashboards.lastModifiedAt timestamp on the affected dashboard ids.
  • Response is { success, message, chartsRefreshed, chartsFailed, dashboardsUpdated, totalCharts, failedChartIds? }. Returns 207 Multi-Status when partial.

Used by the Refresh Charts action (App/src/actions/dashboards/refresh-charts-action.ts) and can be scheduled via Supabase cron.

5. sync-to-redis

Path: supabase/functions/sync-to-redis/index.ts

Method: POST.

Request body:

{
action: "add" | "remove";
campaign_id: string;
voter_id: string;
}

Behavior:

  • Connects to Upstash via the REDIS_URL env var (parses host/port/password, enables TLS for rediss: protocol).
  • Adds or removes voter_id from the campaign:{campaign_id}:signers set.

This function used to be invoked by a Postgres trigger on signature inserts/deletes. That trigger was removed in 2026-03-25 …_redis_sync_function_trig_dropped.sql — Redis sync now happens explicitly from server actions / App/src/lib/redis.ts helpers.