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 permissions and team-based access.
    • 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.

High-level Architecture

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

    • Route structure under src/app with parallel layouts:
      • src/app/(base-layout) for core unauthenticated/outer pages.
      • src/app/[primaryTeam]/(team-layout) for team-scoped features (campaigns, permissions, petitions, dashboards).
      • Auth flows in src/app/auth (sign-in, sign-up, callback, confirmations).
    • UI components under src/components grouped by domain (campaign, petitions, tables, dashboard, permissions, teams, forms, ui).
    • Client-side helpers and hooks in src/hooks and src/lib.
  • Backend (within Next.js):

    • Middleware: src/middleware.ts delegates to src/lib/supabase/updateSession.ts to:
      • Hydrate Supabase session via cookies.
      • Redirect unauthenticated users to /auth/sign-in, preserving intended path via redirect_url cookie.
      • Enforce route-level permission pipelines defined in src/middleware/routePipelines.ts with handlers (feature gates, team access, API guards).
    • API routes: src/app/api/...:
      • essearch and essearch-common: Proxy to a Directus instance which authenticates and calls a custom endpoint (/essearch/validation) for voter/person search. Uses short-lived HTTP-only cookies for Directus tokens.
      • duplicate: Checks if a voter has already signed a campaign using Redis (Upstash).
      • invite-team-member: Uses Supabase service role to invite users, upsert membership, and call a Supabase Edge Function (send-team-invitation).
    • Server Actions / Services:
      • Domain “services” under src/services/** use Supabase (server client) to query/update Postgres (e.g., petitions, teams, permissions, campaigns).
      • Server actions under src/actions/** for mutations across auth, campaigns, petitions, teams, transactions, Responsibilities, credentials.
      • Permission and route control logic under src/middleware/** (validators, pipelines, types).
  • Data Stores

    • Supabase Postgres: Primary system of record for users, teams, campaigns, petitions, permissions, transactions, etc.
    • Redis (Upstash): Membership checks for duplicates per campaign (e.g., set campaign:{campaignId}:signers).
    • Cookies: Session cookies for Supabase SSR and short-lived Directus authentication tokens.
  • External Services

    • Supabase:
      • Auth (email invitation, session cookies via SSR client).
      • Postgres database access via @supabase/supabase-js.
      • Edge Functions (e.g., send-team-invitation).
    • 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

    • Middleware enforces:
      • Authenticated access outside /auth/*.
      • Dynamic, team/campaign-aware permissions via Supabase RPC get_user_combined_permissions and route pipelines in src/middleware/routePipelines.ts.
    • API routes guard external calls and validate credentials loaded from environment variables.
    • Service Role usage restricted to server-only contexts (e.g., invitations).

Tech Stack

  • Framework

    • Next.js 14.2.25 (App Router)
    • React 18
    • TypeScript 5
    • TailwindCSS 3.4, tailwind-merge, tailwindcss-animate
    • Radix UI primitives (avatar, checkbox, dialog, dropdown, popover, select, tabs, toast, tooltip, etc.)
    • @tanstack/react-table for complex table UIs
  • Backend/runtime

    • Next.js API routes and middleware for server logic
    • Supabase:
      • @supabase/ssr for server/client instantiation and cookie management
      • @supabase/supabase-js for DB/auth/edge functions
    • Directus SDK (@directus/sdk) for REST and token-based auth to proxy Elasticsearch queries
    • Redis (redis npm package) for duplication checks (configured for Upstash TLS)
    • Deployed on Vercel
  • Node / Runtime

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

    • Typescript config includes path alias @/* -> ./src/*.
    • Next image config allows Supabase storage host.
    • Middleware matcher excludes static assets and images.
    • Environment variables expected:
      • Supabase: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and SERVICE_ROLE_KEY (server only).
      • Directus: Either a generic credential triplet provided dynamically via request (credentials) 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 with Supabase server client.
    • src/actions/**: server actions handling create/update flows for key entities.
    • src/middleware/**: permission pipelines and validators; integrates with Supabase RPC for combined permissions.
    • src/components/tables/**: rich data tables (sorting, filtering, pagination) powered by TanStack Table.
    • src/lib/supabase/**: SSR-safe client setup for both client and server, including a special client for service role operations.