-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase-setup.sql
More file actions
531 lines (460 loc) · 18.3 KB
/
database-setup.sql
File metadata and controls
531 lines (460 loc) · 18.3 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
-- Likey Social Media App - Complete Database Setup
-- Run this SINGLE file in your fresh Supabase SQL Editor
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Storage bucket for images
INSERT INTO storage.buckets (id, name, public)
VALUES ('images', 'images', true)
ON CONFLICT (id) DO NOTHING;
-- Users table (extends Supabase auth.users)
CREATE TABLE public.profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
username VARCHAR(20) UNIQUE NOT NULL,
display_name VARCHAR(50) NOT NULL,
bio TEXT,
profile_pic_url TEXT,
followers_count INTEGER DEFAULT 0,
following_count INTEGER DEFAULT 0,
posts_count INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Posts table
CREATE TABLE public.posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
caption TEXT,
image_urls TEXT[] NOT NULL,
like_count INTEGER DEFAULT 0,
comment_count INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Likes table
CREATE TABLE public.likes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
post_id UUID REFERENCES public.posts(id) ON DELETE CASCADE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, post_id)
);
-- Comments table
CREATE TABLE public.comments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
post_id UUID REFERENCES public.posts(id) ON DELETE CASCADE NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Follows table
CREATE TABLE public.follows (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
follower_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
following_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(follower_id, following_id),
CHECK (follower_id != following_id)
);
-- Notifications table
CREATE TABLE public.notifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
type VARCHAR(20) NOT NULL CHECK (type IN ('like', 'comment', 'follow')),
message TEXT NOT NULL,
read BOOLEAN DEFAULT FALSE,
related_user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE,
related_post_id UUID REFERENCES public.posts(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Direct Messages - Conversations table
CREATE TABLE public.conversations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user1_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
user2_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_message_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user1_id, user2_id),
CHECK (user1_id != user2_id)
);
-- Direct Messages - Messages table
CREATE TABLE public.messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
conversation_id UUID REFERENCES public.conversations(id) ON DELETE CASCADE NOT NULL,
sender_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
content TEXT NOT NULL,
read BOOLEAN DEFAULT FALSE,
edited_at TIMESTAMP WITH TIME ZONE,
forwarded_from UUID REFERENCES public.messages(id) ON DELETE SET NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Trending posts table
CREATE TABLE public.trending_posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
post_id UUID REFERENCES public.posts(id) ON DELETE CASCADE NOT NULL,
score NUMERIC NOT NULL,
trending_date DATE DEFAULT CURRENT_DATE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(post_id)
);
-- User recommendations table
CREATE TABLE public.user_recommendations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
recommended_user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
reason VARCHAR(50) NOT NULL,
score INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, recommended_user_id)
);
-- Create indexes for better performance
CREATE INDEX idx_posts_user_id ON public.posts(user_id);
CREATE INDEX idx_posts_created_at ON public.posts(created_at DESC);
CREATE INDEX idx_likes_post_id ON public.likes(post_id);
CREATE INDEX idx_likes_user_id ON public.likes(user_id);
CREATE INDEX idx_comments_post_id ON public.comments(post_id);
CREATE INDEX idx_follows_follower_id ON public.follows(follower_id);
CREATE INDEX idx_follows_following_id ON public.follows(following_id);
CREATE INDEX idx_notifications_user_id ON public.notifications(user_id);
CREATE INDEX idx_profiles_username ON public.profiles(username);
CREATE INDEX idx_conversations_user1 ON public.conversations(user1_id);
CREATE INDEX idx_conversations_user2 ON public.conversations(user2_id);
CREATE INDEX idx_conversations_last_message ON public.conversations(last_message_at DESC);
CREATE INDEX idx_messages_conversation ON public.messages(conversation_id);
CREATE INDEX idx_messages_sender ON public.messages(sender_id);
CREATE INDEX idx_messages_created_at ON public.messages(created_at DESC);
CREATE INDEX idx_messages_forwarded_from ON public.messages(forwarded_from);
CREATE INDEX idx_messages_conversation_created ON public.messages(conversation_id, created_at);
CREATE INDEX idx_trending_posts_score ON public.trending_posts(score DESC);
CREATE INDEX idx_trending_posts_date ON public.trending_posts(trending_date DESC);
CREATE INDEX idx_user_recommendations_user ON public.user_recommendations(user_id);
CREATE INDEX idx_user_recommendations_score ON public.user_recommendations(score DESC);
-- Full-text search indexes
CREATE INDEX idx_profiles_search ON public.profiles USING GIN (
to_tsvector('english', username || ' ' || display_name)
);
-- Functions to update counts with proper security
CREATE OR REPLACE FUNCTION public.update_like_count()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE public.posts SET like_count = like_count + 1 WHERE id = NEW.post_id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
UPDATE public.posts SET like_count = like_count - 1 WHERE id = OLD.post_id;
RETURN OLD;
END IF;
RETURN NULL;
END;
$$;
CREATE OR REPLACE FUNCTION public.update_comment_count()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE public.posts SET comment_count = comment_count + 1 WHERE id = NEW.post_id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
UPDATE public.posts SET comment_count = comment_count - 1 WHERE id = OLD.post_id;
RETURN OLD;
END IF;
RETURN NULL;
END;
$$;
CREATE OR REPLACE FUNCTION public.update_follow_counts()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE public.profiles SET following_count = following_count + 1 WHERE id = NEW.follower_id;
UPDATE public.profiles SET followers_count = followers_count + 1 WHERE id = NEW.following_id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
UPDATE public.profiles SET following_count = following_count - 1 WHERE id = OLD.follower_id;
UPDATE public.profiles SET followers_count = followers_count - 1 WHERE id = OLD.following_id;
RETURN OLD;
END IF;
RETURN NULL;
END;
$$;
CREATE OR REPLACE FUNCTION public.update_posts_count()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE public.profiles SET posts_count = posts_count + 1 WHERE id = NEW.user_id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
UPDATE public.profiles SET posts_count = posts_count - 1 WHERE id = OLD.user_id;
RETURN OLD;
END IF;
RETURN NULL;
END;
$$;
CREATE OR REPLACE FUNCTION public.update_conversation_timestamp()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
UPDATE public.conversations
SET last_message_at = NEW.created_at
WHERE id = NEW.conversation_id;
RETURN NEW;
END;
$$;
-- Function to calculate trending score
CREATE OR REPLACE FUNCTION public.calculate_trending_score(post_id UUID)
RETURNS NUMERIC
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
like_count INTEGER;
comment_count INTEGER;
post_age_hours NUMERIC;
score NUMERIC;
BEGIN
SELECT p.like_count, p.comment_count,
EXTRACT(EPOCH FROM (NOW() - p.created_at)) / 3600
INTO like_count, comment_count, post_age_hours
FROM public.posts p WHERE p.id = post_id;
-- Trending algorithm: (likes * 2 + comments * 3) / (age_hours + 1)^1.5
score := (like_count * 2 + comment_count * 3) / POWER(post_age_hours + 1, 1.5);
RETURN score;
END;
$$;
-- Function to refresh trending posts
CREATE OR REPLACE FUNCTION public.refresh_trending_posts()
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
-- Clear old trending data (older than 7 days)
DELETE FROM public.trending_posts
WHERE trending_date < CURRENT_DATE - INTERVAL '7 days';
-- Insert/update trending posts for today
INSERT INTO public.trending_posts (post_id, score, trending_date)
SELECT
p.id,
public.calculate_trending_score(p.id),
CURRENT_DATE
FROM public.posts p
WHERE p.created_at > CURRENT_DATE - INTERVAL '7 days'
AND (p.like_count > 0 OR p.comment_count > 0)
ON CONFLICT (post_id)
DO UPDATE SET
score = EXCLUDED.score,
trending_date = EXCLUDED.trending_date;
END;
$$;
-- Function to generate mutual follow recommendations
CREATE OR REPLACE FUNCTION public.generate_mutual_follow_recommendations(target_user_id UUID)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
-- Clear old recommendations for this user
DELETE FROM public.user_recommendations WHERE user_id = target_user_id;
-- Insert mutual follower recommendations
INSERT INTO public.user_recommendations (user_id, recommended_user_id, reason, score)
SELECT DISTINCT
target_user_id,
mutual_user.id,
'mutual_followers',
COUNT(*) as score
FROM public.follows f1
JOIN public.follows f2 ON f1.following_id = f2.follower_id
JOIN public.profiles mutual_user ON f2.following_id = mutual_user.id
WHERE f1.follower_id = target_user_id
AND mutual_user.id != target_user_id
AND mutual_user.id NOT IN (
SELECT following_id FROM public.follows WHERE follower_id = target_user_id
)
GROUP BY mutual_user.id
HAVING COUNT(*) >= 2
ORDER BY score DESC
LIMIT 20;
END;
$$;
-- Create triggers
CREATE TRIGGER like_count_trigger
AFTER INSERT OR DELETE ON public.likes
FOR EACH ROW EXECUTE FUNCTION public.update_like_count();
CREATE TRIGGER comment_count_trigger
AFTER INSERT OR DELETE ON public.comments
FOR EACH ROW EXECUTE FUNCTION public.update_comment_count();
CREATE TRIGGER follow_counts_trigger
AFTER INSERT OR DELETE ON public.follows
FOR EACH ROW EXECUTE FUNCTION public.update_follow_counts();
CREATE TRIGGER posts_count_trigger
AFTER INSERT OR DELETE ON public.posts
FOR EACH ROW EXECUTE FUNCTION public.update_posts_count();
CREATE TRIGGER message_timestamp_trigger
AFTER INSERT ON public.messages
FOR EACH ROW EXECUTE FUNCTION public.update_conversation_timestamp();
-- Row Level Security (RLS) Policies
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.likes ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.follows ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.notifications ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.conversations ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.trending_posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.user_recommendations ENABLE ROW LEVEL SECURITY;
-- Profiles policies
CREATE POLICY "Public profiles are viewable by everyone" ON public.profiles
FOR SELECT USING (true);
CREATE POLICY "Authenticated users can create profiles" ON public.profiles
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Users can update own profile" ON public.profiles
FOR UPDATE USING (auth.uid() = id);
-- Posts policies
CREATE POLICY "Posts are viewable by everyone" ON public.posts
FOR SELECT USING (true);
CREATE POLICY "Users can insert their own posts" ON public.posts
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own posts" ON public.posts
FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own posts" ON public.posts
FOR DELETE USING (auth.uid() = user_id);
-- Likes policies (fixed for real-time support)
CREATE POLICY "Likes are viewable by everyone" ON public.likes
FOR SELECT USING (true);
CREATE POLICY "Users can insert their own likes" ON public.likes
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "likes_delete_authenticated" ON public.likes
FOR DELETE TO authenticated
USING (user_id = auth.uid());
-- Allow real-time to read likes for broadcasting
CREATE POLICY "likes_realtime_select" ON public.likes
FOR SELECT TO anon
USING (true);
-- Comments policies (fixed for real-time support)
CREATE POLICY "Comments are viewable by everyone" ON public.comments
FOR SELECT USING (true);
CREATE POLICY "Users can insert their own comments" ON public.comments
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "comments_update_authenticated" ON public.comments
FOR UPDATE TO authenticated
USING (user_id = auth.uid());
CREATE POLICY "comments_delete_authenticated" ON public.comments
FOR DELETE TO authenticated
USING (user_id = auth.uid());
-- Allow real-time to read comments for broadcasting
CREATE POLICY "comments_realtime_select" ON public.comments
FOR SELECT TO anon
USING (true);
-- Follows policies (fixed for real-time support)
CREATE POLICY "Follows are viewable by everyone" ON public.follows
FOR SELECT USING (true);
CREATE POLICY "Users can insert their own follows" ON public.follows
FOR INSERT WITH CHECK (auth.uid() = follower_id);
CREATE POLICY "follows_delete_authenticated" ON public.follows
FOR DELETE TO authenticated
USING (follower_id = auth.uid());
-- Allow real-time to read follows for broadcasting
CREATE POLICY "follows_realtime_select" ON public.follows
FOR SELECT TO anon
USING (true);
-- Notifications policies (fixed for real-time support)
CREATE POLICY "notifications_select_authenticated" ON public.notifications
FOR SELECT TO authenticated
USING (user_id = auth.uid());
CREATE POLICY "notifications_update_authenticated" ON public.notifications
FOR UPDATE TO authenticated
USING (user_id = auth.uid());
CREATE POLICY "Authenticated users can create notifications for others" ON public.notifications
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
-- CRITICAL: Allow real-time to read notifications for broadcasting
CREATE POLICY "notifications_realtime_select" ON public.notifications
FOR SELECT TO anon
USING (true);
-- Conversations policies
CREATE POLICY "Users can view their own conversations" ON public.conversations
FOR SELECT USING (auth.uid() = user1_id OR auth.uid() = user2_id);
CREATE POLICY "Users can create conversations" ON public.conversations
FOR INSERT WITH CHECK (auth.uid() = user1_id OR auth.uid() = user2_id);
-- Messages policies
CREATE POLICY "Users can view messages in their conversations" ON public.messages
FOR SELECT USING (
conversation_id IN (
SELECT id FROM public.conversations
WHERE auth.uid() = user1_id OR auth.uid() = user2_id
)
);
CREATE POLICY "Users can send messages to their conversations" ON public.messages
FOR INSERT WITH CHECK (
auth.uid() = sender_id AND
conversation_id IN (
SELECT id FROM public.conversations
WHERE auth.uid() = user1_id OR auth.uid() = user2_id
)
);
CREATE POLICY "Users can update their own messages" ON public.messages
FOR UPDATE USING (auth.uid() = sender_id);
-- Trending posts policies
CREATE POLICY "Trending posts are viewable by everyone" ON public.trending_posts
FOR SELECT USING (true);
-- User recommendations policies
CREATE POLICY "Users can view their own recommendations" ON public.user_recommendations
FOR SELECT USING (auth.uid() = user_id);
-- Storage policies
CREATE POLICY "Images are publicly accessible" ON storage.objects
FOR SELECT USING (bucket_id = 'images');
CREATE POLICY "Users can upload images" ON storage.objects
FOR INSERT WITH CHECK (bucket_id = 'images' AND auth.role() = 'authenticated');
CREATE POLICY "Users can update own images" ON storage.objects
FOR UPDATE USING (bucket_id = 'images' AND auth.uid()::text = (storage.foldername(name))[1]);
CREATE POLICY "Users can delete own images" ON storage.objects
FOR DELETE USING (bucket_id = 'images' AND auth.uid()::text = (storage.foldername(name))[1]);
-- Enable real-time for tables that need live updates
ALTER PUBLICATION supabase_realtime ADD TABLE public.messages;
ALTER PUBLICATION supabase_realtime ADD TABLE public.notifications;
ALTER PUBLICATION supabase_realtime ADD TABLE public.likes;
ALTER PUBLICATION supabase_realtime ADD TABLE public.comments;
ALTER PUBLICATION supabase_realtime ADD TABLE public.follows;
-- Create security helper function for conversation access
CREATE OR REPLACE FUNCTION user_can_access_conversation(conv_id uuid, user_id uuid)
RETURNS boolean
LANGUAGE sql
SECURITY DEFINER
STABLE
AS $$
SELECT EXISTS (
SELECT 1 FROM public.conversations
WHERE id = conv_id
AND (user1_id = user_id OR user2_id = user_id)
);
$$;
-- Grant execute permissions
GRANT EXECUTE ON FUNCTION user_can_access_conversation(uuid, uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION user_can_access_conversation(uuid, uuid) TO anon;
-- Set replica identity to FULL for better real-time support
ALTER TABLE public.messages REPLICA IDENTITY FULL;
ALTER TABLE public.conversations REPLICA IDENTITY FULL;
ALTER TABLE public.notifications REPLICA IDENTITY FULL;
ALTER TABLE public.likes REPLICA IDENTITY FULL;
ALTER TABLE public.comments REPLICA IDENTITY FULL;
ALTER TABLE public.follows REPLICA IDENTITY FULL;