realtimex-crm 0.3.2 → 0.4.1

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.
package/dist/setup.sql ADDED
@@ -0,0 +1,976 @@
1
+ create table "public"."companies" (
2
+ "id" bigint generated by default as identity not null,
3
+ "created_at" timestamp with time zone not null default now(),
4
+ "name" text not null,
5
+ "sector" text,
6
+ "size" smallint,
7
+ "linkedin_url" text,
8
+ "website" text,
9
+ "phone_number" text,
10
+ "address" text,
11
+ "zipcode" text,
12
+ "city" text,
13
+ "stateAbbr" text,
14
+ "sales_id" bigint,
15
+ "context_links" json,
16
+ "country" text,
17
+ "description" text,
18
+ "revenue" text,
19
+ "tax_identifier" text,
20
+ "logo" jsonb
21
+ );
22
+
23
+
24
+ alter table "public"."companies" enable row level security;
25
+
26
+ create table "public"."contactNotes" (
27
+ "id" bigint generated by default as identity not null,
28
+ "contact_id" bigint not null,
29
+ "text" text,
30
+ "date" timestamp with time zone default now(),
31
+ "sales_id" bigint,
32
+ "status" text,
33
+ "attachments" jsonb[]
34
+ );
35
+
36
+
37
+ alter table "public"."contactNotes" enable row level security;
38
+
39
+ create table "public"."contacts" (
40
+ "id" bigint generated by default as identity not null,
41
+ "first_name" text,
42
+ "last_name" text,
43
+ "gender" text,
44
+ "title" text,
45
+ "email" text,
46
+ "phone_1_number" text,
47
+ "phone_1_type" text,
48
+ "phone_2_number" text,
49
+ "phone_2_type" text,
50
+ "background" text,
51
+ "acquisition" text,
52
+ "avatar" jsonb,
53
+ "first_seen" timestamp with time zone,
54
+ "last_seen" timestamp with time zone,
55
+ "has_newsletter" boolean,
56
+ "status" text,
57
+ "tags" bigint[],
58
+ "company_id" bigint,
59
+ "sales_id" bigint,
60
+ "linkedin_url" text
61
+ );
62
+
63
+
64
+ alter table "public"."contacts" enable row level security;
65
+
66
+ create table "public"."dealNotes" (
67
+ "id" bigint generated by default as identity not null,
68
+ "deal_id" bigint not null,
69
+ "type" text,
70
+ "text" text,
71
+ "date" timestamp with time zone default now(),
72
+ "sales_id" bigint,
73
+ "attachments" jsonb[]
74
+ );
75
+
76
+
77
+ alter table "public"."dealNotes" enable row level security;
78
+
79
+ create table "public"."deals" (
80
+ "id" bigint generated by default as identity not null,
81
+ "name" text not null,
82
+ "company_id" bigint,
83
+ "contact_ids" bigint[],
84
+ "category" text,
85
+ "stage" text not null,
86
+ "description" text,
87
+ "amount" bigint,
88
+ "created_at" timestamp with time zone not null default now(),
89
+ "updated_at" timestamp with time zone not null default now(),
90
+ "archived_at" timestamp with time zone default null,
91
+ "expected_closing_date" timestamp with time zone default null,
92
+ "sales_id" bigint,
93
+ "index" smallint
94
+ );
95
+
96
+
97
+ alter table "public"."deals" enable row level security;
98
+
99
+ create table "public"."sales" (
100
+ "id" bigint generated by default as identity not null,
101
+ "first_name" text not null,
102
+ "last_name" text not null,
103
+ "email" text not null,
104
+ "administrator" boolean not null,
105
+ "user_id" uuid not null,
106
+ "avatar" jsonb,
107
+ "disabled" boolean not null default FALSE
108
+ );
109
+
110
+
111
+ alter table "public"."sales" enable row level security;
112
+
113
+ create table "public"."tags" (
114
+ "id" bigint generated by default as identity not null,
115
+ "name" text not null,
116
+ "color" text not null
117
+ );
118
+
119
+
120
+ alter table "public"."tags" enable row level security;
121
+
122
+ create table "public"."tasks" (
123
+ "id" bigint generated by default as identity not null,
124
+ "contact_id" bigint not null,
125
+ "type" text,
126
+ "text" text,
127
+ "due_date" timestamp with time zone not null,
128
+ "done_date" timestamp with time zone
129
+ );
130
+
131
+
132
+ alter table "public"."tasks" enable row level security;
133
+
134
+ CREATE UNIQUE INDEX companies_pkey ON public.companies USING btree (id);
135
+
136
+ CREATE UNIQUE INDEX "contactNotes_pkey" ON public."contactNotes" USING btree (id);
137
+
138
+ CREATE UNIQUE INDEX contacts_pkey ON public.contacts USING btree (id);
139
+
140
+ CREATE UNIQUE INDEX "dealNotes_pkey" ON public."dealNotes" USING btree (id);
141
+
142
+ CREATE UNIQUE INDEX deals_pkey ON public.deals USING btree (id);
143
+
144
+ CREATE UNIQUE INDEX sales_pkey ON public.sales USING btree (id);
145
+
146
+ CREATE UNIQUE INDEX tags_pkey ON public.tags USING btree (id);
147
+
148
+ CREATE UNIQUE INDEX tasks_pkey ON public.tasks USING btree (id);
149
+
150
+ alter table "public"."companies" add constraint "companies_pkey" PRIMARY KEY using index "companies_pkey";
151
+
152
+ alter table "public"."contactNotes" add constraint "contactNotes_pkey" PRIMARY KEY using index "contactNotes_pkey";
153
+
154
+ alter table "public"."contacts" add constraint "contacts_pkey" PRIMARY KEY using index "contacts_pkey";
155
+
156
+ alter table "public"."dealNotes" add constraint "dealNotes_pkey" PRIMARY KEY using index "dealNotes_pkey";
157
+
158
+ alter table "public"."deals" add constraint "deals_pkey" PRIMARY KEY using index "deals_pkey";
159
+
160
+ alter table "public"."sales" add constraint "sales_pkey" PRIMARY KEY using index "sales_pkey";
161
+
162
+ alter table "public"."tags" add constraint "tags_pkey" PRIMARY KEY using index "tags_pkey";
163
+
164
+ alter table "public"."tasks" add constraint "tasks_pkey" PRIMARY KEY using index "tasks_pkey";
165
+
166
+ alter table "public"."companies" add constraint "companies_sales_id_fkey" FOREIGN KEY (sales_id) REFERENCES sales(id) not valid;
167
+
168
+ alter table "public"."companies" validate constraint "companies_sales_id_fkey";
169
+
170
+ alter table "public"."contactNotes" add constraint "contactNotes_contact_id_fkey" FOREIGN KEY (contact_id) REFERENCES contacts(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
171
+
172
+ alter table "public"."contactNotes" validate constraint "contactNotes_contact_id_fkey";
173
+
174
+ alter table "public"."contactNotes" add constraint "contactNotes_sales_id_fkey" FOREIGN KEY (sales_id) REFERENCES sales(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
175
+
176
+ alter table "public"."contactNotes" validate constraint "contactNotes_sales_id_fkey";
177
+
178
+ alter table "public"."contacts" add constraint "contacts_company_id_fkey" FOREIGN KEY (company_id) REFERENCES companies(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
179
+
180
+ alter table "public"."contacts" validate constraint "contacts_company_id_fkey";
181
+
182
+ alter table "public"."contacts" add constraint "contacts_sales_id_fkey" FOREIGN KEY (sales_id) REFERENCES sales(id) not valid;
183
+
184
+ alter table "public"."contacts" validate constraint "contacts_sales_id_fkey";
185
+
186
+ alter table "public"."dealNotes" add constraint "dealNotes_deal_id_fkey" FOREIGN KEY (deal_id) REFERENCES deals(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
187
+
188
+ alter table "public"."dealNotes" validate constraint "dealNotes_deal_id_fkey";
189
+
190
+ alter table "public"."dealNotes" add constraint "dealNotes_sales_id_fkey" FOREIGN KEY (sales_id) REFERENCES sales(id) not valid;
191
+
192
+ alter table "public"."dealNotes" validate constraint "dealNotes_sales_id_fkey";
193
+
194
+ alter table "public"."deals" add constraint "deals_company_id_fkey" FOREIGN KEY (company_id) REFERENCES companies(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
195
+
196
+ alter table "public"."deals" validate constraint "deals_company_id_fkey";
197
+
198
+ alter table "public"."deals" add constraint "deals_sales_id_fkey" FOREIGN KEY (sales_id) REFERENCES sales(id) not valid;
199
+
200
+ alter table "public"."deals" validate constraint "deals_sales_id_fkey";
201
+
202
+ alter table "public"."sales" add constraint "sales_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) not valid;
203
+
204
+ alter table "public"."sales" validate constraint "sales_user_id_fkey";
205
+
206
+ alter table "public"."tasks" add constraint "tasks_contact_id_fkey" FOREIGN KEY (contact_id) REFERENCES contacts(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
207
+
208
+ alter table "public"."tasks" validate constraint "tasks_contact_id_fkey";
209
+
210
+ set check_function_bodies = off;
211
+
212
+ grant delete on table "public"."companies" to "authenticated";
213
+ grant insert on table "public"."companies" to "authenticated";
214
+ grant select on table "public"."companies" to "authenticated";
215
+ grant update on table "public"."companies" to "authenticated";
216
+
217
+ grant delete on table "public"."companies" to "service_role";
218
+ grant insert on table "public"."companies" to "service_role";
219
+ grant references on table "public"."companies" to "service_role";
220
+ grant select on table "public"."companies" to "service_role";
221
+ grant trigger on table "public"."companies" to "service_role";
222
+ grant truncate on table "public"."companies" to "service_role";
223
+ grant update on table "public"."companies" to "service_role";
224
+
225
+ grant delete on table "public"."contactNotes" to "authenticated";
226
+ grant insert on table "public"."contactNotes" to "authenticated";
227
+ grant select on table "public"."contactNotes" to "authenticated";
228
+ grant update on table "public"."contactNotes" to "authenticated";
229
+
230
+ grant delete on table "public"."contactNotes" to "service_role";
231
+ grant insert on table "public"."contactNotes" to "service_role";
232
+ grant references on table "public"."contactNotes" to "service_role";
233
+ grant select on table "public"."contactNotes" to "service_role";
234
+ grant trigger on table "public"."contactNotes" to "service_role";
235
+ grant truncate on table "public"."contactNotes" to "service_role";
236
+ grant update on table "public"."contactNotes" to "service_role";
237
+
238
+ grant delete on table "public"."contacts" to "authenticated";
239
+ grant insert on table "public"."contacts" to "authenticated";
240
+ grant select on table "public"."contacts" to "authenticated";
241
+ grant update on table "public"."contacts" to "authenticated";
242
+
243
+ grant delete on table "public"."contacts" to "service_role";
244
+ grant insert on table "public"."contacts" to "service_role";
245
+ grant references on table "public"."contacts" to "service_role";
246
+ grant select on table "public"."contacts" to "service_role";
247
+ grant trigger on table "public"."contacts" to "service_role";
248
+ grant truncate on table "public"."contacts" to "service_role";
249
+ grant update on table "public"."contacts" to "service_role";
250
+
251
+ grant delete on table "public"."dealNotes" to "authenticated";
252
+ grant insert on table "public"."dealNotes" to "authenticated";
253
+ grant select on table "public"."dealNotes" to "authenticated";
254
+ grant update on table "public"."dealNotes" to "authenticated";
255
+
256
+ grant delete on table "public"."dealNotes" to "service_role";
257
+ grant insert on table "public"."dealNotes" to "service_role";
258
+ grant references on table "public"."dealNotes" to "service_role";
259
+ grant select on table "public"."dealNotes" to "service_role";
260
+ grant trigger on table "public"."dealNotes" to "service_role";
261
+ grant truncate on table "public"."dealNotes" to "service_role";
262
+ grant update on table "public"."dealNotes" to "service_role";
263
+
264
+
265
+ grant delete on table "public"."deals" to "authenticated";
266
+ grant insert on table "public"."deals" to "authenticated";
267
+ grant select on table "public"."deals" to "authenticated";
268
+ grant update on table "public"."deals" to "authenticated";
269
+
270
+ grant delete on table "public"."deals" to "service_role";
271
+ grant insert on table "public"."deals" to "service_role";
272
+ grant references on table "public"."deals" to "service_role";
273
+ grant select on table "public"."deals" to "service_role";
274
+ grant trigger on table "public"."deals" to "service_role";
275
+ grant truncate on table "public"."deals" to "service_role";
276
+ grant update on table "public"."deals" to "service_role";
277
+
278
+ grant delete on table "public"."sales" to "authenticated";
279
+ grant insert on table "public"."sales" to "authenticated";
280
+ grant select on table "public"."sales" to "authenticated";
281
+ grant update on table "public"."sales" to "authenticated";
282
+
283
+ grant delete on table "public"."sales" to "service_role";
284
+ grant insert on table "public"."sales" to "service_role";
285
+ grant references on table "public"."sales" to "service_role";
286
+ grant select on table "public"."sales" to "service_role";
287
+ grant trigger on table "public"."sales" to "service_role";
288
+ grant truncate on table "public"."sales" to "service_role";
289
+ grant update on table "public"."sales" to "service_role";
290
+
291
+ grant delete on table "public"."tags" to "authenticated";
292
+ grant insert on table "public"."tags" to "authenticated";
293
+ grant select on table "public"."tags" to "authenticated";
294
+ grant update on table "public"."tags" to "authenticated";
295
+
296
+ grant delete on table "public"."tags" to "service_role";
297
+ grant insert on table "public"."tags" to "service_role";
298
+ grant references on table "public"."tags" to "service_role";
299
+ grant select on table "public"."tags" to "service_role";
300
+ grant trigger on table "public"."tags" to "service_role";
301
+ grant truncate on table "public"."tags" to "service_role";
302
+ grant update on table "public"."tags" to "service_role";
303
+
304
+ grant delete on table "public"."tasks" to "authenticated";
305
+ grant insert on table "public"."tasks" to "authenticated";
306
+ grant select on table "public"."tasks" to "authenticated";
307
+ grant update on table "public"."tasks" to "authenticated";
308
+
309
+ grant delete on table "public"."tasks" to "service_role";
310
+ grant insert on table "public"."tasks" to "service_role";
311
+ grant references on table "public"."tasks" to "service_role";
312
+ grant select on table "public"."tasks" to "service_role";
313
+ grant trigger on table "public"."tasks" to "service_role";
314
+ grant truncate on table "public"."tasks" to "service_role";
315
+ grant update on table "public"."tasks" to "service_role";
316
+
317
+ create policy "Enable insert for authenticated users only"
318
+ on "public"."companies"
319
+ as permissive
320
+ for insert
321
+ to authenticated
322
+ with check (true);
323
+
324
+
325
+ create policy "Enable read access for authenticated users"
326
+ on "public"."companies"
327
+ as permissive
328
+ for select
329
+ to authenticated
330
+ using (true);
331
+
332
+
333
+ create policy "Enable update for authenticated users only"
334
+ on "public"."companies"
335
+ as permissive
336
+ for update
337
+ to authenticated
338
+ using (true)
339
+ with check (true);
340
+
341
+
342
+ create policy "Enable insert for authenticated users only"
343
+ on "public"."contactNotes"
344
+ as permissive
345
+ for insert
346
+ to authenticated
347
+ with check (true);
348
+
349
+
350
+ create policy "Enable read access for authenticated users"
351
+ on "public"."contactNotes"
352
+ as permissive
353
+ for select
354
+ to authenticated
355
+ using (true);
356
+
357
+
358
+ create policy "Enable insert for authenticated users only"
359
+ on "public"."contacts"
360
+ as permissive
361
+ for insert
362
+ to authenticated
363
+ with check (true);
364
+
365
+
366
+ create policy "Enable read access for authenticated users"
367
+ on "public"."contacts"
368
+ as permissive
369
+ for select
370
+ to authenticated
371
+ using (true);
372
+
373
+
374
+ create policy "Enable update for authenticated users only"
375
+ on "public"."contacts"
376
+ as permissive
377
+ for update
378
+ to authenticated
379
+ using (true)
380
+ with check (true);
381
+
382
+
383
+ create policy "Enable insert for authenticated users only"
384
+ on "public"."dealNotes"
385
+ as permissive
386
+ for insert
387
+ to authenticated
388
+ with check (true);
389
+
390
+
391
+ create policy "Enable read access for authenticated users"
392
+ on "public"."dealNotes"
393
+ as permissive
394
+ for select
395
+ to authenticated
396
+ using (true);
397
+
398
+
399
+ create policy "Enable insert for authenticated users only"
400
+ on "public"."deals"
401
+ as permissive
402
+ for insert
403
+ to authenticated
404
+ with check (true);
405
+
406
+
407
+ create policy "Enable read access for authenticated users"
408
+ on "public"."deals"
409
+ as permissive
410
+ for select
411
+ to authenticated
412
+ using (true);
413
+
414
+
415
+ create policy "Enable update for authenticated users only"
416
+ on "public"."deals"
417
+ as permissive
418
+ for update
419
+ to authenticated
420
+ using (true)
421
+ with check (true);
422
+
423
+
424
+ create policy "Enable insert for authenticated users only"
425
+ on "public"."sales"
426
+ as permissive
427
+ for insert
428
+ to authenticated
429
+ with check (true);
430
+
431
+
432
+ create policy "Enable update for authenticated users only"
433
+ on "public"."sales"
434
+ as permissive
435
+ for update
436
+ to authenticated
437
+ using (true)
438
+ with check (true);
439
+
440
+
441
+ create policy "Enable read access for authenticated users"
442
+ on "public"."sales"
443
+ as permissive
444
+ for select
445
+ to authenticated
446
+ using (true);
447
+
448
+
449
+ create policy "Enable insert for authenticated users only"
450
+ on "public"."tags"
451
+ as permissive
452
+ for insert
453
+ to authenticated
454
+ with check (true);
455
+
456
+
457
+ create policy "Enable read access for authenticated users"
458
+ on "public"."tags"
459
+ as permissive
460
+ for select
461
+ to authenticated
462
+ using (true);
463
+
464
+
465
+ create policy "Enable insert for authenticated users only"
466
+ on "public"."tasks"
467
+ as permissive
468
+ for insert
469
+ to authenticated
470
+ with check (true);
471
+
472
+
473
+ create policy "Enable read access for authenticated users"
474
+ on "public"."tasks"
475
+ as permissive
476
+ for select
477
+ to authenticated
478
+ using (true);
479
+
480
+
481
+ create policy "Company Delete Policy"
482
+ on "public"."companies"
483
+ as permissive
484
+ for delete
485
+ to authenticated
486
+ using (true);
487
+
488
+
489
+ create policy "Contact Notes Delete Policy"
490
+ on "public"."contactNotes"
491
+ as permissive
492
+ for delete
493
+ to authenticated
494
+ using (true);
495
+
496
+
497
+ create policy "Contact Notes Update policy"
498
+ on "public"."contactNotes"
499
+ as permissive
500
+ for update
501
+ to authenticated
502
+ using (true);
503
+
504
+
505
+ create policy "Contact Delete Policy"
506
+ on "public"."contacts"
507
+ as permissive
508
+ for delete
509
+ to authenticated
510
+ using (true);
511
+
512
+
513
+ create policy "Deal Notes Delete Policy"
514
+ on "public"."dealNotes"
515
+ as permissive
516
+ for delete
517
+ to authenticated
518
+ using (true);
519
+
520
+
521
+ create policy "Deal Notes Update Policy"
522
+ on "public"."dealNotes"
523
+ as permissive
524
+ for update
525
+ to authenticated
526
+ using (true);
527
+
528
+
529
+ create policy "Deals Delete Policy"
530
+ on "public"."deals"
531
+ as permissive
532
+ for delete
533
+ to authenticated
534
+ using (true);
535
+
536
+
537
+ create policy "Task Delete Policy"
538
+ on "public"."tasks"
539
+ as permissive
540
+ for delete
541
+ to authenticated
542
+ using (true);
543
+
544
+
545
+ create policy "Task Update Policy"
546
+ on "public"."tasks"
547
+ as permissive
548
+ for update
549
+ to authenticated
550
+ using (true);
551
+
552
+
553
+ -- Use Postgres to create a bucket.
554
+
555
+ insert into storage.buckets
556
+ (id, name, public)
557
+ values
558
+ ('attachments', 'attachments', true);
559
+
560
+ CREATE POLICY "Attachments 1mt4rzk_0" ON storage.objects FOR SELECT TO authenticated USING (bucket_id = 'attachments');
561
+ CREATE POLICY "Attachments 1mt4rzk_1" ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'attachments');
562
+ CREATE POLICY "Attachments 1mt4rzk_3" ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'attachments');
563
+
564
+ -- Use Postgres to create views for companies.
565
+
566
+ create view "public"."companies_summary"
567
+ with (security_invoker=on)
568
+ as
569
+ select
570
+ c.*,
571
+ count(distinct d.id) as nb_deals,
572
+ count(distinct co.id) as nb_contacts
573
+ from
574
+ "public"."companies" c
575
+ left join
576
+ "public"."deals" d on c.id = d.company_id
577
+ left join
578
+ "public"."contacts" co on c.id = co.company_id
579
+ group by
580
+ c.id;
581
+
582
+ -- Use Postgres to create views for contacts.
583
+
584
+ create view "public"."contacts_summary"
585
+ with (security_invoker=on)
586
+ as
587
+ select
588
+ co.*,
589
+ c.name as company_name,
590
+ count(distinct t.id) as nb_tasks
591
+ from
592
+ "public"."contacts" co
593
+ left join
594
+ "public"."tasks" t on co.id = t.contact_id
595
+ left join
596
+ "public"."companies" c on co.company_id = c.id
597
+ group by
598
+ co.id, c.name;
599
+
600
+
601
+ create function public.handle_new_user()
602
+ returns trigger
603
+ language plpgsql
604
+ security definer set search_path = ''
605
+ as $$
606
+ declare
607
+ sales_count int;
608
+ begin
609
+ select count(id) into sales_count
610
+ from public.sales;
611
+
612
+ insert into public.sales (first_name, last_name, email, user_id, administrator)
613
+ values (
614
+ new.raw_user_meta_data ->> 'first_name',
615
+ new.raw_user_meta_data ->> 'last_name',
616
+ new.email,
617
+ new.id,
618
+ case when sales_count > 0 then FALSE else TRUE end
619
+ );
620
+ return new;
621
+ end;
622
+ $$;
623
+
624
+ create function public.handle_update_user()
625
+ returns trigger
626
+ language plpgsql
627
+ security definer set search_path = ''
628
+ as $$
629
+ begin
630
+ update public.sales
631
+ set
632
+ first_name = new.raw_user_meta_data ->> 'first_name',
633
+ last_name = new.raw_user_meta_data ->> 'last_name',
634
+ email = new.email
635
+ where user_id = new.id;
636
+
637
+ return new;
638
+ end;
639
+ $$;
640
+
641
+
642
+ create unique index "uq__sales__user_id" on public.sales (user_id);
643
+
644
+ create trigger on_auth_user_created
645
+ after insert on auth.users
646
+ for each row execute procedure public.handle_new_user();
647
+
648
+ create trigger on_auth_user_updated
649
+ after update on auth.users
650
+ for each row execute procedure public.handle_update_user();
651
+
652
+ create view init_state
653
+ with (security_invoker=off)
654
+ as
655
+ select count(id) as is_initialized
656
+ from public.sales
657
+ limit 1;
658
+ alter table "public"."tasks" add column "sales_id" bigint;-- this will drop the `contacts_summary` view as well
659
+ ALTER TABLE "public"."contacts"
660
+ DROP COLUMN acquisition CASCADE;
661
+
662
+ -- We need to recreate the view with the new schema
663
+
664
+ create view "public"."contacts_summary"
665
+ as
666
+ select
667
+ co.*,
668
+ c.name as company_name,
669
+ count(distinct t.id) as nb_tasks
670
+ from
671
+ "public"."contacts" co
672
+ left join
673
+ "public"."tasks" t on co.id = t.contact_id
674
+ left join
675
+ "public"."companies" c on co.company_id = c.id
676
+ group by
677
+ co.id, c.name;
678
+ create or replace view init_state
679
+ with (security_invoker=off)
680
+ as
681
+ select count(id) as is_initialized
682
+ from (
683
+ select id
684
+ from public.sales
685
+ limit 1
686
+ ) as sub;
687
+ create policy "Enable delete for authenticated users only"
688
+ on "public"."tags"
689
+ as permissive
690
+ for delete
691
+ to authenticated
692
+ using (true);
693
+
694
+
695
+ create policy "Enable update for authenticated users only"
696
+ on "public"."tags"
697
+ as permissive
698
+ for update
699
+ to authenticated
700
+ using (true);
701
+
702
+
703
+
704
+
705
+ create schema if not exists "private";
706
+
707
+ set check_function_bodies = off;
708
+
709
+ drop policy "Enable insert for authenticated users only" on "public"."sales";
710
+
711
+ drop policy "Enable update for authenticated users only" on "public"."sales";
712
+ alter table contacts add column email_jsonb jsonb;
713
+
714
+ update contacts set email_jsonb = ('[{"email": "' || email || '", "type": "Other"}]')::jsonb;
715
+
716
+ drop view contacts_summary;
717
+
718
+ alter table contacts drop column email;
719
+
720
+ create view contacts_summary
721
+ as
722
+ select
723
+ co.id,
724
+ co.first_name,
725
+ co.last_name,
726
+ co.gender,
727
+ co.title,
728
+ co.email_jsonb,
729
+ jsonb_path_query_array(co.email_jsonb, '$[*].email')::text as email_fts,
730
+ co.phone_1_number,
731
+ co.phone_1_type,
732
+ co.phone_2_number,
733
+ co.phone_2_type,
734
+ co.background,
735
+ co.avatar,
736
+ co.first_seen,
737
+ co.last_seen,
738
+ co.has_newsletter,
739
+ co.status,
740
+ co.tags,
741
+ co.company_id,
742
+ co.sales_id,
743
+ co.linkedin_url,
744
+ c.name as company_name,
745
+ count(distinct t.id) as nb_tasks
746
+ from
747
+ contacts co
748
+ left join
749
+ tasks t on co.id = t.contact_id
750
+ left join
751
+ companies c on co.company_id = c.id
752
+ group by
753
+ co.id, c.name;
754
+
755
+ alter table contacts add column phone_jsonb jsonb;
756
+
757
+ update contacts set phone_jsonb =
758
+ concat(
759
+ '[',
760
+ case when phone_1_number is not null then
761
+ concat(
762
+ '{"number":"',
763
+ phone_1_number,
764
+ '","type":"',
765
+ phone_1_type,
766
+ '"}'
767
+ )
768
+ else null end,
769
+ case when phone_2_number is not null then
770
+ concat(
771
+ ',',
772
+ '{"number":"',
773
+ phone_2_number,
774
+ '","type":"',
775
+ phone_2_type,
776
+ '"}'
777
+ )
778
+ else null end,
779
+ ']'
780
+ )::jsonb;
781
+
782
+ drop view contacts_summary;
783
+
784
+ alter table contacts drop column phone_1_number;
785
+ alter table contacts drop column phone_1_type;
786
+ alter table contacts drop column phone_2_number;
787
+ alter table contacts drop column phone_2_type;
788
+
789
+ create view contacts_summary
790
+ as
791
+ select
792
+ co.id,
793
+ co.first_name,
794
+ co.last_name,
795
+ co.gender,
796
+ co.title,
797
+ co.email_jsonb,
798
+ jsonb_path_query_array(co.email_jsonb, '$[*].email')::text as email_fts,
799
+ co.phone_jsonb,
800
+ jsonb_path_query_array(co.phone_jsonb, '$[*].number')::text as phone_fts,
801
+ co.background,
802
+ co.avatar,
803
+ co.first_seen,
804
+ co.last_seen,
805
+ co.has_newsletter,
806
+ co.status,
807
+ co.tags,
808
+ co.company_id,
809
+ co.sales_id,
810
+ co.linkedin_url,
811
+ c.name as company_name,
812
+ count(distinct t.id) as nb_tasks
813
+ from
814
+ contacts co
815
+ left join
816
+ tasks t on co.id = t.contact_id
817
+ left join
818
+ companies c on co.company_id = c.id
819
+ group by
820
+ co.id, c.name;
821
+
822
+ -- Create function to merge contacts server-side with transaction support
823
+ CREATE OR REPLACE FUNCTION merge_contacts(loser_id bigint, winner_id bigint)
824
+ RETURNS bigint
825
+ LANGUAGE plpgsql
826
+ SECURITY INVOKER
827
+ AS $$
828
+ DECLARE
829
+ winner_contact contacts%ROWTYPE;
830
+ loser_contact contacts%ROWTYPE;
831
+ deal_record RECORD;
832
+ merged_emails jsonb;
833
+ merged_phones jsonb;
834
+ merged_tags bigint[];
835
+ winner_emails jsonb;
836
+ loser_emails jsonb;
837
+ winner_phones jsonb;
838
+ loser_phones jsonb;
839
+ email_map jsonb;
840
+ phone_map jsonb;
841
+ BEGIN
842
+ -- Fetch both contacts
843
+ SELECT * INTO winner_contact FROM contacts WHERE id = winner_id;
844
+ SELECT * INTO loser_contact FROM contacts WHERE id = loser_id;
845
+
846
+ IF winner_contact IS NULL OR loser_contact IS NULL THEN
847
+ RAISE EXCEPTION 'Contact not found';
848
+ END IF;
849
+
850
+ -- 1. Reassign tasks from loser to winner
851
+ UPDATE tasks SET contact_id = winner_id WHERE contact_id = loser_id;
852
+
853
+ -- 2. Reassign contact notes from loser to winner
854
+ UPDATE "contactNotes" SET contact_id = winner_id WHERE contact_id = loser_id;
855
+
856
+ -- 3. Update deals - replace loser with winner in contact_ids array
857
+ FOR deal_record IN
858
+ SELECT id, contact_ids
859
+ FROM deals
860
+ WHERE contact_ids @> ARRAY[loser_id]
861
+ LOOP
862
+ UPDATE deals
863
+ SET contact_ids = (
864
+ SELECT ARRAY(
865
+ SELECT DISTINCT unnest(
866
+ array_remove(deal_record.contact_ids, loser_id) || ARRAY[winner_id]
867
+ )
868
+ )
869
+ )
870
+ WHERE id = deal_record.id;
871
+ END LOOP;
872
+
873
+ -- 4. Merge contact data
874
+
875
+ -- Get email arrays
876
+ winner_emails := COALESCE(winner_contact.email_jsonb, '[]'::jsonb);
877
+ loser_emails := COALESCE(loser_contact.email_jsonb, '[]'::jsonb);
878
+
879
+ -- Merge emails with deduplication by email address
880
+ -- Build a map of email -> email object, then convert back to array
881
+ email_map := '{}'::jsonb;
882
+
883
+ -- Add winner emails to map
884
+ IF jsonb_array_length(winner_emails) > 0 THEN
885
+ FOR i IN 0..jsonb_array_length(winner_emails)-1 LOOP
886
+ email_map := email_map || jsonb_build_object(
887
+ winner_emails->i->>'email',
888
+ winner_emails->i
889
+ );
890
+ END LOOP;
891
+ END IF;
892
+
893
+ -- Add loser emails to map (won't overwrite existing keys)
894
+ IF jsonb_array_length(loser_emails) > 0 THEN
895
+ FOR i IN 0..jsonb_array_length(loser_emails)-1 LOOP
896
+ IF NOT email_map ? (loser_emails->i->>'email') THEN
897
+ email_map := email_map || jsonb_build_object(
898
+ loser_emails->i->>'email',
899
+ loser_emails->i
900
+ );
901
+ END IF;
902
+ END LOOP;
903
+ END IF;
904
+
905
+ -- Convert map back to array
906
+ merged_emails := (SELECT jsonb_agg(value) FROM jsonb_each(email_map));
907
+ merged_emails := COALESCE(merged_emails, '[]'::jsonb);
908
+
909
+ -- Get phone arrays
910
+ winner_phones := COALESCE(winner_contact.phone_jsonb, '[]'::jsonb);
911
+ loser_phones := COALESCE(loser_contact.phone_jsonb, '[]'::jsonb);
912
+
913
+ -- Merge phones with deduplication by number
914
+ phone_map := '{}'::jsonb;
915
+
916
+ -- Add winner phones to map
917
+ IF jsonb_array_length(winner_phones) > 0 THEN
918
+ FOR i IN 0..jsonb_array_length(winner_phones)-1 LOOP
919
+ phone_map := phone_map || jsonb_build_object(
920
+ winner_phones->i->>'number',
921
+ winner_phones->i
922
+ );
923
+ END LOOP;
924
+ END IF;
925
+
926
+ -- Add loser phones to map (won't overwrite existing keys)
927
+ IF jsonb_array_length(loser_phones) > 0 THEN
928
+ FOR i IN 0..jsonb_array_length(loser_phones)-1 LOOP
929
+ IF NOT phone_map ? (loser_phones->i->>'number') THEN
930
+ phone_map := phone_map || jsonb_build_object(
931
+ loser_phones->i->>'number',
932
+ loser_phones->i
933
+ );
934
+ END IF;
935
+ END LOOP;
936
+ END IF;
937
+
938
+ -- Convert map back to array
939
+ merged_phones := (SELECT jsonb_agg(value) FROM jsonb_each(phone_map));
940
+ merged_phones := COALESCE(merged_phones, '[]'::jsonb);
941
+
942
+ -- Merge tags (remove duplicates)
943
+ merged_tags := ARRAY(
944
+ SELECT DISTINCT unnest(
945
+ COALESCE(winner_contact.tags, ARRAY[]::bigint[]) ||
946
+ COALESCE(loser_contact.tags, ARRAY[]::bigint[])
947
+ )
948
+ );
949
+
950
+ -- 5. Update winner with merged data
951
+ UPDATE contacts SET
952
+ avatar = COALESCE(winner_contact.avatar, loser_contact.avatar),
953
+ gender = COALESCE(winner_contact.gender, loser_contact.gender),
954
+ first_name = COALESCE(winner_contact.first_name, loser_contact.first_name),
955
+ last_name = COALESCE(winner_contact.last_name, loser_contact.last_name),
956
+ title = COALESCE(winner_contact.title, loser_contact.title),
957
+ company_id = COALESCE(winner_contact.company_id, loser_contact.company_id),
958
+ email_jsonb = merged_emails,
959
+ phone_jsonb = merged_phones,
960
+ linkedin_url = COALESCE(winner_contact.linkedin_url, loser_contact.linkedin_url),
961
+ background = COALESCE(winner_contact.background, loser_contact.background),
962
+ has_newsletter = COALESCE(winner_contact.has_newsletter, loser_contact.has_newsletter),
963
+ first_seen = LEAST(COALESCE(winner_contact.first_seen, loser_contact.first_seen), COALESCE(loser_contact.first_seen, winner_contact.first_seen)),
964
+ last_seen = GREATEST(COALESCE(winner_contact.last_seen, loser_contact.last_seen), COALESCE(loser_contact.last_seen, winner_contact.last_seen)),
965
+ sales_id = COALESCE(winner_contact.sales_id, loser_contact.sales_id),
966
+ tags = merged_tags
967
+ WHERE id = winner_id;
968
+
969
+ -- 6. Delete loser contact
970
+ DELETE FROM contacts WHERE id = loser_id;
971
+
972
+ RETURN winner_id;
973
+ END;
974
+ $$;
975
+ -- Drop the merge_contacts function (reverting to edge function approach)
976
+ DROP FUNCTION IF EXISTS merge_contacts(bigint, bigint);