-- Migration 014: Create messaging tables -- Conversation: message thread -- ConversationParticipant: user participation (polymorphic: team_profile or customer_profile) -- Message: individual message -- MessageReadReceipt: tracks when messages are read -- Message thread/conversation CREATE TABLE conversations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), subject VARCHAR(500), conversation_type conversation_type NOT NULL DEFAULT 'DIRECT', -- Polymorphic: entity this conversation is about entity_type VARCHAR(50), entity_id UUID, -- Polymorphic: who created the conversation created_by_type VARCHAR(20), -- 'team_profile' or 'customer_profile' created_by_id UUID, last_message_at TIMESTAMPTZ, is_archived BOOLEAN NOT NULL DEFAULT FALSE, metadata JSONB NOT NULL DEFAULT '{}'::JSONB ); -- Conversation participant (polymorphic) CREATE TABLE conversation_participants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, -- Polymorphic: participant type participant_type VARCHAR(20) NOT NULL, -- 'team_profile' or 'customer_profile' participant_id UUID NOT NULL, last_read_at TIMESTAMPTZ, unread_count INTEGER NOT NULL DEFAULT 0 CHECK (unread_count >= 0), is_muted BOOLEAN NOT NULL DEFAULT FALSE, is_archived BOOLEAN NOT NULL DEFAULT FALSE, joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- One entry per participant per conversation UNIQUE (conversation_id, participant_type, participant_id) ); -- Individual message CREATE TABLE messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, -- Polymorphic: author type author_type VARCHAR(20) NOT NULL, -- 'team_profile' or 'customer_profile' author_id UUID NOT NULL, content TEXT NOT NULL, is_deleted BOOLEAN NOT NULL DEFAULT FALSE, -- For threaded replies reply_to_id UUID REFERENCES messages(id) ON DELETE SET NULL, -- File attachments (JSONB array of attachment metadata) attachments JSONB NOT NULL DEFAULT '[]'::JSONB, -- System-generated message (e.g., "User joined conversation") is_system_message BOOLEAN NOT NULL DEFAULT FALSE, -- Additional metadata (formatting, mentions, etc.) metadata JSONB NOT NULL DEFAULT '{}'::JSONB ); -- Message read receipts (tracks when specific messages are read) CREATE TABLE message_read_receipts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE, -- Polymorphic: reader type reader_type VARCHAR(20) NOT NULL, -- 'team_profile' or 'customer_profile' reader_id UUID NOT NULL, read_at TIMESTAMPTZ NOT NULL, -- One receipt per message per reader UNIQUE (message_id, reader_type, reader_id) ); -- Indexes CREATE INDEX idx_conversations_type ON conversations(conversation_type); CREATE INDEX idx_conversations_entity ON conversations(entity_type, entity_id); CREATE INDEX idx_conversations_last_message ON conversations(last_message_at DESC); CREATE INDEX idx_conversation_participants_conversation ON conversation_participants(conversation_id); CREATE INDEX idx_conversation_participants_participant ON conversation_participants(participant_type, participant_id); CREATE INDEX idx_messages_conversation ON messages(conversation_id); CREATE INDEX idx_messages_author ON messages(author_type, author_id); CREATE INDEX idx_messages_created ON messages(created_at DESC); CREATE INDEX idx_message_read_receipts_message ON message_read_receipts(message_id);