Files
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

365 lines
16 KiB
PL/PgSQL

-- ============================================================
-- Triggers for user_table_rows (from 0156 schema addition)
-- Drizzle does not generate trigger SQL, so they are applied here.
-- ============================================================
CREATE OR REPLACE FUNCTION increment_user_table_row_count()
RETURNS TRIGGER AS $$
DECLARE
current_count INTEGER;
max_allowed INTEGER;
BEGIN
SELECT row_count, max_rows INTO current_count, max_allowed
FROM user_table_definitions
WHERE id = NEW.table_id;
IF current_count >= max_allowed THEN
RAISE EXCEPTION 'Maximum row limit (%) reached for table %', max_allowed, NEW.table_id;
END IF;
UPDATE user_table_definitions
SET row_count = row_count + 1,
updated_at = now()
WHERE id = NEW.table_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
--> statement-breakpoint
CREATE OR REPLACE FUNCTION decrement_user_table_row_count()
RETURNS TRIGGER AS $$
BEGIN
UPDATE user_table_definitions
SET row_count = GREATEST(row_count - 1, 0),
updated_at = now()
WHERE id = OLD.table_id;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
--> statement-breakpoint
CREATE TRIGGER user_table_rows_insert_trigger
BEFORE INSERT ON user_table_rows
FOR EACH ROW
EXECUTE FUNCTION increment_user_table_row_count();
--> statement-breakpoint
CREATE TRIGGER user_table_rows_delete_trigger
AFTER DELETE ON user_table_rows
FOR EACH ROW
EXECUTE FUNCTION decrement_user_table_row_count();
--> statement-breakpoint
-- ============================================================
-- Credential system schema + backfill
-- ============================================================
CREATE TYPE "public"."credential_member_role" AS ENUM('admin', 'member');--> statement-breakpoint
CREATE TYPE "public"."credential_member_status" AS ENUM('active', 'pending', 'revoked');--> statement-breakpoint
CREATE TYPE "public"."credential_type" AS ENUM('oauth', 'env_workspace', 'env_personal');--> statement-breakpoint
CREATE TABLE "credential" (
"id" text PRIMARY KEY NOT NULL,
"workspace_id" text NOT NULL,
"type" "credential_type" NOT NULL,
"display_name" text NOT NULL,
"description" text,
"provider_id" text,
"account_id" text,
"env_key" text,
"env_owner_user_id" text,
"created_by" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "credential_oauth_source_check" CHECK ((type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)),
CONSTRAINT "credential_workspace_env_source_check" CHECK ((type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)),
CONSTRAINT "credential_personal_env_source_check" CHECK ((type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL))
);
--> statement-breakpoint
CREATE TABLE "credential_member" (
"id" text PRIMARY KEY NOT NULL,
"credential_id" text NOT NULL,
"user_id" text NOT NULL,
"role" "credential_member_role" DEFAULT 'member' NOT NULL,
"status" "credential_member_status" DEFAULT 'active' NOT NULL,
"joined_at" timestamp,
"invited_by" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "pending_credential_draft" (
"id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"workspace_id" text NOT NULL,
"provider_id" text NOT NULL,
"display_name" text NOT NULL,
"description" text,
"credential_id" text,
"expires_at" timestamp NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
DROP INDEX "account_user_provider_unique";--> statement-breakpoint
ALTER TABLE "credential" ADD CONSTRAINT "credential_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "credential" ADD CONSTRAINT "credential_account_id_account_id_fk" FOREIGN KEY ("account_id") REFERENCES "public"."account"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "credential" ADD CONSTRAINT "credential_env_owner_user_id_user_id_fk" FOREIGN KEY ("env_owner_user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "credential" ADD CONSTRAINT "credential_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "credential_member" ADD CONSTRAINT "credential_member_credential_id_credential_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."credential"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "credential_member" ADD CONSTRAINT "credential_member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "credential_member" ADD CONSTRAINT "credential_member_invited_by_user_id_fk" FOREIGN KEY ("invited_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "pending_credential_draft" ADD CONSTRAINT "pending_credential_draft_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "pending_credential_draft" ADD CONSTRAINT "pending_credential_draft_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "pending_credential_draft" ADD CONSTRAINT "pending_credential_draft_credential_id_credential_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."credential"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "credential_workspace_id_idx" ON "credential" USING btree ("workspace_id");--> statement-breakpoint
CREATE INDEX "credential_type_idx" ON "credential" USING btree ("type");--> statement-breakpoint
CREATE INDEX "credential_provider_id_idx" ON "credential" USING btree ("provider_id");--> statement-breakpoint
CREATE INDEX "credential_account_id_idx" ON "credential" USING btree ("account_id");--> statement-breakpoint
CREATE INDEX "credential_env_owner_user_id_idx" ON "credential" USING btree ("env_owner_user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "credential_workspace_account_unique" ON "credential" USING btree ("workspace_id","account_id") WHERE account_id IS NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "credential_workspace_env_unique" ON "credential" USING btree ("workspace_id","type","env_key") WHERE type = 'env_workspace';--> statement-breakpoint
CREATE UNIQUE INDEX "credential_workspace_personal_env_unique" ON "credential" USING btree ("workspace_id","type","env_key","env_owner_user_id") WHERE type = 'env_personal';--> statement-breakpoint
CREATE INDEX "credential_member_credential_id_idx" ON "credential_member" USING btree ("credential_id");--> statement-breakpoint
CREATE INDEX "credential_member_user_id_idx" ON "credential_member" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "credential_member_role_idx" ON "credential_member" USING btree ("role");--> statement-breakpoint
CREATE INDEX "credential_member_status_idx" ON "credential_member" USING btree ("status");--> statement-breakpoint
CREATE UNIQUE INDEX "credential_member_unique" ON "credential_member" USING btree ("credential_id","user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "pending_draft_user_provider_ws" ON "pending_credential_draft" USING btree ("user_id","provider_id","workspace_id");
--> statement-breakpoint
-- ============================================================
-- BACKFILL: Create credentials and members from existing data
-- ============================================================
-- Helper CTE: all workspace members (from permissions + workspace owners)
-- Used by all three backfill sections below.
-- ----------------------------------------------------------
-- 1. OAuth credentials
-- ----------------------------------------------------------
-- For each (account, workspace) where account owner has workspace access,
-- create a "Default <Service Name> Credential".
-- Account owner = admin, other workspace members = member.
WITH provider_names(pid, sname) AS (
VALUES
('google-email', 'Gmail'),
('google-drive', 'Google Drive'),
('google-docs', 'Google Docs'),
('google-sheets', 'Google Sheets'),
('google-forms', 'Google Forms'),
('google-calendar', 'Google Calendar'),
('google-vault', 'Google Vault'),
('google-slides', 'Google Slides'),
('google-groups', 'Google Groups'),
('slack', 'Slack'),
('notion', 'Notion'),
('confluence', 'Confluence'),
('jira', 'Jira'),
('jira-service-management', 'Jira Service Management'),
('linear', 'Linear'),
('airtable', 'Airtable'),
('asana', 'Asana'),
('hubspot', 'HubSpot'),
('salesforce', 'Salesforce'),
('pipedrive', 'Pipedrive'),
('microsoft-teams', 'Microsoft Teams'),
('microsoft-planner', 'Microsoft Planner'),
('microsoft-excel', 'Microsoft Excel'),
('outlook', 'Outlook'),
('onedrive', 'OneDrive'),
('sharepoint', 'SharePoint'),
('dropbox', 'Dropbox'),
('wordpress', 'WordPress'),
('webflow', 'Webflow'),
('wealthbox', 'Wealthbox'),
('spotify', 'Spotify'),
('x', 'X'),
('reddit', 'Reddit'),
('linkedin', 'LinkedIn'),
('trello', 'Trello'),
('shopify', 'Shopify'),
('zoom', 'Zoom'),
('calcom', 'Cal.com'),
('discord', 'Discord'),
('box', 'Box'),
('github-repo', 'GitHub'),
('vertex-ai', 'Vertex AI'),
('supabase', 'Supabase')
),
oauth_targets AS (
SELECT
'cred_' || md5(wua.workspace_id || ':' || a.id) AS cred_id,
wua.workspace_id,
a.id AS account_id,
a.user_id AS account_owner_id,
a.provider_id,
COALESCE(u.name, 'User') || '''s ' || COALESCE(pn.sname, a.provider_id) AS display_name
FROM "account" a
INNER JOIN (
SELECT DISTINCT w.id AS workspace_id, p.user_id
FROM "permissions" p
INNER JOIN "workspace" w ON w.id = p.entity_id
WHERE p.entity_type = 'workspace'
UNION
SELECT w.id, w.owner_id FROM "workspace" w
) wua ON wua.user_id = a.user_id
INNER JOIN "user" u ON u.id = a.user_id
LEFT JOIN provider_names pn ON pn.pid = a.provider_id
WHERE a.provider_id NOT IN ('credential', 'github', 'google')
),
oauth_workspace_members AS (
SELECT DISTINCT w.id AS workspace_id, p.user_id
FROM "permissions" p
INNER JOIN "workspace" w ON w.id = p.entity_id
WHERE p.entity_type = 'workspace'
UNION
SELECT w.id, w.owner_id FROM "workspace" w
),
_oauth_insert AS (
INSERT INTO "credential" (
"id", "workspace_id", "type", "display_name", "provider_id", "account_id",
"created_by", "created_at", "updated_at"
)
SELECT cred_id, workspace_id, 'oauth'::"credential_type", display_name,
provider_id, account_id, account_owner_id, now(), now()
FROM oauth_targets
ON CONFLICT DO NOTHING
)
INSERT INTO "credential_member" (
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
)
SELECT
'credm_' || md5(ot.cred_id || ':' || owm.user_id),
ot.cred_id,
owm.user_id,
CASE WHEN ot.account_owner_id = owm.user_id THEN 'admin'::"credential_member_role" ELSE 'member'::"credential_member_role" END,
'active'::"credential_member_status",
now(),
ot.account_owner_id,
now(),
now()
FROM oauth_targets ot
INNER JOIN oauth_workspace_members owm ON owm.workspace_id = ot.workspace_id
ON CONFLICT DO NOTHING;
--> statement-breakpoint
-- ----------------------------------------------------------
-- 2. Workspace environment variable credentials
-- ----------------------------------------------------------
-- For each key in workspace_environment.variables JSON,
-- create a credential. Workspace admins = admin, others = member.
WITH ws_env_keys AS (
SELECT
we.workspace_id,
key AS env_key,
w.owner_id
FROM "workspace_environment" we
INNER JOIN "workspace" w ON w.id = we.workspace_id
CROSS JOIN LATERAL json_object_keys(we.variables::json) AS key
),
ws_env_targets AS (
SELECT
'cred_' || md5(wek.workspace_id || ':env_workspace:' || wek.env_key) AS cred_id,
wek.workspace_id,
wek.env_key,
wek.owner_id
FROM ws_env_keys wek
),
ws_workspace_members AS (
SELECT DISTINCT ON (workspace_id, user_id)
workspace_id, user_id, permission_type
FROM (
SELECT w.id AS workspace_id, p.user_id, p.permission_type
FROM "permissions" p
INNER JOIN "workspace" w ON w.id = p.entity_id
WHERE p.entity_type = 'workspace'
UNION ALL
SELECT w.id, w.owner_id, 'admin'::"permission_type"
FROM "workspace" w
) sub
ORDER BY workspace_id, user_id, (permission_type = 'admin') DESC
),
_ws_env_insert AS (
INSERT INTO "credential" (
"id", "workspace_id", "type", "display_name", "env_key",
"created_by", "created_at", "updated_at"
)
SELECT cred_id, workspace_id, 'env_workspace'::"credential_type",
env_key, env_key, owner_id, now(), now()
FROM ws_env_targets
ON CONFLICT DO NOTHING
)
INSERT INTO "credential_member" (
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
)
SELECT
'credm_' || md5(wet.cred_id || ':' || wm.user_id),
wet.cred_id,
wm.user_id,
CASE WHEN wm.permission_type = 'admin' THEN 'admin'::"credential_member_role" ELSE 'member'::"credential_member_role" END,
'active'::"credential_member_status",
now(),
wet.owner_id,
now(),
now()
FROM ws_env_targets wet
INNER JOIN ws_workspace_members wm ON wm.workspace_id = wet.workspace_id
ON CONFLICT DO NOTHING;
--> statement-breakpoint
-- ----------------------------------------------------------
-- 3. Personal environment variable credentials
-- ----------------------------------------------------------
-- For each key in environment.variables JSON, for each workspace
-- the user belongs to, create a credential with the user as admin.
WITH personal_env_keys AS (
SELECT
e.user_id,
key AS env_key
FROM "environment" e
CROSS JOIN LATERAL json_object_keys(e.variables::json) AS key
),
personal_env_targets AS (
SELECT
'cred_' || md5(wua.workspace_id || ':env_personal:' || pek.env_key || ':' || pek.user_id) AS cred_id,
wua.workspace_id,
pek.env_key,
pek.user_id
FROM personal_env_keys pek
INNER JOIN (
SELECT DISTINCT w.id AS workspace_id, p.user_id
FROM "permissions" p
INNER JOIN "workspace" w ON w.id = p.entity_id
WHERE p.entity_type = 'workspace'
UNION
SELECT w.id, w.owner_id FROM "workspace" w
) wua ON wua.user_id = pek.user_id
),
_personal_env_insert AS (
INSERT INTO "credential" (
"id", "workspace_id", "type", "display_name", "env_key", "env_owner_user_id",
"created_by", "created_at", "updated_at"
)
SELECT cred_id, workspace_id, 'env_personal'::"credential_type",
env_key, env_key, user_id, user_id, now(), now()
FROM personal_env_targets
ON CONFLICT DO NOTHING
)
INSERT INTO "credential_member" (
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
)
SELECT
'credm_' || md5(pet.cred_id || ':' || pet.user_id),
pet.cred_id,
pet.user_id,
'admin'::"credential_member_role",
'active'::"credential_member_status",
now(),
pet.user_id,
now(),
now()
FROM personal_env_targets pet
ON CONFLICT DO NOTHING;