CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY NOT NULL, username TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('ape', 'agent')), password_hash TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) ); CREATE TABLE IF NOT EXISTS api_tokens ( id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL REFERENCES users(id), token_hash TEXT UNIQUE NOT NULL, token_prefix TEXT NOT NULL, name TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) ); CREATE TABLE IF NOT EXISTS channels ( id TEXT PRIMARY KEY NOT NULL, name TEXT UNIQUE NOT NULL, description TEXT NOT NULL DEFAULT '', created_by TEXT NOT NULL REFERENCES users(id), created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) ); CREATE TABLE IF NOT EXISTS messages ( id TEXT PRIMARY KEY NOT NULL, seq INTEGER NOT NULL, channel_id TEXT NOT NULL REFERENCES channels(id), user_id TEXT NOT NULL REFERENCES users(id), type TEXT NOT NULL CHECK (type IN ('text', 'code', 'result', 'error', 'plan')), content TEXT NOT NULL, metadata TEXT, reply_to TEXT REFERENCES messages(id), created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), updated_at TEXT, deleted_at TEXT ); CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages(channel_id, seq); CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(channel_id, created_at); -- Auto-increment seq per channel using a trigger CREATE TABLE IF NOT EXISTS channel_seq ( channel_id TEXT PRIMARY KEY NOT NULL REFERENCES channels(id), next_seq INTEGER NOT NULL DEFAULT 1 ); CREATE TRIGGER IF NOT EXISTS trg_message_seq AFTER INSERT ON messages BEGIN INSERT INTO channel_seq (channel_id, next_seq) VALUES (NEW.channel_id, 2) ON CONFLICT(channel_id) DO UPDATE SET next_seq = next_seq + 1; END; -- Enforce reply_to same-channel constraint CREATE TRIGGER IF NOT EXISTS trg_reply_same_channel BEFORE INSERT ON messages WHEN NEW.reply_to IS NOT NULL BEGIN SELECT RAISE(ABORT, 'reply_to must reference a message in the same channel') WHERE (SELECT channel_id FROM messages WHERE id = NEW.reply_to) != NEW.channel_id; END; -- Seed user for development (no auth yet) INSERT OR IGNORE INTO users (id, username, display_name, role) VALUES ('00000000-0000-0000-0000-000000000001', 'benji', 'Benji', 'ape'); INSERT OR IGNORE INTO users (id, username, display_name, role) VALUES ('00000000-0000-0000-0000-000000000002', 'neeraj', 'Neeraj', 'ape'); -- Seed a general channel INSERT OR IGNORE INTO channels (id, name, description, created_by) VALUES ('00000000-0000-0000-0000-000000000010', 'general', 'General discussion', '00000000-0000-0000-0000-000000000001');