-- Velocity Comms Schema -- Run this migration against your asyncpg pool database. BEGIN; CREATE EXTENSION IF NOT EXISTS pgcrypto; -- Threads (conversations) CREATE TABLE IF NOT EXISTS comms_threads ( thread_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), provider TEXT NOT NULL DEFAULT 'mock', external_thread_id TEXT, person_id UUID NULL REFERENCES crm_people(person_id) ON DELETE SET NULL, phone_e164 TEXT NOT NULL, display_name TEXT, channel TEXT NOT NULL DEFAULT 'whatsapp', status TEXT NOT NULL DEFAULT 'open', assigned_user_id UUID NULL, last_message_at TIMESTAMPTZ, unread_count INT NOT NULL DEFAULT 0, metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_comms_threads_phone ON comms_threads(phone_e164); CREATE INDEX IF NOT EXISTS idx_comms_threads_person ON comms_threads(person_id) WHERE person_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_comms_threads_status ON comms_threads(status); CREATE INDEX IF NOT EXISTS idx_comms_threads_last_message ON comms_threads(last_message_at DESC NULLS LAST); -- Messages CREATE TABLE IF NOT EXISTS comms_messages ( message_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), thread_id UUID NOT NULL REFERENCES comms_threads(thread_id) ON DELETE CASCADE, provider TEXT NOT NULL DEFAULT 'mock', external_message_id TEXT, direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound', 'system')), message_type TEXT NOT NULL DEFAULT 'text', body TEXT NOT NULL DEFAULT '', media_url TEXT, media_mime_type TEXT, delivery_status TEXT NOT NULL DEFAULT 'pending', sent_at TIMESTAMPTZ, delivered_at TIMESTAMPTZ, read_at TIMESTAMPTZ, raw_payload JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_comms_messages_thread ON comms_messages(thread_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_comms_messages_external ON comms_messages(external_message_id) WHERE external_message_id IS NOT NULL; -- Call logs CREATE TABLE IF NOT EXISTS comms_call_logs ( call_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), thread_id UUID NULL REFERENCES comms_threads(thread_id) ON DELETE SET NULL, person_id UUID NULL REFERENCES crm_people(person_id) ON DELETE SET NULL, provider TEXT NOT NULL DEFAULT 'mock', external_call_id TEXT, phone_e164 TEXT NOT NULL, direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')), status TEXT NOT NULL DEFAULT 'completed', started_at TIMESTAMPTZ NOT NULL, ended_at TIMESTAMPTZ, duration_seconds INT, recording_url TEXT, transcript_id UUID, transcript_text TEXT, raw_payload JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_comms_call_logs_phone ON comms_call_logs(phone_e164); CREATE INDEX IF NOT EXISTS idx_comms_call_logs_thread ON comms_call_logs(thread_id) WHERE thread_id IS NOT NULL; -- Settings (key-value JSON) CREATE TABLE IF NOT EXISTS comms_settings ( key TEXT PRIMARY KEY, value_json JSONB NOT NULL DEFAULT '{}'::jsonb, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Insert default settings INSERT INTO comms_settings (key, value_json) VALUES ('config', '{ "provider": "mock", "provider_base_url": "", "provider_api_key": "", "instance_id": "", "phone_number_id": "", "webhook_callback_url": "", "webhook_secret_set": false, "default_assignment_user_id": null, "auto_link_by_phone": true, "create_crm_interaction_on_inbound": true, "default_country_code": "91", "media_storage_dir": "/opt/dlami/nvme/assets/comms", "transcription_provider": "none" }'::jsonb) ON CONFLICT (key) DO NOTHING; COMMIT;