Design and implement per-user tenancy #40
@@ -18,6 +18,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.26'
|
go-version: '1.26'
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
|
||||||
|
- name: Enable pnpm
|
||||||
|
run: corepack enable
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -34,6 +42,14 @@ jobs:
|
|||||||
- name: Tidy modules
|
- name: Tidy modules
|
||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: Install UI dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
working-directory: ui
|
||||||
|
|
||||||
|
- name: Build UI assets
|
||||||
|
run: pnpm run build
|
||||||
|
working-directory: ui
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,13 @@ CREATE SEQUENCE IF NOT EXISTS public.identity_thought_links_id
|
|||||||
START 1
|
START 1
|
||||||
CACHE 1;
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS public.identity_thought_learning_links_id
|
||||||
|
INCREMENT 1
|
||||||
|
MINVALUE 1
|
||||||
|
MAXVALUE 9223372036854775807
|
||||||
|
START 1
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
CREATE SEQUENCE IF NOT EXISTS public.identity_embeddings_id
|
CREATE SEQUENCE IF NOT EXISTS public.identity_embeddings_id
|
||||||
INCREMENT 1
|
INCREMENT 1
|
||||||
MINVALUE 1
|
MINVALUE 1
|
||||||
@@ -332,6 +339,7 @@ CREATE TABLE IF NOT EXISTS public.thoughts (
|
|||||||
id bigserial NOT NULL,
|
id bigserial NOT NULL,
|
||||||
metadata jsonb DEFAULT '{}'::jsonb,
|
metadata jsonb DEFAULT '{}'::jsonb,
|
||||||
project_id bigint,
|
project_id bigint,
|
||||||
|
tenant_key text,
|
||||||
updated_at timestamptz DEFAULT now()
|
updated_at timestamptz DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -341,7 +349,8 @@ CREATE TABLE IF NOT EXISTS public.projects (
|
|||||||
guid uuid NOT NULL DEFAULT gen_random_uuid(),
|
guid uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||||
id bigserial NOT NULL,
|
id bigserial NOT NULL,
|
||||||
last_active_at timestamptz DEFAULT now(),
|
last_active_at timestamptz DEFAULT now(),
|
||||||
name text NOT NULL
|
name text NOT NULL,
|
||||||
|
tenant_key text
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS public.thought_links (
|
CREATE TABLE IF NOT EXISTS public.thought_links (
|
||||||
@@ -352,6 +361,14 @@ CREATE TABLE IF NOT EXISTS public.thought_links (
|
|||||||
to_id bigint NOT NULL
|
to_id bigint NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.thought_learning_links (
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
id bigserial NOT NULL,
|
||||||
|
learning_id bigint NOT NULL,
|
||||||
|
relation text NOT NULL DEFAULT 'source',
|
||||||
|
thought_id bigint NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS public.embeddings (
|
CREATE TABLE IF NOT EXISTS public.embeddings (
|
||||||
created_at timestamptz DEFAULT now(),
|
created_at timestamptz DEFAULT now(),
|
||||||
dim integer NOT NULL,
|
dim integer NOT NULL,
|
||||||
@@ -375,6 +392,7 @@ CREATE TABLE IF NOT EXISTS public.stored_files (
|
|||||||
project_id bigint,
|
project_id bigint,
|
||||||
sha256 text NOT NULL,
|
sha256 text NOT NULL,
|
||||||
size_bytes bigint NOT NULL,
|
size_bytes bigint NOT NULL,
|
||||||
|
tenant_key text,
|
||||||
thought_id bigint,
|
thought_id bigint,
|
||||||
updated_at timestamptz NOT NULL DEFAULT now()
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
@@ -390,6 +408,7 @@ CREATE TABLE IF NOT EXISTS public.chat_histories (
|
|||||||
project_id bigint,
|
project_id bigint,
|
||||||
session_id text NOT NULL,
|
session_id text NOT NULL,
|
||||||
summary text,
|
summary text,
|
||||||
|
tenant_key text,
|
||||||
title text,
|
title text,
|
||||||
updated_at timestamptz NOT NULL DEFAULT now()
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
@@ -424,6 +443,7 @@ CREATE TABLE IF NOT EXISTS public.learnings (
|
|||||||
summary text NOT NULL,
|
summary text NOT NULL,
|
||||||
supersedes_learning_id bigint,
|
supersedes_learning_id bigint,
|
||||||
tags text[] NOT NULL DEFAULT '{}',
|
tags text[] NOT NULL DEFAULT '{}',
|
||||||
|
tenant_key text,
|
||||||
updated_at timestamptz NOT NULL DEFAULT now()
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -450,6 +470,7 @@ CREATE TABLE IF NOT EXISTS public.plans (
|
|||||||
status text NOT NULL DEFAULT 'draft',
|
status text NOT NULL DEFAULT 'draft',
|
||||||
supersedes_plan_id bigint,
|
supersedes_plan_id bigint,
|
||||||
tags text[] NOT NULL DEFAULT '{}',
|
tags text[] NOT NULL DEFAULT '{}',
|
||||||
|
tenant_key text,
|
||||||
title text NOT NULL,
|
title text NOT NULL,
|
||||||
updated_at timestamptz NOT NULL DEFAULT now()
|
updated_at timestamptz NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
@@ -1552,6 +1573,19 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thoughts'
|
||||||
|
AND column_name = 'tenant_key'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thoughts ADD COLUMN tenant_key text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -1643,6 +1677,19 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'projects'
|
||||||
|
AND column_name = 'tenant_key'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.projects ADD COLUMN tenant_key text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -1708,6 +1755,71 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thought_learning_links'
|
||||||
|
AND column_name = 'created_at'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links ADD COLUMN created_at timestamptz NOT NULL DEFAULT now();
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thought_learning_links'
|
||||||
|
AND column_name = 'id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links ADD COLUMN id bigserial NOT NULL;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thought_learning_links'
|
||||||
|
AND column_name = 'learning_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links ADD COLUMN learning_id bigint NOT NULL;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thought_learning_links'
|
||||||
|
AND column_name = 'relation'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links ADD COLUMN relation text NOT NULL DEFAULT 'source';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thought_learning_links'
|
||||||
|
AND column_name = 'thought_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links ADD COLUMN thought_id bigint NOT NULL;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -1955,6 +2067,19 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'stored_files'
|
||||||
|
AND column_name = 'tenant_key'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.stored_files ADD COLUMN tenant_key text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -2111,6 +2236,19 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'chat_histories'
|
||||||
|
AND column_name = 'tenant_key'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.chat_histories ADD COLUMN tenant_key text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -2475,6 +2613,19 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'learnings'
|
||||||
|
AND column_name = 'tenant_key'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.learnings ADD COLUMN tenant_key text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -2735,6 +2886,19 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'plans'
|
||||||
|
AND column_name = 'tenant_key'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.plans ADD COLUMN tenant_key text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -5177,6 +5341,29 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'thoughts'
|
||||||
|
AND a.attname = 'tenant_key'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['text']) THEN
|
||||||
|
ALTER TABLE public.thoughts
|
||||||
|
ALTER COLUMN tenant_key TYPE text USING tenant_key::text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_type text;
|
current_type text;
|
||||||
@@ -5338,6 +5525,29 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'projects'
|
||||||
|
AND a.attname = 'tenant_key'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['text']) THEN
|
||||||
|
ALTER TABLE public.projects
|
||||||
|
ALTER COLUMN tenant_key TYPE text USING tenant_key::text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_type text;
|
current_type text;
|
||||||
@@ -5453,6 +5663,121 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'thought_learning_links'
|
||||||
|
AND a.attname = 'created_at'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['timestamptz', 'timestamp with time zone']) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links
|
||||||
|
ALTER COLUMN created_at TYPE timestamptz USING created_at::timestamptz;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'thought_learning_links'
|
||||||
|
AND a.attname = 'id'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['bigint']) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links
|
||||||
|
ALTER COLUMN id TYPE bigint USING id::bigint;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'thought_learning_links'
|
||||||
|
AND a.attname = 'learning_id'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['bigint']) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links
|
||||||
|
ALTER COLUMN learning_id TYPE bigint USING learning_id::bigint;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'thought_learning_links'
|
||||||
|
AND a.attname = 'relation'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['text']) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links
|
||||||
|
ALTER COLUMN relation TYPE text USING relation::text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'thought_learning_links'
|
||||||
|
AND a.attname = 'thought_id'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['bigint']) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links
|
||||||
|
ALTER COLUMN thought_id TYPE bigint USING thought_id::bigint;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_type text;
|
current_type text;
|
||||||
@@ -5890,6 +6215,29 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'stored_files'
|
||||||
|
AND a.attname = 'tenant_key'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['text']) THEN
|
||||||
|
ALTER TABLE public.stored_files
|
||||||
|
ALTER COLUMN tenant_key TYPE text USING tenant_key::text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_type text;
|
current_type text;
|
||||||
@@ -6166,6 +6514,29 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'chat_histories'
|
||||||
|
AND a.attname = 'tenant_key'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['text']) THEN
|
||||||
|
ALTER TABLE public.chat_histories
|
||||||
|
ALTER COLUMN tenant_key TYPE text USING tenant_key::text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_type text;
|
current_type text;
|
||||||
@@ -6810,6 +7181,29 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'learnings'
|
||||||
|
AND a.attname = 'tenant_key'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['text']) THEN
|
||||||
|
ALTER TABLE public.learnings
|
||||||
|
ALTER COLUMN tenant_key TYPE text USING tenant_key::text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_type text;
|
current_type text;
|
||||||
@@ -7270,6 +7664,29 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_type text;
|
||||||
|
BEGIN
|
||||||
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||||
|
INTO current_type
|
||||||
|
FROM pg_attribute a
|
||||||
|
JOIN pg_class t ON t.oid = a.attrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE n.nspname = 'public'
|
||||||
|
AND t.relname = 'plans'
|
||||||
|
AND a.attname = 'tenant_key'
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped;
|
||||||
|
|
||||||
|
IF current_type IS NOT NULL
|
||||||
|
AND current_type <> ALL(ARRAY['text']) THEN
|
||||||
|
ALTER TABLE public.plans
|
||||||
|
ALTER COLUMN tenant_key TYPE text USING tenant_key::text;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_type text;
|
current_type text;
|
||||||
@@ -9035,6 +9452,50 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
current_pk_name text;
|
||||||
|
current_pk_matches boolean := false;
|
||||||
|
BEGIN
|
||||||
|
SELECT tc.constraint_name,
|
||||||
|
COALESCE(
|
||||||
|
ARRAY(
|
||||||
|
SELECT a.attname::text
|
||||||
|
FROM pg_constraint c
|
||||||
|
JOIN pg_class t ON t.oid = c.conrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
JOIN unnest(c.conkey) WITH ORDINALITY AS cols(attnum, ord)
|
||||||
|
ON TRUE
|
||||||
|
JOIN pg_attribute a
|
||||||
|
ON a.attrelid = t.oid
|
||||||
|
AND a.attnum = cols.attnum
|
||||||
|
WHERE c.contype = 'p'
|
||||||
|
AND n.nspname = 'public'
|
||||||
|
AND t.relname = 'thought_learning_links'
|
||||||
|
ORDER BY cols.ord
|
||||||
|
),
|
||||||
|
ARRAY[]::text[]
|
||||||
|
) = ARRAY['id']
|
||||||
|
INTO current_pk_name, current_pk_matches
|
||||||
|
FROM information_schema.table_constraints tc
|
||||||
|
WHERE tc.table_schema = 'public'
|
||||||
|
AND tc.table_name = 'thought_learning_links'
|
||||||
|
AND tc.constraint_type = 'PRIMARY KEY';
|
||||||
|
|
||||||
|
IF current_pk_name IS NOT NULL
|
||||||
|
AND NOT current_pk_matches
|
||||||
|
AND current_pk_name IN ('thought_learning_links_pkey', 'public_thought_learning_links_pkey') THEN
|
||||||
|
EXECUTE 'ALTER TABLE public.thought_learning_links DROP CONSTRAINT ' || quote_ident(current_pk_name) || ' CASCADE';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Add the desired primary key only when no matching primary key already exists.
|
||||||
|
IF current_pk_name IS NULL
|
||||||
|
OR (NOT current_pk_matches AND current_pk_name IN ('thought_learning_links_pkey', 'public_thought_learning_links_pkey')) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links ADD CONSTRAINT pk_public_thought_learning_links PRIMARY KEY (id);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
current_pk_name text;
|
current_pk_name text;
|
||||||
@@ -9708,9 +10169,18 @@ CREATE INDEX IF NOT EXISTS idx_project_personas_project_id_persona_id
|
|||||||
CREATE INDEX IF NOT EXISTS idx_arc_stage_parts_stage_id_part_id
|
CREATE INDEX IF NOT EXISTS idx_arc_stage_parts_stage_id_part_id
|
||||||
ON public.arc_stage_parts USING btree (stage_id, part_id);
|
ON public.arc_stage_parts USING btree (stage_id, part_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_thoughts_tenant_key_project_id
|
||||||
|
ON public.thoughts USING btree (tenant_key, project_id);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uidx_projects_tenant_key_name
|
||||||
|
ON public.projects USING btree (tenant_key, name);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_thought_links_from_id_to_id_relation
|
CREATE INDEX IF NOT EXISTS idx_thought_links_from_id_to_id_relation
|
||||||
ON public.thought_links USING btree (from_id, to_id, relation);
|
ON public.thought_links USING btree (from_id, to_id, relation);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uidx_thought_learning_links_thought_id_learning_id
|
||||||
|
ON public.thought_learning_links USING btree (thought_id, learning_id);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS uidx_embeddings_thought_id_model
|
CREATE UNIQUE INDEX IF NOT EXISTS uidx_embeddings_thought_id_model
|
||||||
ON public.embeddings USING btree (thought_id, model);
|
ON public.embeddings USING btree (thought_id, model);
|
||||||
|
|
||||||
@@ -9865,19 +10335,6 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.table_constraints
|
|
||||||
WHERE table_schema = 'public'
|
|
||||||
AND table_name = 'projects'
|
|
||||||
AND constraint_name = 'ukey_projects_name'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE public.projects ADD CONSTRAINT ukey_projects_name UNIQUE (name);
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
@@ -10328,6 +10785,38 @@ BEGIN
|
|||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
$$;DO $$
|
$$;DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.table_constraints
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thought_learning_links'
|
||||||
|
AND constraint_name = 'fk_thought_learning_links_learning_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links
|
||||||
|
ADD CONSTRAINT fk_thought_learning_links_learning_id
|
||||||
|
FOREIGN KEY (learning_id)
|
||||||
|
REFERENCES public.learnings (id)
|
||||||
|
ON DELETE NO ACTION
|
||||||
|
ON UPDATE NO ACTION;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.table_constraints
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'thought_learning_links'
|
||||||
|
AND constraint_name = 'fk_thought_learning_links_thought_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.thought_learning_links
|
||||||
|
ADD CONSTRAINT fk_thought_learning_links_thought_id
|
||||||
|
FOREIGN KEY (thought_id)
|
||||||
|
REFERENCES public.thoughts (id)
|
||||||
|
ON DELETE NO ACTION
|
||||||
|
ON UPDATE NO ACTION;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
SELECT 1 FROM information_schema.table_constraints
|
SELECT 1 FROM information_schema.table_constraints
|
||||||
@@ -10912,15 +11401,15 @@ BEGIN
|
|||||||
IF EXISTS (
|
IF EXISTS (
|
||||||
SELECT 1 FROM pg_class c
|
SELECT 1 FROM pg_class c
|
||||||
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
|
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
WHERE c.relname = 'identity_persona_arc_id'
|
WHERE c.relname = 'identity_persona_arc_persona_id'
|
||||||
AND n.nspname = 'public'
|
AND n.nspname = 'public'
|
||||||
AND c.relkind = 'S'
|
AND c.relkind = 'S'
|
||||||
) THEN
|
) THEN
|
||||||
SELECT COALESCE(MAX(id), 0) + 1
|
SELECT COALESCE(MAX(persona_id), 0) + 1
|
||||||
FROM public.persona_arc
|
FROM public.persona_arc
|
||||||
INTO m_cnt;
|
INTO m_cnt;
|
||||||
|
|
||||||
PERFORM setval('public.identity_persona_arc_id'::regclass, m_cnt);
|
PERFORM setval('public.identity_persona_arc_persona_id'::regclass, m_cnt);
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
@@ -10982,6 +11471,25 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
DO $$
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
m_cnt bigint;
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM pg_class c
|
||||||
|
INNER JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
WHERE c.relname = 'identity_thought_learning_links_id'
|
||||||
|
AND n.nspname = 'public'
|
||||||
|
AND c.relkind = 'S'
|
||||||
|
) THEN
|
||||||
|
SELECT COALESCE(MAX(id), 0) + 1
|
||||||
|
FROM public.thought_learning_links
|
||||||
|
INTO m_cnt;
|
||||||
|
|
||||||
|
PERFORM setval('public.identity_thought_learning_links_id'::regclass, m_cnt);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
m_cnt bigint;
|
m_cnt bigint;
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -11295,5 +11803,6 @@ $$;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ alter table plans add column if not exists tenant_key text;
|
|||||||
alter table chat_histories add column if not exists tenant_key text;
|
alter table chat_histories add column if not exists tenant_key text;
|
||||||
|
|
||||||
-- Project names are now unique inside a tenant rather than globally.
|
-- Project names are now unique inside a tenant rather than globally.
|
||||||
|
alter table projects drop constraint if exists ukey_projects_name;
|
||||||
|
alter table projects drop constraint if exists projects_name_key;
|
||||||
drop index if exists projects_name_key;
|
drop index if exists projects_name_key;
|
||||||
create unique index if not exists projects_tenant_key_name_idx
|
create unique index if not exists projects_tenant_key_name_idx
|
||||||
on projects (coalesce(tenant_key, ''), name);
|
on projects (coalesce(tenant_key, ''), name);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Table stored_files {
|
|||||||
guid uuid [unique, not null, default: `gen_random_uuid()`]
|
guid uuid [unique, not null, default: `gen_random_uuid()`]
|
||||||
thought_id bigint [ref: > thoughts.id]
|
thought_id bigint [ref: > thoughts.id]
|
||||||
project_id bigint [ref: > projects.id]
|
project_id bigint [ref: > projects.id]
|
||||||
|
tenant_key text
|
||||||
name text [not null]
|
name text [not null]
|
||||||
media_type text [not null]
|
media_type text [not null]
|
||||||
kind text [not null, default: 'file']
|
kind text [not null, default: 'file']
|
||||||
@@ -16,6 +17,7 @@ Table stored_files {
|
|||||||
indexes {
|
indexes {
|
||||||
thought_id
|
thought_id
|
||||||
project_id
|
project_id
|
||||||
|
tenant_key
|
||||||
sha256
|
sha256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Table chat_histories {
|
|||||||
channel text
|
channel text
|
||||||
agent_id text
|
agent_id text
|
||||||
project_id bigint [ref: > projects.id]
|
project_id bigint [ref: > projects.id]
|
||||||
|
tenant_key text
|
||||||
messages jsonb [not null, default: `'[]'`]
|
messages jsonb [not null, default: `'[]'`]
|
||||||
summary text
|
summary text
|
||||||
metadata jsonb [not null, default: `'{}'`]
|
metadata jsonb [not null, default: `'{}'`]
|
||||||
@@ -15,6 +16,7 @@ Table chat_histories {
|
|||||||
indexes {
|
indexes {
|
||||||
session_id
|
session_id
|
||||||
project_id
|
project_id
|
||||||
|
tenant_key
|
||||||
channel
|
channel
|
||||||
agent_id
|
agent_id
|
||||||
created_at
|
created_at
|
||||||
@@ -46,6 +48,7 @@ Table learnings {
|
|||||||
source_type text
|
source_type text
|
||||||
source_ref text
|
source_ref text
|
||||||
project_id bigint [ref: > projects.id]
|
project_id bigint [ref: > projects.id]
|
||||||
|
tenant_key text
|
||||||
related_thought_id bigint [ref: > thoughts.id]
|
related_thought_id bigint [ref: > thoughts.id]
|
||||||
related_skill_id bigint [ref: > agent_skills.id]
|
related_skill_id bigint [ref: > agent_skills.id]
|
||||||
reviewed_by text
|
reviewed_by text
|
||||||
@@ -58,6 +61,7 @@ Table learnings {
|
|||||||
|
|
||||||
indexes {
|
indexes {
|
||||||
project_id
|
project_id
|
||||||
|
tenant_key
|
||||||
category
|
category
|
||||||
area
|
area
|
||||||
status
|
status
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Table plans {
|
|||||||
status text [not null, default: 'draft'] // draft, active, blocked, completed, cancelled, superseded
|
status text [not null, default: 'draft'] // draft, active, blocked, completed, cancelled, superseded
|
||||||
priority text [not null, default: 'medium'] // low, medium, high, critical
|
priority text [not null, default: 'medium'] // low, medium, high, critical
|
||||||
project_id bigint [ref: > projects.id]
|
project_id bigint [ref: > projects.id]
|
||||||
|
tenant_key text
|
||||||
owner text
|
owner text
|
||||||
due_date timestamptz
|
due_date timestamptz
|
||||||
completed_at timestamptz
|
completed_at timestamptz
|
||||||
@@ -18,6 +19,7 @@ Table plans {
|
|||||||
|
|
||||||
indexes {
|
indexes {
|
||||||
project_id
|
project_id
|
||||||
|
tenant_key
|
||||||
status
|
status
|
||||||
priority
|
priority
|
||||||
owner
|
owner
|
||||||
|
|||||||
Reference in New Issue
Block a user