-- 010: Channel Kinds — text, voice, stage, forum, announcement -- -- ALTER: -- channel — add channel_kind + voice/forum/stage fields -- message_thread — add forum post fields (title, tags, pinned, locked) -- -- New tables: -- forum_tag, voice_participant, stage, -- message_poll, message_poll_option, message_poll_vote -- ============================================================ -- 1. ALTER channel — add channel_kind + voice/forum/stage fields -- ============================================================ ALTER TABLE channel ADD COLUMN IF NOT EXISTS channel_kind TEXT NOT NULL DEFAULT 'text'; ALTER TABLE channel ADD COLUMN IF NOT EXISTS position INTEGER NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS nsfw BOOLEAN NOT NULL DEFAULT FALSE; -- Voice / Stage specific ALTER TABLE channel ADD COLUMN IF NOT EXISTS bitrate INTEGER NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS user_limit INTEGER NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS rtc_region TEXT NULL; -- Forum specific ALTER TABLE channel ADD COLUMN IF NOT EXISTS default_auto_archive_duration INTEGER NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS default_reaction_emoji TEXT NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS default_sort_order TEXT NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS default_forum_layout TEXT NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS require_tag BOOLEAN NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS available_tags JSONB NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS default_thread_rate_limit INTEGER NULL; -- General ALTER TABLE channel ADD COLUMN IF NOT EXISTS rate_limit_per_user INTEGER NULL; ALTER TABLE channel ADD COLUMN IF NOT EXISTS parent_channel_id UUID NULL REFERENCES channel(id) ON DELETE SET NULL; CREATE INDEX IF NOT EXISTS idx_channel_channel_kind ON channel (channel_kind); CREATE INDEX IF NOT EXISTS idx_channel_parent_channel_id ON channel (parent_channel_id); -- ============================================================ -- 2. ALTER message_thread — add forum post fields -- ============================================================ ALTER TABLE message_thread ADD COLUMN IF NOT EXISTS title TEXT NULL; ALTER TABLE message_thread ADD COLUMN IF NOT EXISTS tags TEXT[] NOT NULL DEFAULT '{}'; ALTER TABLE message_thread ADD COLUMN IF NOT EXISTS pinned BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE message_thread ADD COLUMN IF NOT EXISTS locked BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE message_thread ADD COLUMN IF NOT EXISTS rate_limit_per_user INTEGER NULL; ALTER TABLE message_thread ADD COLUMN IF NOT EXISTS auto_archive_at TIMESTAMPTZ NULL; CREATE INDEX IF NOT EXISTS idx_message_thread_pinned ON message_thread (pinned) WHERE pinned; -- ============================================================ -- 3. Forum Tags -- ============================================================ -- models/channels/forum_tags.rs → forum_tag CREATE TABLE IF NOT EXISTS forum_tag ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE, name TEXT NOT NULL, emoji_id TEXT NULL, emoji_name TEXT NULL, moderated BOOLEAN NOT NULL, position INTEGER NOT NULL, created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, CONSTRAINT uq_forum_tag_channel_name UNIQUE (channel_id, name) ); CREATE INDEX IF NOT EXISTS idx_forum_tag_channel_id ON forum_tag (channel_id); -- ============================================================ -- 4. Voice Participants -- ============================================================ -- models/channels/voice_participants.rs → voice_participant CREATE TABLE IF NOT EXISTS voice_participant ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, session_id TEXT NULL, deafened BOOLEAN NOT NULL, muted BOOLEAN NOT NULL, self_deafened BOOLEAN NOT NULL, self_muted BOOLEAN NOT NULL, self_video BOOLEAN NOT NULL, streaming BOOLEAN NOT NULL, speaking BOOLEAN NOT NULL, joined_at TIMESTAMPTZ NOT NULL, left_at TIMESTAMPTZ NULL ); CREATE INDEX IF NOT EXISTS idx_voice_participant_channel_id ON voice_participant (channel_id); CREATE INDEX IF NOT EXISTS idx_voice_participant_user_id ON voice_participant (user_id); CREATE INDEX IF NOT EXISTS idx_voice_participant_channel_user ON voice_participant (channel_id, user_id); -- ============================================================ -- 5. Stages -- ============================================================ -- models/channels/stages.rs → stage CREATE TABLE IF NOT EXISTS stage ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE, topic TEXT NOT NULL, privacy_level TEXT NOT NULL, discoverable BOOLEAN NOT NULL, started_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, started_at TIMESTAMPTZ NOT NULL, ended_at TIMESTAMPTZ NULL, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, CONSTRAINT uq_stage_active_channel UNIQUE (channel_id, ended_at) ); CREATE INDEX IF NOT EXISTS idx_stage_channel_id ON stage (channel_id); CREATE INDEX IF NOT EXISTS idx_stage_channel_active ON stage (channel_id) WHERE ended_at IS NULL; -- ============================================================ -- 6. Message Polls -- ============================================================ -- models/channels/message_polls.rs → message_poll CREATE TABLE IF NOT EXISTS message_poll ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), message_id UUID NOT NULL REFERENCES message(id) ON DELETE CASCADE, channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE, question TEXT NOT NULL, description TEXT NULL, layout TEXT NOT NULL, allow_multiselect BOOLEAN NOT NULL, duration_hours INTEGER NULL, ends_at TIMESTAMPTZ NULL, total_votes BIGINT NOT NULL, metadata JSONB NULL, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, CONSTRAINT uq_message_poll_message_id UNIQUE (message_id) ); CREATE INDEX IF NOT EXISTS idx_message_poll_channel_id ON message_poll (channel_id); CREATE INDEX IF NOT EXISTS idx_message_poll_ends_at ON message_poll (ends_at) WHERE ends_at IS NOT NULL; -- ============================================================ -- 7. Message Poll Options -- ============================================================ -- models/channels/message_poll_options.rs → message_poll_option CREATE TABLE IF NOT EXISTS message_poll_option ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), poll_id UUID NOT NULL REFERENCES message_poll(id) ON DELETE CASCADE, position INTEGER NOT NULL, text TEXT NOT NULL, emoji_id TEXT NULL, emoji_name TEXT NULL, vote_count BIGINT NOT NULL, created_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS idx_message_poll_option_poll_id ON message_poll_option (poll_id); -- ============================================================ -- 8. Message Poll Votes -- ============================================================ -- models/channels/message_poll_votes.rs → message_poll_vote CREATE TABLE IF NOT EXISTS message_poll_vote ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), poll_id UUID NOT NULL REFERENCES message_poll(id) ON DELETE CASCADE, option_id UUID NOT NULL REFERENCES message_poll_option(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, voted_at TIMESTAMPTZ NOT NULL, CONSTRAINT uq_message_poll_vote_user_option UNIQUE (poll_id, option_id, user_id) ); CREATE INDEX IF NOT EXISTS idx_message_poll_vote_poll_id ON message_poll_vote (poll_id); CREATE INDEX IF NOT EXISTS idx_message_poll_vote_user_id ON message_poll_vote (user_id); CREATE INDEX IF NOT EXISTS idx_message_poll_vote_option_id ON message_poll_vote (option_id); -- ============================================================ -- 9. Triggers — auto-refresh updated_at -- ============================================================ DROP TRIGGER IF EXISTS trg_forum_tag_updated_at ON forum_tag; CREATE TRIGGER trg_forum_tag_updated_at BEFORE UPDATE ON forum_tag FOR EACH ROW EXECUTE FUNCTION set_updated_at(); DROP TRIGGER IF EXISTS trg_stage_updated_at ON stage; CREATE TRIGGER trg_stage_updated_at BEFORE UPDATE ON stage FOR EACH ROW EXECUTE FUNCTION set_updated_at(); DROP TRIGGER IF EXISTS trg_message_poll_updated_at ON message_poll; CREATE TRIGGER trg_message_poll_updated_at BEFORE UPDATE ON message_poll FOR EACH ROW EXECUTE FUNCTION set_updated_at();