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.ts—requireAuth(req)for bearer-JWT validation + admin client._shared/supabase.ts—createServiceRoleClient()andcreateUserClient(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(requiresteam_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_usersjoined toroles.scope. - If
roles.scope === "inherit_team", the user is a team admin → permissions come fromteam_p_sets(team-wide bundles). - Otherwise, permissions come from
role_p_setsfor the user's role. - All bundles are flattened to perm keys via
permission_set_keys.perm_id.
- Reads
- With
campaign_id:- Allowed campaigns = union of
campaign_user_roles_team∪campaign_user_team_sets(∪campaign_teamfor 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].
- Team admin →
- Returned
permissionKeysis the union of team- and campaign-level keys.
- Allowed campaigns = union of
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 == :idvia 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,contains→ilike).- Groups by
groupBy(comma-separated). Tokens likecreatedAt:month/:date/:yearbucket date columns intoYYYY-MM/YYYY-MM-DD/YYYYkeys. - Computes per-metric aggregations (
count,sum,avg,min,max) with optional per-metricsegmentfilters.
- Persists
data + lastModifiedAton 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, dashboardfor every row incharts. - Fans out a parallel POST to
generate-chart-data/<id>for each chart (using the project anon key +Authorizationheader). - Waits with
Promise.allSettled. - For successful charts, updates the
dashboards.lastModifiedAttimestamp on the affected dashboard ids. - Response is
{ success, message, chartsRefreshed, chartsFailed, dashboardsUpdated, totalCharts, failedChartIds? }. Returns207 Multi-Statuswhen 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_URLenv var (parses host/port/password, enables TLS forrediss:protocol). - Adds or removes
voter_idfrom thecampaign:{campaign_id}:signersset.
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.