97 lines
4.4 KiB
PL/PgSQL
97 lines
4.4 KiB
PL/PgSQL
-- ═══════════════════════════════════════════════════════════════════════════
|
|
-- Migration 013: Phase 6 — Design Language Files table
|
|
--
|
|
-- Creates the `design_language_files` table used by the DLF token system.
|
|
-- Each row is one uploaded version of a workspace's token/constraint file.
|
|
-- Only one row per workspace is "active" at a time (enforced by partial index).
|
|
--
|
|
-- Spec reference: SOURCE-AWARE-CANVAS Phase 6 §9.2
|
|
-- All changes use IF NOT EXISTS / DO $$ guards for idempotency.
|
|
-- ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS design_language_files (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
name text NOT NULL,
|
|
-- Full parsed token + constraint document stored as JSONB.
|
|
-- Schema validated in-app before insert via DesignLanguageFileBodySchema.
|
|
schema_jsonb jsonb NOT NULL,
|
|
version integer NOT NULL DEFAULT 1,
|
|
-- Only one row per workspace should be active. The partial unique index below
|
|
-- enforces this at the DB level. Use the deactivation step in the POST route
|
|
-- to set this to false on all prior rows before inserting a new active one.
|
|
is_active boolean NOT NULL DEFAULT false,
|
|
created_by text,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- ── Indexes ───────────────────────────────────────────────────────────────────
|
|
|
|
-- Fast lookup of the latest active file for a workspace.
|
|
CREATE INDEX IF NOT EXISTS design_language_files_workspace_version_idx
|
|
ON design_language_files (workspace_id, version DESC);
|
|
|
|
-- Enforce at most one active row per workspace.
|
|
-- Partial unique index: only rows where is_active = true are checked.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS design_language_files_one_active_per_workspace_idx
|
|
ON design_language_files (workspace_id)
|
|
WHERE (is_active = true);
|
|
|
|
-- ── Auto-update updated_at ────────────────────────────────────────────────────
|
|
|
|
CREATE OR REPLACE FUNCTION update_design_language_files_updated_at()
|
|
RETURNS TRIGGER LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS trg_design_language_files_updated_at ON design_language_files;
|
|
CREATE TRIGGER trg_design_language_files_updated_at
|
|
BEFORE UPDATE ON design_language_files
|
|
FOR EACH ROW EXECUTE FUNCTION update_design_language_files_updated_at();
|
|
|
|
-- ── Row Level Security ────────────────────────────────────────────────────────
|
|
-- Service-role key (used by API routes) bypasses RLS entirely.
|
|
-- Browser clients (anon or authenticated) are restricted to their workspace.
|
|
|
|
ALTER TABLE design_language_files ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Workspace members may read design language files for their workspace.
|
|
-- Relies on the `team_members` table introduced in migration 001.
|
|
DO $$ BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_policies
|
|
WHERE tablename = 'design_language_files' AND policyname = 'dlf_workspace_select'
|
|
) THEN
|
|
CREATE POLICY dlf_workspace_select ON design_language_files
|
|
FOR SELECT
|
|
USING (
|
|
workspace_id IN (
|
|
SELECT workspace_id FROM team_members
|
|
WHERE user_id = auth.uid()::text
|
|
)
|
|
);
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Only workspace owners / designers may insert / update.
|
|
DO $$ BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_policies
|
|
WHERE tablename = 'design_language_files' AND policyname = 'dlf_workspace_write'
|
|
) THEN
|
|
CREATE POLICY dlf_workspace_write ON design_language_files
|
|
FOR ALL
|
|
USING (
|
|
workspace_id IN (
|
|
SELECT workspace_id FROM team_members
|
|
WHERE user_id = auth.uid()::text
|
|
AND role IN ('OWNER', 'DESIGNER')
|
|
)
|
|
);
|
|
END IF;
|
|
END $$;
|