Files
nick-doc/02 - Data Models/Chat.md
2026-05-23 20:35:34 +03:30

6.9 KiB

title, tags, aliases
title tags aliases
Chat
data-model
mongoose
Conversation
IChat
IMessage

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 definition backend/src/models/Chat.ts:69 — message subdocument schema backend/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 timestamps Unlike most models, this schema does not pass { timestamps: true }. It uses its own metadata.createdAt / metadata.updatedAt instead, 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 through relatedTo.

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.