Skip to content

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:

  1. upload file
  2. create attachment row
  3. optionally send message later
  4. attach uploaded file to message
  5. 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

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]

Atachment lifecycle

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 Participant as the source of truth for conversation membership
  • use last_read_at for unread tracking rather than per-message read rows
  • keep attachment records even before linking to messages
  • prefer soft deletion for messages
  • keep data JSON 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