6.9 KiB
title, tags, aliases
| title | tags | aliases | |||||
|---|---|---|---|---|---|---|---|
| Chat |
|
|
Chat
Conversation container with embedded messages. Used for buyer-seller direct chats, group chats, and support tickets. Each chat carries a list of participants, an embedded messages[] array (with reactions, replies, edit history), a denormalised lastMessage snapshot for list views, and per-user unreadCounts. A chat can be linked to any other entity through the relatedTo discriminator (currently PurchaseRequest, SellerOffer, or Transaction).
[!note] Source
backend/src/models/Chat.ts:130— chat schema definitionbackend/src/models/Chat.ts:69— message subdocument schemabackend/src/models/Chat.ts:348— model export
[!warning] Embedded messages Messages live inside the chat document. Very long-running chats can grow past the 16 MB document limit. Treat this as a known constraint of the current schema.
Schema — Chat
| Field | Type | Required | Default | Validation | Index | Description |
|---|---|---|---|---|---|---|
type |
String | yes | direct |
enum: direct / group / support |
yes | Conversation type. |
name |
String | no | — | maxlength 100 | — | Display name (group chats). |
description |
String | no | — | maxlength 500 | — | Optional description. |
participants[].userId |
ObjectId → User | yes | — | — | yes | Member id. |
participants[].role |
String | no | member |
enum: member / admin / owner |
— | Member role. |
participants[].joinedAt |
Date | no | Date.now |
— | — | Join time. |
participants[].lastSeen |
Date | no | — | — | — | Last activity. |
participants[].leftAt |
Date | no | — | — | — | If left, when. |
participants[].isActive |
Boolean | no | true |
— | — | Still a participant. |
messages[] |
Subdocument[] | no | [] |
— | yes (messages.timestamp) |
Embedded messages. |
relatedTo.type |
String | no | — | enum: PurchaseRequest / SellerOffer / Transaction |
yes (compound) | Linked entity kind. |
relatedTo.id |
ObjectId | no | — | — | yes (compound) | Linked entity id. |
lastMessage.content |
String | no | — | — | — | Snapshot for list views. |
lastMessage.senderId |
ObjectId → User | no | — | — | — | Last sender. |
lastMessage.timestamp |
Date | no | — | — | — | Last message time. |
lastMessage.messageType |
String | no | — | — | — | Last message type. |
unreadCounts[].userId |
ObjectId → User | no | — | — | — | User the counter belongs to. |
unreadCounts[].count |
Number | no | 0 |
— | — | Number of unread messages. |
settings.isArchived |
Boolean | no | false |
— | — | Archived flag. |
settings.isMuted |
Boolean | no | false |
— | — | Muted flag. |
settings.mutedUntil |
Date | no | — | — | — | Mute expiry. |
settings.notifications |
Boolean | no | true |
— | — | Per-chat notification toggle. |
metadata.createdBy |
ObjectId → User | yes | — | — | — | Original creator. |
metadata.createdAt |
Date | no | Date.now |
— | — | Created timestamp. |
metadata.updatedAt |
Date | no | Date.now |
— | — | Touched by pre-save. |
metadata.lastActivity |
Date | no | Date.now |
— | yes (desc) | Sort key for chat lists. |
[!note] No top-level
timestampsUnlike most models, this schema does not pass{ timestamps: true }. It uses its ownmetadata.createdAt/metadata.updatedAtinstead, maintained by the pre-save hook.
Schema — Message (embedded)
| Field | Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|
senderId |
ObjectId → User | yes | — | — | Author. |
senderType |
String | no | User |
— | Currently fixed. |
content |
String | yes | — | maxlength 5000 | Message body. |
messageType |
String | no | text |
enum: text / image / file / system |
Body kind. |
fileUrl |
String | no | — | — | If file/image. |
fileName |
String | no | — | — | Original filename. |
fileSize |
Number | no | — | — | Bytes. |
timestamp |
Date | no | Date.now |
— | Sent time. |
isRead |
Boolean | no | false |
— | Read flag. |
isEdited |
Boolean | no | false |
— | Edited flag. |
editedAt |
Date | no | — | — | When edited. |
replyTo |
ObjectId | no | — | — | Reply target message id. |
reactions[].userId |
ObjectId → User | no | — | — | Reacting user. |
reactions[].reaction |
String | no | — | maxlength 10 | Emoji. |
Virtuals
| Virtual | Returns | Definition |
|---|---|---|
participantsCount |
Count of active participants | backend/src/models/Chat.ts:259 |
Indexes
Defined at backend/src/models/Chat.ts:243-247:
{ 'participants.userId': 1 }{ 'metadata.lastActivity': -1 }{ 'relatedTo.type': 1, 'relatedTo.id': 1 }{ 'messages.timestamp': -1 }{ type: 1 }
Pre/Post Hooks
| Hook | Behaviour |
|---|---|
pre('save') (backend/src/models/Chat.ts:250) |
Updates metadata.updatedAt and refreshes metadata.lastActivity when there are messages. |
Instance Methods
| Signature | Purpose |
|---|---|
getUnreadCount(userId: Types.ObjectId): number |
Returns the unread counter for a participant. backend/src/models/Chat.ts:264 |
addMessage(messageData: Partial<IMessage>): IMessage |
Pushes a message, updates lastMessage, increments unread counters for everyone except the sender, and bumps lastActivity. backend/src/models/Chat.ts:270 |
markAsRead(userId, messageIds?: Types.ObjectId[]): void |
Marks listed messages (or all) as read for the user, zeros their unread counter, and updates lastSeen. backend/src/models/Chat.ts:308 |
Static Methods
None defined.
Relationships
- References: User (
participants[].userId,messages[].senderId,messages[].reactions[].userId,lastMessage.senderId,unreadCounts[].userId,metadata.createdBy). - Referenced by: Dispute (
chatId), and any PurchaseRequest / SellerOffer indirectly throughrelatedTo.
State Transitions
No top-level status. Chat-level archival is a boolean flag (settings.isArchived):
stateDiagram-v2
[*] --> active
active --> muted : user mutes
muted --> active : unmute / mute expires
active --> archived : user archives
archived --> active : restore
Common Queries
// A user's recent chats
Chat.find({ 'participants.userId': userId, 'participants.isActive': true })
.sort({ 'metadata.lastActivity': -1 });
// Chat for a purchase request
Chat.findOne({ 'relatedTo.type': 'PurchaseRequest', 'relatedTo.id': prId });
// Append a message
const chat = await Chat.findById(id);
chat.addMessage({ senderId, content: 'hi', messageType: 'text' });
await chat.save();
// Mark read
chat.markAsRead(userId);
await chat.save();
Related: User, PurchaseRequest, SellerOffer, Dispute.