\set ON_ERROR_STOP on BEGIN; CREATE TABLE IF NOT EXISTS bot_instance ( id TEXT PRIMARY KEY, name TEXT NOT NULL, enabled BOOLEAN NOT NULL DEFAULT TRUE, access_password TEXT NOT NULL DEFAULT '', workspace_dir TEXT NOT NULL UNIQUE, docker_status TEXT NOT NULL DEFAULT 'STOPPED', current_state TEXT DEFAULT 'IDLE', last_action TEXT, image_tag TEXT NOT NULL DEFAULT 'nanobot-base:v0.1.4', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS bot_image ( tag TEXT PRIMARY KEY, image_id TEXT, version TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'READY', source_dir TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS bot_message ( id SERIAL PRIMARY KEY, bot_id TEXT NOT NULL REFERENCES bot_instance(id), role TEXT NOT NULL, text TEXT NOT NULL, media_json TEXT, feedback TEXT, feedback_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS sys_login_log ( id SERIAL PRIMARY KEY, auth_type TEXT NOT NULL, token_hash TEXT NOT NULL, subject_id TEXT NOT NULL, bot_id TEXT, auth_source TEXT NOT NULL DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, last_seen_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, revoked_at TIMESTAMP, revoke_reason TEXT, client_ip TEXT, user_agent TEXT, device_info TEXT ); CREATE TABLE IF NOT EXISTS sys_setting ( key VARCHAR(120) PRIMARY KEY, name VARCHAR(200) NOT NULL DEFAULT '', category VARCHAR(64) NOT NULL DEFAULT 'general', description TEXT NOT NULL DEFAULT '', value_type VARCHAR(32) NOT NULL DEFAULT 'json', value_json TEXT NOT NULL DEFAULT '{}', is_public BOOLEAN NOT NULL DEFAULT FALSE, sort_order INTEGER NOT NULL DEFAULT 100, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS bot_request_usage ( id SERIAL PRIMARY KEY, bot_id TEXT NOT NULL REFERENCES bot_instance(id), message_id INTEGER, request_id VARCHAR(120) NOT NULL, channel VARCHAR(64) NOT NULL DEFAULT 'dashboard', status VARCHAR(32) NOT NULL DEFAULT 'PENDING', provider VARCHAR(120), model VARCHAR(255), token_source VARCHAR(32) NOT NULL DEFAULT 'estimated', input_tokens INTEGER NOT NULL DEFAULT 0, output_tokens INTEGER NOT NULL DEFAULT 0, total_tokens INTEGER NOT NULL DEFAULT 0, input_text_preview TEXT, output_text_preview TEXT, attachments_json TEXT, error_text TEXT, metadata_json TEXT, started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS bot_activity_event ( id SERIAL PRIMARY KEY, bot_id TEXT NOT NULL REFERENCES bot_instance(id), request_id VARCHAR(120), event_type VARCHAR(64) NOT NULL, channel VARCHAR(64) NOT NULL DEFAULT 'dashboard', detail TEXT, metadata_json TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS skill_market_item ( id SERIAL PRIMARY KEY, skill_key VARCHAR(120) NOT NULL, display_name VARCHAR(255) NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', zip_filename VARCHAR(255) NOT NULL, zip_size_bytes INTEGER NOT NULL DEFAULT 0, entry_names_json TEXT NOT NULL DEFAULT '[]', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_skill_market_item_skill_key UNIQUE (skill_key), CONSTRAINT uq_skill_market_item_zip_filename UNIQUE (zip_filename) ); CREATE TABLE IF NOT EXISTS bot_skill_install ( id SERIAL PRIMARY KEY, bot_id TEXT NOT NULL REFERENCES bot_instance(id), skill_market_item_id INTEGER NOT NULL REFERENCES skill_market_item(id), installed_entries_json TEXT NOT NULL DEFAULT '[]', source_zip_filename VARCHAR(255) NOT NULL DEFAULT '', status VARCHAR(32) NOT NULL DEFAULT 'INSTALLED', last_error TEXT, installed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_bot_skill_install_bot_market UNIQUE (bot_id, skill_market_item_id) ); CREATE TABLE IF NOT EXISTS topic_topic ( id SERIAL PRIMARY KEY, bot_id TEXT NOT NULL REFERENCES bot_instance(id), topic_key TEXT NOT NULL, name TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', is_active BOOLEAN NOT NULL DEFAULT TRUE, is_default_fallback BOOLEAN NOT NULL DEFAULT FALSE, routing_json TEXT NOT NULL DEFAULT '{}', view_schema_json TEXT NOT NULL DEFAULT '{}', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_topic_topic_bot_topic_key UNIQUE (bot_id, topic_key) ); CREATE TABLE IF NOT EXISTS topic_item ( id SERIAL PRIMARY KEY, bot_id TEXT NOT NULL REFERENCES bot_instance(id), topic_key TEXT NOT NULL, title TEXT NOT NULL DEFAULT '', content TEXT NOT NULL DEFAULT '', level TEXT NOT NULL DEFAULT 'info', tags_json TEXT, view_json TEXT, source TEXT NOT NULL DEFAULT 'mcp', dedupe_key TEXT, is_read BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_bot_instance_enabled ON bot_instance (enabled); CREATE INDEX IF NOT EXISTS idx_bot_instance_docker_status ON bot_instance (docker_status); CREATE INDEX IF NOT EXISTS idx_bot_message_bot_id ON bot_message (bot_id); CREATE INDEX IF NOT EXISTS idx_bot_message_role ON bot_message (role); CREATE INDEX IF NOT EXISTS idx_bot_message_feedback ON bot_message (feedback); CREATE INDEX IF NOT EXISTS idx_bot_message_created_at ON bot_message (created_at); CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_login_log_token_hash ON sys_login_log (token_hash); CREATE INDEX IF NOT EXISTS idx_sys_login_log_auth_type ON sys_login_log (auth_type); CREATE INDEX IF NOT EXISTS idx_sys_login_log_subject_id ON sys_login_log (subject_id); CREATE INDEX IF NOT EXISTS idx_sys_login_log_bot_id ON sys_login_log (bot_id); CREATE INDEX IF NOT EXISTS idx_sys_login_log_auth_source ON sys_login_log (auth_source); CREATE INDEX IF NOT EXISTS idx_sys_login_log_created_at ON sys_login_log (created_at); CREATE INDEX IF NOT EXISTS idx_sys_login_log_expires_at ON sys_login_log (expires_at); CREATE INDEX IF NOT EXISTS idx_sys_login_log_last_seen_at ON sys_login_log (last_seen_at); CREATE INDEX IF NOT EXISTS idx_sys_login_log_revoked_at ON sys_login_log (revoked_at); CREATE INDEX IF NOT EXISTS idx_sys_setting_category ON sys_setting (category); CREATE INDEX IF NOT EXISTS idx_sys_setting_is_public ON sys_setting (is_public); CREATE INDEX IF NOT EXISTS idx_sys_setting_sort_order ON sys_setting (sort_order); CREATE INDEX IF NOT EXISTS idx_sys_setting_updated_at ON sys_setting (updated_at); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_bot_id ON bot_request_usage (bot_id); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_message_id ON bot_request_usage (message_id); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_request_id ON bot_request_usage (request_id); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_channel ON bot_request_usage (channel); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_status ON bot_request_usage (status); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_started_at ON bot_request_usage (started_at); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_completed_at ON bot_request_usage (completed_at); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_created_at ON bot_request_usage (created_at); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_updated_at ON bot_request_usage (updated_at); CREATE INDEX IF NOT EXISTS idx_bot_request_usage_started_at_bot_id ON bot_request_usage (started_at, bot_id); CREATE INDEX IF NOT EXISTS idx_bot_activity_event_bot_id ON bot_activity_event (bot_id); CREATE INDEX IF NOT EXISTS idx_bot_activity_event_request_id ON bot_activity_event (request_id); CREATE INDEX IF NOT EXISTS idx_bot_activity_event_event_type ON bot_activity_event (event_type); CREATE INDEX IF NOT EXISTS idx_bot_activity_event_channel ON bot_activity_event (channel); CREATE INDEX IF NOT EXISTS idx_bot_activity_event_created_at ON bot_activity_event (created_at); CREATE INDEX IF NOT EXISTS idx_bot_activity_event_bot_id_request_present ON bot_activity_event (bot_id) WHERE request_id IS NOT NULL AND request_id <> ''; CREATE INDEX IF NOT EXISTS idx_skill_market_item_created_at ON skill_market_item (created_at); CREATE INDEX IF NOT EXISTS idx_skill_market_item_updated_at ON skill_market_item (updated_at); CREATE INDEX IF NOT EXISTS idx_bot_skill_install_bot_id ON bot_skill_install (bot_id); CREATE INDEX IF NOT EXISTS idx_bot_skill_install_skill_market_item_id ON bot_skill_install (skill_market_item_id); CREATE INDEX IF NOT EXISTS idx_bot_skill_install_status ON bot_skill_install (status); CREATE INDEX IF NOT EXISTS idx_bot_skill_install_installed_at ON bot_skill_install (installed_at); CREATE INDEX IF NOT EXISTS idx_bot_skill_install_updated_at ON bot_skill_install (updated_at); CREATE INDEX IF NOT EXISTS idx_topic_topic_bot_id ON topic_topic (bot_id); CREATE INDEX IF NOT EXISTS idx_topic_topic_topic_key ON topic_topic (topic_key); CREATE INDEX IF NOT EXISTS idx_topic_topic_created_at ON topic_topic (created_at); CREATE INDEX IF NOT EXISTS idx_topic_topic_updated_at ON topic_topic (updated_at); CREATE INDEX IF NOT EXISTS idx_topic_topic_bot_fallback ON topic_topic (bot_id, is_default_fallback); CREATE INDEX IF NOT EXISTS idx_topic_item_bot_id ON topic_item (bot_id); CREATE INDEX IF NOT EXISTS idx_topic_item_topic_key ON topic_item (topic_key); CREATE INDEX IF NOT EXISTS idx_topic_item_level ON topic_item (level); CREATE INDEX IF NOT EXISTS idx_topic_item_source ON topic_item (source); CREATE INDEX IF NOT EXISTS idx_topic_item_is_read ON topic_item (is_read); CREATE INDEX IF NOT EXISTS idx_topic_item_created_at ON topic_item (created_at); CREATE INDEX IF NOT EXISTS idx_topic_item_bot_topic_created_at ON topic_item (bot_id, topic_key, created_at); CREATE INDEX IF NOT EXISTS idx_topic_item_bot_dedupe ON topic_item (bot_id, dedupe_key); COMMIT;