-- +goose Up -- Фаза 1: аккаунты, инстанс, серверы, роли, комнаты и права (AGENT.md 6.1). CREATE TABLE users ( id INTEGER PRIMARY KEY, username TEXT NOT NULL, username_lower TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL DEFAULT '', email_enc TEXT NOT NULL, email_index TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, avatar_file_id INTEGER, banner_file_id INTEGER, bio TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'online', custom_status TEXT NOT NULL DEFAULT '', custom_status_emoji TEXT NOT NULL DEFAULT '', flags INTEGER NOT NULL DEFAULT 0, is_instance_admin INTEGER NOT NULL DEFAULT 0, badges_json TEXT NOT NULL DEFAULT '[]', locale TEXT NOT NULL DEFAULT 'ru', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), deleted_at TEXT ); CREATE INDEX users_created_idx ON users (created_at); CREATE INDEX users_admin_idx ON users (is_instance_admin) WHERE is_instance_admin = 1; CREATE TABLE sessions ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, user_agent TEXT NOT NULL DEFAULT '', ip TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), expires_at TEXT NOT NULL, last_seen TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), stepped_up_at TEXT ); CREATE INDEX sessions_user_idx ON sessions (user_id); CREATE INDEX sessions_expires_idx ON sessions (expires_at); CREATE TABLE totp_secrets ( user_id INTEGER PRIMARY KEY REFERENCES users (id) ON DELETE CASCADE, secret_encrypted TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 0, recovery_codes_json TEXT NOT NULL DEFAULT '[]', confirmed_at TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); -- Таблица создаётся сразу, используется с Фазы 7 (AGENT.md 6.1). CREATE TABLE webauthn_credentials ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, credential_id TEXT NOT NULL UNIQUE, public_key BLOB NOT NULL, sign_count INTEGER NOT NULL DEFAULT 0, transports TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE security_events ( id INTEGER PRIMARY KEY, user_id INTEGER REFERENCES users (id) ON DELETE SET NULL, type TEXT NOT NULL, ip TEXT NOT NULL DEFAULT '', user_agent TEXT NOT NULL DEFAULT '', metadata_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX security_events_user_idx ON security_events (user_id, id DESC); CREATE TABLE guilds ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, owner_id INTEGER NOT NULL REFERENCES users (id), icon_file_id INTEGER, banner_file_id INTEGER, splash_file_id INTEGER, accent_color INTEGER NOT NULL DEFAULT 0, description TEXT NOT NULL DEFAULT '', is_main INTEGER NOT NULL DEFAULT 0, is_discoverable INTEGER NOT NULL DEFAULT 0, limits_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX guilds_owner_idx ON guilds (owner_id); CREATE INDEX guilds_discoverable_idx ON guilds (is_discoverable) WHERE is_discoverable = 1; CREATE TABLE guild_members ( guild_id INTEGER NOT NULL REFERENCES guilds (id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE, nickname TEXT, joined_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), timeout_until TEXT, PRIMARY KEY (guild_id, user_id) ); CREATE INDEX guild_members_user_idx ON guild_members (user_id); CREATE TABLE roles ( id INTEGER PRIMARY KEY, guild_id INTEGER NOT NULL REFERENCES guilds (id) ON DELETE CASCADE, name TEXT NOT NULL, color INTEGER NOT NULL DEFAULT 0, position INTEGER NOT NULL DEFAULT 0, permissions INTEGER NOT NULL DEFAULT 0, is_default INTEGER NOT NULL DEFAULT 0, mentionable INTEGER NOT NULL DEFAULT 0, hoist INTEGER NOT NULL DEFAULT 0, cosmetic_frame_id INTEGER, cosmetic_badge_id INTEGER, nick_color INTEGER, nick_effect TEXT NOT NULL DEFAULT 'none', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX roles_guild_idx ON roles (guild_id, position DESC); CREATE TABLE member_roles ( guild_id INTEGER NOT NULL, user_id INTEGER NOT NULL, role_id INTEGER NOT NULL REFERENCES roles (id) ON DELETE CASCADE, PRIMARY KEY (guild_id, user_id, role_id), FOREIGN KEY (guild_id, user_id) REFERENCES guild_members (guild_id, user_id) ON DELETE CASCADE ); CREATE INDEX member_roles_user_idx ON member_roles (user_id); CREATE INDEX member_roles_role_idx ON member_roles (role_id); CREATE TABLE channels ( id INTEGER PRIMARY KEY, guild_id INTEGER REFERENCES guilds (id) ON DELETE CASCADE, type TEXT NOT NULL, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', position INTEGER NOT NULL DEFAULT 0, parent_id INTEGER REFERENCES channels (id) ON DELETE CASCADE, slowmode_seconds INTEGER NOT NULL DEFAULT 0, nsfw INTEGER NOT NULL DEFAULT 0, background TEXT, voice_status TEXT NOT NULL DEFAULT '', user_limit INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX channels_guild_idx ON channels (guild_id, position); CREATE INDEX channels_parent_idx ON channels (parent_id); CREATE TABLE channel_overrides ( channel_id INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE, target_type TEXT NOT NULL, target_id INTEGER NOT NULL, allow INTEGER NOT NULL DEFAULT 0, deny INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (channel_id, target_type, target_id) ); CREATE TABLE audit_log ( id INTEGER PRIMARY KEY, guild_id INTEGER REFERENCES guilds (id) ON DELETE CASCADE, actor_id INTEGER, actor_instance_admin INTEGER NOT NULL DEFAULT 0, action TEXT NOT NULL, target_type TEXT NOT NULL DEFAULT '', target_id INTEGER, reason TEXT NOT NULL DEFAULT '', changes_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX audit_log_guild_idx ON audit_log (guild_id, id DESC); INSERT INTO instance_settings (key, value) VALUES ('max_guilds_per_user', '10'), ('max_members_per_guild', '250'), ('allow_guild_creation', 'true'), ('main_guild_id', '') ON CONFLICT (key) DO NOTHING; -- +goose Down DROP TABLE audit_log; DROP TABLE channel_overrides; DROP TABLE channels; DROP TABLE member_roles; DROP TABLE roles; DROP TABLE guild_members; DROP TABLE guilds; DROP TABLE security_events; DROP TABLE webauthn_credentials; DROP TABLE totp_secrets; DROP TABLE sessions; DROP TABLE users;