database schema adjustments to server/realm/scene
This commit is contained in:
parent
a102c96bb4
commit
09590edd95
79 changed files with 7100 additions and 100 deletions
174
db/schema/tables/050_chat.sql
Normal file
174
db/schema/tables/050_chat.sql
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
-- Chattyness Chat Schema Tables
|
||||
-- PostgreSQL 18
|
||||
--
|
||||
-- Messages, whispers, shouts, and reactions
|
||||
-- Load via: psql -f schema/tables/050_chat.sql
|
||||
--
|
||||
-- NOTE: This table is designed for future partitioning by created_at.
|
||||
-- When message volume grows, convert to a partitioned table:
|
||||
-- 1. Rename chat.messages to chat.messages_old
|
||||
-- 2. Create new partitioned chat.messages with PARTITION BY RANGE (created_at)
|
||||
-- 3. Create monthly partitions (e.g., chat.messages_2025_01)
|
||||
-- 4. Migrate data from old table
|
||||
-- 5. Consider pg_partman extension for automatic partition management
|
||||
-- 6. Add archive table for long-term storage of old partitions
|
||||
|
||||
\set ON_ERROR_STOP on
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- =============================================================================
|
||||
-- Instance Messages (Scene-wide chat)
|
||||
-- =============================================================================
|
||||
-- Messages visible to everyone in an instance.
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE chat.messages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
instance_id UUID NOT NULL REFERENCES scene.instances(id) ON DELETE CASCADE,
|
||||
|
||||
-- Sender (either user or guest)
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
guest_session_id UUID REFERENCES auth.guest_sessions(id) ON DELETE SET NULL,
|
||||
|
||||
-- Cached sender info (in case account deleted)
|
||||
sender_name public.display_name NOT NULL,
|
||||
|
||||
-- Message content
|
||||
message_type chat.message_type NOT NULL DEFAULT 'normal',
|
||||
content TEXT NOT NULL,
|
||||
|
||||
-- Position when message was sent (for speech bubble placement)
|
||||
position public.virtual_point,
|
||||
|
||||
-- Reply threading
|
||||
reply_to_id UUID REFERENCES chat.messages(id) ON DELETE SET NULL,
|
||||
|
||||
-- Moderation
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
deleted_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
-- Either user_id or guest_session_id must be set
|
||||
CONSTRAINT chk_chat_messages_sender CHECK (
|
||||
(user_id IS NOT NULL AND guest_session_id IS NULL) OR
|
||||
(user_id IS NULL AND guest_session_id IS NOT NULL)
|
||||
)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat.messages IS 'Instance messages (design supports future time-based partitioning)';
|
||||
COMMENT ON COLUMN chat.messages.position IS 'Sender position when message sent (for speech bubbles)';
|
||||
COMMENT ON COLUMN chat.messages.reply_to_id IS 'ID of message being replied to';
|
||||
|
||||
CREATE INDEX idx_chat_messages_instance_time ON chat.messages (instance_id, created_at DESC);
|
||||
CREATE INDEX idx_chat_messages_user ON chat.messages (user_id, created_at DESC)
|
||||
WHERE user_id IS NOT NULL;
|
||||
CREATE INDEX idx_chat_messages_reply ON chat.messages (reply_to_id)
|
||||
WHERE reply_to_id IS NOT NULL;
|
||||
CREATE INDEX idx_chat_messages_created ON chat.messages (created_at DESC);
|
||||
|
||||
-- =============================================================================
|
||||
-- Shouts (Server-wide broadcasts)
|
||||
-- =============================================================================
|
||||
-- Messages broadcast to all users on the server.
|
||||
-- Typically limited to admins or special events.
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE chat.shouts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
sender_name public.display_name NOT NULL,
|
||||
|
||||
content TEXT NOT NULL,
|
||||
|
||||
-- Targeting
|
||||
target_realm_id UUID REFERENCES realm.realms(id) ON DELETE CASCADE, -- NULL = all realms
|
||||
|
||||
-- TTL for display
|
||||
expires_at TIMESTAMPTZ,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat.shouts IS 'Server-wide or realm-wide broadcast messages';
|
||||
COMMENT ON COLUMN chat.shouts.target_realm_id IS 'NULL = broadcast to all realms';
|
||||
|
||||
CREATE INDEX idx_chat_shouts_time ON chat.shouts (created_at DESC);
|
||||
CREATE INDEX idx_chat_shouts_realm ON chat.shouts (target_realm_id, created_at DESC)
|
||||
WHERE target_realm_id IS NOT NULL;
|
||||
|
||||
-- =============================================================================
|
||||
-- Whispers (Private Direct Messages)
|
||||
-- =============================================================================
|
||||
-- Private messages between two users, cross-realm capable.
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE chat.whispers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Sender and recipient
|
||||
sender_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
recipient_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
|
||||
content TEXT NOT NULL,
|
||||
|
||||
-- Read status
|
||||
read_at TIMESTAMPTZ,
|
||||
|
||||
-- Moderation
|
||||
is_deleted_by_sender BOOLEAN NOT NULL DEFAULT false,
|
||||
is_deleted_by_recipient BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
CONSTRAINT chk_chat_whispers_not_self CHECK (sender_id != recipient_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat.whispers IS 'Private direct messages between users';
|
||||
|
||||
CREATE INDEX idx_chat_whispers_sender ON chat.whispers (sender_id, created_at DESC);
|
||||
CREATE INDEX idx_chat_whispers_recipient ON chat.whispers (recipient_id, created_at DESC);
|
||||
CREATE INDEX idx_chat_whispers_unread ON chat.whispers (recipient_id, read_at)
|
||||
WHERE read_at IS NULL;
|
||||
|
||||
-- Composite index for conversation view between two users
|
||||
CREATE INDEX idx_chat_whispers_conversation ON chat.whispers (
|
||||
LEAST(sender_id, recipient_id),
|
||||
GREATEST(sender_id, recipient_id),
|
||||
created_at DESC
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- Message Reactions
|
||||
-- =============================================================================
|
||||
-- Emoji reactions to channel messages.
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE chat.reactions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
message_id UUID NOT NULL REFERENCES chat.messages(id) ON DELETE CASCADE,
|
||||
|
||||
-- Reactor
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
|
||||
-- Reaction (emoji or predefined reaction code)
|
||||
reaction TEXT NOT NULL CHECK (length(reaction) <= 32),
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
-- One reaction type per user per message
|
||||
CONSTRAINT uq_chat_reactions UNIQUE (message_id, user_id, reaction)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat.reactions IS 'Emoji reactions to channel messages';
|
||||
|
||||
CREATE INDEX idx_chat_reactions_message ON chat.reactions (message_id);
|
||||
CREATE INDEX idx_chat_reactions_user ON chat.reactions (user_id);
|
||||
|
||||
COMMIT;
|
||||
Loading…
Add table
Add a link
Reference in a new issue