You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
synctv/migrations/20260426015_create_chat_mes...

215 lines
8.9 KiB
SQL

CREATE TABLE IF NOT EXISTS chat_messages (
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
room_id BIGINT NOT NULL,
user_id BIGINT,
client_message_id TEXT,
content TEXT NOT NULL DEFAULT '',
message_type SMALLINT NOT NULL DEFAULT 1,
status SMALLINT NOT NULL DEFAULT 1,
version BIGINT NOT NULL DEFAULT 1,
reply_to_message_id BIGINT,
reply_to_message_created_at TIMESTAMPTZ,
metadata JSONB,
edited_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
deleted_by BIGINT,
delete_reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
content_search TSVECTOR GENERATED ALWAYS AS (to_tsvector('simple', content)) STORED,
CHECK (version >= 1),
CHECK (metadata IS NULL OR jsonb_typeof(metadata) = 'object'),
CHECK (
(reply_to_message_id IS NULL AND reply_to_message_created_at IS NULL)
OR (reply_to_message_id IS NOT NULL AND reply_to_message_created_at IS NOT NULL)
),
PRIMARY KEY (id, created_at),
UNIQUE (room_id, id, created_at),
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE RESTRICT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (deleted_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (room_id, reply_to_message_id, reply_to_message_created_at)
REFERENCES chat_messages(room_id, id, created_at)
ON DELETE SET NULL (reply_to_message_id, reply_to_message_created_at)
) PARTITION BY RANGE (created_at);
CREATE INDEX IF NOT EXISTS idx_chat_messages_room_pagination
ON chat_messages(room_id, created_at DESC, id DESC);
CREATE INDEX IF NOT EXISTS idx_chat_messages_user_created
ON chat_messages(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at
ON chat_messages(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_chat_messages_status
ON chat_messages(room_id, status, created_at DESC, id DESC);
CREATE INDEX IF NOT EXISTS idx_chat_messages_reply_target
ON chat_messages(reply_to_message_id, reply_to_message_created_at);
CREATE INDEX IF NOT EXISTS idx_chat_messages_content_search
ON chat_messages USING gin(content_search);
CREATE INDEX IF NOT EXISTS idx_chat_messages_content_trgm
ON chat_messages USING gin(content gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_chat_messages_playback_media
ON chat_messages(
room_id,
((metadata #>> '{playback,mediaId}')),
(
CASE
WHEN jsonb_typeof(metadata #> '{playback,positionSeconds}') = 'number'
THEN (metadata #>> '{playback,positionSeconds}')::double precision
ELSE NULL
END
),
created_at,
id
)
WHERE metadata ? 'playback';
CREATE INDEX IF NOT EXISTS idx_chat_messages_playback_playlist_target
ON chat_messages(
room_id,
((metadata #>> '{playback,playlistId}')),
((metadata #>> '{playback,targetHash}')),
(
CASE
WHEN jsonb_typeof(metadata #> '{playback,positionSeconds}') = 'number'
THEN (metadata #>> '{playback,positionSeconds}')::double precision
ELSE NULL
END
),
created_at,
id
)
WHERE metadata ? 'playback';
CREATE TABLE IF NOT EXISTS chat_message_events (
sequence BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
event_id TEXT NOT NULL UNIQUE,
room_id BIGINT NOT NULL,
actor_user_id BIGINT NOT NULL,
message_id BIGINT NOT NULL,
message_created_at TIMESTAMPTZ NOT NULL,
event_type TEXT NOT NULL,
event_version BIGINT NOT NULL DEFAULT 1,
message_version BIGINT NOT NULL,
payload JSONB NOT NULL,
summary JSONB NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (event_version >= 1),
CHECK (message_version >= 1),
CHECK (jsonb_typeof(payload) = 'object'),
CHECK (jsonb_typeof(summary) = 'object'),
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE,
FOREIGN KEY (room_id, message_id, message_created_at)
REFERENCES chat_messages(room_id, id, created_at) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_chat_message_events_room_sequence
ON chat_message_events(room_id, sequence);
CREATE INDEX IF NOT EXISTS idx_chat_message_events_message_sequence
ON chat_message_events(room_id, message_id, message_created_at, sequence);
CREATE INDEX IF NOT EXISTS idx_chat_message_events_created_at
ON chat_message_events(created_at, sequence);
CREATE INDEX IF NOT EXISTS idx_chat_message_events_type_sequence
ON chat_message_events(room_id, event_type, sequence);
CREATE TABLE IF NOT EXISTS chat_message_idempotency (
room_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
client_message_id TEXT NOT NULL,
request_hash TEXT NOT NULL,
message_id BIGINT,
message_created_at TIMESTAMPTZ,
event_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (room_id, user_id, client_message_id),
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (room_id, message_id, message_created_at)
REFERENCES chat_messages(room_id, id, created_at) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_chat_message_idempotency_created
ON chat_message_idempotency(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_chat_message_idempotency_message
ON chat_message_idempotency(message_id, message_created_at);
CREATE INDEX IF NOT EXISTS idx_chat_message_idempotency_event
ON chat_message_idempotency(event_id)
WHERE event_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS chat_message_attachments (
id TEXT NOT NULL,
kind SMALLINT NOT NULL,
room_id BIGINT NOT NULL,
message_id BIGINT NOT NULL,
message_created_at TIMESTAMPTZ NOT NULL,
filename TEXT,
storage_backend TEXT NOT NULL,
object_key TEXT NOT NULL,
url TEXT,
mime_type TEXT,
size_bytes BIGINT,
width INTEGER,
height INTEGER,
metadata JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (size_bytes IS NULL OR size_bytes > 0),
CHECK (width IS NULL OR width > 0),
CHECK (height IS NULL OR height > 0),
CHECK (jsonb_typeof(metadata) = 'object'),
PRIMARY KEY (room_id, message_id, message_created_at, id),
FOREIGN KEY (room_id, message_id, message_created_at)
REFERENCES chat_messages(room_id, id, created_at) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_chat_message_attachments_message
ON chat_message_attachments(message_id, message_created_at, created_at, id);
CREATE INDEX IF NOT EXISTS idx_chat_message_attachments_room
ON chat_message_attachments(room_id, created_at DESC);
CREATE TABLE IF NOT EXISTS chat_message_operation_idempotency (
room_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
client_operation_id TEXT NOT NULL,
operation_kind SMALLINT NOT NULL,
request_hash TEXT NOT NULL,
message_id BIGINT NOT NULL,
message_created_at TIMESTAMPTZ NOT NULL,
event_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (room_id, user_id, client_operation_id),
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (room_id, message_id, message_created_at)
REFERENCES chat_messages(room_id, id, created_at) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_chat_message_operation_idempotency_message
ON chat_message_operation_idempotency(room_id, message_id, message_created_at);
CREATE INDEX IF NOT EXISTS idx_chat_message_operation_idempotency_event
ON chat_message_operation_idempotency(event_id)
WHERE event_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS chat_read_states (
room_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
last_read_message_id BIGINT,
last_read_message_created_at TIMESTAMPTZ,
last_read_event_id TEXT,
last_read_event_sequence BIGINT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (room_id, user_id),
CHECK (
(last_read_message_id IS NULL AND last_read_message_created_at IS NULL)
OR (last_read_message_id IS NOT NULL AND last_read_message_created_at IS NOT NULL)
),
CHECK (last_read_event_sequence IS NULL OR last_read_event_sequence >= 1),
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (room_id, last_read_message_id, last_read_message_created_at)
REFERENCES chat_messages(room_id, id, created_at)
ON DELETE SET NULL (last_read_message_id, last_read_message_created_at)
);
CREATE INDEX IF NOT EXISTS idx_chat_read_states_room_updated
ON chat_read_states(room_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_chat_read_states_message
ON chat_read_states(last_read_message_id, last_read_message_created_at);