-- Migration 005: video_connections — a user's connected video account -- (data-model.md VIDEO_CONNECTION). The OAuth refresh token never lives here; -- token_ref is the opaque SecretStore reference that resolves to it. Revocation -- flips status, it does not delete the row (history is kept). -- -- One connection per (user, provider): re-connecting the same provider upserts -- in place (the connect flow's ON CONFLICT (user_id, provider) target). CREATE TABLE video_connections ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider TEXT NOT NULL, provider_account TEXT, token_ref TEXT NOT NULL, status TEXT NOT NULL, connected_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT video_connections_user_provider_unique UNIQUE (user_id, provider) ); CREATE INDEX idx_video_connections_user_id ON video_connections(user_id); -- Per-user isolation, identical to migration 003's pattern: this is user-owned -- data, so user A must never read or write user B's connections even with a wrong -- application-level WHERE. ENABLE + FORCE so the table owner (tapir) is subject to -- the policy too; the policy keys off the per-request GUC tapir.current_user_id -- set by the store's withUser helper. An unset GUC yields NULL -> deny-all. ALTER TABLE video_connections ENABLE ROW LEVEL SECURITY; ALTER TABLE video_connections FORCE ROW LEVEL SECURITY; CREATE POLICY video_connections_isolation ON video_connections FOR ALL USING (user_id = current_setting('tapir.current_user_id', true)::uuid);