wacrm/supabase/migrations/011_profile_beta_features.sql
Arnas Donauskas 9c4a46d990
feat(beta): per-account beta_features flag on profiles, gating Flows
Scaffolding for the upcoming WhatsApp Flows feature. The whole Flows
stack (interactive button/list send + parse, server-side runner, no-
code builder, templates) will land across the next ~4 PRs *behind*
this gate so the owner can dogfood the feature on their own account
before exposing it to teammates or downstream pullers of the OSS.

What this lands:

  1. `profiles.beta_features TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[]`
     — generic opt-in column, lives on the existing profiles table.
     Empty by default for every existing row.

  2. `src/lib/flows/feature-flag.ts` — `isFlowsEnabled(profile)`
     predicate + `BETA_FEATURE_FLOWS` constant. Single source of
     truth for the gate; later PRs import this in every guarded
     surface (sidebar nav, /flows page, /api/flows routes, webhook
     dispatcher).

  3. `Profile.beta_features` added to the shared TS type and the
     auth hook's local copy. Auth hook loads the column and
     defaults it to `[]` defensively if the DB read returns null.

  4. 11 unit tests covering null/undefined/empty/mismatched cases,
     including case sensitivity (only lowercase `'flows'` opts in)
     and defensive guards against malformed input.

How the owner enables it for their own account, once merged:

    UPDATE profiles
    SET beta_features = array_append(beta_features, 'flows')
    WHERE user_id = '<their-auth-uid>';

How we ship it to everyone, when ready: grep-and-delete the
`isFlowsEnabled()` callsites in one cleanup PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:57:33 +03:00

41 lines
1.9 KiB
SQL

-- ============================================================
-- Per-account beta feature flag column on `profiles`.
--
-- Adds an array of opted-in beta feature keys to each profile row.
-- Currently used to gate the Flows feature (`'flows'`); shape is
-- generic so subsequent betas (e.g. `'ai_replies'`, `'voice_notes'`)
-- can land in this column without another migration.
--
-- Why a per-account flag rather than a global env var:
-- - Self-hosted wacrm instances are multi-user (small teams, shared
-- workspaces). A global flag would force every account on the
-- instance to opt into a not-yet-stable feature simultaneously.
-- - The owner wanted to dogfood the feature on their own account
-- before exposing it to teammates. Flipping a column via
-- Supabase Studio (`UPDATE profiles SET beta_features = ...
-- WHERE user_id = '<theirs>'`) is the lowest-friction toggle.
-- - DB-managed flags survive env rotation, deploy-restart timing,
-- and (since beta_features is a TEXT[]) extend naturally to
-- additional features without further schema work.
--
-- Default is the empty array, so every existing profile row opts
-- out of every beta feature on apply. NOT NULL keeps callers from
-- having to defend against `beta_features == null` at every site.
--
-- Idempotent — safe to run multiple times.
-- ============================================================
ALTER TABLE profiles
ADD COLUMN IF NOT EXISTS beta_features TEXT[]
NOT NULL
DEFAULT ARRAY[]::TEXT[];
-- No new RLS policy needed: the existing `Users can view own profile` /
-- `Users can update own profile` policies (migration 001) already gate
-- access to this column. Server-side reads via service_role bypass RLS
-- as they do for every other column.
--
-- No index needed: the column is read on the login codepath (one row
-- lookup by primary key / user_id, both already indexed) and very
-- rarely written.