Files
appks/migrate/010_channel_kinds.sql
T
2026-06-07 11:30:56 +08:00

188 lines
8.6 KiB
SQL

-- 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();