Sealearn’s chat system pairs a set of REST endpoints for managing conversations and paginating message history with a Socket.IO namespace for real-time delivery. The REST layer handles the data model — creating rooms, fetching history, tracking unread counts — while theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/DerBasilisk/SEA-ServicioEvaluaconAsistida/llms.txt
Use this file to discover all available pages before exploring further.
/chat Socket.IO namespace handles live events like typing indicators, read receipts, and message broadcasting. Both layers share the same JWT authentication mechanism.
REST routes are mounted under
/api/chat and protected by verificarToken. The Socket.IO namespace is /chat — make sure to specify it when connecting. Images must be uploaded via the REST upload endpoint before being sent through the socket.Data Models
Conversation
AConversation document represents either a 1-on-1 direct chat ("direct") or a named group ("group"). The server enforces uniqueness on direct conversations so that two users always share exactly one direct conversation regardless of how many times the client calls openDirect.
| Field | Type | Description |
|---|---|---|
_id | ObjectId | Unique conversation ID. |
type | "direct" | "group" | Chat type. |
name | string | null | Group name (max 50 characters); null for direct conversations. |
avatar | string | null | Group avatar URL; null for direct conversations. |
participants | ObjectId[] → User | All users who are members of this conversation. |
createdBy | ObjectId → User | null | User who created the group; null for direct conversations. |
lastMessage | ObjectId → Message | null | Reference to the most recent message, used for conversation list previews. |
lastActivity | Date | Updated whenever a new message is sent; used for sorting the conversation list. |
lastDuel | ObjectId → Duel | null | Reference to the most recent duel associated with this conversation. |
createdAt | Date | Mongoose auto-timestamp. |
updatedAt | Date | Mongoose auto-timestamp. |
Message
AMessage document records a single message within a conversation. Messages are never hard-deleted — a deletedAt timestamp is set instead (soft delete). The type field controls how the client renders the message bubble.
| Field | Type | Description |
|---|---|---|
_id | ObjectId | Unique message ID. |
conversation | ObjectId → Conversation | Parent conversation. |
sender | ObjectId → User | User who sent the message. |
type | "text" | "image" | "duel_invite" | "duel_result" | Rendering hint for the client. |
content | string | Text content or Cloudinary image URL (max 2 000 characters). |
duelData | object | null | Present on duel_invite and duel_result messages; contains duelId, resultSummary, inviteCode, and expiresAt. |
readBy | ObjectId[] → User | IDs of users who have read the message. |
deletedAt | Date | null | Set on soft-delete; null while the message is visible. |
edited | boolean | true if the sender has edited the content after sending. |
createdAt | Date | Mongoose auto-timestamp. |
REST Endpoints
GET /api/chat/conversations
Returns all conversations the authenticated user belongs to, sorted bylastActivity descending.
true on success.Array of populated Conversation documents, each with its
lastMessage and participants populated.POST /api/chat/conversations/direct
Gets an existing direct conversation between the authenticated user andtargetUserId, or creates one if it does not yet exist. Safe to call multiple times — idempotent.
MongoDB
_id of the other user. Cannot be the same as the authenticated user’s own ID.true on success.The direct Conversation document (existing or newly created).
POST /api/chat/conversations/group
Creates a new group conversation. The authenticated user is automatically added toparticipants as the creator.
Group name. Must be a non-empty string, maximum 50 characters.
Array of user
_id strings to include in the group. Must contain at least one entry (in addition to the creator).true on success (201 Created).The newly created group Conversation document.
POST /api/chat/conversations/:id/participants
Adds a new user to an existing group conversation.The
_id of the group Conversation.MongoDB
_id of the user to add.true on success.Updated Conversation document.
DELETE /api/chat/conversations/:id/participants/me
Removes the authenticated user from a group conversation. If the group becomes empty after the user leaves, it is deleted entirely.The
_id of the group Conversation to leave.true on success.true if the conversation was deleted because it became empty.GET /api/chat/conversations/:id/messages
Fetches the message history for a conversation with cursor-based pagination. Returns messages sorted newest-first. Pass thebefore query parameter to load earlier pages.
The
_id of the Conversation.Message
_id cursor. When provided, only messages created before the referenced message are returned, enabling infinite-scroll pagination.true on success.Array of Message documents (newest first), each with
sender populated.GET /api/chat/unread
Returns an object mapping each conversation ID to the count of unread messages. Use this to display notification badges without fetching full message history.true on success.A map of
{ conversationId: unreadCount }. Conversations with no unread messages are omitted.DELETE /api/chat/messages/:id
Soft-deletes a message. Only the message’s ownsender can delete it. The document remains in the database with deletedAt set to the current timestamp; clients should render deleted messages as "Mensaje eliminado".
The
_id of the Message to delete.true on success.POST /api/chat/upload
Uploads an image to Cloudinary and returns its URL. After receiving the URL, send it over the socket using thechat:send_image event. Images must be under 5 MB and must be a valid image/* MIME type.
The request must use multipart/form-data.
The image file to upload (max 5 MB, any
image/* content type).true on success.Cloudinary
secure_url of the uploaded image.Socket.IO — /chat Namespace
The /chat namespace handles all real-time messaging. It shares the same Socket.IO server as the duel system but uses a dedicated namespace to keep event spaces clean.
Authentication
Pass the JWT in theauth object when connecting. The middleware decodes the token and attaches socket.userId for all subsequent events.
Rooms
On connection every socket is automatically joined touser:<userId>. This personal room ensures the user receives messages from conversations that are not currently open on screen (used for unread badge notifications). When the user opens a specific conversation, the client emits chat:join to also subscribe to conv:<conversationId>.
Client → Server Events
chat:join
Open a conversation and mark all its messages as read.
| Field | Type | Description |
|---|---|---|
conversationId | string | The _id of the Conversation to join. |
chat:joined on success, or chat:error on failure.
chat:leave
Stop receiving room-scoped events for a conversation without disconnecting.
chat:send
Send a plain-text message to a conversation. Content is trimmed and must not exceed 2 000 characters.
| Field | Type | Description |
|---|---|---|
conversationId | string | Target conversation. |
content | string | Message text (max 2 000 characters). |
chat:send_image
Send an image message. Upload the file first with POST /api/chat/upload, then emit the returned URL here.
chat:typing
Broadcast a typing indicator to all other participants in a conversation.
| Field | Type | Description |
|---|---|---|
conversationId | string | Target conversation. |
isTyping | boolean | true when the user starts typing; false when they stop. |
chat:mark_read
Explicitly mark all unread messages in a conversation as read. Also called automatically by chat:join.
chat:open_direct
Socket-side shortcut to get or create a direct conversation without a separate REST call. Responds with chat:conversation_ready.
chat:edit
Edit the content of a message the authenticated user previously sent.
Server → Client Events
chat:message
Emitted to all sockets in conv:<conversationId> whenever a new message (text, image, or duel result) is saved.
| Field | Type | Description |
|---|---|---|
conversationId | string | Conversation the message belongs to. |
message._id | string | Message document ID. |
message.type | string | "text", "image", "duel_invite", or "duel_result". |
message.content | string | Message text or image URL. |
message.sender | object | Populated sender (_id, username, displayName, avatar). |
message.readBy | string[] | Array of user IDs that have read the message. |
message.createdAt | string | ISO 8601 creation timestamp. |
message.duelData | object | undefined | Present on duel_result messages; contains duelId and resultSummary. |
chat:new_message_notify
Emitted to all sockets in the conversation room except those who are also in the conv: room (i.e. users who have the conversation open). Use this to update unread badge counts in the conversation list sidebar.
chat:joined
Confirmation that chat:join succeeded.
chat:typing
Broadcasts another user’s typing status within the conversation.
chat:read
Notifies participants that a specific user has read messages in the conversation.
chat:read_confirmed
Sent back to the socket that emitted chat:mark_read, confirming how many messages were marked.
chat:message_edited
Emitted to the conversation room when a message is edited.
chat:conversation_ready
Response to chat:open_direct — provides the ready conversation object.