-- Migration 009: Secure Decentralized Messenger System -- Creates tables for encrypted P2P messaging via IPFS/libp2p -- Messenger profile for each user (one per user) CREATE TABLE IF NOT EXISTS messenger_profiles ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE, messaging_id UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(), peer_id VARCHAR(100), -- libp2p peer ID (when connected) display_name VARCHAR(100), -- Optional display name for messaging status VARCHAR(20) DEFAULT 'offline', -- online, offline, away, busy, invisible status_message VARCHAR(255), -- Custom status message last_seen_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- User encryption keys (multiple keys per user for rotation) CREATE TABLE IF NOT EXISTS messenger_keys ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, key_type VARCHAR(20) NOT NULL, -- 'identity' (Ed25519), 'encryption' (X25519) public_key BYTEA NOT NULL, -- Public key bytes encrypted_private_key BYTEA NOT NULL, -- Private key encrypted with user's derived key key_salt BYTEA NOT NULL, -- Salt for key derivation is_active BOOLEAN DEFAULT TRUE, -- Current active key expires_at TIMESTAMP WITH TIME ZONE, -- Optional expiration for rotation created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), revoked_at TIMESTAMP WITH TIME ZONE, -- When key was revoked UNIQUE(user_id, key_type, is_active) DEFERRABLE INITIALLY DEFERRED ); -- Contacts/friends list with fine-grained control CREATE TABLE IF NOT EXISTS messenger_contacts ( id SERIAL PRIMARY KEY, owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, contact_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, contact_messaging_id UUID NOT NULL, -- Cached messaging_id for fast lookups alias VARCHAR(100), -- Custom name for this contact status VARCHAR(20) DEFAULT 'pending', -- pending, accepted, blocked, muted trust_level INTEGER DEFAULT 0, -- 0-100 trust score notes TEXT, -- Private notes about contact group_ids INTEGER[], -- Array of group IDs this contact belongs to can_see_status BOOLEAN DEFAULT TRUE, -- Can this contact see our status can_send_messages BOOLEAN DEFAULT TRUE, -- Can this contact message us muted_until TIMESTAMP WITH TIME ZONE, -- Temporary mute created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(owner_id, contact_user_id) ); -- Contact groups for organization CREATE TABLE IF NOT EXISTS messenger_contact_groups ( id SERIAL PRIMARY KEY, owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL, color VARCHAR(7), -- Hex color for UI icon VARCHAR(50), -- Icon identifier description TEXT, sort_order INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(owner_id, name) ); -- Blocklist with detailed control CREATE TABLE IF NOT EXISTS messenger_blocklist ( id SERIAL PRIMARY KEY, owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, blocked_user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, blocked_peer_id VARCHAR(100), -- Can block by peer ID even without user blocked_messaging_id UUID, -- Can block by messaging ID reason VARCHAR(255), block_type VARCHAR(20) DEFAULT 'full', -- full, messages_only, status_only expires_at TIMESTAMP WITH TIME ZONE, -- Temporary block created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(owner_id, blocked_user_id), CHECK (blocked_user_id IS NOT NULL OR blocked_peer_id IS NOT NULL OR blocked_messaging_id IS NOT NULL) ); -- Conversations (1:1 and group chats) CREATE TABLE IF NOT EXISTS messenger_conversations ( id SERIAL PRIMARY KEY, conversation_id UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(), type VARCHAR(20) NOT NULL, -- 'direct', 'group', 'channel' name VARCHAR(100), -- Group name (NULL for direct) description TEXT, owner_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- Creator/admin avatar_ipfs_cid VARCHAR(100), -- Group avatar on IPFS encrypted_group_key BYTEA, -- Encrypted symmetric key for group settings JSONB DEFAULT '{}', -- Group settings is_public BOOLEAN DEFAULT FALSE, -- Public groups can be discovered max_members INTEGER DEFAULT 100, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Conversation participants CREATE TABLE IF NOT EXISTS messenger_conversation_members ( id SERIAL PRIMARY KEY, conversation_id UUID NOT NULL REFERENCES messenger_conversations(conversation_id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, role VARCHAR(20) DEFAULT 'member', -- owner, admin, moderator, member joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), invited_by INTEGER REFERENCES users(id), can_send BOOLEAN DEFAULT TRUE, can_invite BOOLEAN DEFAULT FALSE, is_muted BOOLEAN DEFAULT FALSE, muted_until TIMESTAMP WITH TIME ZONE, last_read_at TIMESTAMP WITH TIME ZONE, -- For unread tracking encrypted_member_key BYTEA, -- Member's copy of group key UNIQUE(conversation_id, user_id) ); -- Messages stored with IPFS CID reference CREATE TABLE IF NOT EXISTS messenger_messages ( id SERIAL PRIMARY KEY, message_id UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL REFERENCES messenger_conversations(conversation_id) ON DELETE CASCADE, sender_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, sender_messaging_id UUID NOT NULL, -- Content storage (encrypted content on IPFS) ipfs_cid VARCHAR(100), -- IPFS CID of encrypted content content_preview VARCHAR(100), -- Optional encrypted preview for notifications content_type VARCHAR(50) DEFAULT 'text', -- text, image, file, audio, video, reaction -- Message metadata reply_to_id UUID REFERENCES messenger_messages(message_id), edited_at TIMESTAMP WITH TIME ZONE, deleted_at TIMESTAMP WITH TIME ZONE, -- Soft delete -- Delivery tracking signature BYTEA NOT NULL, -- Ed25519 signature of message nonce BYTEA NOT NULL, -- Encryption nonce created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Message read receipts CREATE TABLE IF NOT EXISTS messenger_read_receipts ( id SERIAL PRIMARY KEY, message_id UUID NOT NULL REFERENCES messenger_messages(message_id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(message_id, user_id) ); -- Peer ID registry for IPFS nodes (for relay/bootstrap management) CREATE TABLE IF NOT EXISTS messenger_peer_registry ( id SERIAL PRIMARY KEY, peer_id VARCHAR(100) NOT NULL UNIQUE, user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- NULL for relay nodes peer_type VARCHAR(20) NOT NULL, -- 'user', 'relay', 'bootstrap' multiaddrs TEXT[], -- Known multiaddresses is_trusted BOOLEAN DEFAULT FALSE, -- Manually trusted is_banned BOOLEAN DEFAULT FALSE, -- Banned from network ban_reason VARCHAR(255), reputation INTEGER DEFAULT 50, -- 0-100 reputation score last_seen_at TIMESTAMP WITH TIME ZONE, first_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), connection_count INTEGER DEFAULT 0, bytes_transferred BIGINT DEFAULT 0, metadata JSONB DEFAULT '{}' ); -- Relay/bootstrap node configuration CREATE TABLE IF NOT EXISTS messenger_relay_nodes ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, peer_id VARCHAR(100) NOT NULL UNIQUE, multiaddrs TEXT[] NOT NULL, region VARCHAR(50), -- Geographic region priority INTEGER DEFAULT 50, -- Higher = preferred is_active BOOLEAN DEFAULT TRUE, is_public BOOLEAN DEFAULT TRUE, -- Public bootstrap or private relay max_connections INTEGER DEFAULT 1000, current_connections INTEGER DEFAULT 0, uptime_percent DECIMAL(5,2) DEFAULT 100, last_health_check TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Indexes for performance CREATE INDEX IF NOT EXISTS idx_messenger_profiles_messaging_id ON messenger_profiles(messaging_id); CREATE INDEX IF NOT EXISTS idx_messenger_profiles_peer_id ON messenger_profiles(peer_id); CREATE INDEX IF NOT EXISTS idx_messenger_profiles_status ON messenger_profiles(status); CREATE INDEX IF NOT EXISTS idx_messenger_keys_user_active ON messenger_keys(user_id, is_active); CREATE INDEX IF NOT EXISTS idx_messenger_keys_type ON messenger_keys(key_type); CREATE INDEX IF NOT EXISTS idx_messenger_contacts_owner ON messenger_contacts(owner_id); CREATE INDEX IF NOT EXISTS idx_messenger_contacts_status ON messenger_contacts(status); CREATE INDEX IF NOT EXISTS idx_messenger_contacts_messaging_id ON messenger_contacts(contact_messaging_id); CREATE INDEX IF NOT EXISTS idx_messenger_blocklist_owner ON messenger_blocklist(owner_id); CREATE INDEX IF NOT EXISTS idx_messenger_blocklist_blocked ON messenger_blocklist(blocked_user_id); CREATE INDEX IF NOT EXISTS idx_messenger_blocklist_peer ON messenger_blocklist(blocked_peer_id); CREATE INDEX IF NOT EXISTS idx_messenger_conversations_type ON messenger_conversations(type); CREATE INDEX IF NOT EXISTS idx_messenger_conversations_owner ON messenger_conversations(owner_id); CREATE INDEX IF NOT EXISTS idx_messenger_members_conversation ON messenger_conversation_members(conversation_id); CREATE INDEX IF NOT EXISTS idx_messenger_members_user ON messenger_conversation_members(user_id); CREATE INDEX IF NOT EXISTS idx_messenger_messages_conversation ON messenger_messages(conversation_id); CREATE INDEX IF NOT EXISTS idx_messenger_messages_sender ON messenger_messages(sender_id); CREATE INDEX IF NOT EXISTS idx_messenger_messages_created ON messenger_messages(created_at DESC); CREATE INDEX IF NOT EXISTS idx_messenger_messages_ipfs ON messenger_messages(ipfs_cid); CREATE INDEX IF NOT EXISTS idx_messenger_peer_registry_type ON messenger_peer_registry(peer_type); CREATE INDEX IF NOT EXISTS idx_messenger_peer_registry_trusted ON messenger_peer_registry(is_trusted); CREATE INDEX IF NOT EXISTS idx_messenger_relay_active ON messenger_relay_nodes(is_active, priority DESC); -- Trigger for updated_at timestamps CREATE OR REPLACE FUNCTION update_messenger_timestamp() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER messenger_profiles_updated BEFORE UPDATE ON messenger_profiles FOR EACH ROW EXECUTE FUNCTION update_messenger_timestamp(); CREATE TRIGGER messenger_contacts_updated BEFORE UPDATE ON messenger_contacts FOR EACH ROW EXECUTE FUNCTION update_messenger_timestamp(); CREATE TRIGGER messenger_contact_groups_updated BEFORE UPDATE ON messenger_contact_groups FOR EACH ROW EXECUTE FUNCTION update_messenger_timestamp(); CREATE TRIGGER messenger_conversations_updated BEFORE UPDATE ON messenger_conversations FOR EACH ROW EXECUTE FUNCTION update_messenger_timestamp(); CREATE TRIGGER messenger_relay_nodes_updated BEFORE UPDATE ON messenger_relay_nodes FOR EACH ROW EXECUTE FUNCTION update_messenger_timestamp(); -- Comments for documentation COMMENT ON TABLE messenger_profiles IS 'User profiles for the secure messenger system'; COMMENT ON TABLE messenger_keys IS 'Encryption and signing keys for E2E encrypted messaging'; COMMENT ON TABLE messenger_contacts IS 'User contact lists with fine-grained permissions'; COMMENT ON TABLE messenger_blocklist IS 'Blocked users/peers with granular control'; COMMENT ON TABLE messenger_conversations IS 'Direct and group conversations'; COMMENT ON TABLE messenger_messages IS 'Message metadata with IPFS content references'; COMMENT ON TABLE messenger_peer_registry IS 'Registry of all known IPFS peers'; COMMENT ON TABLE messenger_relay_nodes IS 'Dedicated relay and bootstrap nodes';