improved a lot of things
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
-- Origin Graph — Artboard Ancestry Materialized View
|
||||
-- Migration: 002
|
||||
-- Pre-computes all ancestor/descendant relationships so the app never needs
|
||||
-- recursive CTEs at query time. Updated automatically on artboards INSERT.
|
||||
|
||||
CREATE MATERIALIZED VIEW artboard_ancestry AS
|
||||
WITH RECURSIVE ancestry(artboard_id, ancestor_id, depth) AS (
|
||||
-- Base: each artboard is at depth 0 relative to itself
|
||||
SELECT id AS artboard_id, id AS ancestor_id, 0 AS depth
|
||||
FROM artboards
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Recurse: walk up the parent chain
|
||||
SELECT a.id AS artboard_id, anc.ancestor_id, anc.depth + 1
|
||||
FROM artboards a
|
||||
JOIN ancestry anc ON a.parent_artboard_id = anc.artboard_id
|
||||
)
|
||||
SELECT artboard_id, ancestor_id, depth
|
||||
FROM ancestry
|
||||
WHERE artboard_id <> ancestor_id -- exclude self-reference
|
||||
ORDER BY artboard_id, depth;
|
||||
|
||||
CREATE UNIQUE INDEX artboard_ancestry_pk
|
||||
ON artboard_ancestry(artboard_id, ancestor_id);
|
||||
|
||||
CREATE INDEX artboard_ancestry_ancestor_idx
|
||||
ON artboard_ancestry(ancestor_id);
|
||||
|
||||
-- ── Refresh trigger ───────────────────────────────────────────────────────────
|
||||
-- Refreshes the materialized view concurrently whenever an artboard is
|
||||
-- inserted. CONCURRENTLY requires the unique index above — it allows reads
|
||||
-- to continue during refresh.
|
||||
|
||||
CREATE OR REPLACE FUNCTION refresh_artboard_ancestry()
|
||||
RETURNS TRIGGER LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
REFRESH MATERIALIZED VIEW CONCURRENTLY artboard_ancestry;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_artboard_ancestry_refresh
|
||||
AFTER INSERT OR UPDATE OF parent_artboard_id ON artboards
|
||||
FOR EACH STATEMENT
|
||||
EXECUTE FUNCTION refresh_artboard_ancestry();
|
||||
@@ -0,0 +1,111 @@
|
||||
-- Origin Graph — Row-Level Security Policies
|
||||
-- Migration: 003
|
||||
-- All tables are workspace-scoped. A user may only read or write rows in
|
||||
-- workspaces where they have a team_members record. The Clerk JWT is verified
|
||||
-- server-side; auth.uid() maps to the Clerk user_id column.
|
||||
|
||||
-- Enable RLS on every table
|
||||
ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE artboards ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE origins ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE intent_diffs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE agent_sessions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE design_language_files ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE team_members ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- ── Helper: is the current user a member of the given workspace? ──────────────
|
||||
|
||||
CREATE OR REPLACE FUNCTION is_workspace_member(ws_id UUID)
|
||||
RETURNS BOOLEAN LANGUAGE sql SECURITY DEFINER AS $$
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM team_members
|
||||
WHERE workspace_id = ws_id
|
||||
AND user_id = auth.uid()::TEXT
|
||||
);
|
||||
$$;
|
||||
|
||||
-- ── workspaces ────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE POLICY workspaces_select ON workspaces
|
||||
FOR SELECT USING (is_workspace_member(id));
|
||||
|
||||
CREATE POLICY workspaces_insert ON workspaces
|
||||
FOR INSERT WITH CHECK (owner_id = auth.uid()::TEXT);
|
||||
|
||||
CREATE POLICY workspaces_update ON workspaces
|
||||
FOR UPDATE USING (owner_id = auth.uid()::TEXT);
|
||||
|
||||
-- ── artboards ─────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE POLICY artboards_select ON artboards
|
||||
FOR SELECT USING (is_workspace_member(workspace_id));
|
||||
|
||||
CREATE POLICY artboards_insert ON artboards
|
||||
FOR INSERT WITH CHECK (is_workspace_member(workspace_id));
|
||||
|
||||
CREATE POLICY artboards_update ON artboards
|
||||
FOR UPDATE USING (is_workspace_member(workspace_id));
|
||||
|
||||
CREATE POLICY artboards_delete ON artboards
|
||||
FOR DELETE USING (is_workspace_member(workspace_id));
|
||||
|
||||
-- ── intent_diffs ──────────────────────────────────────────────────────────────
|
||||
-- Derived from artboard's workspace membership
|
||||
CREATE POLICY intent_diffs_select ON intent_diffs
|
||||
FOR SELECT USING (
|
||||
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
|
||||
);
|
||||
|
||||
CREATE POLICY intent_diffs_insert ON intent_diffs
|
||||
FOR INSERT WITH CHECK (
|
||||
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
|
||||
);
|
||||
|
||||
CREATE POLICY intent_diffs_update ON intent_diffs
|
||||
FOR UPDATE USING (
|
||||
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
|
||||
);
|
||||
|
||||
-- ── agent_sessions ────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE POLICY agent_sessions_select ON agent_sessions
|
||||
FOR SELECT USING (
|
||||
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
|
||||
);
|
||||
|
||||
CREATE POLICY agent_sessions_insert ON agent_sessions
|
||||
FOR INSERT WITH CHECK (
|
||||
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
|
||||
);
|
||||
|
||||
CREATE POLICY agent_sessions_update ON agent_sessions
|
||||
FOR UPDATE USING (
|
||||
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
|
||||
);
|
||||
|
||||
-- ── design_language_files ─────────────────────────────────────────────────────
|
||||
|
||||
CREATE POLICY dlf_select ON design_language_files
|
||||
FOR SELECT USING (is_workspace_member(workspace_id));
|
||||
|
||||
CREATE POLICY dlf_insert ON design_language_files
|
||||
FOR INSERT WITH CHECK (is_workspace_member(workspace_id));
|
||||
|
||||
CREATE POLICY dlf_update ON design_language_files
|
||||
FOR UPDATE USING (is_workspace_member(workspace_id));
|
||||
|
||||
-- ── team_members ──────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE POLICY team_members_select ON team_members
|
||||
FOR SELECT USING (is_workspace_member(workspace_id));
|
||||
|
||||
-- Only workspace owners can add/remove members
|
||||
CREATE POLICY team_members_insert ON team_members
|
||||
FOR INSERT WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM workspaces WHERE id = workspace_id AND owner_id = auth.uid()::TEXT)
|
||||
);
|
||||
|
||||
CREATE POLICY team_members_delete ON team_members
|
||||
FOR DELETE USING (
|
||||
EXISTS (SELECT 1 FROM workspaces WHERE id = workspace_id AND owner_id = auth.uid()::TEXT)
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
-- ── Migration 004: Add notes column to intent_diffs ──────────────────────────
|
||||
-- Allows the coding agent to record why a diff was blocked or any implementation
|
||||
-- notes when updating status via the Agent Bridge MCP tool.
|
||||
|
||||
ALTER TABLE intent_diffs
|
||||
ADD COLUMN IF NOT EXISTS notes TEXT;
|
||||
@@ -0,0 +1,26 @@
|
||||
-- Migration: 005 — projects
|
||||
-- A Project groups artboards for a specific application inside a workspace.
|
||||
-- Users connect their running app to a project (via app_url) and work
|
||||
-- on its artboards in the canvas.
|
||||
|
||||
CREATE TABLE projects (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
app_url TEXT, -- e.g. https://localhost:3000 or https://staging.myapp.com
|
||||
framework TEXT, -- e.g. 'react', 'next', 'vue', 'svelte'
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX projects_workspace_idx ON projects(workspace_id);
|
||||
|
||||
CREATE TRIGGER trg_projects_updated_at
|
||||
BEFORE UPDATE ON projects
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- Add optional project_id to artboards so artboards can be scoped to a project.
|
||||
-- Nullable: existing artboards without a project remain workspace-level.
|
||||
ALTER TABLE artboards ADD COLUMN IF NOT EXISTS project_id UUID REFERENCES projects(id) ON DELETE SET NULL;
|
||||
CREATE INDEX IF NOT EXISTS artboards_project_idx ON artboards(project_id);
|
||||
Reference in New Issue
Block a user