Skip to main content

Operations

This page summarizes how to run and operate the current contributor portal repo.

Local Development

Install and run:

npm install
npm run dev

The Vite dev server defaults to:

http://localhost:5173

Required browser environment:

VITE_SUPABASE_URL
VITE_SUPABASE_PUBLISHABLE_KEY

Server-only local environment:

SUPABASE_SECRET_KEY
SUPABASE_SERVICE_ROLE_KEY
SUPABASE_ACCESS_TOKEN
CONTRIBUTOR_LINK_INTENT_SECRET
PLAYROLL_APP_SUPABASE_URL
PLAYROLL_APP_SUPABASE_SERVICE_ROLE_KEY

Do not expose server-only keys through VITE_ variables or React code. Vite bundles all VITE_ variables into the browser.

Supabase Project

Current documented Supabase URL:

https://xlsjwlmsqonllvtyplml.supabase.co

Related Playroll app Supabase URL:

https://vtyolotvuvlbvqbgbzde.supabase.co

Production auth allowlist:

Site URL:
https://playroll.extrinsiccognition.com

Redirect URLs:
https://playroll.extrinsiccognition.com/**
http://127.0.0.1:5173/**
http://localhost:5173/**

The app uses Supabase PKCE email login. Avoid hash fragments in emailRedirectTo; hashes can interfere with callback parsing and implicit tokens expose sensitive values in the URL fragment.

Auth Email Templates

Supabase sends different emails for new and existing users:

new email address -> Confirm signup
existing user -> Magic Link

Both templates must stay branded:

supabase/email-templates/confirm-signup.html
supabase/email-templates/magic-link.html

Deployment scripts:

npm run deploy:auth-email
npm run deploy:smtp

Auth rate-limit deployment:

npm run deploy:auth-rate-limits

Schema Deployment

Apply the full configured schema deployment:

npm run deploy:schema

Apply one migration:

SUPABASE_MIGRATION_FILE=supabase/migrations/<file>.sql npm run deploy:schema

The identity link requires migrations in both Supabase projects:

# App project: playroll_supa_new / vtyolotvuvlbvqbgbzde
$env:SUPABASE_PROJECT_REF = 'vtyolotvuvlbvqbgbzde'
$env:SUPABASE_MIGRATION_FILE = 'C:\EC\playroll-cpp\supabase\migrations\20260511160000_add_contributor_status_projection.sql'
npm run deploy:schema

# Portal project: playroll_contributor_portal / xlsjwlmsqonllvtyplml
$env:SUPABASE_PROJECT_REF = 'xlsjwlmsqonllvtyplml'
$env:SUPABASE_MIGRATION_FILE = 'supabase/migrations/20260511161000_add_contributor_app_links.sql'
npm run deploy:schema

Useful shortcuts:

npm run deploy:schema:validation-payout
npm run deploy:schema:time-milestones
npm run deploy:schema:payout-receiving
npm run deploy:schema:payout-iban-validation
npm run deploy:schema:payout-beneficiary

Ops: Altitude Recipient Export

To key confirmed contributors into Altitude's "Add recipient" form (or to see who is missing beneficiary data), run in the portal repo:

npm run ops:export-recipients # readable list, IBANs unmasked
npm run ops:export-recipients -- --csv # CSV to stdout
npm run ops:export-recipients -- --note "payout run 2026-07"

Requires SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY in .env.server.local. Each run is recorded in ops_payout_export_log — full-IBAN access always leaves a trace. Rows are flagged READY or MISSING DATA with the exact missing fields; contributors complete missing beneficiary addresses themselves via the portal payout form (confirmed IBANs are not re-confirmed).

Cloudflare Pages

Production Pages settings:

Project name: playroll-contributor-portal
Production branch: main
Build command: npm run build
Build output directory: dist
Custom domain: playroll.extrinsiccognition.com

First-time project creation:

npm run pages:create

Deploy:

npm run deploy:pages

npm run deploy:pages builds first, then runs Wrangler Pages deploy.

Edge Functions

Deploy the app-side intent creator from C:\EC\playroll-cpp:

npx -y supabase@latest functions deploy create-contributor-link-intent \
--project-ref vtyolotvuvlbvqbgbzde \
--no-verify-jwt

Deploy the portal-side activator from C:\EC\playroll-contributor-portal:

npx -y supabase@latest functions deploy activate-contributor-app-link \
--project-ref xlsjwlmsqonllvtyplml \
--no-verify-jwt

Required app project function secrets:

CONTRIBUTOR_LINK_INTENT_SECRET
CONTRIBUTOR_PORTAL_URL

Required portal project function secrets:

CONTRIBUTOR_LINK_INTENT_SECRET
PLAYROLL_APP_SUPABASE_URL
PLAYROLL_APP_SUPABASE_SERVICE_ROLE_KEY

CONTRIBUTOR_LINK_INTENT_SECRET must be identical in both projects. Store these values in Supabase project secrets or ignored local/server environment files. Never commit them.

Production Preflight

Before exposing the portal broadly:

  • npm run build succeeds.
  • Cloudflare Pages has only browser-safe environment variables.
  • Supabase auth Site URL and Redirect URLs include production and local dev URLs.
  • Confirm signup and magic-link templates are both customized.
  • Public privacy/legal pages resolve under /legal/.
  • Public application remains open; do not gate it to EC-domain users.
  • Admin/review surfaces, if added later, are separate and EC-domain gated.
  • Schema migrations are applied in the expected order.
  • RLS policies still require own authenticated rows for contributor writes/reads.
  • SEPA IBAN validation exists in both browser and database.
  • Receiving-method confirmation emails return with payout_confirm_token=<single-use 64-char hex token>; the portal consumes the token via confirm_payout_receiving_method_with_token SECURITY DEFINER RPC. The bare payout_confirm=1 flag is no longer accepted (audit 2026-05-16, EC-231 M1).
  • App identity links can be created only through signed intent and portal activation gate.
  • create-contributor-link-intent and activate-contributor-app-link return request_id on errors.

Runbook Checks

Application insert should fail unless:

auth_user_id = auth.uid()
submitted email = verified Supabase email
email_verified_at is not null
status = new
source in playroll_public_page or playroll_referral_link

Onboarding insert should fail unless:

auth_user_id = auth.uid()
submitted email = verified Supabase email
city is a known ISTAT comune
required acceptances are true

SEPA receiving method should not become payable until:

payout_receiving_methods.status = confirmed
payout_receiving_methods.email_confirmation_status = confirmed

A confirmed method is Altitude-ready only when the beneficiary details are also complete (altitude_ready = true in the ops export): first/last name, country, street, city, postal code, and state/provincia for IT and US.

Manual review cases should remain blocked for automatic payout until an ops/payment system approves a supported alternative outside this repo.

Unauthenticated function calls should fail but prove the deployed functions are live:

POST https://vtyolotvuvlbvqbgbzde.supabase.co/functions/v1/create-contributor-link-intent
-> 401 missing_authorization with request_id

POST https://xlsjwlmsqonllvtyplml.supabase.co/functions/v1/activate-contributor-app-link
-> 401 missing_authorization with request_id

The custom domain should return 200 and serve the latest built bundle:

GET https://playroll.extrinsiccognition.com