Skip to main content

Introduction

Project Overview

Purpose of the Project

  • Primary goal: A petition validation and campaign management platform that helps teams organize campaigns, manage teams and roles, collect and validate petition signatures, and search voter records.
  • Problems it solves:
    • Centralizes petition workflows (campaign setup, formats, circulators, signature entry and validation).
    • Enforces fine-grained, layered permissions across teams, sub-teams, and campaigns.
    • Integrates voter search via an external service (Directus-backed Elasticsearch) for validation.
    • Prevents duplicate signers per campaign using Redis.
    • Manages invitations and onboarding via Supabase Auth and Edge Functions.
    • Provides configurable per-campaign dashboards and chart generation.
    • Tracks turn-ins, rates, transactions, and regions for campaign accounting.

High-level Architecture

  • Front-end: Next.js 14 App Router (React 18, TypeScript, TailwindCSS, Radix UI, shadcn/ui).

    • Route structure under src/app with three top-level layouts:
      • src/app/(base-layout) — root pages outside any team scope (landing/team chooser, account, no-access).
      • src/app/[primaryTeam]/(team-layout) — team-scoped features (campaigns list, members, roles, permission keys/sets, credentials, voter-search, rates, transactions, turn-ins, regions, sub-teams, circulator dashboards).
      • src/app/[primaryTeam]/(campaign-layout)/campaign/[slug] — campaign-scoped features (signatures entry, petitions, dashboards, validators/circulators/households views, sub-teams, transactions, turn-ins, regions).
    • Auth flows in src/app/auth (sign-in, sign-up, callback, confirmations, restricted, invite/login confirmations).
    • UI components under src/components grouped by domain (campaign, petitions, signatures, dashboard, permissions, permission-sets, roles, regions, teams, transactions, turn-in, tables, forms, ui, routes).
    • Sidebar navigation is composed from per-area "route" components under src/components/routes/* that read permissionKeys from TeamsContext.
  • Backend (within Next.js):

    • Edge middleware: src/middleware.ts delegates to src/lib/supabase/updateSession.ts to:
      • Hydrate the Supabase session via cookies (uses getClaims() to verify the JWT locally — no network round-trip per request).
      • Redirect unauthenticated requests to /auth/sign-in, preserving the intended path via the redirect_url cookie.
      • On a real browser refresh (F5 / Ctrl+Shift+R), POST to /api/permissions/revalidate to invalidate the cached permissions snapshot for the current user. Edge middleware no longer fetches permissions itself (permission gating moved to layouts).
    • Layout-based permission gating: (team-layout) and (campaign-layout) server layouts call getRoutePermissions(\{ teamId, campaignId \}), then redirect("/") or redirect("/$\{teamId\}/campaign/no-access") when access is missing. Page-level guards are added with requireAccess(\{ teamId, campaignId, key \}).
    • API routes (src/app/api/...):
      • essearch and essearch-common — proxy to Directus, which authenticates and calls a custom /essearch/validation endpoint for voter/person search. Tokens are cached in HTTP-only cookies.
      • duplicate — checks if a voter has already signed for a given campaign using Redis (Upstash) set membership.
      • permissions/revalidate — drops the current user's cached permissions snapshot via revalidateTag(\permissions-${userId}`)`. Called by middleware and the "Refresh permissions" button on the access-denied page.
      • charts/[id] — runs configurable aggregations against Supabase tables (count/sum/avg/min/max with grouping and filters) and persists generated chart data.
    • Server Actions / Services:
      • Domain "services" under src/services/** use the Supabase server client to query/update Postgres (campaigns, petitions, teams, permissions, transactions, dashboards, regions, etc.).
      • Server actions under src/actions/** for mutations (auth, campaigns, petitions, teams, transactions, responsibilities/permission-sets, dashboards, signatures, credentials, role-based-access).
      • Access-control helpers live in src/services/accessCheck/** (accessCheck, getRoutePermissions, requireAccess).
  • Data Stores

    • Supabase Postgres: Primary system of record for users, teams, campaigns, petitions, signatures, permission keys/sets, role assignments, dashboards, charts, transactions, turn-ins, rates, regions.
    • Redis (Upstash): Per-campaign signer set for fast duplicate detection (campaign:\{campaignId\}:signers).
    • Cookies: Supabase SSR session cookies, short-lived Directus access tokens, and a redirect_url cookie used by the edge middleware.
  • External Services

    • Supabase:
      • Auth (email invitations, session cookies via the SSR client, JWT claims verification).
      • Postgres database via @supabase/supabase-js and @supabase/ssr.
      • Edge Functions: get-permissions-snapshot, send-email, generate-chart-data, refresh-all-charts, sync-to-redis.
    • Directus:
      • Authentication and REST client via @directus/sdk.
      • Proxies an Elasticsearch-backed endpoint (/essearch/validation) for voter/person search.
    • Upstash Redis:
      • TLS-enabled Redis used for signature duplication checks.
    • Deployment:
      • Vercel (vercel.json present). Git-based deployments enabled for main, dev, staging.
  • Security and Access Control

    • Edge middleware enforces authenticated access outside /auth/* and keeps Supabase cookies in sync.
    • Permission resolution is delegated to the get-permissions-snapshot edge function, which inlines logic that previously lived in the get_user_combined_permissions SQL helper. It returns { teamAccess, campaignAccess?, permissionKeys? }.
    • Snapshots are memoized per (userId, teamId, campaignId) using unstable_cache and tagged permissions-$\{userId\} for surgical invalidation.
    • Layouts and pages call getRoutePermissions / requireAccess to enforce per-route gates; client UI also receives permissionKeys via TeamsContext to conditionally render sidebar items.
    • Service Role usage is restricted to server-only contexts (e.g., the chart generation route handler and the edge function).
    • Role hierarchy (roles.hierarchy_level) prevents lower-level admins from acting on roles at or above their own level.

Tech Stack

  • Framework

    • Next.js 14.2.25 (App Router)
    • React 18
    • TypeScript 5
    • TailwindCSS 3.4, tailwind-merge, tailwindcss-animate
    • Radix UI primitives + shadcn/ui (components.json)
    • @tanstack/react-table for complex table UIs
    • react-hook-form + zod + @hookform/resolvers for forms
    • recharts for charts
    • lucide-react for icons
  • Backend/runtime

    • Next.js API routes and Edge middleware for server logic
    • Supabase:
      • @supabase/ssr for server/client instantiation and cookie management
      • @supabase/supabase-js for DB / auth / edge function invocation
    • Supabase Edge Functions (Deno) for permission snapshots, email sending, chart generation, Redis sync
    • Directus SDK (@directus/sdk) for REST and token-based auth to proxy Elasticsearch queries
    • Redis (redis npm package) for duplication checks (Upstash, TLS)
    • Deployed on Vercel
  • Node / Runtime

    • No explicit engines field. Recommended Node 18+ (Next.js 14 compatible). Type definitions target @types/node ^20.
    • Vercel default Node runtime is used.
  • Configuration Highlights

    • TypeScript path alias @/* -> ./src/*.
    • Next image config allows the Supabase storage host.
    • Middleware matcher excludes static assets and images.
    • Environment variables expected (see docs/deployment/environment-variables.md):
      • Supabase: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and SERVICE_ROLE_KEY (server only).
      • Super-admin: SUPER_ADMIN_TEAM_ID (server only; surfaced to clients via TeamsContext).
      • Directus: dynamic credentials provided per request (credentials.\{name,emailKey,passwordKey,endpointKey\}) or fixed envs for common search:
        • DIRECTUS_API_EMAIL_SEARCH, DIRECTUS_API_PASSWORD_SEARCH, DIRECTUS_URL_SEARCH
      • Redis: REDIS_URL
  • Notable App Conventions

    • src/services/**: server-side data access using the Supabase server client. Returns QueryResponse ({ message, error, data }).
    • src/actions/**: server actions that handle create/update flows for key entities. Returns FormState ({ message, error, data?, zodIssues? }).
    • src/middleware/**: legacy permission pipelines and validators retained for reference. The active middleware only manages the Supabase session — page/layout-level gating is the source of truth.
    • src/services/accessCheck/**: route-level permission checks (accessCheck, getRoutePermissions, requireAccess).
    • src/lib/permissions/**: edge-safe helper for fetching the permissions snapshot.
    • src/lib/team-member-hierarchy.ts + src/services/roles/getUserRoleHierarchyLevelForTeam.ts: role-hierarchy gates used by member-management actions.
    • src/components/tables/**: rich data tables (sorting, filtering, pagination) powered by TanStack Table and the useFetchTableData hook.
    • src/components/routes/**: per-section sidebar navigation (campaign, campaign admin, accounting, common, super-admin, team-management, circulator, user-settings) that reads permissionKeys from TeamsContext.
    • src/lib/supabase/**: SSR-safe client setup for browser, server, and a special server client for service-role operations.
    • supabase/functions/**: Deno Edge Functions for permission snapshots, email, chart generation/refresh, and Redis sync.

Where to go next

TopicDoc
Folder-by-folder breakdown of src/Folder / File Structure
App Router routes, layouts and dynamic segmentsApp Directory
Server-side guards (middleware + layouts + pages)Middleware & Route Protection
Permission resolution + caching + invalidationPermissions Snapshot
Role hierarchy and member-management gatesRole Hierarchy
API route handlers under app/api/**API Route Handlers
Sidebar route components patternSidebar Route Bundles
Supabase Edge Functions and migrationsSupabase Backend
Deployment + environment variablesEnvironment Variables