-- 091_messaging_redesign.sql -- Unified conversation model: replaces separate private_messages + message_group -- Conversations (1:1 and group), messages, reactions, pins BEGIN; -- Unified conversations (1:1 or group) CREATE TABLE conversations ( id SERIAL PRIMARY KEY, name VARCHAR(255), is_group BOOLEAN NOT NULL DEFAULT FALSE, owner_id INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), last_message_id INTEGER -- FK added below after conv_messages exists ); CREATE INDEX idx_conversations_owner ON conversations(owner_id); CREATE INDEX idx_conversations_updated ON conversations(updated_at DESC); -- Conversation membership CREATE TABLE conversation_members ( conversation_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, role VARCHAR(20) NOT NULL DEFAULT 'member', last_read_at TIMESTAMP, is_muted BOOLEAN NOT NULL DEFAULT FALSE, is_archived BOOLEAN NOT NULL DEFAULT FALSE, joined_at TIMESTAMP NOT NULL DEFAULT NOW(), added_by_id INTEGER REFERENCES users(id) ON DELETE SET NULL, PRIMARY KEY (conversation_id, user_id) ); CREATE INDEX idx_conversation_members_user ON conversation_members(user_id); -- Messages within a conversation CREATE TABLE conv_messages ( id SERIAL PRIMARY KEY, conversation_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, sender_id INTEGER REFERENCES users(id) ON DELETE SET NULL, content TEXT NOT NULL, reply_to_id INTEGER REFERENCES conv_messages(id) ON DELETE SET NULL, edited_at TIMESTAMP, is_deleted BOOLEAN NOT NULL DEFAULT FALSE, link_preview JSONB, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_conv_messages_conversation ON conv_messages(conversation_id); CREATE INDEX idx_conv_messages_created ON conv_messages(created_at); CREATE INDEX idx_conv_messages_conversation_created ON conv_messages(conversation_id, created_at); -- Now add the deferred FK from conversations.last_message_id -> conv_messages.id ALTER TABLE conversations ADD CONSTRAINT fk_conversations_last_message FOREIGN KEY (last_message_id) REFERENCES conv_messages(id) ON DELETE SET NULL; -- Emoji reactions on messages CREATE TABLE message_reactions ( id SERIAL PRIMARY KEY, message_id INTEGER NOT NULL REFERENCES conv_messages(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, emoji VARCHAR(10) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), CONSTRAINT uq_message_reaction UNIQUE (message_id, user_id, emoji) ); CREATE INDEX idx_message_reactions_message ON message_reactions(message_id); -- Pinned messages CREATE TABLE message_pins ( id SERIAL PRIMARY KEY, conversation_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, message_id INTEGER NOT NULL REFERENCES conv_messages(id) ON DELETE CASCADE, pinned_by_id INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_message_pins_conversation ON message_pins(conversation_id); -- Extend message_attachments to support conv_messages ALTER TABLE message_attachments ADD COLUMN conv_message_id INTEGER REFERENCES conv_messages(id) ON DELETE CASCADE; CREATE INDEX idx_message_attachments_conv ON message_attachments(conv_message_id) WHERE conv_message_id IS NOT NULL; -- Grants GRANT ALL ON TABLE conversations TO nordabiz_app; GRANT ALL ON TABLE conversation_members TO nordabiz_app; GRANT ALL ON TABLE conv_messages TO nordabiz_app; GRANT ALL ON TABLE message_reactions TO nordabiz_app; GRANT ALL ON TABLE message_pins TO nordabiz_app; GRANT USAGE, SELECT ON SEQUENCE conversations_id_seq TO nordabiz_app; GRANT USAGE, SELECT ON SEQUENCE conv_messages_id_seq TO nordabiz_app; GRANT USAGE, SELECT ON SEQUENCE message_reactions_id_seq TO nordabiz_app; GRANT USAGE, SELECT ON SEQUENCE message_pins_id_seq TO nordabiz_app; COMMIT;