Chat — Models¶
Overview¶
The chat app provides the persistence layer for real-time and asynchronous messaging between an internal organization and a customer organization.
The model layer supports:
- cross-organization conversations
- participant membership
- message history
- file attachments
- read tracking
- soft deletion of messages
This is the foundation for both:
- REST-based chat history and management
- websocket-based live messaging
Core models¶
Conversation¶
Represents a chat thread between one internal org and one customer org.
A conversation is the top-level container for:
- participants
- messages
- attachments
- optional topic linkage
Purpose¶
A Conversation defines the boundary of a chat exchange.
It answers:
- which two orgs are involved?
- what is this discussion about?
- is the conversation still open?
Fields¶
id-
UUID primary key
-
internal_org -
foreign key to the internal
Organization -
customer_org -
foreign key to the customer
Organization -
title -
optional human-readable conversation title
-
topic_type - optional topic type string for future linkage
-
example:
reports.ServiceReport -
topic_id -
optional topic identifier
-
created_at -
conversation creation timestamp
-
closed_at - optional timestamp indicating the conversation is closed
Notes¶
The model currently supports a simple two-tenant conversation pattern:
- one internal org
- one customer org
This is a good fit for service-style communication such as:
- customer support threads
- report discussions
- job-related chat
- follow-up conversations
Indexes¶
internal_org,customer_org,-created_at
This supports efficient filtering by org pair and recent conversations.
Participant¶
Represents a user who participates in a conversation.
A participant is the per-user membership record for a conversation.
Purpose¶
The Participant model is used for:
- access control
- active/inactive membership
- per-user read tracking
- participant role labeling
Fields¶
id-
UUID primary key
-
conversation -
foreign key to
Conversation -
user -
foreign key to the user model
-
role - optional participant role label
-
examples:
- engineer
- dispatcher
- customer_user
-
is_active -
whether the participant is currently active in the conversation
-
last_read_at -
last known read timestamp for that user in this conversation
-
joined_at - timestamp when the user joined
Constraints¶
Unique together:
conversation,user
This ensures a user appears at most once per conversation.
Notes¶
This model is central to chat authorization.
Most chat access checks depend on whether the user has an active participant row.
It also enables unread count logic by comparing:
last_read_at- message timestamps
Message¶
Represents a single chat message inside a conversation.
This is the core message history record.
Purpose¶
The Message model stores:
- sender
- text content
- structured payload data
- edit metadata
- soft deletion state
Fields¶
id-
UUID primary key
-
conversation -
foreign key to
Conversation -
sender -
foreign key to the sending user
-
body - text message content
-
may be blank for attachment-only messages
-
data - optional structured JSON content
-
can be used for metadata or richer client payloads
-
created_at -
message creation timestamp
-
edited_at -
optional edit timestamp
-
deleted_at - optional soft-delete timestamp
Notes¶
Messages are soft-deleted rather than removed immediately.
That allows:
- preserving history shape
- avoiding broken references from attachments
- supporting deletion UX without hard loss
The data JSON field gives flexibility for future message types such as:
- system messages
- structured message cards
- client-side metadata
- attachment references
Indexes¶
conversation,-created_at
This supports efficient retrieval of message history for a conversation ordered by recency.
MessageAttachment¶
Represents a file uploaded into a conversation.
Attachments may exist before being linked to a message.
Purpose¶
The MessageAttachment model supports the attachment workflow:
- upload file
- create attachment row
- optionally send message later
- attach uploaded file to message
- clean up orphan uploads if unused
Fields¶
id-
UUID primary key
-
conversation -
foreign key to
Conversation -
uploaded_by -
foreign key to user who uploaded the file
-
message - optional foreign key to
Message -
null means the upload has not yet been attached to a message
-
file -
stored uploaded file
-
filename -
original file name
-
content_type -
MIME type
-
size_bytes -
file size in bytes
-
created_at - upload timestamp
Notes¶
This model allows a frontend-friendly upload-first flow.
A client can:
- upload a file
- receive an attachment ID
- send a websocket or REST message referencing that attachment ID
If the attachment is never linked to a message, a cleanup task can remove it later.
This is particularly useful for mobile clients where upload and send may happen in separate steps.
Model relationships¶
flowchart TD
C[Conversation] --> P[Participant]
C --> M[Message]
C --> A[MessageAttachment]
P --> U1[User]
M --> U2[Sender]
A --> U3[Uploaded by]
M --> A2[Attached files]
A -. optional link .-> M
¶
flowchart TD
C[Conversation] --> P[Participant]
C --> M[Message]
C --> A[MessageAttachment]
P --> U1[User]
M --> U2[Sender]
A --> U3[Uploaded by]
M --> A2[Attached files]
A -. optional link .-> M
Cnversation structure¶
flowchart LR
I[Internal Org] --> C[Conversation]
CU[Customer Org] --> C
C --> P1[Internal participants]
C --> P2[Customer participants]
C --> M[Messages]
C --> A[Attachments]
¶
flowchart LR
I[Internal Org] --> C[Conversation]
CU[Customer Org] --> C
C --> P1[Internal participants]
C --> P2[Customer participants]
C --> M[Messages]
C --> A[Attachments]Atachment lifecycle¶
stateDiagram-v2
[*] --> Uploaded
Uploaded --> AttachedToMessage
Uploaded --> DeletedByCleanup
AttachedToMessage --> [*]
DeletedByCleanup --> [*]
¶
stateDiagram-v2
[*] --> Uploaded
Uploaded --> AttachedToMessage
Uploaded --> DeletedByCleanup
AttachedToMessage --> [*]
DeletedByCleanup --> [*]Read tracking fow¶
flowchart TD
A[Participant joins conversation] --> B[last_read_at is null]
B --> C[Messages arrive]
C --> D[User marks conversation read]
D --> E[last_read_at updated]
E --> F[Unread count = messages after last_read_at not sent by user]
Responsibilities of the model layer¶
The chat model layer is responsible for:
- storing conversations
- storing who belongs to each conversation
- storing messages
- storing uploaded files
- storing per-user read position
- supporting soft deletion and delayed attachment linking
What models do not do¶
These models do not:
- decide who is allowed to create a conversation
- decide who may add or remove participants
- calculate unread counts directly
- broadcast websocket events
- validate upload policy in full
- send notifications
- define frontend payload shapes
Those responsibilities belong to:
- services
- views
- serializers
- websocket consumers
- tasks
Design principles¶
Conversation as the top-level boundary¶
Everything in chat belongs to a conversation.
This keeps permissions, history, and attachments scoped cleanly.
Participant-driven authorization¶
Access is modeled through Participant.
This makes it easy to answer:
- who can view this conversation?
- who can send messages?
- who has read up to what point?
Message history is append-first¶
Messages are created and kept as historical records.
Edits and deletions are represented through timestamps rather than destructive rewrites.
Upload-first attachment flow¶
Attachments can be uploaded before a message exists.
This is especially useful for frontend and mobile workflows where:
- files upload separately
- message send may happen later
- retries may be needed
Soft deletion over hard deletion¶
Messages are soft-deleted to preserve integrity and client consistency.
This is safer than immediate hard deletion.
Best practices¶
- use
Participantas the source of truth for conversation membership - use
last_read_atfor unread tracking rather than per-message read rows - keep attachment records even before linking to messages
- prefer soft deletion for messages
- keep
dataJSON small and structured - use services for all workflow changes rather than mutating models directly from views
Future extensions¶
Possible future improvements include:
- explicit conversation status field instead of only
closed_at - per-message delivery state
- message reactions
- reply/thread relationships
- system message types
- attachment type-specific metadata
- per-participant mute/archive settings
- topic foreign key normalization instead of string-based topic linkage
- per-message read receipts if needed later
Summary¶
The chat model layer provides the persistence structure for:
- conversations
- participants
- messages
- attachments
- read tracking
Together, these models support a chat system that is:
- tenant-aware
- participant-aware
- realtime-friendly
- attachment-friendly
- ready for both REST and websocket interaction