Skip to content

Commit c2edb50

Browse files
committed
readd migration
1 parent c50d194 commit c2edb50

File tree

3 files changed

+12352
-0
lines changed

3 files changed

+12352
-0
lines changed
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
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+
oauth_targets AS (
133+
SELECT
134+
'cred_' || md5(wua.workspace_id || ':' || a.id) AS cred_id,
135+
wua.workspace_id,
136+
a.id AS account_id,
137+
a.user_id AS account_owner_id,
138+
a.provider_id,
139+
COALESCE(u.name, 'User') || '''s ' || COALESCE(pn.sname, a.provider_id) AS display_name
140+
FROM "account" a
141+
INNER JOIN (
142+
SELECT DISTINCT w.id AS workspace_id, p.user_id
143+
FROM "permissions" p
144+
INNER JOIN "workspace" w ON w.id = p.entity_id
145+
WHERE p.entity_type = 'workspace'
146+
UNION
147+
SELECT w.id, w.owner_id FROM "workspace" w
148+
) wua ON wua.user_id = a.user_id
149+
INNER JOIN "user" u ON u.id = a.user_id
150+
LEFT JOIN provider_names pn ON pn.pid = a.provider_id
151+
WHERE a.provider_id NOT IN ('credential', 'github', 'google')
152+
),
153+
oauth_workspace_members AS (
154+
SELECT DISTINCT w.id AS workspace_id, p.user_id
155+
FROM "permissions" p
156+
INNER JOIN "workspace" w ON w.id = p.entity_id
157+
WHERE p.entity_type = 'workspace'
158+
UNION
159+
SELECT w.id, w.owner_id FROM "workspace" w
160+
),
161+
_oauth_insert AS (
162+
INSERT INTO "credential" (
163+
"id", "workspace_id", "type", "display_name", "provider_id", "account_id",
164+
"created_by", "created_at", "updated_at"
165+
)
166+
SELECT cred_id, workspace_id, 'oauth'::"credential_type", display_name,
167+
provider_id, account_id, account_owner_id, now(), now()
168+
FROM oauth_targets
169+
ON CONFLICT DO NOTHING
170+
)
171+
INSERT INTO "credential_member" (
172+
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
173+
)
174+
SELECT
175+
'credm_' || md5(ot.cred_id || ':' || owm.user_id),
176+
ot.cred_id,
177+
owm.user_id,
178+
CASE WHEN ot.account_owner_id = owm.user_id THEN 'admin'::"credential_member_role" ELSE 'member'::"credential_member_role" END,
179+
'active'::"credential_member_status",
180+
now(),
181+
ot.account_owner_id,
182+
now(),
183+
now()
184+
FROM oauth_targets ot
185+
INNER JOIN oauth_workspace_members owm ON owm.workspace_id = ot.workspace_id
186+
ON CONFLICT DO NOTHING;
187+
188+
--> statement-breakpoint
189+
-- ----------------------------------------------------------
190+
-- 2. Workspace environment variable credentials
191+
-- ----------------------------------------------------------
192+
-- For each key in workspace_environment.variables JSON,
193+
-- create a credential. Workspace admins = admin, others = member.
194+
195+
WITH ws_env_keys AS (
196+
SELECT
197+
we.workspace_id,
198+
key AS env_key,
199+
w.owner_id
200+
FROM "workspace_environment" we
201+
INNER JOIN "workspace" w ON w.id = we.workspace_id
202+
CROSS JOIN LATERAL json_object_keys(we.variables::json) AS key
203+
),
204+
ws_env_targets AS (
205+
SELECT
206+
'cred_' || md5(wek.workspace_id || ':env_workspace:' || wek.env_key) AS cred_id,
207+
wek.workspace_id,
208+
wek.env_key,
209+
wek.owner_id
210+
FROM ws_env_keys wek
211+
),
212+
ws_workspace_members AS (
213+
SELECT DISTINCT ON (workspace_id, user_id)
214+
workspace_id, user_id, permission_type
215+
FROM (
216+
SELECT w.id AS workspace_id, p.user_id, p.permission_type
217+
FROM "permissions" p
218+
INNER JOIN "workspace" w ON w.id = p.entity_id
219+
WHERE p.entity_type = 'workspace'
220+
UNION ALL
221+
SELECT w.id, w.owner_id, 'admin'::"permission_type"
222+
FROM "workspace" w
223+
) sub
224+
ORDER BY workspace_id, user_id, (permission_type = 'admin') DESC
225+
),
226+
_ws_env_insert AS (
227+
INSERT INTO "credential" (
228+
"id", "workspace_id", "type", "display_name", "env_key",
229+
"created_by", "created_at", "updated_at"
230+
)
231+
SELECT cred_id, workspace_id, 'env_workspace'::"credential_type",
232+
env_key, env_key, owner_id, now(), now()
233+
FROM ws_env_targets
234+
ON CONFLICT DO NOTHING
235+
)
236+
INSERT INTO "credential_member" (
237+
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
238+
)
239+
SELECT
240+
'credm_' || md5(wet.cred_id || ':' || wm.user_id),
241+
wet.cred_id,
242+
wm.user_id,
243+
CASE WHEN wm.permission_type = 'admin' THEN 'admin'::"credential_member_role" ELSE 'member'::"credential_member_role" END,
244+
'active'::"credential_member_status",
245+
now(),
246+
wet.owner_id,
247+
now(),
248+
now()
249+
FROM ws_env_targets wet
250+
INNER JOIN ws_workspace_members wm ON wm.workspace_id = wet.workspace_id
251+
ON CONFLICT DO NOTHING;
252+
253+
--> statement-breakpoint
254+
-- ----------------------------------------------------------
255+
-- 3. Personal environment variable credentials
256+
-- ----------------------------------------------------------
257+
-- For each key in environment.variables JSON, for each workspace
258+
-- the user belongs to, create a credential with the user as admin.
259+
260+
WITH personal_env_keys AS (
261+
SELECT
262+
e.user_id,
263+
key AS env_key
264+
FROM "environment" e
265+
CROSS JOIN LATERAL json_object_keys(e.variables::json) AS key
266+
),
267+
personal_env_targets AS (
268+
SELECT
269+
'cred_' || md5(wua.workspace_id || ':env_personal:' || pek.env_key || ':' || pek.user_id) AS cred_id,
270+
wua.workspace_id,
271+
pek.env_key,
272+
pek.user_id
273+
FROM personal_env_keys pek
274+
INNER JOIN (
275+
SELECT DISTINCT w.id AS workspace_id, p.user_id
276+
FROM "permissions" p
277+
INNER JOIN "workspace" w ON w.id = p.entity_id
278+
WHERE p.entity_type = 'workspace'
279+
UNION
280+
SELECT w.id, w.owner_id FROM "workspace" w
281+
) wua ON wua.user_id = pek.user_id
282+
),
283+
_personal_env_insert AS (
284+
INSERT INTO "credential" (
285+
"id", "workspace_id", "type", "display_name", "env_key", "env_owner_user_id",
286+
"created_by", "created_at", "updated_at"
287+
)
288+
SELECT cred_id, workspace_id, 'env_personal'::"credential_type",
289+
env_key, env_key, user_id, user_id, now(), now()
290+
FROM personal_env_targets
291+
ON CONFLICT DO NOTHING
292+
)
293+
INSERT INTO "credential_member" (
294+
"id", "credential_id", "user_id", "role", "status", "joined_at", "invited_by", "created_at", "updated_at"
295+
)
296+
SELECT
297+
'credm_' || md5(pet.cred_id || ':' || pet.user_id),
298+
pet.cred_id,
299+
pet.user_id,
300+
'admin'::"credential_member_role",
301+
'active'::"credential_member_status",
302+
now(),
303+
pet.user_id,
304+
now(),
305+
now()
306+
FROM personal_env_targets pet
307+
ON CONFLICT DO NOTHING;

0 commit comments

Comments
 (0)