Files
RustBox/migrations/pgsql/schema.sql
T
Mateusz Gruszczyński dbe7103687
RustBox CI / rustbox (push) Canceled after 0s
v0.2.16
2026-11-02 23:32:23 +01:00

464 lines
18 KiB
SQL

-- RustBox 0.2.0 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.
-- Squashed compatibility baseline: V050; current schema: V051.
-- 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 50,
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,
adaptive_email_colors 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,
checksum_sha256 TEXT,
created_at TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP::text)
);
CREATE TABLE {{p}}blob_delete_outbox (
id TEXT PRIMARY KEY,
storage_key TEXT NOT NULL,
attempts BIGINT NOT NULL DEFAULT 0,
available_at BIGINT NOT NULL DEFAULT 0,
claimed_until BIGINT NOT NULL DEFAULT 0,
last_error TEXT,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
CREATE INDEX {{p}}idx_blob_delete_outbox_ready ON {{p}}blob_delete_outbox(available_at, claimed_until, updated_at);
CREATE TABLE {{p}}search_outbox (
id TEXT PRIMARY KEY,
generation TEXT NOT NULL,
event_type TEXT NOT NULL,
message_uid BIGINT NOT NULL,
folder TEXT NOT NULL,
payload TEXT,
attempts BIGINT NOT NULL DEFAULT 0,
available_at BIGINT NOT NULL DEFAULT 0,
claimed_until BIGINT NOT NULL DEFAULT 0,
dead_letter BIGINT NOT NULL DEFAULT 0,
last_error TEXT,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
CREATE INDEX {{p}}idx_search_outbox_ready ON {{p}}search_outbox(dead_letter, available_at, claimed_until, updated_at);
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,
counter_generation 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',
signature_insert_mode TEXT NOT NULL DEFAULT 'silent',
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 300,
message_loading_mode TEXT NOT NULL DEFAULT 'pagination',
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,
compact_wide_rows BIGINT NOT NULL DEFAULT 1
);
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