Skip to main content

Chats model

StatusImplementation
✅ Shippedsrc/infra/db/schema/chat.schema.ts

chats

ColumnTypeNotes
idUUID PKdefaultRandom()
typeenum chat_typeapproved_group, direct (direct planned)
created_attimestamptz
CREATE TYPE chat_type AS ENUM ('approved_group', 'direct');

Source: chat.schema.ts.

chat_participants

ColumnTypeNotes
chat_idUUID FK → chats.id ON DELETE CASCADE
user_idUUID FK → auth.users.id
joined_attimestamptz
CREATE UNIQUE INDEX chat_participants_uniq
ON chat_participants (chat_id, user_id);
CREATE INDEX chat_participants_user_idx
ON chat_participants (user_id);

messages

ColumnTypeNotes
idUUID PKdefaultRandom()
chat_idUUID FK → chats.id ON DELETE CASCADE
sender_idUUID FK → auth.users.id
contenttextMessage content
created_attimestamptz
updated_attimestamptz
deleted_attimestamptzSoft delete
CREATE INDEX messages_chat_created_idx
ON messages (chat_id, created_at DESC);

CREATE INDEX messages_sender_idx ON messages (sender_id);

RLS

Per the conventions in api/docs/RLS-NOTES.md:

  • chat_participants: a user may SELECT only rows where user_id = auth.uid(). All cross-membership reads (e.g. "who else is in this chat") happen through the API as service_role.
  • messages: SELECT allowed when the caller has a non-left_at row in chat_participants for the message's chat_id. Same rule for INSERT (with sender_id = auth.uid()).
  • chats: same membership rule via a SECURITY DEFINER helper.

These RLS predicates are what makes Supabase Realtime safe — the realtime layer applies the same RLS, so a client subscribed to postgres_changes on messages automatically only receives rows for chats they participate in.

Relationship overview