-- GoChat Initial Schema -- Reference: Chatwoot db/migrate/ initial schema migration -- Creates core tables matching Chatwoot's data model -- Accounts (ref: Chatwoot accounts table) CREATE TABLE IF NOT EXISTS accounts ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, locale VARCHAR(10) DEFAULT 'en', timezone VARCHAR(50) DEFAULT 'UTC', domain VARCHAR(255), feature_flags TEXT DEFAULT '{}', limits JSONB NOT NULL DEFAULT '{}', status VARCHAR(50) DEFAULT 'active', active BOOLEAN DEFAULT TRUE, auto_resolve_duration INTEGER DEFAULT 0, inbox_limit INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_accounts_deleted_at ON accounts(deleted_at); CREATE INDEX idx_accounts_status ON accounts(status) WHERE deleted_at IS NULL; -- Users (ref: Chatwoot users table) CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, password_digest VARCHAR(255), provider VARCHAR(50) DEFAULT 'email', uid VARCHAR(255), avatar_url VARCHAR(512), display_name VARCHAR(255), role VARCHAR(50) DEFAULT 'agent', type VARCHAR(50) DEFAULT 'user', active BOOLEAN DEFAULT TRUE, available BOOLEAN DEFAULT FALSE, availability_status VARCHAR(50) DEFAULT 'offline', confirmed BOOLEAN DEFAULT FALSE, totp_secret VARCHAR(255), totp_enabled BOOLEAN DEFAULT FALSE, custom_role_id INTEGER, sign_in_count INTEGER DEFAULT 0, confirmed_at TIMESTAMP WITH TIME ZONE, last_sign_in_at TIMESTAMP WITH TIME ZONE, current_sign_in_at TIMESTAMP WITH TIME ZONE, locale VARCHAR(10) DEFAULT 'en', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_users_deleted_at ON users(deleted_at); CREATE INDEX idx_users_email ON users(email) WHERE deleted_at IS NULL; CREATE INDEX idx_users_account_id ON users(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_users_availability ON users(availability_status) WHERE deleted_at IS NULL; CREATE INDEX idx_users_custom_role_id ON users(custom_role_id) WHERE deleted_at IS NULL; -- Account Users (ref: Chatwoot account_users join table) CREATE TABLE IF NOT EXISTS account_users ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, account_id INTEGER NOT NULL, role VARCHAR(50) NOT NULL DEFAULT 'agent', custom_role_id INTEGER DEFAULT 0, availability VARCHAR(50) DEFAULT 'offline', invited_by INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE, UNIQUE(user_id, account_id) ); CREATE INDEX idx_account_users_deleted_at ON account_users(deleted_at); CREATE INDEX idx_account_users_user_id ON account_users(user_id) WHERE deleted_at IS NULL; CREATE INDEX idx_account_users_account_id ON account_users(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_account_users_role ON account_users(role) WHERE deleted_at IS NULL; -- Custom Roles (ref: Chatwoot enterprise/app/models/custom_role.rb) CREATE TABLE IF NOT EXISTS custom_roles ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, permissions JSONB NOT NULL DEFAULT '[]', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_custom_roles_deleted_at ON custom_roles(deleted_at); CREATE INDEX idx_custom_roles_account_id ON custom_roles(account_id) WHERE deleted_at IS NULL; -- Contacts (ref: Chatwoot contacts table) CREATE TABLE IF NOT EXISTS contacts ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, name VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(50), avatar_url VARCHAR(512), identifier VARCHAR(255), last_activity_at BIGINT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_contacts_deleted_at ON contacts(deleted_at); CREATE INDEX idx_contacts_account_id ON contacts(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_contacts_email ON contacts(email) WHERE deleted_at IS NULL; CREATE INDEX idx_contacts_phone_number ON contacts(phone_number) WHERE deleted_at IS NULL; CREATE INDEX idx_contacts_identifier ON contacts(identifier) WHERE deleted_at IS NULL; CREATE INDEX idx_contacts_last_activity_at ON contacts(last_activity_at) WHERE deleted_at IS NULL; -- Inboxes (ref: Chatwoot inboxes table) CREATE TABLE IF NOT EXISTS inboxes ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, channel_type VARCHAR(50) NOT NULL, channel_id INTEGER NOT NULL, enable_auto_assignment BOOLEAN DEFAULT FALSE, auto_assignment_limit INTEGER DEFAULT 0, enabled BOOLEAN DEFAULT TRUE, channel_config TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_inboxes_deleted_at ON inboxes(deleted_at); CREATE INDEX idx_inboxes_account_id ON inboxes(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_inboxes_channel_type ON inboxes(channel_type) WHERE deleted_at IS NULL; CREATE INDEX idx_inboxes_channel_id ON inboxes(channel_id) WHERE deleted_at IS NULL; -- Contact Inboxes (ref: Chatwoot contact_inboxes) CREATE TABLE IF NOT EXISTS contact_inboxes ( id SERIAL PRIMARY KEY, contact_id INTEGER NOT NULL, inbox_id INTEGER NOT NULL, source_id VARCHAR(255), hmac_token VARCHAR(255), pubsub_token VARCHAR(255), has_around_flag BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_contact_inboxes_deleted_at ON contact_inboxes(deleted_at); CREATE INDEX idx_contact_inboxes_contact_id ON contact_inboxes(contact_id) WHERE deleted_at IS NULL; CREATE INDEX idx_contact_inboxes_inbox_id ON contact_inboxes(inbox_id) WHERE deleted_at IS NULL; CREATE INDEX idx_contact_inboxes_source_id ON contact_inboxes(source_id) WHERE deleted_at IS NULL; -- Inbox Members (ref: Chatwoot inbox_members) CREATE TABLE IF NOT EXISTS inbox_members ( id SERIAL PRIMARY KEY, inbox_id INTEGER NOT NULL, user_id INTEGER NOT NULL, role VARCHAR(50) DEFAULT 'agent', availability_status VARCHAR(50) DEFAULT 'offline', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_inbox_members_deleted_at ON inbox_members(deleted_at); CREATE INDEX idx_inbox_members_inbox_id ON inbox_members(inbox_id) WHERE deleted_at IS NULL; CREATE INDEX idx_inbox_members_user_id ON inbox_members(user_id) WHERE deleted_at IS NULL; -- Conversations (ref: Chatwoot conversations table) CREATE TABLE IF NOT EXISTS conversations ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, inbox_id INTEGER NOT NULL, contact_id INTEGER NOT NULL, assignee_id INTEGER, status VARCHAR(50) DEFAULT 'open', priority VARCHAR(50) DEFAULT 'none', channel_type VARCHAR(50) NOT NULL, channel VARCHAR(50) NOT NULL, labels TEXT, last_message_at BIGINT, last_non_system_message_at BIGINT, uuid VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_conversations_deleted_at ON conversations(deleted_at); CREATE INDEX idx_conversations_account ON conversations(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_conversations_inbox ON conversations(inbox_id) WHERE deleted_at IS NULL; CREATE INDEX idx_conversations_contact ON conversations(contact_id) WHERE deleted_at IS NULL; CREATE INDEX idx_conversations_status ON conversations(status) WHERE deleted_at IS NULL; CREATE INDEX idx_conversations_assignee ON conversations(assignee_id) WHERE deleted_at IS NULL; CREATE INDEX idx_conversations_last_message ON conversations(last_message_at) WHERE deleted_at IS NULL; -- Messages (ref: Chatwoot messages table) CREATE TABLE IF NOT EXISTS messages ( id SERIAL PRIMARY KEY, conversation_id INTEGER NOT NULL REFERENCES conversations(id), account_id INTEGER NOT NULL REFERENCES accounts(id), inbox_id INTEGER NOT NULL REFERENCES inboxes(id), sender_id INTEGER, sender_type VARCHAR(50) NOT NULL, content TEXT NOT NULL, content_type VARCHAR(50) DEFAULT 'text', message_type VARCHAR(50) NOT NULL DEFAULT 'incoming', private BOOLEAN DEFAULT FALSE, external BOOLEAN DEFAULT FALSE, source_id VARCHAR(255), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_messages_deleted_at ON messages(deleted_at); CREATE INDEX idx_messages_conversation ON messages(conversation_id) WHERE deleted_at IS NULL; CREATE INDEX idx_messages_account ON messages(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_messages_inbox ON messages(inbox_id) WHERE deleted_at IS NULL; CREATE INDEX idx_messages_sender ON messages(sender_id, sender_type) WHERE deleted_at IS NULL; CREATE INDEX idx_messages_message_type ON messages(message_type) WHERE deleted_at IS NULL; -- Attachments (ref: Chatwoot attachments) CREATE TABLE IF NOT EXISTS attachments ( id SERIAL PRIMARY KEY, message_id INTEGER NOT NULL, account_id INTEGER NOT NULL, file_type VARCHAR(50) NOT NULL, external_url VARCHAR(512), file_url VARCHAR(512), thumb_url VARCHAR(512), file_size INTEGER, file_name VARCHAR(255), width INTEGER DEFAULT 0, height INTEGER DEFAULT 0, alt_text VARCHAR(255), metadata JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_attachments_deleted_at ON attachments(deleted_at); CREATE INDEX idx_attachments_message_id ON attachments(message_id) WHERE deleted_at IS NULL; CREATE INDEX idx_attachments_account_id ON attachments(account_id) WHERE deleted_at IS NULL; -- Notifications (ref: Chatwoot notifications) CREATE TABLE IF NOT EXISTS notifications ( id SERIAL PRIMARY KEY, account_id INTEGER, user_id INTEGER NOT NULL, notification_type VARCHAR(100) NOT NULL, primary_actor_type VARCHAR(100), primary_actor_id INTEGER, secondary_actor_type VARCHAR(100), secondary_actor_id INTEGER, read_at TIMESTAMP WITH TIME ZONE, push_enabled BOOLEAN DEFAULT FALSE, email_enabled BOOLEAN DEFAULT FALSE, additional_attributes JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_notifications_deleted_at ON notifications(deleted_at); CREATE INDEX idx_notifications_user_id ON notifications(user_id) WHERE deleted_at IS NULL; CREATE INDEX idx_notifications_account_id ON notifications(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_notifications_type ON notifications(notification_type) WHERE deleted_at IS NULL; CREATE INDEX idx_notifications_created_at ON notifications(created_at) WHERE deleted_at IS NULL; -- Notification Preferences (ref: Chatwoot notification_preferences) CREATE TABLE IF NOT EXISTS notification_preferences ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, account_id INTEGER, channel VARCHAR(100) NOT NULL, event_type VARCHAR(100) NOT NULL, enabled BOOLEAN DEFAULT TRUE, preferences JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, account_id, channel, event_type) ); CREATE INDEX idx_notification_preferences_user_id ON notification_preferences(user_id); -- Platform Apps (ref: Chatwoot platform_apps / agent_bots) CREATE TABLE IF NOT EXISTS platform_apps ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, api_key VARCHAR(255) NOT NULL UNIQUE, account_id INTEGER, description VARCHAR(500), type VARCHAR(50) DEFAULT 'api', status VARCHAR(50) DEFAULT 'active', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_platform_apps_deleted_at ON platform_apps(deleted_at); CREATE INDEX idx_platform_apps_api_key ON platform_apps(api_key) WHERE deleted_at IS NULL; CREATE INDEX idx_platform_apps_account_id ON platform_apps(account_id) WHERE deleted_at IS NULL; -- Reporting Events (ref: Chatwoot reporting_events) CREATE TABLE IF NOT EXISTS reporting_events ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, inbox_id INTEGER, user_id INTEGER, conversation_id INTEGER, name VARCHAR(50) NOT NULL, value DOUBLE PRECISION NOT NULL, value_in_business_hours DOUBLE PRECISION DEFAULT 0, event_start_time TIMESTAMP WITH TIME ZONE, event_end_time TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_reporting_events_deleted_at ON reporting_events(deleted_at); CREATE INDEX idx_reporting_events_account_id ON reporting_events(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_reporting_events_inbox_id ON reporting_events(inbox_id) WHERE deleted_at IS NULL; CREATE INDEX idx_reporting_events_user_id ON reporting_events(user_id) WHERE deleted_at IS NULL; CREATE INDEX idx_reporting_events_conversation_id ON reporting_events(conversation_id) WHERE deleted_at IS NULL; CREATE INDEX idx_reporting_events_name ON reporting_events(name) WHERE deleted_at IS NULL; -- Reporting Events Rollups (ref: Chatwoot reporting_events_rollups) CREATE TABLE IF NOT EXISTS reporting_events_rollups ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, dimension VARCHAR(50) NOT NULL, dimension_value VARCHAR(255) NOT NULL, metric_name VARCHAR(50) NOT NULL, value DOUBLE PRECISION NOT NULL, value_in_business_hours DOUBLE PRECISION DEFAULT 0, period VARCHAR(50) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE, UNIQUE(account_id, dimension, dimension_value, metric_name, period) ); CREATE INDEX idx_reporting_events_rollups_deleted_at ON reporting_events_rollups(deleted_at); -- Dashboard Apps (ref: Chatwoot dashboard_apps) CREATE TABLE IF NOT EXISTS dashboard_apps ( id SERIAL PRIMARY KEY, title VARCHAR(255) NOT NULL, description TEXT, icon VARCHAR(255), url TEXT NOT NULL, type VARCHAR(50) DEFAULT 'internal', enabled BOOLEAN DEFAULT TRUE, position INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_dashboard_apps_deleted_at ON dashboard_apps(deleted_at); -- Channels base table (ref: Chatwoot channelable pattern) CREATE TABLE IF NOT EXISTS channels ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, inbox_id INTEGER NOT NULL, channel_type VARCHAR(50) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_channels_deleted_at ON channels(deleted_at); CREATE INDEX idx_channels_account_id ON channels(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_channels_inbox_id ON channels(inbox_id) WHERE deleted_at IS NULL; CREATE INDEX idx_channels_channel_type ON channels(channel_type) WHERE deleted_at IS NULL; -- Channel: Telegram CREATE TABLE IF NOT EXISTS channel_telegrams ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, inbox_id INTEGER, bot_token VARCHAR(255) NOT NULL UNIQUE, bot_name VARCHAR(255), welcome_message TEXT, reauthorization_required BOOLEAN DEFAULT FALSE, webhook_url VARCHAR(512), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_channel_telegrams_deleted_at ON channel_telegrams(deleted_at); CREATE INDEX idx_channel_telegrams_account_id ON channel_telegrams(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_channel_telegrams_inbox_id ON channel_telegrams(inbox_id) WHERE deleted_at IS NULL; -- Channel: Web Widget CREATE TABLE IF NOT EXISTS channel_web_widgets ( inbox_id INTEGER NOT NULL UNIQUE, website_url VARCHAR(1024) NOT NULL, welcome_title VARCHAR(255), welcome_tagline VARCHAR(255), widget_color VARCHAR(20) DEFAULT '#1f93ff', reply_time VARCHAR(50) DEFAULT 'in_a_few_minutes', hmac_token VARCHAR(255), pre_chat_form_enabled BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_channel_web_widgets_inbox_id ON channel_web_widgets(inbox_id) WHERE deleted_at IS NULL; -- Channel: Facebook Pages CREATE TABLE IF NOT EXISTS channel_facebook_pages ( inbox_id INTEGER NOT NULL UNIQUE, page_id VARCHAR(255) NOT NULL, page_access_token VARCHAR(512) NOT NULL, page_name VARCHAR(255), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_channel_facebook_pages_inbox_id ON channel_facebook_pages(inbox_id) WHERE deleted_at IS NULL; -- Channel: WhatsApp CREATE TABLE IF NOT EXISTS channel_whatsapps ( inbox_id INTEGER NOT NULL UNIQUE, phone_number_id VARCHAR(255) NOT NULL, business_id VARCHAR(255), whatsapp_account_name VARCHAR(255), access_token VARCHAR(512) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX idx_channel_whatsapps_inbox_id ON channel_whatsapps(inbox_id) WHERE deleted_at IS NULL;