-- All schema changes go into this file until 1.0.0 is tagged. -- There will not be migrations during the early development phase. -- After 1.0.0, new changes get their own numbered migration files. -- Users: accounts and authentication CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, -- UUID nick TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, last_seen_at DATETIME ); -- Auth tokens: one user can have multiple active tokens (multiple devices) CREATE TABLE IF NOT EXISTS auth_tokens ( token TEXT PRIMARY KEY, -- random token string user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME, -- NULL = no expiry last_used_at DATETIME ); CREATE INDEX IF NOT EXISTS idx_auth_tokens_user_id ON auth_tokens(user_id); -- Channels: chat rooms CREATE TABLE IF NOT EXISTS channels ( id TEXT PRIMARY KEY, -- UUID name TEXT NOT NULL UNIQUE, -- #general, etc. topic TEXT NOT NULL DEFAULT '', modes TEXT NOT NULL DEFAULT '', -- +i, +m, +s, +t, +n created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- Channel members: who is in which channel, with per-user modes CREATE TABLE IF NOT EXISTS channel_members ( channel_id TEXT NOT NULL REFERENCES channels(id) ON DELETE CASCADE, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, modes TEXT NOT NULL DEFAULT '', -- +o (operator), +v (voice) joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (channel_id, user_id) ); CREATE INDEX IF NOT EXISTS idx_channel_members_user_id ON channel_members(user_id); -- Messages: channel and DM history (rotated per MAX_HISTORY) CREATE TABLE IF NOT EXISTS messages ( id TEXT PRIMARY KEY, -- UUID ts DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, from_user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, from_nick TEXT NOT NULL, -- denormalized for history target TEXT NOT NULL, -- #channel name or user UUID for DMs type TEXT NOT NULL DEFAULT 'message', -- message, action, notice, join, part, quit, topic, mode, nick, system body TEXT NOT NULL DEFAULT '', meta TEXT NOT NULL DEFAULT '{}', -- JSON extensible metadata created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_messages_target_ts ON messages(target, ts); CREATE INDEX IF NOT EXISTS idx_messages_from_user ON messages(from_user_id); -- Message queue: per-user pending delivery (unread messages) CREATE TABLE IF NOT EXISTS message_queue ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE, queued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, message_id) ); CREATE INDEX IF NOT EXISTS idx_message_queue_user_id ON message_queue(user_id, queued_at); -- Sessions: server-held session state CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, -- UUID user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, last_active_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME -- idle timeout ); CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); -- Server links: federation peer configuration CREATE TABLE IF NOT EXISTS server_links ( id TEXT PRIMARY KEY, -- UUID name TEXT NOT NULL UNIQUE, -- human-readable peer name url TEXT NOT NULL, -- base URL of peer server shared_key_hash TEXT NOT NULL, -- hashed shared secret is_active INTEGER NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, last_seen_at DATETIME );