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 buildsucceeds.- 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 viaconfirm_payout_receiving_method_with_tokenSECURITY DEFINER RPC. The barepayout_confirm=1flag 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-intentandactivate-contributor-app-linkreturnrequest_idon 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.
Identity Link Smoke Checks
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