Banco de dadosTables
conversation_members
Membros de uma conversa, com role e estado per-user (lido, mutado, oculto).
Tabela de junção users ↔ conversations. Cada row é a "presença" de um user numa conversa, com estado per-user:
- Role (em grupos):
owner | admin | member - Permissions (em grupos):
{ can_send_messages, can_send_files } - Read state:
last_read_at(timestamp da última msg lida) - Mute:
is_muted(não recebe notif/push) - Archive:
is_archived(some da lista mas mensagens preservadas) - Hide ephemeral:
hidden_at(modo Snapchat per-user)
Colunas
| Coluna | Tipo | Nullable | Default |
|---|---|---|---|
| id ● | uuid | NOT NULL | gen_random_uuid() |
| conversation_id | uuid | NOT NULL | — |
| user_id | uuid | NOT NULL | — |
| role | enumcolumn | NOT NULL | member |
| permissions | jsonb | NOT NULL | {"can_send_messages":true,"can_send_files":true} |
| joined_at | timestamp | NOT NULL | now() |
| last_read_at | timestamp | NULL ok | — |
| is_archived | boolean | NOT NULL | false |
| hidden_at | timestamp | NULL ok | — |
| is_muted | boolean | NOT NULL | false |
● primary key ◆ unique
Funcionalidade dos campos
id— UUID, PKconversation_id— FK praconversations. Cascade delete.user_id— FK prausers. Cascade delete.role—owner | admin | member(significa em grupos; em DM, membros são sempremember).permissions— JSONB{ can_send_messages: bool, can_send_files: bool }. Default{ true, true }. Útil em grupos pra restringir.joined_at— entrou na conversa.last_read_at— timestamp da última mensagem que ele leu. Use emWHERE created_at > last_read_atpra contar não-lidas.is_archived— esconde da lista principal sem deletar.hidden_at— modo Snapchat per-user — após este momento, mensagens novas ficam invisíveis pro user (são apagadas pelo cron de cleanup quando todos os destinatários tiveremhidden_at).is_muted— silencia notificações/push. Mensagens chegam normal, só não tocam.
Foreign keys
| Coluna(s) | Referência | ON DELETE | ON UPDATE |
|---|---|---|---|
| conversation_id | conversations.id | cascade | no action |
| user_id | users.id | cascade | no action |
Índices
| Nome | Único | Colunas | WHERE (parcial) |
|---|---|---|---|
| conversation_members_unique | sim | conversation_id, user_id | — |
conversation_members_unique em (conversation_id, user_id) impede duplicação. Tentar adicionar membro existente deve ser tratado como no-op no service.
Constraints
PRIMARY KEY (id)
Read receipts
Ao receber notificação de "mensagem lida":
- Cliente do user A chama
POST /chat/conversations/:id/read - Backend atualiza
last_read_at = now()no row do A - Backend emite socket pro outro lado: "user A leu até agora"
- Frontend marca os check duplos como "lido"
Mas se A tem users.show_read_receipts = false: o backend NÃO emite o evento — privacy do user respeitada.
Modo ephemeral per-user
Implementação:
- User A liga modo no chat —
POST /chat/conversations/:id/temporary→conversation_members[A].hidden_at = now() - Mensagens novas após esse momento são marcadas
is_temporary=trueemmessages - Cleanup cron deleta mensagens onde TODOS os destinatários têm
hidden_for_user_idscobrindo o user
Padrões de uso
- Adicionar a grupo — INSERT (apenas
admin/ownerpodem) - Mudar role — UPDATE
role(apenasowner/admin) - Marcar lido — UPDATE
last_read_at - Mute — UPDATE
is_muted - Arquivar — UPDATE
is_archived - Sair — DELETE row (cascade simples)
Tabelas relacionadas
conversations— pai (cascade)users— membro (cascade)messages—last_read_até checado contramessages.created_at