-- Migration 002: Messaging System -- Creates tables for conversations, messages, participants, reactions, calls, and files -- ============================================================================ -- CONVERSATIONS -- ============================================================================ -- Ensure all required columns exist for index creation ALTER TABLE conversations ADD COLUMN IF NOT EXISTS type VARCHAR(20); ALTER TABLE conversations ADD COLUMN IF NOT EXISTS created_by VARCHAR; ALTER TABLE conversations ADD COLUMN IF NOT EXISTS gameforge_project_id VARCHAR; ALTER TABLE conversations ADD COLUMN IF NOT EXISTS is_archived BOOLEAN DEFAULT false; ALTER TABLE conversations ADD COLUMN IF NOT EXISTS created_at TIMESTAMP DEFAULT NOW(); ALTER TABLE conversations ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP DEFAULT NOW(); ALTER TABLE conversations ADD COLUMN IF NOT EXISTS title VARCHAR(200); ALTER TABLE conversations ADD COLUMN IF NOT EXISTS description TEXT; ALTER TABLE conversations ADD COLUMN IF NOT EXISTS avatar_url VARCHAR(500); CREATE TABLE IF NOT EXISTS conversations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), type VARCHAR(20) NOT NULL CHECK (type IN ('direct', 'group', 'channel')), title VARCHAR(200), description TEXT, avatar_url VARCHAR(500), created_by VARCHAR REFERENCES users(id) ON DELETE SET NULL, gameforge_project_id VARCHAR, -- For GameForge integration (future) is_archived BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Create index on type column CREATE INDEX idx_conversations_type ON conversations(type); -- Create index on creator column CREATE INDEX idx_conversations_creator ON conversations(created_by); -- Create index on project column CREATE INDEX idx_conversations_project ON conversations(gameforge_project_id); -- Create index on updated_at column CREATE INDEX idx_conversations_updated ON conversations(updated_at DESC); -- ============================================================================ -- CONVERSATION PARTICIPANTS -- ============================================================================ -- Update conversation_participants to match actual types and remove reference to identities CREATE TABLE IF NOT EXISTS conversation_participants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, user_id VARCHAR NOT NULL REFERENCES users(id) ON DELETE CASCADE, identity_id VARCHAR, role VARCHAR(20) DEFAULT 'member' CHECK (role IN ('admin', 'moderator', 'member')), joined_at TIMESTAMP DEFAULT NOW(), last_read_at TIMESTAMP, notification_settings JSONB DEFAULT '{"enabled": true, "mentions_only": false}'::jsonb, UNIQUE(conversation_id, user_id) ); -- Create index on conversation column CREATE INDEX idx_participants_conversation ON conversation_participants(conversation_id); -- Create index on user column CREATE INDEX idx_participants_user ON conversation_participants(user_id); -- Create index on identity column CREATE INDEX idx_participants_identity ON conversation_participants(identity_id); -- ============================================================================ -- MESSAGES -- ============================================================================ CREATE TABLE IF NOT EXISTS messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, sender_identity_id UUID REFERENCES identities(id) ON DELETE SET NULL, content_encrypted TEXT NOT NULL, -- Encrypted message content content_type VARCHAR(20) DEFAULT 'text' CHECK (content_type IN ('text', 'image', 'video', 'audio', 'file')), metadata JSONB, -- Attachments, mentions, reactions, etc. reply_to_id UUID REFERENCES messages(id) ON DELETE SET NULL, edited_at TIMESTAMP, deleted_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); -- Ensure reply_to_id column exists for index creation ALTER TABLE messages ADD COLUMN IF NOT EXISTS reply_to_id UUID; -- Create index on conversation and created_at columns CREATE INDEX idx_messages_conversation ON messages(conversation_id, created_at DESC); -- Create index on sender column CREATE INDEX idx_messages_sender ON messages(sender_id); -- Create index on reply_to column CREATE INDEX idx_messages_reply_to ON messages(reply_to_id); -- Create index on created_at column CREATE INDEX idx_messages_created ON messages(created_at DESC); -- ============================================================================ -- MESSAGE REACTIONS -- ============================================================================ -- Update message_reactions to match actual types CREATE TABLE IF NOT EXISTS message_reactions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE, user_id VARCHAR NOT NULL REFERENCES users(id) ON DELETE CASCADE, emoji VARCHAR(20) NOT NULL, created_at TIMESTAMP DEFAULT NOW(), UNIQUE(message_id, user_id, emoji) ); -- Create index on message column CREATE INDEX idx_reactions_message ON message_reactions(message_id); -- Create index on user column CREATE INDEX idx_reactions_user ON message_reactions(user_id); -- ============================================================================ -- FILES -- ============================================================================ CREATE TABLE IF NOT EXISTS files ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), uploader_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, conversation_id UUID REFERENCES conversations(id) ON DELETE SET NULL, filename VARCHAR(255) NOT NULL, original_filename VARCHAR(255) NOT NULL, mime_type VARCHAR(100) NOT NULL, size_bytes BIGINT NOT NULL, storage_url VARCHAR(500) NOT NULL, -- GCP Cloud Storage URL or Supabase Storage thumbnail_url VARCHAR(500), -- For images/videos encryption_key TEXT, -- If file is encrypted created_at TIMESTAMP DEFAULT NOW(), expires_at TIMESTAMP -- For temporary files ); -- Ensure uploader_id column exists for index creation ALTER TABLE files ADD COLUMN IF NOT EXISTS uploader_id VARCHAR; -- Ensure conversation_id column exists for index creation ALTER TABLE files ADD COLUMN IF NOT EXISTS conversation_id UUID; -- Create index on uploader column CREATE INDEX idx_files_uploader ON files(uploader_id); -- Create index on conversation column CREATE INDEX idx_files_conversation ON files(conversation_id); -- Create index on created_at column CREATE INDEX idx_files_created ON files(created_at DESC); -- ============================================================================ -- CALLS -- ============================================================================ -- Update calls to match actual types CREATE TABLE IF NOT EXISTS calls ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID REFERENCES conversations(id) ON DELETE SET NULL, type VARCHAR(20) NOT NULL CHECK (type IN ('voice', 'video')), initiator_id VARCHAR NOT NULL REFERENCES users(id) ON DELETE CASCADE, status VARCHAR(20) DEFAULT 'ringing' CHECK (status IN ('ringing', 'active', 'ended', 'missed', 'declined')), started_at TIMESTAMP, ended_at TIMESTAMP, duration_seconds INTEGER, created_at TIMESTAMP DEFAULT NOW() ); -- Create index on conversation column CREATE INDEX idx_calls_conversation ON calls(conversation_id); -- Create index on initiator column CREATE INDEX idx_calls_initiator ON calls(initiator_id); -- Create index on status column CREATE INDEX idx_calls_status ON calls(status); -- Create index on created_at column CREATE INDEX idx_calls_created ON calls(created_at DESC); -- ============================================================================ -- CALL PARTICIPANTS -- ============================================================================ -- Update call_participants to match actual types CREATE TABLE IF NOT EXISTS call_participants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), call_id UUID NOT NULL REFERENCES calls(id) ON DELETE CASCADE, user_id VARCHAR NOT NULL REFERENCES users(id) ON DELETE CASCADE, joined_at TIMESTAMP, left_at TIMESTAMP, media_state JSONB DEFAULT '{"audio": true, "video": false, "screen_share": false}'::jsonb, UNIQUE(call_id, user_id) ); -- Create index on call column CREATE INDEX idx_call_participants_call ON call_participants(call_id); -- Create index on user column CREATE INDEX idx_call_participants_user ON call_participants(user_id); -- ============================================================================ -- FUNCTIONS AND TRIGGERS -- ============================================================================ -- Function to update conversation updated_at timestamp when messages are added CREATE OR REPLACE FUNCTION update_conversation_timestamp() RETURNS TRIGGER AS $$ BEGIN UPDATE conversations SET updated_at = NOW() WHERE id = NEW.conversation_id; RETURN NEW; END; $$ LANGUAGE plpgsql; -- Trigger to update conversation timestamp on new message CREATE TRIGGER trigger_update_conversation_timestamp AFTER INSERT ON messages FOR EACH ROW EXECUTE FUNCTION update_conversation_timestamp(); -- Function to automatically create direct conversation if it doesn't exist CREATE OR REPLACE FUNCTION get_or_create_direct_conversation(user1_id UUID, user2_id UUID) RETURNS UUID AS $$ DECLARE conv_id UUID; BEGIN -- Try to find existing direct conversation between these users SELECT c.id INTO conv_id FROM conversations c WHERE c.type = 'direct' AND EXISTS ( SELECT 1 FROM conversation_participants cp1 WHERE cp1.conversation_id = c.id AND cp1.user_id = user1_id ) AND EXISTS ( SELECT 1 FROM conversation_participants cp2 WHERE cp2.conversation_id = c.id AND cp2.user_id = user2_id ); -- If not found, create new direct conversation IF conv_id IS NULL THEN INSERT INTO conversations (type, created_by) VALUES ('direct', user1_id) RETURNING id INTO conv_id; -- Add both participants INSERT INTO conversation_participants (conversation_id, user_id) VALUES (conv_id, user1_id), (conv_id, user2_id); END IF; RETURN conv_id; END; $$ LANGUAGE plpgsql;