-- +goose Up -- Фаза 2: сообщения, реакции, закрепления, read states и полнотекстовый поиск -- (AGENT.md 6.1, 7.6, 7.15). CREATE TABLE messages ( id INTEGER PRIMARY KEY, channel_id INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE, author_id INTEGER REFERENCES users (id) ON DELETE SET NULL, content TEXT NOT NULL DEFAULT '', reply_to_id INTEGER REFERENCES messages (id) ON DELETE SET NULL, type TEXT NOT NULL DEFAULT 'default', -- Вложения хранятся списком метаданных: сами файлы лежат на диске (7.7). attachments_json TEXT NOT NULL DEFAULT '[]', mentions_json TEXT NOT NULL DEFAULT '[]', edited_at TEXT, pinned INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX messages_channel_idx ON messages (channel_id, id DESC); CREATE INDEX messages_author_idx ON messages (author_id, id DESC); CREATE INDEX messages_pinned_idx ON messages (channel_id, id) WHERE pinned = 1; CREATE TABLE message_reactions ( message_id INTEGER NOT NULL REFERENCES messages (id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, emoji TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), PRIMARY KEY (message_id, user_id, emoji) ); CREATE INDEX message_reactions_emoji_idx ON message_reactions (message_id, emoji); -- Состояние прочтения на пользователя и комнату: синхронизируется между -- устройствами через Gateway (AGENT.md 7.16). CREATE TABLE channel_read_states ( user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, channel_id INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE, last_message_id INTEGER NOT NULL DEFAULT 0, mention_count INTEGER NOT NULL DEFAULT 0, updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), PRIMARY KEY (user_id, channel_id) ); CREATE INDEX channel_read_states_channel_idx ON channel_read_states (channel_id); -- Вложения: файл регистрируется отдельно от сообщения, чтобы работали -- предпросмотры и очистка сирот (AGENT.md 7.7). CREATE TABLE files ( id INTEGER PRIMARY KEY, uploader_id INTEGER REFERENCES users (id) ON DELETE SET NULL, guild_id INTEGER REFERENCES guilds (id) ON DELETE CASCADE, channel_id INTEGER REFERENCES channels (id) ON DELETE CASCADE, message_id INTEGER REFERENCES messages (id) ON DELETE CASCADE, filename TEXT NOT NULL, content_type TEXT NOT NULL DEFAULT 'application/octet-stream', size_bytes INTEGER NOT NULL DEFAULT 0, width INTEGER NOT NULL DEFAULT 0, height INTEGER NOT NULL DEFAULT 0, storage_path TEXT NOT NULL DEFAULT '', sha256 TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX files_message_idx ON files (message_id); -- Полнотекстовый поиск по сообщениям (AGENT.md 7.15): индексируем содержимое, -- права проверяются при запросе. CREATE VIRTUAL TABLE messages_fts USING fts5 ( content, content = 'messages', content_rowid = 'id', tokenize = 'unicode61 remove_diacritics 2' ); -- Триггеры содержат `;` внутри тела: goose требует явных границ оператора. -- +goose StatementBegin CREATE TRIGGER messages_fts_insert AFTER INSERT ON messages BEGIN INSERT INTO messages_fts (rowid, content) VALUES (new.id, new.content); END; -- +goose StatementEnd -- +goose StatementBegin CREATE TRIGGER messages_fts_delete AFTER DELETE ON messages BEGIN INSERT INTO messages_fts (messages_fts, rowid, content) VALUES ('delete', old.id, old.content); END; -- +goose StatementEnd -- +goose StatementBegin CREATE TRIGGER messages_fts_update AFTER UPDATE OF content ON messages BEGIN INSERT INTO messages_fts (messages_fts, rowid, content) VALUES ('delete', old.id, old.content); INSERT INTO messages_fts (rowid, content) VALUES (new.id, new.content); END; -- +goose StatementEnd -- Приглашения (AGENT.md 7.9): код, лимит использований и срок жизни. CREATE TABLE invites ( code TEXT PRIMARY KEY, guild_id INTEGER NOT NULL REFERENCES guilds (id) ON DELETE CASCADE, channel_id INTEGER REFERENCES channels (id) ON DELETE SET NULL, creator_id INTEGER REFERENCES users (id) ON DELETE SET NULL, max_uses INTEGER NOT NULL DEFAULT 0, uses INTEGER NOT NULL DEFAULT 0, max_age_sec INTEGER NOT NULL DEFAULT 0, expires_at TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX invites_guild_idx ON invites (guild_id, created_at DESC); CREATE INDEX invites_expires_idx ON invites (expires_at); -- +goose Down DROP TABLE invites; DROP TRIGGER messages_fts_update; DROP TRIGGER messages_fts_delete; DROP TRIGGER messages_fts_insert; DROP TABLE messages_fts; DROP TABLE files; DROP TABLE channel_read_states; DROP TABLE message_reactions; DROP TABLE messages;