@realtimex/folio 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/api/src/middleware/auth.ts +77 -0
  2. package/api/src/routes/chat.ts +7 -1
  3. package/api/src/routes/index.ts +2 -0
  4. package/api/src/routes/ingestions.ts +45 -5
  5. package/api/src/routes/policies.ts +50 -7
  6. package/api/src/routes/stats.ts +9 -5
  7. package/api/src/routes/workspaces.ts +290 -0
  8. package/api/src/services/ChatService.ts +8 -2
  9. package/api/src/services/IngestionService.ts +38 -26
  10. package/api/src/services/PolicyEngine.ts +4 -1
  11. package/api/src/services/PolicyLearningService.ts +31 -6
  12. package/api/src/services/PolicyLoader.ts +44 -25
  13. package/api/src/services/RAGService.ts +52 -12
  14. package/dist/api/src/middleware/auth.js +59 -0
  15. package/dist/api/src/routes/chat.js +1 -1
  16. package/dist/api/src/routes/index.js +2 -0
  17. package/dist/api/src/routes/ingestions.js +45 -8
  18. package/dist/api/src/routes/policies.js +49 -7
  19. package/dist/api/src/routes/stats.js +9 -5
  20. package/dist/api/src/routes/workspaces.js +220 -0
  21. package/dist/api/src/services/ChatService.js +7 -2
  22. package/dist/api/src/services/IngestionService.js +35 -30
  23. package/dist/api/src/services/PolicyEngine.js +2 -1
  24. package/dist/api/src/services/PolicyLearningService.js +28 -6
  25. package/dist/api/src/services/PolicyLoader.js +29 -25
  26. package/dist/api/src/services/RAGService.js +43 -11
  27. package/dist/assets/index-CTn5FcC4.js +113 -0
  28. package/dist/assets/index-Dq9sxoZK.css +1 -0
  29. package/dist/index.html +2 -2
  30. package/package.json +1 -1
  31. package/supabase/functions/workspace-invite/index.ts +110 -0
  32. package/supabase/migrations/20260223000000_initial_foundation.sql +5 -0
  33. package/supabase/migrations/20260224000004_add_avatars_storage.sql +4 -0
  34. package/supabase/migrations/20260224000006_add_policies_table.sql +5 -0
  35. package/supabase/migrations/20260224000008_add_ingestions_table.sql +2 -0
  36. package/supabase/migrations/20260225000000_setup_compatible_mode.sql +17 -4
  37. package/supabase/migrations/20260225000003_add_baseline_configs.sql +4 -3
  38. package/supabase/migrations/20260226000000_add_processing_events.sql +1 -0
  39. package/supabase/migrations/20260226000002_add_dynamic_rag.sql +1 -0
  40. package/supabase/migrations/20260226000005_add_chat_tables.sql +3 -0
  41. package/supabase/migrations/20260228000001_add_policy_match_feedback.sql +4 -0
  42. package/supabase/migrations/20260302064608_add_ingestion_llm_settings_compat.sql +15 -0
  43. package/supabase/migrations/20260303000000_add_workspaces_phase1.sql +459 -0
  44. package/supabase/migrations/20260303010000_add_workspace_management_rpc.sql +310 -0
  45. package/supabase/migrations/20260303020000_workspace_scope_document_chunks.sql +139 -0
  46. package/dist/assets/index-DzN8-j-e.css +0 -1
  47. package/dist/assets/index-dnBz6SWG.js +0 -113
@@ -0,0 +1,459 @@
1
+ -- Phase 1 multi-user foundations:
2
+ -- - Introduce workspaces + workspace_members
3
+ -- - Scope policies, ingestions, and policy learning feedback by workspace_id
4
+ -- - Keep user_id for actor/audit trails and backward compatibility
5
+
6
+ -- ---------------------------------------------------------------------------
7
+ -- Workspace primitives
8
+ -- ---------------------------------------------------------------------------
9
+
10
+ CREATE TABLE IF NOT EXISTS public.workspaces (
11
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
12
+ name TEXT NOT NULL,
13
+ owner_user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
14
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
15
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
16
+ );
17
+
18
+ CREATE TABLE IF NOT EXISTS public.workspace_members (
19
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
20
+ workspace_id UUID NOT NULL REFERENCES public.workspaces(id) ON DELETE CASCADE,
21
+ user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
22
+ role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'admin', 'member')),
23
+ status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'invited', 'disabled')),
24
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
25
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
26
+ UNIQUE (workspace_id, user_id)
27
+ );
28
+
29
+ CREATE INDEX IF NOT EXISTS workspace_members_user_id_status_idx
30
+ ON public.workspace_members(user_id, status);
31
+
32
+ CREATE INDEX IF NOT EXISTS workspace_members_workspace_id_status_idx
33
+ ON public.workspace_members(workspace_id, status);
34
+
35
+ DROP TRIGGER IF EXISTS workspaces_updated_at ON public.workspaces;
36
+ CREATE TRIGGER workspaces_updated_at
37
+ BEFORE UPDATE ON public.workspaces
38
+ FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
39
+
40
+ DROP TRIGGER IF EXISTS workspace_members_updated_at ON public.workspace_members;
41
+ CREATE TRIGGER workspace_members_updated_at
42
+ BEFORE UPDATE ON public.workspace_members
43
+ FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
44
+
45
+ ALTER TABLE public.workspaces ENABLE ROW LEVEL SECURITY;
46
+ ALTER TABLE public.workspace_members ENABLE ROW LEVEL SECURITY;
47
+
48
+ DROP POLICY IF EXISTS "Members can read workspaces" ON public.workspaces;
49
+ CREATE POLICY "Members can read workspaces"
50
+ ON public.workspaces FOR SELECT
51
+ USING (
52
+ EXISTS (
53
+ SELECT 1
54
+ FROM public.workspace_members wm
55
+ WHERE wm.workspace_id = workspaces.id
56
+ AND wm.user_id = auth.uid()
57
+ AND wm.status = 'active'
58
+ )
59
+ );
60
+
61
+ DROP POLICY IF EXISTS "Users can create owned workspaces" ON public.workspaces;
62
+ CREATE POLICY "Users can create owned workspaces"
63
+ ON public.workspaces FOR INSERT
64
+ WITH CHECK (owner_user_id = auth.uid());
65
+
66
+ DROP POLICY IF EXISTS "Admins can update workspaces" ON public.workspaces;
67
+ CREATE POLICY "Admins can update workspaces"
68
+ ON public.workspaces FOR UPDATE
69
+ USING (
70
+ EXISTS (
71
+ SELECT 1
72
+ FROM public.workspace_members wm
73
+ WHERE wm.workspace_id = workspaces.id
74
+ AND wm.user_id = auth.uid()
75
+ AND wm.status = 'active'
76
+ AND wm.role IN ('owner', 'admin')
77
+ )
78
+ );
79
+
80
+ DROP POLICY IF EXISTS "Owners can delete workspaces" ON public.workspaces;
81
+ CREATE POLICY "Owners can delete workspaces"
82
+ ON public.workspaces FOR DELETE
83
+ USING (
84
+ EXISTS (
85
+ SELECT 1
86
+ FROM public.workspace_members wm
87
+ WHERE wm.workspace_id = workspaces.id
88
+ AND wm.user_id = auth.uid()
89
+ AND wm.status = 'active'
90
+ AND wm.role = 'owner'
91
+ )
92
+ );
93
+
94
+ DROP POLICY IF EXISTS "Users can read their workspace memberships" ON public.workspace_members;
95
+ CREATE POLICY "Users can read their workspace memberships"
96
+ ON public.workspace_members FOR SELECT
97
+ USING (user_id = auth.uid());
98
+
99
+ DROP POLICY IF EXISTS "Users can manage their workspace memberships" ON public.workspace_members;
100
+ CREATE POLICY "Users can manage their workspace memberships"
101
+ ON public.workspace_members FOR ALL
102
+ USING (user_id = auth.uid())
103
+ WITH CHECK (user_id = auth.uid());
104
+
105
+ CREATE OR REPLACE FUNCTION public.is_workspace_member(
106
+ p_workspace_id UUID,
107
+ p_user_id UUID DEFAULT auth.uid()
108
+ )
109
+ RETURNS BOOLEAN
110
+ LANGUAGE sql
111
+ STABLE
112
+ SECURITY DEFINER
113
+ SET search_path = public
114
+ AS $$
115
+ SELECT EXISTS (
116
+ SELECT 1
117
+ FROM public.workspace_members wm
118
+ WHERE wm.workspace_id = p_workspace_id
119
+ AND wm.user_id = p_user_id
120
+ AND wm.status = 'active'
121
+ );
122
+ $$;
123
+
124
+ GRANT EXECUTE ON FUNCTION public.is_workspace_member(UUID, UUID) TO authenticated;
125
+
126
+ -- ---------------------------------------------------------------------------
127
+ -- Backfill one default workspace per existing user
128
+ -- ---------------------------------------------------------------------------
129
+
130
+ WITH users_without_workspace AS (
131
+ SELECT u.id AS user_id
132
+ FROM auth.users u
133
+ WHERE NOT EXISTS (
134
+ SELECT 1
135
+ FROM public.workspace_members wm
136
+ WHERE wm.user_id = u.id
137
+ AND wm.status = 'active'
138
+ )
139
+ )
140
+ INSERT INTO public.workspaces (name, owner_user_id)
141
+ SELECT
142
+ COALESCE(
143
+ NULLIF(TRIM(COALESCE(p.first_name, '') || ' ' || COALESCE(p.last_name, '')), ''),
144
+ NULLIF(SPLIT_PART(COALESCE(u.email, ''), '@', 1), ''),
145
+ 'Workspace'
146
+ ) || '''s Workspace',
147
+ uw.user_id
148
+ FROM users_without_workspace uw
149
+ JOIN auth.users u ON u.id = uw.user_id
150
+ LEFT JOIN public.profiles p ON p.id = uw.user_id;
151
+
152
+ INSERT INTO public.workspace_members (workspace_id, user_id, role, status)
153
+ SELECT w.id, w.owner_user_id, 'owner', 'active'
154
+ FROM public.workspaces w
155
+ WHERE NOT EXISTS (
156
+ SELECT 1
157
+ FROM public.workspace_members wm
158
+ WHERE wm.workspace_id = w.id
159
+ AND wm.user_id = w.owner_user_id
160
+ );
161
+
162
+ -- ---------------------------------------------------------------------------
163
+ -- Workspace scoping columns
164
+ -- ---------------------------------------------------------------------------
165
+
166
+ ALTER TABLE public.policies ADD COLUMN IF NOT EXISTS workspace_id UUID;
167
+ ALTER TABLE public.ingestions ADD COLUMN IF NOT EXISTS workspace_id UUID;
168
+ ALTER TABLE public.policy_match_feedback ADD COLUMN IF NOT EXISTS workspace_id UUID;
169
+
170
+ WITH user_primary_workspace AS (
171
+ SELECT DISTINCT ON (wm.user_id)
172
+ wm.user_id,
173
+ wm.workspace_id
174
+ FROM public.workspace_members wm
175
+ WHERE wm.status = 'active'
176
+ ORDER BY
177
+ wm.user_id,
178
+ CASE wm.role
179
+ WHEN 'owner' THEN 0
180
+ WHEN 'admin' THEN 1
181
+ ELSE 2
182
+ END,
183
+ wm.created_at,
184
+ wm.workspace_id
185
+ )
186
+ UPDATE public.policies p
187
+ SET workspace_id = upw.workspace_id
188
+ FROM user_primary_workspace upw
189
+ WHERE p.workspace_id IS NULL
190
+ AND p.user_id = upw.user_id;
191
+
192
+ WITH user_primary_workspace AS (
193
+ SELECT DISTINCT ON (wm.user_id)
194
+ wm.user_id,
195
+ wm.workspace_id
196
+ FROM public.workspace_members wm
197
+ WHERE wm.status = 'active'
198
+ ORDER BY
199
+ wm.user_id,
200
+ CASE wm.role
201
+ WHEN 'owner' THEN 0
202
+ WHEN 'admin' THEN 1
203
+ ELSE 2
204
+ END,
205
+ wm.created_at,
206
+ wm.workspace_id
207
+ )
208
+ UPDATE public.ingestions i
209
+ SET workspace_id = upw.workspace_id
210
+ FROM user_primary_workspace upw
211
+ WHERE i.workspace_id IS NULL
212
+ AND i.user_id = upw.user_id;
213
+
214
+ UPDATE public.policy_match_feedback pmf
215
+ SET workspace_id = i.workspace_id
216
+ FROM public.ingestions i
217
+ WHERE pmf.workspace_id IS NULL
218
+ AND pmf.ingestion_id = i.id;
219
+
220
+ WITH user_primary_workspace AS (
221
+ SELECT DISTINCT ON (wm.user_id)
222
+ wm.user_id,
223
+ wm.workspace_id
224
+ FROM public.workspace_members wm
225
+ WHERE wm.status = 'active'
226
+ ORDER BY
227
+ wm.user_id,
228
+ CASE wm.role
229
+ WHEN 'owner' THEN 0
230
+ WHEN 'admin' THEN 1
231
+ ELSE 2
232
+ END,
233
+ wm.created_at,
234
+ wm.workspace_id
235
+ )
236
+ UPDATE public.policy_match_feedback pmf
237
+ SET workspace_id = upw.workspace_id
238
+ FROM user_primary_workspace upw
239
+ WHERE pmf.workspace_id IS NULL
240
+ AND pmf.user_id = upw.user_id;
241
+
242
+ ALTER TABLE public.policies
243
+ ALTER COLUMN workspace_id SET NOT NULL;
244
+
245
+ ALTER TABLE public.ingestions
246
+ ALTER COLUMN workspace_id SET NOT NULL;
247
+
248
+ ALTER TABLE public.policy_match_feedback
249
+ ALTER COLUMN workspace_id SET NOT NULL;
250
+
251
+ DO $$
252
+ BEGIN
253
+ IF NOT EXISTS (
254
+ SELECT 1 FROM pg_constraint
255
+ WHERE conname = 'policies_workspace_id_fkey'
256
+ ) THEN
257
+ ALTER TABLE public.policies
258
+ ADD CONSTRAINT policies_workspace_id_fkey
259
+ FOREIGN KEY (workspace_id) REFERENCES public.workspaces(id) ON DELETE CASCADE;
260
+ END IF;
261
+
262
+ IF NOT EXISTS (
263
+ SELECT 1 FROM pg_constraint
264
+ WHERE conname = 'ingestions_workspace_id_fkey'
265
+ ) THEN
266
+ ALTER TABLE public.ingestions
267
+ ADD CONSTRAINT ingestions_workspace_id_fkey
268
+ FOREIGN KEY (workspace_id) REFERENCES public.workspaces(id) ON DELETE CASCADE;
269
+ END IF;
270
+
271
+ IF NOT EXISTS (
272
+ SELECT 1 FROM pg_constraint
273
+ WHERE conname = 'policy_match_feedback_workspace_id_fkey'
274
+ ) THEN
275
+ ALTER TABLE public.policy_match_feedback
276
+ ADD CONSTRAINT policy_match_feedback_workspace_id_fkey
277
+ FOREIGN KEY (workspace_id) REFERENCES public.workspaces(id) ON DELETE CASCADE;
278
+ END IF;
279
+ END $$;
280
+
281
+ ALTER TABLE public.policies
282
+ DROP CONSTRAINT IF EXISTS policies_user_id_policy_id_key;
283
+
284
+ ALTER TABLE public.policies
285
+ DROP CONSTRAINT IF EXISTS policies_workspace_id_policy_id_key;
286
+
287
+ ALTER TABLE public.policies
288
+ ADD CONSTRAINT policies_workspace_id_policy_id_key UNIQUE (workspace_id, policy_id);
289
+
290
+ ALTER TABLE public.policy_match_feedback
291
+ DROP CONSTRAINT IF EXISTS policy_match_feedback_user_id_ingestion_id_policy_id_key;
292
+
293
+ ALTER TABLE public.policy_match_feedback
294
+ DROP CONSTRAINT IF EXISTS policy_match_feedback_workspace_id_ingestion_id_policy_id_key;
295
+
296
+ ALTER TABLE public.policy_match_feedback
297
+ ADD CONSTRAINT policy_match_feedback_workspace_id_ingestion_id_policy_id_key
298
+ UNIQUE (workspace_id, ingestion_id, policy_id);
299
+
300
+ CREATE INDEX IF NOT EXISTS policies_workspace_id_priority_idx
301
+ ON public.policies(workspace_id, priority DESC);
302
+
303
+ CREATE INDEX IF NOT EXISTS ingestions_workspace_id_created_at_idx
304
+ ON public.ingestions(workspace_id, created_at DESC);
305
+
306
+ CREATE INDEX IF NOT EXISTS idx_ingestions_workspace_file_hash
307
+ ON public.ingestions(workspace_id, file_hash)
308
+ WHERE file_hash IS NOT NULL;
309
+
310
+ CREATE INDEX IF NOT EXISTS policy_match_feedback_workspace_policy_idx
311
+ ON public.policy_match_feedback(workspace_id, policy_id, created_at DESC);
312
+
313
+ CREATE INDEX IF NOT EXISTS policy_match_feedback_workspace_created_idx
314
+ ON public.policy_match_feedback(workspace_id, created_at DESC);
315
+
316
+ -- ---------------------------------------------------------------------------
317
+ -- Update RLS for workspace-scoped tables
318
+ -- ---------------------------------------------------------------------------
319
+
320
+ DROP POLICY IF EXISTS "Users can read own policies" ON public.policies;
321
+ DROP POLICY IF EXISTS "Users can insert own policies" ON public.policies;
322
+ DROP POLICY IF EXISTS "Users can update own policies" ON public.policies;
323
+ DROP POLICY IF EXISTS "Users can delete own policies" ON public.policies;
324
+ DROP POLICY IF EXISTS "Workspace members can read policies" ON public.policies;
325
+ DROP POLICY IF EXISTS "Workspace members can insert policies" ON public.policies;
326
+ DROP POLICY IF EXISTS "Workspace members can update policies" ON public.policies;
327
+ DROP POLICY IF EXISTS "Workspace members can delete policies" ON public.policies;
328
+
329
+ CREATE POLICY "Workspace members can read policies"
330
+ ON public.policies FOR SELECT
331
+ USING (public.is_workspace_member(workspace_id));
332
+
333
+ CREATE POLICY "Workspace members can insert policies"
334
+ ON public.policies FOR INSERT
335
+ WITH CHECK (public.is_workspace_member(workspace_id) AND user_id = auth.uid());
336
+
337
+ CREATE POLICY "Workspace members can update policies"
338
+ ON public.policies FOR UPDATE
339
+ USING (public.is_workspace_member(workspace_id))
340
+ WITH CHECK (public.is_workspace_member(workspace_id));
341
+
342
+ CREATE POLICY "Workspace members can delete policies"
343
+ ON public.policies FOR DELETE
344
+ USING (public.is_workspace_member(workspace_id));
345
+
346
+ DROP POLICY IF EXISTS "Users can manage their own ingestions" ON public.ingestions;
347
+ DROP POLICY IF EXISTS "Workspace members can read ingestions" ON public.ingestions;
348
+ DROP POLICY IF EXISTS "Workspace members can insert ingestions" ON public.ingestions;
349
+ DROP POLICY IF EXISTS "Workspace members can update ingestions" ON public.ingestions;
350
+ DROP POLICY IF EXISTS "Workspace members can delete ingestions" ON public.ingestions;
351
+
352
+ CREATE POLICY "Workspace members can read ingestions"
353
+ ON public.ingestions FOR SELECT
354
+ USING (public.is_workspace_member(workspace_id));
355
+
356
+ CREATE POLICY "Workspace members can insert ingestions"
357
+ ON public.ingestions FOR INSERT
358
+ WITH CHECK (public.is_workspace_member(workspace_id) AND user_id = auth.uid());
359
+
360
+ CREATE POLICY "Workspace members can update ingestions"
361
+ ON public.ingestions FOR UPDATE
362
+ USING (public.is_workspace_member(workspace_id))
363
+ WITH CHECK (public.is_workspace_member(workspace_id));
364
+
365
+ CREATE POLICY "Workspace members can delete ingestions"
366
+ ON public.ingestions FOR DELETE
367
+ USING (public.is_workspace_member(workspace_id));
368
+
369
+ DROP POLICY IF EXISTS "Users can read own policy match feedback" ON public.policy_match_feedback;
370
+ DROP POLICY IF EXISTS "Users can insert own policy match feedback" ON public.policy_match_feedback;
371
+ DROP POLICY IF EXISTS "Users can update own policy match feedback" ON public.policy_match_feedback;
372
+ DROP POLICY IF EXISTS "Users can delete own policy match feedback" ON public.policy_match_feedback;
373
+ DROP POLICY IF EXISTS "Workspace members can read policy match feedback" ON public.policy_match_feedback;
374
+ DROP POLICY IF EXISTS "Workspace members can insert policy match feedback" ON public.policy_match_feedback;
375
+ DROP POLICY IF EXISTS "Workspace members can update policy match feedback" ON public.policy_match_feedback;
376
+ DROP POLICY IF EXISTS "Workspace members can delete policy match feedback" ON public.policy_match_feedback;
377
+
378
+ CREATE POLICY "Workspace members can read policy match feedback"
379
+ ON public.policy_match_feedback FOR SELECT
380
+ USING (public.is_workspace_member(workspace_id));
381
+
382
+ CREATE POLICY "Workspace members can insert policy match feedback"
383
+ ON public.policy_match_feedback FOR INSERT
384
+ WITH CHECK (public.is_workspace_member(workspace_id) AND user_id = auth.uid());
385
+
386
+ CREATE POLICY "Workspace members can update policy match feedback"
387
+ ON public.policy_match_feedback FOR UPDATE
388
+ USING (public.is_workspace_member(workspace_id))
389
+ WITH CHECK (public.is_workspace_member(workspace_id));
390
+
391
+ CREATE POLICY "Workspace members can delete policy match feedback"
392
+ ON public.policy_match_feedback FOR DELETE
393
+ USING (public.is_workspace_member(workspace_id));
394
+
395
+ -- ---------------------------------------------------------------------------
396
+ -- Ensure new auth users automatically get a default workspace + membership
397
+ -- ---------------------------------------------------------------------------
398
+
399
+ CREATE OR REPLACE FUNCTION public.handle_new_auth_user()
400
+ RETURNS TRIGGER
401
+ LANGUAGE plpgsql
402
+ SECURITY DEFINER
403
+ SET search_path = 'pg_catalog', 'public'
404
+ AS $$
405
+ DECLARE
406
+ should_be_admin BOOLEAN;
407
+ workspace_name TEXT;
408
+ created_workspace_id UUID;
409
+ BEGIN
410
+ -- Serialize first-user admin assignment and profile bootstrap.
411
+ PERFORM pg_advisory_xact_lock(602240003);
412
+
413
+ SELECT NOT EXISTS (
414
+ SELECT 1
415
+ FROM public.profiles p
416
+ WHERE p.is_admin = true
417
+ )
418
+ INTO should_be_admin;
419
+
420
+ INSERT INTO public.profiles (id, first_name, last_name, email, is_admin)
421
+ VALUES (
422
+ NEW.id,
423
+ NEW.raw_user_meta_data ->> 'first_name',
424
+ NEW.raw_user_meta_data ->> 'last_name',
425
+ NEW.email,
426
+ should_be_admin
427
+ )
428
+ ON CONFLICT (id) DO UPDATE
429
+ SET
430
+ first_name = COALESCE(EXCLUDED.first_name, public.profiles.first_name),
431
+ last_name = COALESCE(EXCLUDED.last_name, public.profiles.last_name),
432
+ email = EXCLUDED.email,
433
+ updated_at = NOW();
434
+
435
+ INSERT INTO public.user_settings (user_id)
436
+ VALUES (NEW.id)
437
+ ON CONFLICT (user_id) DO NOTHING;
438
+
439
+ workspace_name := COALESCE(
440
+ NULLIF(TRIM(COALESCE(NEW.raw_user_meta_data ->> 'first_name', '') || ' ' || COALESCE(NEW.raw_user_meta_data ->> 'last_name', '')), ''),
441
+ NULLIF(SPLIT_PART(COALESCE(NEW.email, ''), '@', 1), ''),
442
+ 'Workspace'
443
+ ) || '''s Workspace';
444
+
445
+ INSERT INTO public.workspaces (name, owner_user_id)
446
+ VALUES (workspace_name, NEW.id)
447
+ RETURNING id INTO created_workspace_id;
448
+
449
+ INSERT INTO public.workspace_members (workspace_id, user_id, role, status)
450
+ VALUES (created_workspace_id, NEW.id, 'owner', 'active')
451
+ ON CONFLICT (workspace_id, user_id) DO UPDATE
452
+ SET
453
+ role = EXCLUDED.role,
454
+ status = EXCLUDED.status,
455
+ updated_at = NOW();
456
+
457
+ RETURN NEW;
458
+ END;
459
+ $$;