-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDATABASE_SCHEMA.sql
More file actions
528 lines (448 loc) · 17.3 KB
/
DATABASE_SCHEMA.sql
File metadata and controls
528 lines (448 loc) · 17.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
-- Instagram-Style Social Media App - Complete Database Schema
-- Supabase PostgreSQL
-- Enable necessary extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "postgis";
-- ============================================
-- USERS & AUTHENTICATION
-- ============================================
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT UNIQUE,
phone TEXT UNIQUE,
username TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
bio TEXT,
profile_photo_url TEXT,
custom_status TEXT,
profile_theme TEXT DEFAULT 'minimal', -- minimal, neon, dark, pastel, sunset
location GEOGRAPHY(POINT),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_location ON users USING GIST(location);
-- ============================================
-- POSTS
-- ============================================
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
caption TEXT,
media_urls TEXT[], -- array of image/video URLs
media_type TEXT NOT NULL, -- 'photo', 'video'
location_name TEXT,
location_coords GEOGRAPHY(POINT),
hashtags TEXT[],
is_draft BOOLEAN DEFAULT FALSE,
time_locked_until TIMESTAMP WITH TIME ZONE, -- for time-locked posts
visibility TEXT DEFAULT 'public', -- public, friends, group
group_id UUID, -- for follow groups
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
CREATE INDEX idx_posts_location ON posts USING GIST(location_coords);
CREATE INDEX idx_posts_hashtags ON posts USING GIN(hashtags);
CREATE INDEX idx_posts_time_locked ON posts(time_locked_until) WHERE time_locked_until IS NOT NULL;
-- ============================================
-- DUET POSTS (Split Grid Posts)
-- ============================================
CREATE TABLE duet_posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
user1_id UUID REFERENCES users(id) ON DELETE CASCADE,
user2_id UUID REFERENCES users(id) ON DELETE CASCADE,
user1_media_url TEXT,
user2_media_url TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_duet_posts_post_id ON duet_posts(post_id);
-- ============================================
-- STORY CHAINS
-- ============================================
CREATE TABLE story_chains (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
creator_id UUID REFERENCES users(id) ON DELETE CASCADE,
title TEXT,
is_active BOOLEAN DEFAULT TRUE,
expires_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '24 hours',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE story_chain_items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
chain_id UUID REFERENCES story_chains(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
media_url TEXT NOT NULL,
media_type TEXT NOT NULL,
order_index INTEGER,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_story_chains_creator ON story_chains(creator_id);
CREATE INDEX idx_story_chain_items_chain ON story_chain_items(chain_id, order_index);
-- ============================================
-- REVERSE STORIES
-- ============================================
CREATE TABLE reverse_stories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
creator_id UUID REFERENCES users(id) ON DELETE CASCADE,
frame_url TEXT NOT NULL, -- the frame/template
title TEXT,
expires_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '24 hours',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE reverse_story_contributions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
reverse_story_id UUID REFERENCES reverse_stories(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
media_url TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ============================================
-- REPOST TEMPLATES
-- ============================================
CREATE TABLE reposts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
original_post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
template_type TEXT NOT NULL, -- polaroid, meme, collage, moodboard
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_reposts_user ON reposts(user_id);
CREATE INDEX idx_reposts_original ON reposts(original_post_id);
-- ============================================
-- LIKES
-- ============================================
CREATE TABLE likes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, post_id)
);
CREATE INDEX idx_likes_post ON likes(post_id);
CREATE INDEX idx_likes_user ON likes(user_id);
-- ============================================
-- COMMENTS
-- ============================================
CREATE TABLE comments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
parent_comment_id UUID REFERENCES comments(id) ON DELETE CASCADE,
content TEXT,
voice_note_url TEXT, -- for voice note comments
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_comments_post ON comments(post_id);
CREATE INDEX idx_comments_user ON comments(user_id);
CREATE INDEX idx_comments_parent ON comments(parent_comment_id);
-- ============================================
-- FOLLOWS
-- ============================================
CREATE TABLE follows (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
follower_id UUID REFERENCES users(id) ON DELETE CASCADE,
following_id UUID REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(follower_id, following_id)
);
CREATE INDEX idx_follows_follower ON follows(follower_id);
CREATE INDEX idx_follows_following ON follows(following_id);
-- ============================================
-- FOLLOW GROUPS
-- ============================================
CREATE TABLE follow_groups (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL, -- "Best friends", "College friends", etc.
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE follow_group_members (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
group_id UUID REFERENCES follow_groups(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(group_id, user_id)
);
CREATE INDEX idx_follow_groups_user ON follow_groups(user_id);
CREATE INDEX idx_follow_group_members_group ON follow_group_members(group_id);
-- ============================================
-- FRIENDSHIP LEVELS
-- ============================================
CREATE TABLE friendship_levels (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user1_id UUID REFERENCES users(id) ON DELETE CASCADE,
user2_id UUID REFERENCES users(id) ON DELETE CASCADE,
level TEXT DEFAULT 'bronze', -- bronze, silver, gold, platinum
points INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user1_id, user2_id)
);
CREATE INDEX idx_friendship_levels_users ON friendship_levels(user1_id, user2_id);
-- ============================================
-- SAVED POSTS
-- ============================================
CREATE TABLE saved_posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, post_id)
);
CREATE INDEX idx_saved_posts_user ON saved_posts(user_id);
-- ============================================
-- PRIVATE LOCKER
-- ============================================
CREATE TABLE locker_posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
pin_hash TEXT NOT NULL, -- hashed PIN
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, post_id)
);
CREATE INDEX idx_locker_posts_user ON locker_posts(user_id);
-- ============================================
-- MESSAGES & DMs
-- ============================================
CREATE TABLE conversations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
is_group BOOLEAN DEFAULT FALSE,
name TEXT,
emoji_only_mode BOOLEAN DEFAULT FALSE,
auto_delete_after INTERVAL, -- 1 hour, 24 hours, 1 week
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE conversation_participants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(conversation_id, user_id)
);
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE,
sender_id UUID REFERENCES users(id) ON DELETE CASCADE,
content TEXT,
media_url TEXT,
is_emoji_only BOOLEAN DEFAULT FALSE,
delete_at TIMESTAMP WITH TIME ZONE, -- for auto-delete
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_messages_conversation ON messages(conversation_id, created_at DESC);
CREATE INDEX idx_conversation_participants ON conversation_participants(conversation_id);
-- ============================================
-- NOTIFICATIONS
-- ============================================
CREATE TABLE notifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
type TEXT NOT NULL, -- like, comment, follow, message
actor_id UUID REFERENCES users(id) ON DELETE CASCADE,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
comment_id UUID REFERENCES comments(id) ON DELETE CASCADE,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_notifications_user ON notifications(user_id, created_at DESC);
CREATE INDEX idx_notifications_unread ON notifications(user_id, is_read) WHERE is_read = FALSE;
-- ============================================
-- COMMUNITIES
-- ============================================
CREATE TABLE communities (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
type TEXT NOT NULL, -- college, city, workplace
description TEXT,
location GEOGRAPHY(POINT),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE community_members (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
community_id UUID REFERENCES communities(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(community_id, user_id)
);
CREATE INDEX idx_communities_type ON communities(type);
CREATE INDEX idx_community_members_community ON community_members(community_id);
CREATE INDEX idx_community_members_user ON community_members(user_id);
-- ============================================
-- EVENTS
-- ============================================
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
creator_id UUID REFERENCES users(id) ON DELETE CASCADE,
community_id UUID REFERENCES communities(id) ON DELETE SET NULL,
title TEXT NOT NULL,
description TEXT,
location_name TEXT,
location_coords GEOGRAPHY(POINT),
event_date TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_events_location ON events USING GIST(location_coords);
CREATE INDEX idx_events_date ON events(event_date);
CREATE TABLE event_attendees (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
event_id UUID REFERENCES events(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
status TEXT DEFAULT 'going', -- going, interested, not_going
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(event_id, user_id)
);
-- ============================================
-- ANONYMOUS CONFESSIONS
-- ============================================
CREATE TABLE confessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
content TEXT NOT NULL,
is_moderated BOOLEAN DEFAULT FALSE,
is_approved BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE confession_votes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
confession_id UUID REFERENCES confessions(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
vote_type TEXT, -- upvote, downvote
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(confession_id, user_id)
);
CREATE INDEX idx_confessions_created ON confessions(created_at DESC);
-- ============================================
-- WEEKLY CHALLENGES
-- ============================================
CREATE TABLE challenges (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title TEXT NOT NULL,
description TEXT,
category TEXT, -- room_setup, photography, fit_check
start_date TIMESTAMP WITH TIME ZONE,
end_date TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE challenge_submissions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
challenge_id UUID REFERENCES challenges(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
votes_count INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(challenge_id, user_id)
);
CREATE TABLE challenge_votes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
submission_id UUID REFERENCES challenge_submissions(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(submission_id, user_id)
);
CREATE INDEX idx_challenges_dates ON challenges(start_date, end_date);
CREATE INDEX idx_challenge_submissions_challenge ON challenge_submissions(challenge_id);
-- ============================================
-- STORY REACTIONS
-- ============================================
CREATE TABLE story_reactions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
story_id UUID, -- references story (not created yet, but for stories feature)
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
reaction_emoji TEXT NOT NULL,
position_x FLOAT, -- x position on story
position_y FLOAT, -- y position on story
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ============================================
-- FUNCTIONS & TRIGGERS
-- ============================================
-- Function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply trigger to relevant tables
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_posts_updated_at BEFORE UPDATE ON posts
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_comments_updated_at BEFORE UPDATE ON comments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Function to calculate distance between two points
CREATE OR REPLACE FUNCTION get_nearby_posts(
user_lat FLOAT,
user_lng FLOAT,
radius_km FLOAT DEFAULT 3
)
RETURNS TABLE (
post_id UUID,
distance_km FLOAT
) AS $$
BEGIN
RETURN QUERY
SELECT
p.id,
ST_Distance(
p.location_coords::geography,
ST_SetSRID(ST_MakePoint(user_lng, user_lat), 4326)::geography
) / 1000 AS distance_km
FROM posts p
WHERE p.location_coords IS NOT NULL
AND ST_DWithin(
p.location_coords::geography,
ST_SetSRID(ST_MakePoint(user_lng, user_lat), 4326)::geography,
radius_km * 1000
)
ORDER BY distance_km;
END;
$$ LANGUAGE plpgsql;
-- Function to update friendship points
CREATE OR REPLACE FUNCTION update_friendship_points(
uid1 UUID,
uid2 UUID,
points_to_add INTEGER
)
RETURNS VOID AS $$
DECLARE
current_points INTEGER;
new_level TEXT;
BEGIN
-- Ensure user1_id < user2_id for consistency
IF uid1 > uid2 THEN
uid1 := uid2;
uid2 := uid1;
END IF;
-- Insert or update friendship level
INSERT INTO friendship_levels (user1_id, user2_id, points)
VALUES (uid1, uid2, points_to_add)
ON CONFLICT (user1_id, user2_id)
DO UPDATE SET
points = friendship_levels.points + points_to_add,
updated_at = NOW();
-- Get current points
SELECT points INTO current_points
FROM friendship_levels
WHERE user1_id = uid1 AND user2_id = uid2;
-- Determine level
IF current_points >= 1000 THEN
new_level := 'platinum';
ELSIF current_points >= 500 THEN
new_level := 'gold';
ELSIF current_points >= 200 THEN
new_level := 'silver';
ELSE
new_level := 'bronze';
END IF;
-- Update level
UPDATE friendship_levels
SET level = new_level
WHERE user1_id = uid1 AND user2_id = uid2;
END;
$$ LANGUAGE plpgsql;