Skip to content

Commit ed9c35f

Browse files
committed
fix migration
1 parent 80282c3 commit ed9c35f

File tree

3 files changed

+12060
-0
lines changed

3 files changed

+12060
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
CREATE TYPE "public"."credential_member_role" AS ENUM('admin', 'member');--> statement-breakpoint
2+
CREATE TYPE "public"."credential_member_status" AS ENUM('active', 'pending', 'revoked');--> statement-breakpoint
3+
CREATE TYPE "public"."credential_type" AS ENUM('oauth', 'env_workspace', 'env_personal');--> statement-breakpoint
4+
CREATE TABLE "credential" (
5+
"id" text PRIMARY KEY NOT NULL,
6+
"workspace_id" text NOT NULL,
7+
"type" "credential_type" NOT NULL,
8+
"display_name" text NOT NULL,
9+
"description" text,
10+
"provider_id" text,
11+
"account_id" text,
12+
"env_key" text,
13+
"env_owner_user_id" text,
14+
"created_by" text NOT NULL,
15+
"created_at" timestamp DEFAULT now() NOT NULL,
16+
"updated_at" timestamp DEFAULT now() NOT NULL,
17+
CONSTRAINT "credential_oauth_source_check" CHECK ((type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)),
18+
CONSTRAINT "credential_workspace_env_source_check" CHECK ((type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)),
19+
CONSTRAINT "credential_personal_env_source_check" CHECK ((type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL))
20+
);
21+
--> statement-breakpoint
22+
CREATE TABLE "credential_member" (
23+
"id" text PRIMARY KEY NOT NULL,
24+
"credential_id" text NOT NULL,
25+
"user_id" text NOT NULL,
26+
"role" "credential_member_role" DEFAULT 'member' NOT NULL,
27+
"status" "credential_member_status" DEFAULT 'active' NOT NULL,
28+
"joined_at" timestamp,
29+
"invited_by" text,
30+
"created_at" timestamp DEFAULT now() NOT NULL,
31+
"updated_at" timestamp DEFAULT now() NOT NULL
32+
);
33+
--> statement-breakpoint
34+
CREATE TABLE "pending_credential_draft" (
35+
"id" text PRIMARY KEY NOT NULL,
36+
"user_id" text NOT NULL,
37+
"workspace_id" text NOT NULL,
38+
"provider_id" text NOT NULL,
39+
"display_name" text NOT NULL,
40+
"description" text,
41+
"credential_id" text,
42+
"expires_at" timestamp NOT NULL,
43+
"created_at" timestamp DEFAULT now() NOT NULL
44+
);
45+
--> statement-breakpoint
46+
DROP INDEX "account_user_provider_unique";--> statement-breakpoint
47+
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
48+
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
49+
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
50+
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
51+
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
52+
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
53+
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
54+
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
55+
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
56+
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
57+
CREATE INDEX "credential_workspace_id_idx" ON "credential" USING btree ("workspace_id");--> statement-breakpoint
58+
CREATE INDEX "credential_type_idx" ON "credential" USING btree ("type");--> statement-breakpoint
59+
CREATE INDEX "credential_provider_id_idx" ON "credential" USING btree ("provider_id");--> statement-breakpoint
60+
CREATE INDEX "credential_account_id_idx" ON "credential" USING btree ("account_id");--> statement-breakpoint
61+
CREATE INDEX "credential_env_owner_user_id_idx" ON "credential" USING btree ("env_owner_user_id");--> statement-breakpoint
62+
CREATE UNIQUE INDEX "credential_workspace_account_unique" ON "credential" USING btree ("workspace_id","account_id") WHERE account_id IS NOT NULL;--> statement-breakpoint
63+
CREATE UNIQUE INDEX "credential_workspace_env_unique" ON "credential" USING btree ("workspace_id","type","env_key") WHERE type = 'env_workspace';--> statement-breakpoint
64+
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
65+
CREATE INDEX "credential_member_credential_id_idx" ON "credential_member" USING btree ("credential_id");--> statement-breakpoint
66+
CREATE INDEX "credential_member_user_id_idx" ON "credential_member" USING btree ("user_id");--> statement-breakpoint
67+
CREATE INDEX "credential_member_role_idx" ON "credential_member" USING btree ("role");--> statement-breakpoint
68+
CREATE INDEX "credential_member_status_idx" ON "credential_member" USING btree ("status");--> statement-breakpoint
69+
CREATE UNIQUE INDEX "credential_member_unique" ON "credential_member" USING btree ("credential_id","user_id");--> statement-breakpoint
70+
CREATE UNIQUE INDEX "pending_draft_user_provider_ws" ON "pending_credential_draft" USING btree ("user_id","provider_id","workspace_id");
71+
--> statement-breakpoint
72+
-- ============================================================
73+
-- BACKFILL: Create credentials and members from existing data
74+
-- ============================================================
75+
76+
-- Helper CTE: all workspace members (from permissions + workspace owners)
77+
-- Used by all three backfill sections below.
78+
79+
-- ----------------------------------------------------------
80+
-- 1. OAuth credentials
81+
-- ----------------------------------------------------------
82+
-- For each (account, workspace) where account owner has workspace access,
83+
-- create a "Default <Service Name> Credential".
84+
-- Account owner = admin, other workspace members = member.
85+
86+
WITH provider_names(pid, sname) AS (
87+
VALUES
88+
('google-email', 'Gmail'),
89+
('google-drive', 'Google Drive'),
90+
('google-docs', 'Google Docs'),
91+
('google-sheets', 'Google Sheets'),
92+
('google-forms', 'Google Forms'),
93+
('google-calendar', 'Google Calendar'),
94+
('google-vault', 'Google Vault'),
95+
('google-slides', 'Google Slides'),
96+
('google-groups', 'Google Groups'),
97+
('slack', 'Slack'),
98+
('notion', 'Notion'),
99+
('confluence', 'Confluence'),
100+
('jira', 'Jira'),
101+
('jira-service-management', 'Jira Service Management'),
102+
('linear', 'Linear'),
103+
('airtable', 'Airtable'),
104+
('asana', 'Asana'),
105+
('hubspot', 'HubSpot'),
106+
('salesforce', 'Salesforce'),
107+
('pipedrive', 'Pipedrive'),
108+
('microsoft-teams', 'Microsoft Teams'),
109+
('microsoft-planner', 'Microsoft Planner'),
110+
('microsoft-excel', 'Microsoft Excel'),
111+
('outlook', 'Outlook'),
112+
('onedrive', 'OneDrive'),
113+
('sharepoint', 'SharePoint'),
114+
('dropbox', 'Dropbox'),
115+
('wordpress', 'WordPress'),
116+
('webflow', 'Webflow'),
117+
('wealthbox', 'Wealthbox'),
118+
('spotify', 'Spotify'),
119+
('x', 'X'),
120+
('reddit', 'Reddit'),
121+
('linkedin', 'LinkedIn'),
122+
('trello', 'Trello'),
123+
('shopify', 'Shopify'),
124+
('zoom', 'Zoom'),
125+
('calcom', 'Cal.com'),
126+
('discord', 'Discord'),
127+
('box', 'Box'),
128+
('github-repo', 'GitHub'),
129+
('vertex-ai', 'Vertex AI'),
130+
('supabase', 'Supabase')
131+
),
132+
workspace_user_access AS (
133+
SELECT DISTINCT w.id AS workspace_id, p.user_id, p.permission_type
134+
FROM "permissions" p
135+
INNER JOIN "workspace" w ON w.id = p.entity_id
136+
WHERE p.entity_type = 'workspace'
137+
UNION
138+
SELECT w.id, w.owner_id, 'admin'::"permission_type"
139+
FROM "workspace" w
140+
),
141+
oauth_creds AS (
142+
INSERT INTO "credential" (
143+
"id", "workspace_id", "type", "display_name", "provider_id", "account_id",
144+
"created_by", "created_at", "updated_at"
145+
)
146+
SELECT
147+
'cred_' || md5(wua.workspace_id || ':' || a.id) AS id,
148+
wua.workspace_id,
149+
'oauth'::"credential_type",
150+
COALESCE(u.name, 'User') || '''s ' || COALESCE(pn.sname, a.provider_id),
151+
a.provider_id,
152+
a.id,
153+
a.user_id,
154+
now(),
155+
now()
156+
FROM "account" a
157+
INNER JOIN workspace_user_access wua ON wua.user_id = a.user_id
158+
INNER JOIN "user" u ON u.id = a.user_id
159+
LEFT JOIN provider_names pn ON pn.pid = a.provider_id
160+
WHERE a.provider_id NOT IN ('credential', 'github', 'google')
161+
ON CONFLICT DO NOTHING
162+
RETURNING id, workspace_id, account_id
163+
)
164+
INSERT INTO "credential_member" (
165+
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
166+
)
167+
SELECT
168+
'credm_' || md5(oc.id || ':' || wua.user_id),
169+
oc.id,
170+
wua.user_id,
171+
CASE WHEN a.user_id = wua.user_id THEN 'admin'::"credential_member_role" ELSE 'member'::"credential_member_role" END,
172+
'active'::"credential_member_status",
173+
now(),
174+
a.user_id,
175+
now(),
176+
now()
177+
FROM oauth_creds oc
178+
INNER JOIN "account" a ON a.id = oc.account_id
179+
INNER JOIN workspace_user_access wua ON wua.workspace_id = oc.workspace_id
180+
ON CONFLICT DO NOTHING;
181+
182+
--> statement-breakpoint
183+
-- ----------------------------------------------------------
184+
-- 2. Workspace environment variable credentials
185+
-- ----------------------------------------------------------
186+
-- For each key in workspace_environment.variables JSON,
187+
-- create a credential. Workspace admins = admin, others = member.
188+
189+
WITH workspace_user_access AS (
190+
SELECT DISTINCT w.id AS workspace_id, p.user_id, p.permission_type
191+
FROM "permissions" p
192+
INNER JOIN "workspace" w ON w.id = p.entity_id
193+
WHERE p.entity_type = 'workspace'
194+
UNION
195+
SELECT w.id, w.owner_id, 'admin'::"permission_type"
196+
FROM "workspace" w
197+
),
198+
ws_env_keys AS (
199+
SELECT
200+
we.workspace_id,
201+
key AS env_key,
202+
w.owner_id
203+
FROM "workspace_environment" we
204+
INNER JOIN "workspace" w ON w.id = we.workspace_id
205+
CROSS JOIN LATERAL json_object_keys(we.variables::json) AS key
206+
),
207+
ws_env_creds AS (
208+
INSERT INTO "credential" (
209+
"id", "workspace_id", "type", "display_name", "env_key",
210+
"created_by", "created_at", "updated_at"
211+
)
212+
SELECT
213+
'cred_' || md5(wek.workspace_id || ':env_workspace:' || wek.env_key),
214+
wek.workspace_id,
215+
'env_workspace'::"credential_type",
216+
wek.env_key,
217+
wek.env_key,
218+
wek.owner_id,
219+
now(),
220+
now()
221+
FROM ws_env_keys wek
222+
ON CONFLICT DO NOTHING
223+
RETURNING id, workspace_id
224+
)
225+
INSERT INTO "credential_member" (
226+
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
227+
)
228+
SELECT
229+
'credm_' || md5(wec.id || ':' || wua.user_id),
230+
wec.id,
231+
wua.user_id,
232+
CASE WHEN wua.permission_type = 'admin' THEN 'admin'::"credential_member_role" ELSE 'member'::"credential_member_role" END,
233+
'active'::"credential_member_status",
234+
now(),
235+
(SELECT w.owner_id FROM "workspace" w WHERE w.id = wec.workspace_id LIMIT 1),
236+
now(),
237+
now()
238+
FROM ws_env_creds wec
239+
INNER JOIN workspace_user_access wua ON wua.workspace_id = wec.workspace_id
240+
ON CONFLICT DO NOTHING;
241+
242+
--> statement-breakpoint
243+
-- ----------------------------------------------------------
244+
-- 3. Personal environment variable credentials
245+
-- ----------------------------------------------------------
246+
-- For each key in environment.variables JSON, for each workspace
247+
-- the user belongs to, create a credential with the user as admin.
248+
249+
WITH workspace_user_access AS (
250+
SELECT DISTINCT w.id AS workspace_id, p.user_id
251+
FROM "permissions" p
252+
INNER JOIN "workspace" w ON w.id = p.entity_id
253+
WHERE p.entity_type = 'workspace'
254+
UNION
255+
SELECT w.id, w.owner_id
256+
FROM "workspace" w
257+
),
258+
personal_env_keys AS (
259+
SELECT
260+
e.user_id,
261+
key AS env_key
262+
FROM "environment" e
263+
CROSS JOIN LATERAL json_object_keys(e.variables::json) AS key
264+
),
265+
personal_env_creds AS (
266+
INSERT INTO "credential" (
267+
"id", "workspace_id", "type", "display_name", "env_key", "env_owner_user_id",
268+
"created_by", "created_at", "updated_at"
269+
)
270+
SELECT
271+
'cred_' || md5(wua.workspace_id || ':env_personal:' || pek.env_key || ':' || pek.user_id),
272+
wua.workspace_id,
273+
'env_personal'::"credential_type",
274+
pek.env_key,
275+
pek.env_key,
276+
pek.user_id,
277+
pek.user_id,
278+
now(),
279+
now()
280+
FROM personal_env_keys pek
281+
INNER JOIN workspace_user_access wua ON wua.user_id = pek.user_id
282+
ON CONFLICT DO NOTHING
283+
RETURNING id, workspace_id
284+
)
285+
INSERT INTO "credential_member" (
286+
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
287+
)
288+
SELECT
289+
'credm_' || md5(pec.id || ':' || c.env_owner_user_id),
290+
pec.id,
291+
c.env_owner_user_id,
292+
'admin'::"credential_member_role",
293+
'active'::"credential_member_status",
294+
now(),
295+
c.env_owner_user_id,
296+
now(),
297+
now()
298+
FROM personal_env_creds pec
299+
INNER JOIN "credential" c ON c.id = pec.id
300+
ON CONFLICT DO NOTHING;

0 commit comments

Comments
 (0)