-- 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();