Skip to main content

Resource: Chat (messaging)

Tag: Chat · Module: chat

Source of truth for the chat surface is centrifugo_chat_poc.md (the Centrifugo Chat POC). Chat is mid-migration off Supabase Realtime / PubNub onto self-hosted Centrifugo with a transactional outbox. This file maps the target client-facing endpoints onto the conventions (C0–C9) — it does not re-spec that design.

Chat messaging only. The shared Centrifugo connection token lives in realtime.md; user-status presence is its own resource (presence.md). The current chat (CreateDM/getToken for Supabase) + PubNub client are being replaced — do not spec the soon-dead Supabase/PubNub surface; the contract targets the Centrifugo endpoints.


Operations — target endpoint map

Client-facing ops, aligned to conventions. Detailed request/response shapes are defined as each is built per the design doc — this is the operation inventory + the convention rules they must follow, not a re-spec.

OperationIDMethod + PathDesign §Notes
get-subscribe-tokenPOST /chat/subscribe-token§5{ conversationId } → per-chat:-channel JWT (the connection token lives in Realtime). TODO: Check if we really need this endpoint.
send-messagePOST /chat/messages/send§7{ conversationId, content, clientMsgId? }; backend fans out via outbox
list-messagesPOST /chat/messages/list§7history; bidirectional cursor (before/after/around a message id — scrollback, jump-to-unread/pin/hit); {data, page}
search-messagesPOST /chat/messages/search§6in-channel search overlay (optional global); { conversationId?, query, … }{data, page}
edit-messagePOST /chat/messages/{id}/update§10soft edit
delete-messagePOST /chat/messages/{id}/delete§10soft delete
send-typingPOST /chat/typing/send§10ephemeral (direct publish, not outbox)
add-reactionPOST /chat/messages/{id}/reactions/add§10
remove-reactionPOST /chat/messages/{id}/reactions/remove§10
pin-messagePOST /chat/messages/{id}/pin§4pin to the conversation
unpin-messagePOST /chat/messages/{id}/unpin§4
list-conversationsPOST /chat/conversations/list§7recent DMs / sidebar; {data, page}
create-conversationPOST /chat/conversations/create§6DM / group / private / org-wide (one body, visibility/historyMode)
get-conversationPOST /chat/conversations/{id}/get§6
update-conversationPOST /chat/conversations/{id}/update§6rename/describe
archive-conversationPOST /chat/conversations/{id}/archive§6
convert-conversationPOST /chat/conversations/{id}/convert§6group → private-channel (2-col UPDATE)
add-conversation-memberPOST /chat/conversations/{id}/members/add§6admin only
remove-conversation-memberPOST /chat/conversations/{id}/members/remove§6admin only
leave-conversationPOST /chat/conversations/{id}/leave§6
mark-conversation-readPOST /chat/conversations/{id}/read§7read-cursor update (per-member)
update-conversation-preferencesPOST /chat/conversations/{id}/preferences/update§5per-member state { starred?, muteUntil?, notifyLevel? } — NOT update-conversation (which sets shared name/desc/visibility)
list-pinned-messagesPOST /chat/conversations/{id}/pins/list§4/§7pinned bar + Details → Pinned; {data, page}
list-conversation-attachmentsPOST /chat/conversations/{id}/attachments/list§7Details → Shared files grid; {data, page}
chat-attachment-upload-ticketPOST /chat/attachments/upload-ticket§10shared image/file upload pattern (R2)
chat-attachment-finalizePOST /chat/attachments/finalize§10link to message

create-conversation absorbs the legacy CreateDM (POST /api/v1/chat/conversations).

Notes on the additions / model decisions (audited against the Sidebar Chat handoff):

  • Per-member vs shared conversation state. update-conversation-preferences carries the user's own star / mute-timer / notify-level (§5–§7) and is deliberately separate from update-conversation (shared name/desc/visibility, admin-only). muteUntil = RFC3339 (or null = unmuted / sentinel for "until I turn it back on"); notifyLevel = all | mentions | everyone | none (DMs: all | none). mark-read stays its own op (high-frequency cursor).
  • Global/public channels are auto-membership — every org member is implicitly in global channels, so there is no join / browse-channels op; leave-conversation on such a channel hides/mutes it for the user. private channels gain members only via add-conversation-member.
  • Link previews are embedded on the message, not a separate endpoint. The server unfurls at send time (async, via the outbox) and attaches linkPreviews[] to the message DTO, delivered on the realtime update (Slack/Discord model). Shape owned by the design doc; flagged here because the unfurl is a backend job, not client-side (browsers can't fetch arbitrary cross-origin OG tags).
  • Deferred (DM details, §7): block-user / unblock-user and clear-conversation are out of the contract for now — added when the DM block/clear affordances ship (would extend C1b with block/clear).

NOT in the typed client contract

  • The outbox table, Centrifugo config, control calls (/api/unsubscribe|disconnect, §5/§8) — server-side infra, not client ops.
  • Centrifugo's own history buffer — never a client API (§4).
  • The status:-channel subscribe-proxy is presence infra — see presence.md.

Conventions applied

  • IDs: conversation/message ids are int64 today → stringified (C2); user ids are the canonical stringified user id (matches Member/User/occupants).
  • Timestamps: RFC3339 (C3).
  • Lists (list-messages, list-conversations, search-messages, list-pinned-messages, list-conversation-attachments): { data, page } (C4); list-messages pages bidirectionally (before/after/around a message id — scrollback + the §7 delta-fetch/jump model).
  • Paths: explicit-verb, sub-resourced (/chat/conversations/{id}/members/add) per C1; CRUD bare-verb.
  • Attachments reuse the shared image/file upload-ticket→finalize pattern (user.md §Image upload).

Module / backend notes

  • chat carries over (DM creation, repo/service) and grows the lifecycle/history services relocating from Postgres triggers/RPCs into Go (design §2, §7, §15). The connection token → realtime (realtime.md); presence → presence (presence.md).
  • Build order, schema, outbox, rollout sequencing: all governed by the design doc — not duplicated here.
  • Detailed DTOs per op are finalized as the Centrifugo phases land; they must conform to the conventions above.