Spaces:
Sleeping
Sleeping
| -- VinClassroom Database Schema | |
| -- Run this in Supabase SQL Editor | |
| -- Enable necessary extensions | |
| CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; | |
| -- Users (extends Supabase auth.users) | |
| CREATE TABLE profiles ( | |
| id UUID REFERENCES auth.users(id) PRIMARY KEY, | |
| email TEXT UNIQUE NOT NULL, | |
| display_name TEXT NOT NULL, | |
| avatar_url TEXT, | |
| bio TEXT, | |
| color TEXT, | |
| status TEXT DEFAULT 'offline', | |
| last_seen TIMESTAMPTZ DEFAULT NOW(), | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| -- Spaces | |
| CREATE TABLE spaces ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| name TEXT NOT NULL, | |
| description TEXT, | |
| icon_url TEXT, | |
| owner_id UUID REFERENCES profiles(id), | |
| is_private BOOLEAN DEFAULT false, | |
| invite_code TEXT UNIQUE, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| -- Rooms | |
| CREATE TABLE rooms ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| space_id UUID REFERENCES spaces(id) ON DELETE CASCADE, | |
| name TEXT NOT NULL, | |
| description TEXT, | |
| type TEXT DEFAULT 'text', | |
| is_private BOOLEAN DEFAULT false, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| -- Space Members | |
| CREATE TABLE space_members ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| space_id UUID REFERENCES spaces(id) ON DELETE CASCADE, | |
| user_id UUID REFERENCES profiles(id), | |
| role TEXT DEFAULT 'member', | |
| joined_at TIMESTAMPTZ DEFAULT NOW(), | |
| UNIQUE(space_id, user_id) | |
| ); | |
| -- Room Members | |
| CREATE TABLE room_members ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| room_id UUID REFERENCES rooms(id) ON DELETE CASCADE, | |
| user_id UUID REFERENCES profiles(id), | |
| joined_at TIMESTAMPTZ DEFAULT NOW(), | |
| UNIQUE(room_id, user_id) | |
| ); | |
| -- Messages | |
| CREATE TABLE messages ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| room_id UUID REFERENCES rooms(id) ON DELETE CASCADE, | |
| user_id UUID REFERENCES profiles(id), | |
| content TEXT, | |
| reply_to_id UUID REFERENCES messages(id), | |
| is_pinned BOOLEAN DEFAULT false, | |
| is_edited BOOLEAN DEFAULT false, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW(), | |
| deleted_at TIMESTAMPTZ | |
| ); | |
| -- Message Attachments | |
| CREATE TABLE message_attachments ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| message_id UUID REFERENCES messages(id) ON DELETE CASCADE, | |
| file_name TEXT NOT NULL, | |
| file_url TEXT NOT NULL, | |
| file_type TEXT, | |
| file_size INTEGER, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| -- Reactions | |
| CREATE TABLE reactions ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| message_id UUID REFERENCES messages(id) ON DELETE CASCADE, | |
| user_id UUID REFERENCES profiles(id), | |
| emoji TEXT NOT NULL, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| UNIQUE(message_id, user_id, emoji) | |
| ); | |
| -- Direct Messages | |
| CREATE TABLE dm_conversations ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| user1_id UUID REFERENCES profiles(id), | |
| user2_id UUID REFERENCES profiles(id), | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| UNIQUE(user1_id, user2_id) | |
| ); | |
| CREATE TABLE dm_messages ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| conversation_id UUID REFERENCES dm_conversations(id) ON DELETE CASCADE, | |
| sender_id UUID REFERENCES profiles(id), | |
| content TEXT, | |
| is_read BOOLEAN DEFAULT false, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| deleted_at TIMESTAMPTZ | |
| ); | |
| -- Notifications | |
| CREATE TABLE notifications ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| user_id UUID REFERENCES profiles(id), | |
| type TEXT NOT NULL, | |
| title TEXT NOT NULL, | |
| message TEXT, | |
| data JSONB, | |
| is_read BOOLEAN DEFAULT false, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| -- Blocked Users | |
| CREATE TABLE blocked_users ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| blocker_id UUID REFERENCES profiles(id), | |
| blocked_id UUID REFERENCES profiles(id), | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| UNIQUE(blocker_id, blocked_id) | |
| ); | |
| -- Space Invitations | |
| CREATE TABLE space_invitations ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| space_id UUID REFERENCES spaces(id) ON DELETE CASCADE, | |
| email TEXT NOT NULL, | |
| token TEXT UNIQUE NOT NULL, | |
| invited_by UUID REFERENCES profiles(id), | |
| status TEXT DEFAULT 'pending', | |
| expires_at TIMESTAMPTZ, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| -- Files | |
| CREATE TABLE files ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| uploader_id UUID REFERENCES profiles(id), | |
| space_id UUID REFERENCES spaces(id), | |
| room_id UUID REFERENCES rooms(id), | |
| file_name TEXT NOT NULL, | |
| file_url TEXT NOT NULL, | |
| file_type TEXT, | |
| file_size INTEGER, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| -- Indexes for performance | |
| CREATE INDEX idx_profiles_status ON profiles(status); | |
| CREATE INDEX idx_profiles_last_seen ON profiles(last_seen); | |
| CREATE INDEX idx_spaces_owner ON spaces(owner_id); | |
| CREATE INDEX idx_spaces_invite_code ON spaces(invite_code); | |
| CREATE INDEX idx_rooms_space ON rooms(space_id); | |
| CREATE INDEX idx_space_members_space ON space_members(space_id); | |
| CREATE INDEX idx_space_members_user ON space_members(user_id); | |
| CREATE INDEX idx_room_members_room ON room_members(room_id); | |
| CREATE INDEX idx_room_members_user ON room_members(user_id); | |
| CREATE INDEX idx_messages_room ON messages(room_id); | |
| CREATE INDEX idx_messages_user ON messages(user_id); | |
| CREATE INDEX idx_messages_created_at ON messages(created_at); | |
| CREATE INDEX idx_messages_reply_to ON messages(reply_to_id); | |
| CREATE INDEX idx_reactions_message ON reactions(message_id); | |
| CREATE INDEX idx_dm_conversations_user1 ON dm_conversations(user1_id); | |
| CREATE INDEX idx_dm_conversations_user2 ON dm_conversations(user2_id); | |
| CREATE INDEX idx_dm_messages_conversation ON dm_messages(conversation_id); | |
| CREATE INDEX idx_notifications_user ON notifications(user_id); | |
| CREATE INDEX idx_notifications_is_read ON notifications(is_read); | |
| CREATE INDEX idx_files_space ON files(space_id); | |
| CREATE INDEX idx_files_room ON files(room_id); | |
| -- Enable Row Level Security | |
| ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE spaces ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE rooms ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE space_members ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE room_members ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE messages ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE message_attachments ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE reactions ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE dm_conversations ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE dm_messages ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE notifications ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE blocked_users ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE space_invitations ENABLE ROW LEVEL SECURITY; | |
| ALTER TABLE files ENABLE ROW LEVEL SECURITY; | |
| -- RLS Policies | |
| -- Profiles: Users can read all profiles, update only their own | |
| CREATE POLICY "Profiles are viewable by everyone" ON profiles | |
| FOR SELECT USING (true); | |
| CREATE POLICY "Users can update own profile" ON profiles | |
| FOR UPDATE USING (auth.uid() = id); | |
| -- Spaces: Members can view, owner can update/delete | |
| CREATE POLICY "Spaces viewable by members" ON spaces | |
| FOR SELECT USING ( | |
| EXISTS ( | |
| SELECT 1 FROM space_members WHERE space_id = id AND user_id = auth.uid() | |
| ) OR NOT is_private | |
| ); | |
| CREATE POLICY "Spaces insertable by authenticated users" ON spaces | |
| FOR INSERT WITH CHECK (auth.uid() = owner_id); | |
| CREATE POLICY "Spaces updatable by owner" ON spaces | |
| FOR UPDATE USING (auth.uid() = owner_id); | |
| CREATE POLICY "Spaces deletable by owner" ON spaces | |
| FOR DELETE USING (auth.uid() = owner_id); | |
| -- Space Members: Viewable by space members | |
| CREATE POLICY "Space members viewable by space members" ON space_members | |
| FOR SELECT USING ( | |
| EXISTS ( | |
| SELECT 1 FROM space_members AS sm | |
| WHERE sm.space_id = space_id AND sm.user_id = auth.uid() | |
| ) | |
| ); | |
| -- Rooms: Viewable by space members | |
| CREATE POLICY "Rooms viewable by space members" ON rooms | |
| FOR SELECT USING ( | |
| EXISTS ( | |
| SELECT 1 FROM space_members | |
| WHERE space_id = rooms.space_id AND user_id = auth.uid() | |
| ) | |
| ); | |
| -- Messages: Viewable by room members | |
| CREATE POLICY "Messages viewable by room members" ON messages | |
| FOR SELECT USING ( | |
| EXISTS ( | |
| SELECT 1 FROM room_members | |
| WHERE room_id = messages.room_id AND user_id = auth.uid() | |
| ) | |
| ); | |
| CREATE POLICY "Users can insert own messages" ON messages | |
| FOR INSERT WITH CHECK (auth.uid() = user_id); | |
| CREATE POLICY "Users can update own messages" ON messages | |
| FOR UPDATE USING (auth.uid() = user_id); | |
| CREATE POLICY "Users can delete own messages" ON messages | |
| FOR DELETE USING (auth.uid() = user_id); | |
| -- DM Conversations: Viewable by participants | |
| CREATE POLICY "DM conversations viewable by participants" ON dm_conversations | |
| FOR SELECT USING (auth.uid() = user1_id OR auth.uid() = user2_id); | |
| -- DM Messages: Viewable by conversation participants | |
| CREATE POLICY "DM messages viewable by participants" ON dm_messages | |
| FOR SELECT USING ( | |
| EXISTS ( | |
| SELECT 1 FROM dm_conversations | |
| WHERE id = conversation_id AND (user1_id = auth.uid() OR user2_id = auth.uid()) | |
| ) | |
| ); | |
| -- Notifications: Viewable by owner | |
| CREATE POLICY "Notifications viewable by owner" ON notifications | |
| FOR SELECT USING (auth.uid() = user_id); | |
| -- Enable Realtime | |
| ALTER PUBLICATION supabase_realtime ADD TABLE messages; | |
| ALTER PUBLICATION supabase_realtime ADD TABLE dm_messages; | |
| ALTER PUBLICATION supabase_realtime ADD TABLE files; | |
| ALTER PUBLICATION supabase_realtime ADD TABLE reactions; | |
| ALTER PUBLICATION supabase_realtime ADD TABLE notifications; | |
| -- Functions for updated_at | |
| CREATE OR REPLACE FUNCTION update_updated_at_column() | |
| RETURNS TRIGGER AS $$ | |
| BEGIN | |
| NEW.updated_at = NOW(); | |
| RETURN NEW; | |
| END; | |
| $$ language 'plpgsql'; | |
| -- Triggers for updated_at | |
| CREATE TRIGGER update_profiles_updated_at BEFORE UPDATE ON profiles | |
| FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); | |
| CREATE TRIGGER update_spaces_updated_at BEFORE UPDATE ON spaces | |
| FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); | |
| CREATE TRIGGER update_rooms_updated_at BEFORE UPDATE ON rooms | |
| FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); | |
| CREATE TRIGGER update_messages_updated_at BEFORE UPDATE ON messages | |
| FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); | |