-- Migration 002: Messaging System -- Creates tables for conversations, messages, participants, reactions, calls, and files -- ============================================================================ -- CONVERSATIONS -- ============================================================================ 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, gameforge_project_id VARCHAR, is_archived BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_conversations_type ON conversations(type); CREATE INDEX IF NOT EXISTS idx_conversations_creator ON conversations(created_by); CREATE INDEX IF NOT EXISTS idx_conversations_project ON conversations(gameforge_project_id); CREATE INDEX IF NOT EXISTS idx_conversations_updated ON conversations(updated_at DESC); -- ============================================================================ -- CONVERSATION PARTICIPANTS -- ============================================================================ 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 UUID NOT NULL, 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 IF NOT EXISTS idx_participants_conversation ON conversation_participants(conversation_id); CREATE INDEX IF NOT EXISTS idx_participants_user ON conversation_participants(user_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, content_encrypted TEXT NOT NULL, content_type VARCHAR(20) DEFAULT 'text' CHECK (content_type IN ('text', 'image', 'video', 'audio', 'file')), metadata JSONB, reply_to_id UUID REFERENCES messages(id) ON DELETE SET NULL, edited_at TIMESTAMP, deleted_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_messages_sender ON messages(sender_id); CREATE INDEX IF NOT EXISTS idx_messages_reply_to ON messages(reply_to_id); CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at DESC); -- ============================================================================ -- MESSAGE REACTIONS -- ============================================================================ 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 UUID NOT NULL, emoji VARCHAR(20) NOT NULL, created_at TIMESTAMP DEFAULT NOW(), UNIQUE(message_id, user_id, emoji) ); CREATE INDEX IF NOT EXISTS idx_reactions_message ON message_reactions(message_id); CREATE INDEX IF NOT EXISTS idx_reactions_user ON message_reactions(user_id); -- ============================================================================ -- MESSAGE ATTACHMENTS -- ============================================================================ CREATE TABLE IF NOT EXISTS message_attachments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE, file_name VARCHAR(500) NOT NULL, file_url VARCHAR(1000) NOT NULL, file_size INTEGER, file_type VARCHAR(100), uploaded_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_attachments_message ON message_attachments(message_id); -- ============================================================================ -- CALLS -- ============================================================================ CREATE TABLE IF NOT EXISTS calls ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID REFERENCES conversations(id) ON DELETE SET NULL, call_type VARCHAR(20) NOT NULL CHECK (call_type IN ('audio', 'video', 'screen_share')), initiator_id UUID NOT NULL, status VARCHAR(20) DEFAULT 'initiated' CHECK (status IN ('initiated', 'ringing', 'active', 'ended', 'missed', 'declined')), started_at TIMESTAMP, ended_at TIMESTAMP, duration_seconds INTEGER, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_calls_conversation ON calls(conversation_id); CREATE INDEX IF NOT EXISTS idx_calls_initiator ON calls(initiator_id); CREATE INDEX IF NOT EXISTS idx_calls_status ON calls(status); CREATE INDEX IF NOT EXISTS idx_calls_created ON calls(created_at DESC); -- ============================================================================ -- CALL PARTICIPANTS -- ============================================================================ 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 UUID NOT NULL, joined_at TIMESTAMP, left_at TIMESTAMP, is_muted BOOLEAN DEFAULT false, is_camera_on BOOLEAN DEFAULT true, UNIQUE(call_id, user_id) ); CREATE INDEX IF NOT EXISTS idx_call_participants_call ON call_participants(call_id); CREATE INDEX IF NOT EXISTS 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 DROP TRIGGER IF EXISTS trigger_update_conversation_timestamp ON messages; CREATE TRIGGER trigger_update_conversation_timestamp AFTER INSERT ON messages FOR EACH ROW EXECUTE FUNCTION update_conversation_timestamp();