-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema.sql
More file actions
809 lines (743 loc) · 24.1 KB
/
schema.sql
File metadata and controls
809 lines (743 loc) · 24.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
CREATE TABLE IF NOT EXISTS profiles (
id UUID PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
bio TEXT NOT NULL DEFAULT '',
avatar_tone TEXT NOT NULL DEFAULT 'ember',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS boards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
theme TEXT NOT NULL DEFAULT 'sunrise',
accent TEXT NOT NULL DEFAULT 'ember',
template_kind TEXT NOT NULL DEFAULT 'blank',
archived BOOLEAN NOT NULL DEFAULT false,
member_ids UUID[] NOT NULL DEFAULT ARRAY[]::UUID[],
editor_ids UUID[] NOT NULL DEFAULT ARRAY[]::UUID[],
admin_ids UUID[] NOT NULL DEFAULT ARRAY[]::UUID[],
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS board_members (
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (board_id, user_id)
);
CREATE TABLE IF NOT EXISTS board_invites (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
token UUID NOT NULL UNIQUE,
created_by UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('admin', 'member', 'viewer')),
max_uses INTEGER NOT NULL DEFAULT 10 CHECK (max_uses > 0),
uses_count INTEGER NOT NULL DEFAULT 0 CHECK (uses_count >= 0),
note TEXT NOT NULL DEFAULT '',
email TEXT,
email_sent BOOLEAN NOT NULL DEFAULT false,
expires_at TIMESTAMPTZ NOT NULL DEFAULT now() + interval '14 days',
disabled_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS labels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT 'ember',
position DOUBLE PRECISION NOT NULL DEFAULT 1024,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (board_id, name)
);
CREATE TABLE IF NOT EXISTS lists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
title TEXT NOT NULL,
color TEXT NOT NULL DEFAULT 'canvas',
position DOUBLE PRECISION NOT NULL DEFAULT 1024,
archived BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS cards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
list_id UUID NOT NULL REFERENCES lists(id) ON DELETE CASCADE,
created_by UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
cover_style TEXT NOT NULL DEFAULT 'sand',
priority TEXT NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
estimate_points INTEGER NOT NULL DEFAULT 0 CHECK (estimate_points >= 0),
due_at TIMESTAMPTZ,
position DOUBLE PRECISION NOT NULL DEFAULT 1024,
archived BOOLEAN NOT NULL DEFAULT false,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS card_labels (
card_id UUID NOT NULL REFERENCES cards(id) ON DELETE CASCADE,
label_id UUID NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
PRIMARY KEY (card_id, label_id)
);
CREATE TABLE IF NOT EXISTS card_members (
card_id UUID NOT NULL REFERENCES cards(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
PRIMARY KEY (card_id, user_id)
);
CREATE TABLE IF NOT EXISTS checklist_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
card_id UUID NOT NULL REFERENCES cards(id) ON DELETE CASCADE,
title TEXT NOT NULL,
position DOUBLE PRECISION NOT NULL DEFAULT 1024,
done BOOLEAN NOT NULL DEFAULT false,
done_by UUID REFERENCES profiles(id) ON DELETE SET NULL,
done_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
card_id UUID NOT NULL REFERENCES cards(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
body TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS card_links (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
card_id UUID NOT NULL REFERENCES cards(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
url TEXT NOT NULL,
title TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS board_activity (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
board_id UUID NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
card_id UUID REFERENCES cards(id) ON DELETE SET NULL,
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
action TEXT NOT NULL,
summary TEXT NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS app_settings (
id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),
public_signup BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO app_settings (id) VALUES (1) ON CONFLICT DO NOTHING;
CREATE INDEX IF NOT EXISTS idx_boards_owner_id ON boards(owner_id);
CREATE INDEX IF NOT EXISTS idx_boards_updated_at ON boards(updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_boards_member_ids_gin ON boards USING GIN(member_ids);
CREATE INDEX IF NOT EXISTS idx_boards_editor_ids_gin ON boards USING GIN(editor_ids);
CREATE INDEX IF NOT EXISTS idx_boards_admin_ids_gin ON boards USING GIN(admin_ids);
CREATE INDEX IF NOT EXISTS idx_board_members_user_id ON board_members(user_id);
CREATE INDEX IF NOT EXISTS idx_board_invites_board_id ON board_invites(board_id);
CREATE INDEX IF NOT EXISTS idx_board_invites_token ON board_invites(token);
CREATE INDEX IF NOT EXISTS idx_labels_board_id ON labels(board_id, position);
CREATE INDEX IF NOT EXISTS idx_lists_board_id ON lists(board_id, position);
CREATE INDEX IF NOT EXISTS idx_cards_board_list_pos ON cards(board_id, list_id, position);
CREATE INDEX IF NOT EXISTS idx_cards_due_at ON cards(board_id, due_at);
CREATE INDEX IF NOT EXISTS idx_card_members_user_id ON card_members(user_id);
CREATE INDEX IF NOT EXISTS idx_checklist_items_card_id ON checklist_items(card_id, position);
CREATE INDEX IF NOT EXISTS idx_comments_card_id ON comments(card_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_card_links_card_id ON card_links(card_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_board_activity_board_id ON board_activity(board_id, created_at DESC);
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE profiles FORCE ROW LEVEL SECURITY;
ALTER TABLE boards ENABLE ROW LEVEL SECURITY;
ALTER TABLE boards FORCE ROW LEVEL SECURITY;
ALTER TABLE board_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE board_members FORCE ROW LEVEL SECURITY;
ALTER TABLE board_invites ENABLE ROW LEVEL SECURITY;
ALTER TABLE board_invites FORCE ROW LEVEL SECURITY;
ALTER TABLE labels ENABLE ROW LEVEL SECURITY;
ALTER TABLE labels FORCE ROW LEVEL SECURITY;
ALTER TABLE lists ENABLE ROW LEVEL SECURITY;
ALTER TABLE lists FORCE ROW LEVEL SECURITY;
ALTER TABLE cards ENABLE ROW LEVEL SECURITY;
ALTER TABLE cards FORCE ROW LEVEL SECURITY;
ALTER TABLE card_labels ENABLE ROW LEVEL SECURITY;
ALTER TABLE card_labels FORCE ROW LEVEL SECURITY;
ALTER TABLE card_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE card_members FORCE ROW LEVEL SECURITY;
ALTER TABLE checklist_items ENABLE ROW LEVEL SECURITY;
ALTER TABLE checklist_items FORCE ROW LEVEL SECURITY;
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE comments FORCE ROW LEVEL SECURITY;
ALTER TABLE card_links ENABLE ROW LEVEL SECURITY;
ALTER TABLE card_links FORCE ROW LEVEL SECURITY;
ALTER TABLE board_activity ENABLE ROW LEVEL SECURITY;
ALTER TABLE board_activity FORCE ROW LEVEL SECURITY;
ALTER TABLE app_settings ENABLE ROW LEVEL SECURITY;
ALTER TABLE app_settings FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS app_settings_select_public ON app_settings;
CREATE POLICY app_settings_select_public ON app_settings
FOR SELECT
USING (true);
DROP POLICY IF EXISTS profiles_select_shared ON profiles;
DROP POLICY IF EXISTS profiles_insert_self ON profiles;
DROP POLICY IF EXISTS profiles_update_self ON profiles;
DROP POLICY IF EXISTS boards_select_members ON boards;
DROP POLICY IF EXISTS boards_insert_owner ON boards;
DROP POLICY IF EXISTS boards_update_admins ON boards;
DROP POLICY IF EXISTS boards_delete_owner ON boards;
DROP POLICY IF EXISTS board_members_select_members ON board_members;
DROP POLICY IF EXISTS board_members_insert_admins ON board_members;
DROP POLICY IF EXISTS board_members_update_admins ON board_members;
DROP POLICY IF EXISTS board_members_delete_admins ON board_members;
DROP POLICY IF EXISTS board_invites_select_admins ON board_invites;
DROP POLICY IF EXISTS board_invites_insert_admins ON board_invites;
DROP POLICY IF EXISTS board_invites_update_admins ON board_invites;
DROP POLICY IF EXISTS board_invites_delete_admins ON board_invites;
DROP POLICY IF EXISTS labels_select_members ON labels;
DROP POLICY IF EXISTS labels_insert_editors ON labels;
DROP POLICY IF EXISTS labels_update_editors ON labels;
DROP POLICY IF EXISTS labels_delete_editors ON labels;
DROP POLICY IF EXISTS lists_select_members ON lists;
DROP POLICY IF EXISTS lists_insert_editors ON lists;
DROP POLICY IF EXISTS lists_update_editors ON lists;
DROP POLICY IF EXISTS lists_delete_editors ON lists;
DROP POLICY IF EXISTS cards_select_members ON cards;
DROP POLICY IF EXISTS cards_insert_editors ON cards;
DROP POLICY IF EXISTS cards_update_editors ON cards;
DROP POLICY IF EXISTS cards_delete_editors ON cards;
DROP POLICY IF EXISTS card_labels_select_members ON card_labels;
DROP POLICY IF EXISTS card_labels_insert_editors ON card_labels;
DROP POLICY IF EXISTS card_labels_delete_editors ON card_labels;
DROP POLICY IF EXISTS card_members_select_members ON card_members;
DROP POLICY IF EXISTS card_members_insert_editors ON card_members;
DROP POLICY IF EXISTS card_members_delete_editors ON card_members;
DROP POLICY IF EXISTS checklist_select_members ON checklist_items;
DROP POLICY IF EXISTS checklist_insert_editors ON checklist_items;
DROP POLICY IF EXISTS checklist_update_editors ON checklist_items;
DROP POLICY IF EXISTS checklist_delete_editors ON checklist_items;
DROP POLICY IF EXISTS comments_select_members ON comments;
DROP POLICY IF EXISTS comments_insert_editors ON comments;
DROP POLICY IF EXISTS comments_update_author ON comments;
DROP POLICY IF EXISTS comments_delete_author_or_admin ON comments;
DROP POLICY IF EXISTS card_links_select_members ON card_links;
DROP POLICY IF EXISTS card_links_insert_editors ON card_links;
DROP POLICY IF EXISTS card_links_update_author ON card_links;
DROP POLICY IF EXISTS card_links_delete_author_or_admin ON card_links;
DROP POLICY IF EXISTS board_activity_select_members ON board_activity;
DROP POLICY IF EXISTS board_activity_insert_editors ON board_activity;
CREATE POLICY profiles_select_shared ON profiles
FOR SELECT
USING (
id = auth.uid()
OR EXISTS (
SELECT 1
FROM board_members bm
JOIN boards b ON b.id = bm.board_id
WHERE bm.user_id = profiles.id
AND auth.uid() = ANY(b.member_ids)
)
);
CREATE POLICY profiles_insert_self ON profiles
FOR INSERT
WITH CHECK (id = auth.uid());
CREATE POLICY profiles_update_self ON profiles
FOR UPDATE
USING (id = auth.uid())
WITH CHECK (id = auth.uid());
CREATE POLICY boards_select_members ON boards
FOR SELECT
USING (
owner_id = auth.uid()
OR auth.uid() = ANY(member_ids)
);
CREATE POLICY boards_insert_owner ON boards
FOR INSERT
WITH CHECK (
owner_id = auth.uid()
AND auth.uid() = ANY(member_ids)
AND auth.uid() = ANY(editor_ids)
AND auth.uid() = ANY(admin_ids)
);
CREATE POLICY boards_update_admins ON boards
FOR UPDATE
USING (
owner_id = auth.uid()
OR auth.uid() = ANY(admin_ids)
)
WITH CHECK (
owner_id = auth.uid()
OR auth.uid() = ANY(admin_ids)
);
CREATE POLICY boards_delete_owner ON boards
FOR DELETE
USING (owner_id = auth.uid());
CREATE POLICY board_members_select_members ON board_members
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_members.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY board_members_insert_admins ON board_members
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_members.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY board_members_update_admins ON board_members
FOR UPDATE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_members.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
)
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_members.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY board_members_delete_admins ON board_members
FOR DELETE
USING (
user_id = auth.uid()
OR EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_members.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY board_invites_select_admins ON board_invites
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_invites.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY board_invites_insert_admins ON board_invites
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_invites.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY board_invites_update_admins ON board_invites
FOR UPDATE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_invites.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
)
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_invites.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY board_invites_delete_admins ON board_invites
FOR DELETE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_invites.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY labels_select_members ON labels
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = labels.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY labels_insert_editors ON labels
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = labels.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY labels_update_editors ON labels
FOR UPDATE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = labels.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
)
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = labels.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY labels_delete_editors ON labels
FOR DELETE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = labels.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY lists_select_members ON lists
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = lists.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY lists_insert_editors ON lists
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = lists.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY lists_update_editors ON lists
FOR UPDATE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = lists.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
)
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = lists.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY lists_delete_editors ON lists
FOR DELETE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = lists.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY cards_select_members ON cards
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = cards.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY cards_insert_editors ON cards
FOR INSERT
WITH CHECK (
created_by = auth.uid()
AND EXISTS (
SELECT 1
FROM boards b
WHERE b.id = cards.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY cards_update_editors ON cards
FOR UPDATE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = cards.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
)
WITH CHECK (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = cards.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY cards_delete_editors ON cards
FOR DELETE
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = cards.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY card_labels_select_members ON card_labels
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = card_labels.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY card_labels_insert_editors ON card_labels
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = card_labels.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY card_labels_delete_editors ON card_labels
FOR DELETE
USING (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = card_labels.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY card_members_select_members ON card_members
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = card_members.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY card_members_insert_editors ON card_members
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = card_members.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY card_members_delete_editors ON card_members
FOR DELETE
USING (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = card_members.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY checklist_select_members ON checklist_items
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = checklist_items.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY checklist_insert_editors ON checklist_items
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = checklist_items.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY checklist_update_editors ON checklist_items
FOR UPDATE
USING (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = checklist_items.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
)
WITH CHECK (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = checklist_items.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY checklist_delete_editors ON checklist_items
FOR DELETE
USING (
EXISTS (
SELECT 1
FROM cards c
JOIN boards b ON b.id = c.board_id
WHERE c.id = checklist_items.card_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY comments_select_members ON comments
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = comments.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY comments_insert_editors ON comments
FOR INSERT
WITH CHECK (
user_id = auth.uid()
AND EXISTS (
SELECT 1
FROM boards b
WHERE b.id = comments.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY comments_update_author ON comments
FOR UPDATE
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
CREATE POLICY comments_delete_author_or_admin ON comments
FOR DELETE
USING (
user_id = auth.uid()
OR EXISTS (
SELECT 1
FROM boards b
WHERE b.id = comments.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY card_links_select_members ON card_links
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = card_links.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY card_links_insert_editors ON card_links
FOR INSERT
WITH CHECK (
user_id = auth.uid()
AND EXISTS (
SELECT 1
FROM boards b
WHERE b.id = card_links.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
CREATE POLICY card_links_update_author ON card_links
FOR UPDATE
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
CREATE POLICY card_links_delete_author_or_admin ON card_links
FOR DELETE
USING (
user_id = auth.uid()
OR EXISTS (
SELECT 1
FROM boards b
WHERE b.id = card_links.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.admin_ids))
)
);
CREATE POLICY board_activity_select_members ON board_activity
FOR SELECT
USING (
EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_activity.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.member_ids))
)
);
CREATE POLICY board_activity_insert_editors ON board_activity
FOR INSERT
WITH CHECK (
user_id = auth.uid()
AND EXISTS (
SELECT 1
FROM boards b
WHERE b.id = board_activity.board_id
AND (b.owner_id = auth.uid() OR auth.uid() = ANY(b.editor_ids))
)
);
-- Migrations for existing deployments (safe to re-run)
DO $$ BEGIN
ALTER TABLE board_invites ADD COLUMN email TEXT;
EXCEPTION WHEN duplicate_column THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE board_invites ADD COLUMN email_sent BOOLEAN NOT NULL DEFAULT false;
EXCEPTION WHEN duplicate_column THEN NULL;
END $$;