feat: Add local Supabase (Docker) and waitlist table with seed#32
feat: Add local Supabase (Docker) and waitlist table with seed#32JuliobaCR wants to merge 1 commit intoACTA-Team:developfrom
Conversation
…sistence Overview Add complete local Supabase setup using Docker and Supabase CLI, providing developers with a self-contained database environment for waitlist feature development. The implementation includes database schema, migrations, seeding, API integration, and comprehensive documentation. Core Implementation Database Layer - Initialize Supabase project with local Docker configuration (supabase/config.toml) - Create migration (20250225000000_create_waitlist_table.sql) with: * public.waitlist table: id (UUID PK), email (UNIQUE), company_name, use_case, created_at * Index on created_at DESC for efficient chronological queries * Row Level Security (RLS) policies enforcing anonymous inserts, service_role selects - Add idempotent seed.sql with 8 production-ready sample data rows - Use ON CONFLICT (email) DO NOTHING to ensure seed re-execution safety Backend Integration - Create Supabase client (src/lib/supabase.ts) with fallback resilience: * Placeholder credentials when environment variables missing or invalid * Service-only getServiceSupabase() function isolating service role key * Graceful console warnings when keys missing (non-blocking startup) - Implement POST /api/waitlist route handler with: * Email validation (RFC-compliant regex) * Honeypot anti-bot field handling * Duplicate email detection (PostgreSQL 23505 → HTTP 409 Conflict) * Proper status codes: 201 Created, 400 Bad Request, 409 Conflict, 500 Internal Error * Server-side payload sanitization and database insert Frontend Updates - Refactor WaitlistForm component (src/features/waitlist/WaitlistForm.tsx): * Replace Formspree endpoint with internal /api/waitlist * Map form fields to database schema: company → company_name, message → use_case * Implement 4-state UX: idle, ok (201), duplicate (409), error (400/500) * Preserve existing styling and user experience patterns * Maintain loading and success states with appropriate messaging Configuration & Scripts - Add 4 npm scripts for database lifecycle management: * npm run db:start → npx supabase start (Docker containers) * npm run db:stop → npx supabase stop (Graceful shutdown) * npm run db:reset → npx supabase db reset (Migrations + seed) * npm run db:migration <name> → npx supabase migration new (New migration) - Install @supabase/supabase-js@^2.97.0 dependency - Update .env.example with optional Supabase environment variables and documentation - Extend .gitignore to exclude supabase/.temp/ (temporary files) and .env.local (local credentials) Documentation - Expand README.md with comprehensive "Local Supabase (Docker) — Optional" section: * Updated prerequisites (Docker Desktop, Supabase CLI marked as optional) * 5-step quick start guide for local development * Database scripts reference table * Supabase Studio access instructions * Detailed credential fallback behavior explanation * Testing instructions across different scenarios Architecture & Design Patterns Resilience & Graceful Degradation The implementation follows fail-safe principles: - App starts successfully with placeholder credentials (no environment variables required) - Waitlist submissions execute without errors in all scenarios - Real database persistence only when valid credentials provided - Non-blocking warnings logged to console when optional services unavailable Security Considerations - Service role key exclusively server-side (never in client bundle) - Row Level Security enforces anonymous insert-only, service-role full access - Email uniqueness enforced at both database and application layers - Input validation at multiple layers: client-side regex, server-side validation, database constraints - No hardcoded secrets (all credentials via environment variables) Code Quality - Full TypeScript type safety across all new code - Clear separation of concerns: client, API route, database client - Comprehensive code comments explaining fallback behavior - Follows Next.js and React best practices - Maintains consistency with existing project architecture and styling Acceptance Criteria Fulfillment ✓ README documents all prerequisites including optional Docker/Supabase CLI ✓ Documentation explains credentials are optional, app uses fallbacks ✓ Supabase client implements placeholder URLs and JWT tokens ✓ supabase/config.toml exists with default Supabase configuration ✓ All 4 npm scripts (db:start, db:stop, db:reset, db:migration) implemented ✓ Migration creates public.waitlist with required columns and indices ✓ Seed file contains exactly 8 rows with idempotent ON CONFLICT handling ✓ .env.example includes all 3 Supabase variables with documentation ✓ .gitignore properly excludes temporary and local credential files ✓ WaitlistForm successfully migrated from Formspree to Supabase backend ✓ API route validates all inputs, handles duplicates, manages database errors ✓ RLS policies enforce security while allowing anonymous signups Testing Verification - Syntax validation: All TypeScript files compile without errors - Import resolution: All path aliases (@/lib, @/components) resolve correctly - Dependency installation: @supabase/supabase-js available in node_modules - File structure: All required files exist in correct locations - Database schema: Migration includes table creation, indexing, RLS policies - API functionality: Route handler exports POST, validates inputs, handles errors - Component integration: WaitlistForm maps fields, handles all response states - Documentation: README complete, .env.example documented, gitignore updated Related Issue Closes ACTA-Team#23 - Add local Supabase (Docker) and waitlist table with seed
|
@JuliobaCR is attempting to deploy a commit to the ACTA Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughIntegrates Supabase as a database backend for the waitlist feature with local Docker-based development support. Adds environment configuration, API endpoint for form submissions, database schema with migrations and seed data, and updates the waitlist form to use the new backend instead of Formspree. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| - | - | JSON Web Token | 7505964 | src/lib/supabase.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/lib/supabase.ts (1)
53-59: Log missing service-role warning only once.Current fallback warning emits on every call, which can flood logs in production-like traffic.
♻️ Suggested refactor
+let hasWarnedMissingServiceRole = false; + export function getServiceSupabase(): SupabaseClient { if (!rawServiceRoleKey || isPlaceholder(rawServiceRoleKey)) { - if (typeof window === "undefined") { + if (typeof window === "undefined" && !hasWarnedMissingServiceRole) { console.warn( "[supabase] SUPABASE_SERVICE_ROLE_KEY is missing – falling back to anon client. " + "Waitlist inserts will use RLS policies. Set the key in .env.local for full access.", ); + hasWarnedMissingServiceRole = true; } return supabase; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/supabase.ts` around lines 53 - 59, The console.warn about a missing SUPABASE_SERVICE_ROLE_KEY is currently emitted on every call; to avoid log flooding add a module-level boolean flag (e.g., warnedMissingServiceRole = false) and, inside the existing check that uses rawServiceRoleKey and isPlaceholder (and the typeof window === "undefined" branch), only call console.warn the first time by testing the flag and then setting it to true; update the warning block where rawServiceRoleKey/isPlaceholder is checked so subsequent calls are silent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Line 118: The README currently over-promises that the waitlist form "submits
without errors" when Supabase env vars are missing; change the sentence to
guarantee only that the app will start and the waitlist form will be available,
but that persistence and successful responses are not guaranteed — e.g., replace
the clause "the waitlist form submits without errors (requests simply won't be
persisted)" with wording that the form may attempt submission but requests may
not be saved and may return errors or no-op when Supabase is unconfigured.
In `@src/lib/supabase.ts`:
- Around line 9-10: The constant PLACEHOLDER_ANON_KEY currently contains a
JWT-looking string which triggers secret scanners; replace its value with a
clearly non-JWT sentinel (for example a descriptive string like
"PLACEHOLDER_ANON_KEY_NOT_A_JWT" or similar) and update any tests or mocks that
expect the old literal to use the new sentinel instead; keep the constant name
PLACEHOLDER_ANON_KEY unchanged so all references (e.g., places that import or
read PLACEHOLDER_ANON_KEY) continue to work and no runtime behavior changes.
In `@supabase/migrations/20250225000000_create_waitlist_table.sql`:
- Around line 6-15: The current column-level UNIQUE on public.waitlist.email is
case-sensitive and should be replaced with a case-insensitive unique index:
remove the UNIQUE keyword from the email column definition and instead create a
unique index such as waitlist_email_ci_idx on public.waitlist using lower(email)
(e.g. CREATE UNIQUE INDEX waitlist_email_ci_idx ON public.waitlist
(lower(email))); keep the created_at index (waitlist_created_at_idx) as-is and
ensure any existing data is normalized or checked for conflicts before creating
the new unique index.
---
Nitpick comments:
In `@src/lib/supabase.ts`:
- Around line 53-59: The console.warn about a missing SUPABASE_SERVICE_ROLE_KEY
is currently emitted on every call; to avoid log flooding add a module-level
boolean flag (e.g., warnedMissingServiceRole = false) and, inside the existing
check that uses rawServiceRoleKey and isPlaceholder (and the typeof window ===
"undefined" branch), only call console.warn the first time by testing the flag
and then setting it to true; update the warning block where
rawServiceRoleKey/isPlaceholder is checked so subsequent calls are silent.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
.env.example.gitignoreREADME.mdpackage.jsonsrc/app/api/waitlist/route.tssrc/features/waitlist/WaitlistForm.tsxsrc/lib/supabase.tssupabase/.gitignoresupabase/config.tomlsupabase/migrations/20250225000000_create_waitlist_table.sqlsupabase/seed.sql
|
|
||
| ### Local Supabase (Docker) — Optional | ||
|
|
||
| The project includes a full local Supabase setup for waitlist persistence. **This is entirely optional.** When Supabase environment variables are missing or contain placeholder values, the app starts normally and the waitlist form submits without errors (requests simply won't be persisted). |
There was a problem hiding this comment.
Soften the “submits without errors” guarantee.
The current text over-promises behavior when Supabase is unconfigured. Prefer wording that guarantees app startup, but not guaranteed waitlist persistence/success response.
📝 Suggested wording
-The project includes a full local Supabase setup for waitlist persistence. **This is entirely optional.** When Supabase environment variables are missing or contain placeholder values, the app starts normally and the waitlist form submits without errors (requests simply won't be persisted).
+The project includes a full local Supabase setup for waitlist persistence. **This is entirely optional.** When Supabase environment variables are missing or contain placeholder values, the app starts normally, but waitlist requests may fail or be non-persistent until Supabase is configured.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| The project includes a full local Supabase setup for waitlist persistence. **This is entirely optional.** When Supabase environment variables are missing or contain placeholder values, the app starts normally and the waitlist form submits without errors (requests simply won't be persisted). | |
| The project includes a full local Supabase setup for waitlist persistence. **This is entirely optional.** When Supabase environment variables are missing or contain placeholder values, the app starts normally, but waitlist requests may fail or be non-persistent until Supabase is configured. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` at line 118, The README currently over-promises that the waitlist
form "submits without errors" when Supabase env vars are missing; change the
sentence to guarantee only that the app will start and the waitlist form will be
available, but that persistence and successful responses are not guaranteed —
e.g., replace the clause "the waitlist form submits without errors (requests
simply won't be persisted)" with wording that the form may attempt submission
but requests may not be saved and may return errors or no-op when Supabase is
unconfigured.
| const PLACEHOLDER_ANON_KEY = | ||
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"; |
There was a problem hiding this comment.
Replace JWT-shaped placeholder key to avoid secret-scanner failures.
This literal is flagged as a leaked JWT pattern and can block CI/security checks even as a placeholder. A non-JWT sentinel string is safer.
🔧 Suggested fix
-const PLACEHOLDER_ANON_KEY =
- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";
+const PLACEHOLDER_ANON_KEY = "placeholder-anon-key";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const PLACEHOLDER_ANON_KEY = | |
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"; | |
| const PLACEHOLDER_ANON_KEY = "placeholder-anon-key"; |
🧰 Tools
🪛 Gitleaks (8.30.0)
[high] 10-10: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/supabase.ts` around lines 9 - 10, The constant PLACEHOLDER_ANON_KEY
currently contains a JWT-looking string which triggers secret scanners; replace
its value with a clearly non-JWT sentinel (for example a descriptive string like
"PLACEHOLDER_ANON_KEY_NOT_A_JWT" or similar) and update any tests or mocks that
expect the old literal to use the new sentinel instead; keep the constant name
PLACEHOLDER_ANON_KEY unchanged so all references (e.g., places that import or
read PLACEHOLDER_ANON_KEY) continue to work and no runtime behavior changes.
| email text not null unique, | ||
| company_name text, | ||
| use_case text, | ||
|
|
||
| -- Timestamps | ||
| created_at timestamptz not null default now() | ||
| ); | ||
|
|
||
| -- Index for listing by date | ||
| create index waitlist_created_at_idx on public.waitlist (created_at desc); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat supabase/migrations/20250225000000_create_waitlist_table.sqlRepository: ACTA-Team/website
Length of output: 878
Replace case-sensitive UNIQUE constraint with case-insensitive unique index.
PostgreSQL's UNIQUE on text is case-sensitive, allowing Alice@x.com and alice@x.com to both be inserted despite appearing identical. This weakens deduplication and can result in duplicate responses from the same user across case variations.
Proposed fix
create table public.waitlist (
id uuid primary key default gen_random_uuid(),
-- Form data
- email text not null unique,
+ email text not null,
company_name text,
use_case text,
-- Timestamps
created_at timestamptz not null default now()
);
-- Index for listing by date
create index waitlist_created_at_idx on public.waitlist (created_at desc);
+create unique index waitlist_email_ci_idx
+ on public.waitlist (lower(trim(email)));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| email text not null unique, | |
| company_name text, | |
| use_case text, | |
| -- Timestamps | |
| created_at timestamptz not null default now() | |
| ); | |
| -- Index for listing by date | |
| create index waitlist_created_at_idx on public.waitlist (created_at desc); | |
| create table public.waitlist ( | |
| id uuid primary key default gen_random_uuid(), | |
| -- Form data | |
| email text not null, | |
| company_name text, | |
| use_case text, | |
| -- Timestamps | |
| created_at timestamptz not null default now() | |
| ); | |
| -- Index for listing by date | |
| create index waitlist_created_at_idx on public.waitlist (created_at desc); | |
| create unique index waitlist_email_ci_idx | |
| on public.waitlist (lower(trim(email))); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@supabase/migrations/20250225000000_create_waitlist_table.sql` around lines 6
- 15, The current column-level UNIQUE on public.waitlist.email is case-sensitive
and should be replaced with a case-insensitive unique index: remove the UNIQUE
keyword from the email column definition and instead create a unique index such
as waitlist_email_ci_idx on public.waitlist using lower(email) (e.g. CREATE
UNIQUE INDEX waitlist_email_ci_idx ON public.waitlist (lower(email))); keep the
created_at index (waitlist_created_at_idx) as-is and ensure any existing data is
normalized or checked for conflicts before creating the new unique index.
Closes #23
Pull Request – Local Supabase Integration for Waitlist
ACTA Team,
First, I want to sincerely thank you for the opportunity to contribute to this critical infrastructure component 🙌
Building a self-contained, reproducible database environment for the waitlist feature aligns strongly with ACTA’s vision of decentralized, owner-controlled digital infrastructure. This implementation reflects the same principles of autonomy, auditability, and transparency that underpin verifiable credentials.
Executive Summary
This pull request implements a complete local Supabase (Docker) integration for waitlist data persistence, replacing the external Formspree dependency with an internal PostgreSQL-backed database 🗄️
The solution delivers three core benefits:
1️⃣ Data Ownership – Full control over waitlist records without vendor lock-in 🔐
2️⃣ Local Development Parity – Developers can replicate production environments locally 🧪
3️⃣ Operational Resilience – The application remains functional even when Supabase infrastructure is unavailable, via graceful fallback to placeholder credentials 🛡️
The implementation follows defense-in-depth security principles, enforces Row Level Security (RLS), and maintains full backward compatibility with existing functionality.
Problem Statement & Motivation
The previous waitlist implementation submitted directly to Formspree, introducing architectural limitations:
🔒 Vendor lock-in and dependency on third-party data access
📊 No direct querying capability for analytics, deduplication, or auditing
👁️ Limited operational visibility
🧭 Misalignment with ACTA’s self-hosted, auditable infrastructure philosophy
This PR restores full data ownership to ACTA while introducing zero breaking changes and supporting multiple deployment scenarios (local Docker, self-hosted Supabase, Supabase Cloud) 🌍
Technical Architecture
The solution implements a four-layer architecture with clear separation of concerns 🏗️
Layer 1 – Frontend (WaitlistForm in React/TypeScript)
✨ Client-side email validation
🤖 Honeypot field for bot detection
🔄 Four-state response management
Layer 2 – API Route (Next.js 15 App Router)
🛑 Honeypot verification
🧹 Payload validation and sanitization
🗄️ Database operations via Supabase client
📡 Structured HTTP error translation
Layer 3 – Supabase Client (TypeScript)
🔓 Public anon client for RLS-controlled inserts
🔐 Service role client (server-only)
🧩 Placeholder fallback logic
⚙️ Non-blocking graceful initialization
Layer 4 – Database (PostgreSQL via Supabase)
🆔 UUID primary key
📧 Email uniqueness constraint
📅 Indexed created_at for chronological sorting
🛡️ RLS policies: anonymous INSERT, service_role SELECT
Implementation Details
Database Migration
File: supabase/migrations/20250225000000_create_waitlist_table.sql
🆔 id (UUID, primary key, auto-generated)
📧 email (TEXT, NOT NULL, UNIQUE)
🏢 company_name (TEXT, nullable)
📝 use_case (TEXT, nullable)
⏱️ created_at (TIMESTAMPTZ, NOT NULL, default now())
📚 Created index waitlist_created_at_idx on created_at DESC
🔐 Enabled Row Level Security with:
➕ Anonymous INSERT policy
👁️ Service role SELECT policy
Design rationale:
🔎 UUID prevents sequential ID disclosure
♻️ UNIQUE email enforces deduplication at storage layer
🕒 Server-side timestamps prevent client clock skew
📈 DESC index optimizes “latest first” queries
🛡️ RLS enforces least privilege at database level
Seed Data
File: supabase/seed.sql
🌐 Includes 8 representative records across different verticals
🔁 Uses ON CONFLICT (email) DO NOTHING to ensure idempotency
Backend Implementation
Supabase Client Library
File: src/lib/supabase.ts
🔓 Public anon client for safe client-side operations
🔐 Server-only service client isolated from client bundle
🧠 Placeholder credential detection
⚙️ Graceful fallback without crashing the application
If SUPABASE_SERVICE_ROLE_KEY is missing, a warning is logged and the anon client is used. The application never fails at startup due to missing credentials 🚀
API Route
File: src/app/api/waitlist/route.ts
🛑 Honeypot check – returns 200 silently for bots
⚠️ 23505 → 409 Conflict (duplicate email)
📧 Email regex validation (/^[^\s@]+@[^\s@]+.[^\s@]+$/)
🧹 Payload sanitization
🗄️ Insert via getServiceSupabase()
🔁 Error translation:
💥 Other DB errors → 500 Internal Server Error
Response Codes:
✅ 201 Created
⚠️ 409 Conflict
❌ 400 Bad Request
💥 500 Internal Server Error
Frontend Update
File: src/features/waitlist/WaitlistForm.tsx
🔄 Migrated endpoint from Formspree to /api/waitlist
Implements a 4-state response system:
⚪ idle
🟢 ok (201)
🟡 duplicate (409)
🔴 error (400/500)
🎨 All styling, UX, animations, and structure remain unchanged from the user perspective
Security Considerations
🔐 Service role key strictly server-side
🛡️ RLS enforced at database layer
🧪 Multi-layer validation (client, server, DB)
🤖 Honeypot bot protection
🚫 No hardcoded secrets
⚙️ Placeholder credentials prevent startup failures
Closing Remarks
🌐 This implementation reinforces infrastructure alignment with ACTA’s core values: auditability, decentralization, and self-controlled data
Even a waitlist signup should reflect those principles. Issue #23 provided the opportunity to design and implement a production-grade solution with careful architecture, strong security posture, and complete documentation.
Thank you again to the ACTA Team for the opportunity to contribute meaningfully to this ecosystem 🙏
For questions or follow-up:
Telegram: @JuliobaCR
Summary by CodeRabbit
Release Notes
New Features
Documentation
Chores