Skip to content

Supabase & the projects table

One Supabase project backs the entire system. Both the marketing site and the admin read and write the same projects table — the site through capability RPCs, the admin directly as an authenticated user.

The Supabase project ref is recorded in each repo’s CLAUDE.md; it is not reproduced here (it is the value of a VITE_ env var). Region: East US / North Virginia.

Two schemas — same project, fully isolated

Section titled “Two schemas — same project, fully isolated”
SchemaUsed byPurpose
publicProduction (sitecrate.ca, admin.sitecrate.ca)Real client data
stagingStaging (PR previews)Test data — never touches prod

Both schemas are exposed via PostgREST and have an identical projects structure. src/lib/supabase.js reads the VITE_SUPABASE_SCHEMA env var (defaults to 'public'). Staging builds set VITE_SUPABASE_SCHEMA=staging.

ColumnTypeNotes
iduuidPrimary key, gen_random_uuid()
status_tokenuuidCapability token — used in the /#status/{token} URL
client_name, client_emailtextClient contact
business_nametextDisplay name throughout the admin
industry, business_description, target_customer, goalstextIntake brief
has_logo, has_copybooleanBrand asset status
domain_name, color_vibetextBrand inputs
tiertextpresence / growth / platform (legacy: starter / standard)
billingtextone-time / monthly (monthly = care plan)
timelinetextClient’s requested timeline
stagetextnew → brief → building → review → live
stage_changed_attimestamptzUsed for stuck-project detection in the admin
preview_urltextSet by Ahmed — shown on the status page at review/live
client_approvedbooleanSet by the client on the status page
client_feedback, client_feedback_attext / timestamptzClient revision notes
admin_notestextInternal only — never shown to the client
created_at, updated_attimestamptzServer-side defaults
new ──▶ brief ──▶ building ──▶ review ──▶ live
  • stage_changed_at is stamped on each transition; the admin’s isStuck() check compares it against STAGE_WARN_DAYS to surface stalled projects.
  • Moving to review triggers the review email only if preview_url is set. Moving to live triggers the launch email.

tier drives pricing and which Build Brief prompt the admin generates:

TierOne-timeMonthlyBuild Brief
presence$997$49Tier 1 prompt
growth$3997$149Tier 2 prompt
platformcustomcustom— (custom-quoted)

starter and standard are legacy tiers, kept only so older projects render correct historical values. See the admin revenue math.

Against production, via the Supabase Management API (the access token lives in ~/.supabase/access-token; the project ref is in CLAUDE.md):

Terminal window
curl -s "https://api.supabase.com/v1/projects/<project-ref>/database/query" \
-H "Authorization: Bearer $(cat ~/.supabase/access-token)" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT id, business_name, stage FROM projects ORDER BY created_at DESC LIMIT 10;"}'

RLS is enabled on both schemas. The anon role has no direct table access — the next page explains how all public access is mediated instead.