-- RustBox 0.1.1 PostgreSQL schema. -- One physical database contains global sessions plus isolated per-account tables. -- Application tables are materialized by replacing {{p}} with a collision-checked prefix. -- Baseline application schema version: 46. Future migrations start at V047. -- RUSTBOX:GLOBAL-BEGIN CREATE TABLE IF NOT EXISTS rustbox_accounts ( user_hash VARCHAR(128) PRIMARY KEY, table_prefix VARCHAR(64) NOT NULL UNIQUE, schema_version BIGINT NOT NULL DEFAULT 46, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE IF NOT EXISTS sessions ( token VARCHAR(64) PRIMARY KEY, account_id VARCHAR(64) NOT NULL UNIQUE, browser_id VARCHAR(64) NOT NULL, email VARCHAR(320) NOT NULL, password_ciphertext TEXT NOT NULL, user_hash VARCHAR(128) NOT NULL, imap_host VARCHAR(255) NOT NULL, imap_port BIGINT NOT NULL, imap_tls BIGINT NOT NULL, imap_tls_verify BIGINT NOT NULL, smtp_host VARCHAR(255) NOT NULL, smtp_port BIGINT NOT NULL, smtp_tls BIGINT NOT NULL, smtp_tls_verify BIGINT NOT NULL, timeout_secs BIGINT NOT NULL, created_at_unix BIGINT NOT NULL, last_accessed_unix BIGINT NOT NULL, user_agent VARCHAR(512) NOT NULL DEFAULT '' ); CREATE INDEX IF NOT EXISTS idx_sessions_browser ON sessions(browser_id); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_hash); CREATE INDEX IF NOT EXISTS idx_sessions_expiry ON sessions(last_accessed_unix, timeout_secs); -- RUSTBOX:GLOBAL-END -- RUSTBOX:TENANT-BEGIN -- {{p}} is replaced at runtime with the account table prefix. CREATE TABLE {{p}}calendar_events ( id TEXT PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', location TEXT NOT NULL DEFAULT '', start_time TEXT NOT NULL, end_time TEXT NOT NULL, all_day BIGINT NOT NULL DEFAULT 0, recurrence_rule TEXT, attendees TEXT NOT NULL DEFAULT '[]', organizer TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'confirmed', source TEXT NOT NULL DEFAULT 'manual', source_uid TEXT, source_message_uid BIGINT, source_message_folder TEXT, meeting_url TEXT, color TEXT, reminder_minutes BIGINT, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}calendar_settings ( id BIGINT DEFAULT 1 PRIMARY KEY, default_view TEXT NOT NULL DEFAULT 'month', week_starts_on BIGINT NOT NULL DEFAULT 0, working_hours_start TEXT NOT NULL DEFAULT '09:00', working_hours_end TEXT NOT NULL DEFAULT '17:00', time_format TEXT NOT NULL DEFAULT '12h', zoom_link TEXT NOT NULL DEFAULT '', teams_link TEXT NOT NULL DEFAULT '', meet_link TEXT NOT NULL DEFAULT '', caldav_url TEXT NOT NULL DEFAULT '', caldav_username TEXT NOT NULL DEFAULT '', caldav_password TEXT NOT NULL DEFAULT '', caldav_enabled BIGINT NOT NULL DEFAULT 0, updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}contact_group_members ( group_id TEXT NOT NULL, contact_id TEXT NOT NULL, PRIMARY KEY (group_id, contact_id) ); CREATE TABLE {{p}}contact_groups ( id TEXT PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), UNIQUE (name) ); CREATE TABLE {{p}}contact_photos ( contact_id TEXT PRIMARY KEY, content_type TEXT NOT NULL, data BYTEA NOT NULL, updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}contacts ( id TEXT PRIMARY KEY, email TEXT NOT NULL, name TEXT NOT NULL DEFAULT '', company TEXT NOT NULL DEFAULT '', notes TEXT NOT NULL DEFAULT '', is_favorite BIGINT NOT NULL DEFAULT 0, last_contacted TEXT, contact_count BIGINT NOT NULL DEFAULT 0, source TEXT NOT NULL DEFAULT 'manual', created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), phone TEXT NOT NULL DEFAULT '', job_title TEXT NOT NULL DEFAULT '', address TEXT NOT NULL DEFAULT '', website TEXT NOT NULL DEFAULT '', birthday TEXT NOT NULL DEFAULT '', emails_json TEXT NOT NULL DEFAULT '[]', phones_json TEXT NOT NULL DEFAULT '[]', addresses_json TEXT NOT NULL DEFAULT '[]' ); CREATE TABLE {{p}}display_preferences ( id BIGINT DEFAULT 1 PRIMARY KEY, density TEXT NOT NULL DEFAULT 'comfortable', theme TEXT NOT NULL DEFAULT 'system', language TEXT NOT NULL DEFAULT 'en', updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), compose_format TEXT NOT NULL DEFAULT 'html', deep_index BIGINT NOT NULL DEFAULT 0, animation_mode TEXT, preview_pane TEXT NOT NULL DEFAULT 'right', ui_scale TEXT NOT NULL DEFAULT 'normal', color_preset TEXT NOT NULL DEFAULT 'sage', accent_color TEXT NOT NULL DEFAULT '#6f9c86', font_family TEXT NOT NULL DEFAULT 'system', font_size BIGINT NOT NULL DEFAULT 13, row_height BIGINT NOT NULL DEFAULT 58, folder_pane_width BIGINT NOT NULL DEFAULT 228, message_pane_width BIGINT NOT NULL DEFAULT 420, border_radius BIGINT NOT NULL DEFAULT 4, contrast TEXT NOT NULL DEFAULT 'normal', show_avatars BIGINT NOT NULL DEFAULT 1, show_attachment_icons BIGINT NOT NULL DEFAULT 1, show_flags BIGINT NOT NULL DEFAULT 1, reduce_motion BIGINT NOT NULL DEFAULT 0, date_format TEXT NOT NULL DEFAULT 'locale', time_format TEXT NOT NULL DEFAULT 'locale', background_color TEXT NOT NULL DEFAULT '#121513', panel_color TEXT NOT NULL DEFAULT '#181c19', text_color TEXT NOT NULL DEFAULT '#edf0ec', compose_autosave_ms BIGINT NOT NULL DEFAULT 3000, default_priority TEXT NOT NULL DEFAULT 'normal', request_mdn_default BIGINT NOT NULL DEFAULT 0, attachment_reminder BIGINT NOT NULL DEFAULT 1, icon_size BIGINT NOT NULL DEFAULT 18, toolbar_density TEXT NOT NULL DEFAULT 'normal', sidebar_item_height BIGINT NOT NULL DEFAULT 29, reader_max_width BIGINT NOT NULL DEFAULT 0, composer_width BIGINT NOT NULL DEFAULT 900, selection_color TEXT NOT NULL DEFAULT '#28352e', unread_font_weight BIGINT NOT NULL DEFAULT 730, unread_badge_style TEXT NOT NULL DEFAULT 'dot', compact_headers BIGINT NOT NULL DEFAULT 0, show_borders BIGINT NOT NULL DEFAULT 1, scrollbar_density TEXT NOT NULL DEFAULT 'thin', monochrome BIGINT NOT NULL DEFAULT 0, folder_background TEXT NOT NULL DEFAULT '', message_background TEXT NOT NULL DEFAULT '', reader_background TEXT NOT NULL DEFAULT '', compose_font_family TEXT NOT NULL DEFAULT 'system', reply_position TEXT NOT NULL DEFAULT 'above', quote_style TEXT NOT NULL DEFAULT 'standard', forward_mode TEXT NOT NULL DEFAULT 'inline', spellcheck_enabled BIGINT NOT NULL DEFAULT 1, interface_skin TEXT NOT NULL DEFAULT 'rustbox', preset_scale_unlocked BIGINT NOT NULL DEFAULT 0 ); CREATE TABLE {{p}}draft_attachments ( id TEXT PRIMARY KEY, draft_id TEXT NOT NULL, filename TEXT NOT NULL, content_type TEXT NOT NULL, size BIGINT NOT NULL, file_path TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}drafts ( id TEXT PRIMARY KEY, to_addresses TEXT NOT NULL DEFAULT '', cc_addresses TEXT NOT NULL DEFAULT '', bcc_addresses TEXT NOT NULL DEFAULT '', subject TEXT NOT NULL DEFAULT '', text_body TEXT NOT NULL DEFAULT '', html_body TEXT, in_reply_to TEXT, references_header TEXT, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), request_mdn BIGINT NOT NULL DEFAULT 0, priority TEXT NOT NULL DEFAULT 'normal', compose_format TEXT NOT NULL DEFAULT 'html', remote_folder TEXT, remote_uid BIGINT, remote_uid_validity BIGINT, pgp_mode TEXT NOT NULL DEFAULT 'none', pgp_inline BIGINT NOT NULL DEFAULT 0, pgp_attach_public_key BIGINT NOT NULL DEFAULT 0, pgp_attach_recipient_keys BIGINT NOT NULL DEFAULT 0 ); CREATE TABLE {{p}}folder_preferences ( folder_name TEXT PRIMARY KEY, flagged_on_top BIGINT, updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}folders ( name TEXT PRIMARY KEY, delimiter TEXT, parent TEXT, flags TEXT NOT NULL DEFAULT '', is_subscribed BIGINT NOT NULL DEFAULT 1, total_count BIGINT NOT NULL DEFAULT 0, unread_count BIGINT NOT NULL DEFAULT 0, uid_validity BIGINT NOT NULL DEFAULT 0, highest_modseq BIGINT NOT NULL DEFAULT 0, updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), messages_updated_at TEXT ); CREATE TABLE {{p}}identities ( id BIGSERIAL PRIMARY KEY, display_name TEXT NOT NULL DEFAULT '', email TEXT NOT NULL, signature_html TEXT NOT NULL DEFAULT '', is_default BIGINT NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), reply_to TEXT NOT NULL DEFAULT '', bcc TEXT NOT NULL DEFAULT '', organization TEXT NOT NULL DEFAULT '', signature_text TEXT NOT NULL DEFAULT '', signature_new BIGINT NOT NULL DEFAULT 1, signature_reply BIGINT NOT NULL DEFAULT 1, signature_forward BIGINT NOT NULL DEFAULT 1, signature_position TEXT NOT NULL DEFAULT 'above', pgp_fingerprint TEXT, pgp_sign_default BIGINT NOT NULL DEFAULT 0, pgp_encrypt_default BIGINT NOT NULL DEFAULT 0, pgp_inline_default BIGINT NOT NULL DEFAULT 0 ); CREATE TABLE {{p}}known_addresses ( email TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL DEFAULT '' ); CREATE TABLE {{p}}mail_preferences ( id BIGINT PRIMARY KEY, messages_per_page BIGINT NOT NULL DEFAULT 50, mark_read_delay_ms BIGINT NOT NULL DEFAULT 0, remote_images TEXT NOT NULL DEFAULT 'ask', preferred_body TEXT NOT NULL DEFAULT 'auto', thread_mode TEXT NOT NULL DEFAULT 'threaded', collapse_quotes BIGINT NOT NULL DEFAULT 1, show_snippets BIGINT NOT NULL DEFAULT 1, default_sort TEXT NOT NULL DEFAULT 'date_desc', updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), flagged_on_top BIGINT NOT NULL DEFAULT 0, sync_imap_keywords BIGINT NOT NULL DEFAULT 0 ); CREATE TABLE {{p}}meeting_templates ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, url_template TEXT NOT NULL, icon TEXT NOT NULL DEFAULT '', is_default BIGINT NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}message_tags ( tag_id TEXT NOT NULL, message_uid BIGINT NOT NULL, message_folder TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), PRIMARY KEY (tag_id, message_uid, message_folder) ); CREATE TABLE {{p}}messages ( uid BIGINT NOT NULL, folder TEXT NOT NULL, message_id TEXT, in_reply_to TEXT, subject TEXT NOT NULL DEFAULT '', from_address TEXT NOT NULL DEFAULT '', from_name TEXT NOT NULL DEFAULT '', to_addresses TEXT NOT NULL DEFAULT '', cc_addresses TEXT NOT NULL DEFAULT '', date TEXT NOT NULL, flags TEXT NOT NULL DEFAULT '', size BIGINT NOT NULL DEFAULT 0, has_attachments BIGINT NOT NULL DEFAULT 0, snippet TEXT NOT NULL DEFAULT '', body_cached BIGINT NOT NULL DEFAULT 0, body_html TEXT, body_text TEXT, references_header TEXT, date_epoch BIGINT NOT NULL DEFAULT 0, attachments_json TEXT, raw_headers TEXT, reaction TEXT, email_theme BIGINT, PRIMARY KEY (uid, folder) ); CREATE TABLE {{p}}notification_preferences ( id BIGINT DEFAULT 1 PRIMARY KEY, enabled BIGINT NOT NULL DEFAULT 1, sound BIGINT NOT NULL DEFAULT 0, folders TEXT NOT NULL DEFAULT '["INBOX"]', updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), dnd_enabled BIGINT NOT NULL DEFAULT 0, dnd_start TEXT NOT NULL DEFAULT '22:00', dnd_end TEXT NOT NULL DEFAULT '07:00' ); CREATE TABLE {{p}}pgp_keys ( fingerprint TEXT PRIMARY KEY, key_id TEXT NOT NULL, email TEXT NOT NULL DEFAULT '', user_id TEXT NOT NULL DEFAULT '', public_key_armor TEXT NOT NULL, secret_key_path TEXT, has_secret BIGINT NOT NULL DEFAULT 0, trust TEXT NOT NULL DEFAULT 'unknown', source TEXT NOT NULL DEFAULT 'import', created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}pgp_preferences ( id BIGINT PRIMARY KEY, wkd_enabled BIGINT NOT NULL DEFAULT 1, hkp_enabled BIGINT NOT NULL DEFAULT 0, require_trusted_encryption BIGINT NOT NULL DEFAULT 0, updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}response_templates ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, body TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), format TEXT NOT NULL DEFAULT 'text', UNIQUE (name) ); CREATE TABLE {{p}}saved_searches ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, query_string TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}special_folders ( role TEXT PRIMARY KEY, folder_name TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}tags ( id TEXT PRIMARY KEY, name TEXT NOT NULL, color TEXT NOT NULL DEFAULT '#6b7280', created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), updated_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text), imap_keyword TEXT NOT NULL DEFAULT '', UNIQUE (name) ); CREATE TABLE {{p}}trusted_senders ( email TEXT PRIMARY KEY, created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text) ); CREATE TABLE {{p}}user_meta ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); ALTER TABLE {{p}}contact_group_members ADD CONSTRAINT {{p}}fk_contact_group_members_0 FOREIGN KEY (contact_id) REFERENCES {{p}}contacts (id) ON DELETE CASCADE; ALTER TABLE {{p}}contact_group_members ADD CONSTRAINT {{p}}fk_contact_group_members_1 FOREIGN KEY (group_id) REFERENCES {{p}}contact_groups (id) ON DELETE CASCADE; ALTER TABLE {{p}}contact_photos ADD CONSTRAINT {{p}}fk_contact_photos_0 FOREIGN KEY (contact_id) REFERENCES {{p}}contacts (id) ON DELETE CASCADE; ALTER TABLE {{p}}draft_attachments ADD CONSTRAINT {{p}}fk_draft_attachments_0 FOREIGN KEY (draft_id) REFERENCES {{p}}drafts (id) ON DELETE CASCADE; ALTER TABLE {{p}}folder_preferences ADD CONSTRAINT {{p}}fk_folder_preferences_0 FOREIGN KEY (folder_name) REFERENCES {{p}}folders (name) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE {{p}}message_tags ADD CONSTRAINT {{p}}fk_message_tags_0 FOREIGN KEY (message_uid, message_folder) REFERENCES {{p}}messages (uid, folder) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE {{p}}message_tags ADD CONSTRAINT {{p}}fk_message_tags_1 FOREIGN KEY (tag_id) REFERENCES {{p}}tags (id) ON DELETE CASCADE; ALTER TABLE {{p}}messages ADD CONSTRAINT {{p}}fk_messages_0 FOREIGN KEY (folder) REFERENCES {{p}}folders (name) ON DELETE CASCADE; CREATE INDEX {{p}}idx_calendar_events_source_uid ON {{p}}calendar_events (source_uid); CREATE INDEX {{p}}idx_calendar_events_time ON {{p}}calendar_events (start_time, end_time); CREATE INDEX {{p}}idx_cgm_contact_id ON {{p}}contact_group_members (contact_id); CREATE INDEX {{p}}idx_contacts_contact_count ON {{p}}contacts (contact_count DESC); CREATE UNIQUE INDEX {{p}}idx_contacts_email ON {{p}}contacts (email); CREATE INDEX {{p}}idx_contacts_last_contacted ON {{p}}contacts (last_contacted DESC); CREATE INDEX {{p}}idx_contacts_name ON {{p}}contacts (name); CREATE INDEX {{p}}idx_known_addresses_name ON {{p}}known_addresses (name); CREATE INDEX {{p}}idx_message_tags_message ON {{p}}message_tags (message_uid, message_folder); CREATE INDEX {{p}}idx_message_tags_tag ON {{p}}message_tags (tag_id); CREATE INDEX {{p}}idx_messages_folder_date ON {{p}}messages (folder, date_epoch DESC); CREATE INDEX {{p}}idx_messages_in_reply_to ON {{p}}messages (in_reply_to); CREATE INDEX {{p}}idx_messages_message_id ON {{p}}messages (message_id); CREATE INDEX {{p}}idx_pgp_keys_email ON {{p}}pgp_keys (lower(email)); CREATE UNIQUE INDEX {{p}}idx_saved_searches_name ON {{p}}saved_searches (name); CREATE UNIQUE INDEX {{p}}idx_tags_imap_keyword ON {{p}}tags (imap_keyword) WHERE imap_keyword <> ''; -- RUSTBOX:TENANT-END