-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsupabase-setup.pgsql
More file actions
681 lines (588 loc) · 19.8 KB
/
supabase-setup.pgsql
File metadata and controls
681 lines (588 loc) · 19.8 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
-- Create tables and set up RLS policies for Supabase
-- Enable necessary extensions
create extension if not exists "uuid-ossp";
-- Create tables with proper constraints and defaults
-- Profiles table (linked to auth.users)
create table if not exists profiles (
id uuid references auth.users on delete cascade primary key,
display_name text,
photo_url text,
bio text,
updated_at timestamptz default now(),
created_at timestamptz default now()
);
-- Posts table
create table if not exists posts (
id uuid default uuid_generate_v4() primary key,
content text not null,
image_url text,
author_id uuid references profiles(id) on delete cascade not null,
created_at timestamptz default now()
);
-- Promoted posts table
create table if not exists promoted_posts (
id uuid default uuid_generate_v4() primary key,
content text not null,
image_url text,
author_id uuid references profiles(id) on delete cascade not null,
created_at timestamptz default now(),
affiliated_link text,
expires_at timestamptz not null
);
-- Events table
create table if not exists events (
id uuid default uuid_generate_v4() primary key,
title text not null,
description text not null,
event_timestamp timestamptz not null,
venue text not null,
stage text not null check (stage in ('upcoming', 'in-development', 'completed')),
author_id uuid references profiles(id) on delete cascade not null,
created_at timestamptz default now()
);
-- Event organizers junction table
create table if not exists event_organizers (
event_id uuid references events(id) on delete cascade,
user_id uuid references profiles(id) on delete cascade,
joined_at timestamptz default now(),
primary key (event_id, user_id)
);
-- Event participants junction table
create table if not exists event_participants (
event_id uuid references events(id) on delete cascade,
user_id uuid references profiles(id) on delete cascade,
joined_at timestamptz default now(),
primary key (event_id, user_id)
);
-- Chat rooms (both direct messages and groups)
create table chat_rooms (
id uuid default uuid_generate_v4() primary key,
type text not null check (type in ('direct', 'event_organizers', 'event_participants')),
icon_url text, -- Optional, for group chats
event_id uuid references events(id) on delete cascade, -- NULL for direct messages
is_enabled boolean default true, -- For event groups, can be disabled by event author
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- Chat room members
create table chat_members (
chat_id uuid references chat_rooms(id) on delete cascade,
user_id uuid references profiles(id) on delete cascade,
last_read_at timestamptz default now(),
joined_at timestamptz default now(),
primary key (chat_id, user_id)
);
-- Messages within chat rooms
create table chat_messages (
id uuid default uuid_generate_v4() primary key,
chat_id uuid references chat_rooms(id) on delete cascade,
sender_id uuid references profiles(id) on delete cascade,
content text not null,
created_at timestamptz default now()
);
-- Stories table
create table if not exists stories (
id uuid default uuid_generate_v4() primary key,
content text not null,
image_url text,
author_id uuid references profiles(id) on delete cascade not null,
created_at timestamptz default now(),
expires_at timestamptz not null -- Stories expire after 24 hours
);
-- Indexes for performance
create index if not exists posts_created_at_idx on posts (created_at desc);
create index if not exists posts_author_id_idx on posts (author_id);
create index if not exists promoted_posts_created_at_idx on promoted_posts (created_at desc);
create index if not exists promoted_posts_expires_at_idx on promoted_posts (expires_at);
create index if not exists promoted_posts_author_id_idx on promoted_posts (author_id);
create index if not exists events_event_timestamp_idx on events (event_timestamp desc);
create index if not exists events_stage_idx on events (stage);
create index if not exists events_author_id_idx on events (author_id);
create index if not exists event_organizers_user_id_idx on event_organizers (user_id);
create index if not exists event_participants_user_id_idx on event_participants (user_id);
create index chat_rooms_event_id_idx on chat_rooms(event_id);
create index chat_rooms_updated_at_idx on chat_rooms(updated_at desc);
create index chat_messages_chat_id_created_idx on chat_messages(chat_id, created_at desc);
create index chat_messages_sender_id_idx on chat_messages(sender_id);
create index if not exists stories_created_at_idx on stories (created_at desc);
create index if not exists stories_expires_at_idx on stories (expires_at);
create index if not exists stories_author_id_idx on stories (author_id);
-- Function to handle new user creation
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, display_name)
values (
new.id,
coalesce(new.raw_user_meta_data->>'display_name', 'User')
);
return new;
end;
$$ language plpgsql security definer;
-- Trigger to create profile on signup
create or replace trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
-- Enable Row Level Security
alter table profiles enable row level security;
alter table posts enable row level security;
alter table promoted_posts enable row level security;
alter table events enable row level security;
alter table event_organizers enable row level security;
alter table event_participants enable row level security;
alter table chat_rooms enable row level security;
alter table chat_members enable row level security;
alter table chat_messages enable row level security;
alter table stories enable row level security;
-- Profiles policies
create policy "Public profiles are viewable by everyone"
on profiles for select
using (true);
-- Promoted posts policies
create policy "Promoted posts are viewable by everyone"
on promoted_posts for select
using (true);
-- Posts policies
create policy "Posts are viewable by everyone"
on posts for select
using (true);
create policy "Users can create posts as themselves"
on posts for insert
with check (auth.uid() = author_id);
create policy "Users can update their own posts"
on posts for update
using (auth.uid() = author_id);
create policy "Users can delete their own posts"
on posts for delete
using (auth.uid() = author_id);
-- Events policies
create policy "Events are viewable by everyone"
on events for select
using (true);
create policy "Authenticated users can create events"
on events for insert
with check (auth.uid() = author_id);
create policy "Event authors and organizers can update events"
on events for update
using (
auth.uid() = author_id or
exists (
select 1 from event_organizers
where event_id = events.id and user_id = auth.uid()
)
);
create policy "Event authors can delete events"
on events for delete
using (auth.uid() = author_id);
-- Event organizers policies
create policy "Event organizer lists are viewable by everyone"
on event_organizers for select
using (true);
create policy "Event authors can manage organizers"
on event_organizers for insert
with check (
exists (
select 1 from events
where id = event_id and (
author_id = auth.uid()
)
)
);
create policy "Users can join/leave events as organizers"
on event_organizers for insert
with check (
auth.uid() = user_id and -- Can only join as yourself
exists (
select 1 from events
where id = event_id and stage = 'in-development'
)
);
create policy "Event authors and self can remove organizers"
on event_organizers for delete
using (
(
exists (
select 1 from events
where id = event_id and author_id = auth.uid()
) or
user_id = auth.uid()
) and
-- Prevent removing author from organizers
user_id != (
select author_id from events where id = event_id
)
);
-- Event participants policies
create policy "Event participant lists are viewable by everyone"
on event_participants for select
using (true);
create policy "Users can join/leave upcoming events"
on event_participants for insert
with check (
auth.uid() = user_id and -- Can only join as yourself
exists (
select 1 from events
where id = event_id and stage = 'upcoming'
)
);
create policy "Users can remove themselves from events"
on event_participants for delete
using (user_id = auth.uid());
create policy "Event authors and organizers can manage participants"
on event_participants for delete
using (
exists (
select 1 from events e
left join event_organizers eo on e.id = eo.event_id
where e.id = event_id and (
e.author_id = auth.uid() or
eo.user_id = auth.uid()
)
)
);
-- Create functions for participant/organizer counts
create or replace function get_event_participant_count(event_id uuid)
returns integer as $$
select count(*)::integer
from event_participants
where event_id = $1;
$$ language sql security definer;
create or replace function get_event_organizer_count(event_id uuid)
returns integer as $$
select count(*)::integer
from event_organizers
where event_id = $1;
$$ language sql security definer;
-- Chat rooms policies
create policy "Users can view chat rooms they are members of"
on chat_rooms for select
using (
exists (
select 1 from chat_members
where chat_id = chat_rooms.id
and user_id = auth.uid()
)
);
-- Automatic chat room creation handled by backend/functions
-- Function to check if user is member of a chat
create or replace function is_chat_member(chat_id uuid, user_id uuid)
returns boolean as $$
begin
return exists (
select 1 from chat_members
where chat_members.chat_id = $1
and chat_members.user_id = $2
);
end;
$$ language plpgsql security definer;
-- Chat members policies
create policy "Users can view chat members of their chats"
on chat_members for select
using (
is_chat_member(chat_id, auth.uid())
);
-- Chat messages policies
create policy "Users can view messages in their chats"
on chat_messages for select
using (
exists (
select 1 from chat_members
where chat_id = chat_messages.chat_id
and user_id = auth.uid()
)
);
create policy "Users can send messages to enabled chats they are members of"
on chat_messages for insert
with check (
sender_id = auth.uid() and
exists (
select 1 from chat_rooms r
join chat_members m on m.chat_id = r.id
where r.id = chat_messages.chat_id
and m.user_id = auth.uid()
and r.is_enabled = true
)
);
-- Function to create a direct message chat room between two users
create or replace function create_direct_chat(user1_id uuid, user2_id uuid)
returns uuid as $$
declare
chat_id uuid;
begin
-- Check if DM chat already exists
select cr.id into chat_id
from chat_rooms cr
join chat_members cm1 on cr.id = cm1.chat_id
join chat_members cm2 on cr.id = cm2.chat_id
where cr.type = 'direct'
and cm1.user_id = user1_id
and cm2.user_id = user2_id;
-- If not exists, create new chat room
if chat_id is null then
insert into chat_rooms (type) values ('direct')
returning id into chat_id;
-- Add both users as members
insert into chat_members (chat_id, user_id)
values
(chat_id, user1_id),
(chat_id, user2_id);
end if;
return chat_id;
end;
$$ language plpgsql security definer;
-- Function to create event group chats
create or replace function create_event_group_chats(event_id uuid)
returns void as $$
declare
organizer_chat_id uuid;
participant_chat_id uuid;
event_author_id uuid;
begin
-- Create organizers chat
insert into chat_rooms (type, event_id)
values ('event_organizers', event_id)
returning id into organizer_chat_id;
-- Create participants chat
insert into chat_rooms (type, event_id)
values ('event_participants', event_id)
returning id into participant_chat_id;
select author_id into event_author_id
from events where id = event_id;
-- Add event author to both chats
if not is_chat_member(organizer_chat_id, event_author_id) then
insert into chat_members (chat_id, user_id)
values (organizer_chat_id, event_author_id);
end if;
if not is_chat_member(participant_chat_id, event_author_id) then
insert into chat_members (chat_id, user_id)
values (participant_chat_id, event_author_id);
end if;
end;
$$ language plpgsql security definer;
-- Function to add event author as organizer
create or replace function add_event_author_as_organizer(event_id uuid)
returns void as $$
begin
insert into event_organizers (event_id, user_id)
select event_id, author_id
from events where id = event_id
on conflict do nothing;
end;
$$ language plpgsql security definer;
-- Trigger to auto-add organizer and create event group chats
create or replace function handle_new_event()
returns trigger as $$
begin
-- NOTE: Order of operations matters here
perform create_event_group_chats(new.id);
perform add_event_author_as_organizer(new.id);
return new;
end;
$$ language plpgsql security definer;
create trigger on_event_created
after insert on events
for each row execute procedure handle_new_event();
-- Trigger to auto-add organizers to both organizer and participant chats
create or replace function handle_new_organizer()
returns trigger as $$
declare
organizer_chat_id uuid;
participant_chat_id uuid;
begin
-- Get organizer chat id for this event
select id into organizer_chat_id
from chat_rooms
where event_id = new.event_id
and type = 'event_organizers';
-- Add to organizer chat if not already a member
if not is_chat_member(organizer_chat_id, new.user_id) then
insert into chat_members (chat_id, user_id)
values (organizer_chat_id, new.user_id);
end if;
-- Get participant chat id for this event
select id into participant_chat_id
from chat_rooms
where event_id = new.event_id
and type = 'event_participants';
-- Add to participant chat if not already a member
if not is_chat_member(participant_chat_id, new.user_id) then
insert into chat_members (chat_id, user_id)
values (participant_chat_id, new.user_id);
end if;
return new;
end;
$$ language plpgsql security definer;
create trigger on_organizer_added
after insert on event_organizers
for each row execute procedure handle_new_organizer();
-- Trigger to auto-add participants to participant chat
create or replace function handle_new_participant()
returns trigger as $$
declare
participant_chat_id uuid;
begin
-- Get participant chat id for this event
select id into participant_chat_id
from chat_rooms
where event_id = new.event_id
and type = 'event_participants';
-- Add to participant chat if not already a member
if not is_chat_member(participant_chat_id, new.user_id) then
insert into chat_members (chat_id, user_id)
values (participant_chat_id, new.user_id);
end if;
return new;
end;
$$ language plpgsql security definer;
create trigger on_participant_added
after insert on event_participants
for each row execute procedure handle_new_participant();
-- Trigger to auto-remove organizers from chats when they leave
create or replace function handle_organizer_removed()
returns trigger as $$
declare
organizer_chat_id uuid;
participant_chat_id uuid;
begin
-- Get organizer chat id for this event
select id into organizer_chat_id
from chat_rooms
where event_id = old.event_id
and type = 'event_organizers';
-- Remove from organizer chat if they are a member
if is_chat_member(organizer_chat_id, old.user_id) then
delete from chat_members
where chat_id = organizer_chat_id and user_id = old.user_id;
end if;
-- Get participant chat id for this event
select id into participant_chat_id
from chat_rooms
where event_id = old.event_id
and type = 'event_participants';
-- Remove from participant chat if they are a member
if is_chat_member(participant_chat_id, old.user_id) then
delete from chat_members
where chat_id = participant_chat_id and user_id = old.user_id;
end if;
return old;
end;
$$ language plpgsql security definer;
create trigger on_organizer_removed
after delete on event_organizers
for each row execute procedure handle_organizer_removed();
-- Trigger to auto-remove participants from chat when they leave
create or replace function handle_participant_removed()
returns trigger as $$
declare
participant_chat_id uuid;
begin
-- Get participant chat id for this event
select id into participant_chat_id
from chat_rooms
where event_id = old.event_id
and type = 'event_participants';
-- Remove from participant chat if they are a member
if is_chat_member(participant_chat_id, old.user_id) then
delete from chat_members
where chat_id = participant_chat_id and user_id = old.user_id;
end if;
return old;
end;
$$ language plpgsql security definer;
create trigger on_participant_removed
after delete on event_participants
for each row execute procedure handle_participant_removed();
-- Create storage bucket for images
insert into storage.buckets (id, name)
values ('images', 'images')
on conflict do nothing;
-- Set up storage policies
create policy "Images are viewable by everyone"
on storage.objects for select
using (bucket_id = 'images');
create policy "Authenticated users can upload images"
on storage.objects for insert
with check (
bucket_id = 'images' and
auth.role() = 'authenticated'
);
create policy "Users can update their own images"
on storage.objects for update
using (
bucket_id = 'images' and
owner = auth.uid()
);
create policy "Users can delete their own images"
on storage.objects for delete
using (
bucket_id = 'images' and
owner = auth.uid()
);
-- Create a storage bucket for profile pictures
insert into storage.buckets (id, name)
values ('profile-pictures', 'profile-pictures')
on conflict do nothing;
-- Allow authenticated users to upload profile pictures
create policy "Authenticated users can upload profile pictures"
on storage.objects for insert
with check (
bucket_id = 'profile-pictures' and
auth.role() = 'authenticated'
);
-- Allow authenticated users to update their own profile pictures
create policy "Users can update their own profile pictures"
on storage.objects for update
using (
bucket_id = 'profile-pictures' and
owner = auth.uid()
);
-- Allow authenticated users to delete their own profile pictures
create policy "Users can delete their own profile pictures"
on storage.objects for delete
using (
bucket_id = 'profile-pictures' and
owner = auth.uid()
);
-- Allow everyone to view profile pictures
create policy "Profile pictures are viewable by everyone"
on storage.objects for select
using (bucket_id = 'profile-pictures');
-- Create storage bucket for stories
insert into storage.buckets (id, name)
values ('stories', 'stories')
on conflict do nothing;
-- Set up storage policies for stories
create policy "Stories are viewable by everyone"
on storage.objects for select
using (bucket_id = 'stories');
create policy "Authenticated users can upload stories"
on storage.objects for insert
with check (
bucket_id = 'stories' and
auth.role() = 'authenticated'
);
create policy "Users can update their own stories"
on storage.objects for update
using (
bucket_id = 'stories' and
owner = auth.uid()
);
create policy "Users can delete their own stories"
on storage.objects for delete
using (
bucket_id = 'stories' and
owner = auth.uid()
);
-- Stories policies
create policy "Stories are viewable by everyone"
on stories for select
using (true);
create policy "Users can create stories as themselves"
on stories for insert
with check (auth.uid() = author_id);
create policy "Users can update their own stories"
on stories for update
using (auth.uid() = author_id);
create policy "Users can delete their own stories"
on stories for delete
using (auth.uid() = author_id);