Chat — Services¶
Overview¶
The services layer is the workflow core of the chat app.
It is responsible for:
- creating conversations
- listing conversations with unread metadata
- validating participant access
- creating, editing, and deleting messages
- uploading and attaching files
- managing conversation participants
- updating read state
- exposing notification integration points
The services layer should hold the business rules of chat.
It sits between:
- views and serializers
- models
- websocket consumers
- tasks
- notifications
Service structure¶
The chat services are split into focused modules:
services/conversations.pyservices/messages.pyservices/attachments.pyservices/participants.pyservices/read_state.pyservices/notifications.py
This split keeps the app easier to reason about and makes both REST and websocket code reusable.
services/conversations.py¶
This module owns conversation-level workflows.
Main responsibilities¶
- list conversations for internal org users
- list conversations for customer org users
- create conversations
- validate participant access to conversations
- calculate unread counts
- fetch last message summaries
Typical functions¶
participant_exists(...)get_conversation_for_participant(...)conversation_unread_count(...)get_last_message_for_conversation(...)list_conversations_for_internal_org(...)list_conversations_for_customer_org(...)create_conversation(...)
Why it matters¶
Conversation listing for frontend use is more than a plain queryset.
It often needs:
- unread count
- participant count
- latest message preview
This module builds that read model.
services/messages.py¶
This module owns message-level workflows.
Main responsibilities¶
- validate participant access before reading or writing messages
- list paginated message history
- create messages
- link uploaded attachments to messages
- edit messages
- soft-delete messages
Typical functions¶
ensure_participant(...)get_message_for_participant(...)list_messages(...)create_message(...)edit_message(...)delete_message(...)
Why it matters¶
This module becomes the shared write boundary for:
- REST message operations
- websocket message creation
That keeps message behavior consistent across transport layers.
services/attachments.py¶
This module owns attachment upload and linking behavior.
Main responsibilities¶
- validate upload access
- validate upload size and content type
- create attachment rows
- find unclaimed attachment IDs
- attach uploaded files to a message
Typical functions¶
ensure_can_access_conversation(...)validate_attachment_upload(...)upload_attachment(...)list_unclaimed_attachment_ids_for_user(...)attach_attachments_to_message(...)
Why it matters¶
The app supports an upload-first workflow:
- upload file
- get attachment ID
- send message with attachment IDs
- link those attachments to the new message
This module owns that lifecycle.
services/participants.py¶
This module owns participant membership behavior.
Main responsibilities¶
- ensure the acting user is an active participant
- list active participants
- add or reactivate participants
- remove participants by deactivating them
- validate that added users belong to an allowed org for the conversation
Typical functions¶
ensure_active_participant(...)list_participants(...)add_participant(...)remove_participant(...)
Why it matters¶
Participant state controls:
- access to conversations
- access to message history
- access to attachment upload
- websocket eligibility
It is one of the core security boundaries of the app.
services/read_state.py¶
This module owns conversation read tracking.
Main responsibilities¶
- mark a conversation as read for a participant
- update
last_read_at
Typical functions¶
mark_conversation_read(...)
Why it matters¶
The current unread system is simple and efficient:
- each participant stores one read cursor
- unread count is derived by comparing message timestamps against that cursor
This module keeps that logic explicit.
services/notifications.py¶
This module is the integration boundary for notifying participants about new messages.
Main responsibilities¶
- define how chat events trigger notifications
- isolate notification integration from message persistence code
Typical functions¶
notify_participants_new_message(...)
Why it matters¶
Chat notifications are related to messaging, but should not be hardcoded in views or consumers.
Keeping them behind a service boundary makes it easier to:
- align with the notifications app
- change channels later
- keep message creation logic transport-agnostic
Error model¶
Each service area uses domain-specific exceptions.
Examples include:
- permission errors
- validation errors
- conversation errors
- message errors
- attachment errors
- participant errors
- read state errors
Views translate these into HTTP responses such as:
- 400
- 403
- 404
- 409
This keeps services independent from HTTP.
Core workflow flows¶
Conversation creation flow¶
Creating a conversation requires more than just inserting a row.
Main steps¶
- validate the internal org is linked to the target customer org
- create the conversation
- add the creator as a participant
- return the created conversation
Mermaid flow¶
flowchart TD
A[create_conversation] --> B{Customer org linked?}
B -->|no| C[Raise permission error]
B -->|yes| D[Create Conversation]
D --> E[Create Participant for actor]
E --> F[Return conversation]
Conversation list flow¶
Conversation list endpoints need frontend-oriented metadata.
Main steps 1. fetch conversations for the relevant org 2. compute unread count for current user 3. compute participant count 4. fetch latest message 5. return enriched conversation objects
Mermaid flow¶
flowchart TD
A[List conversations] --> B[Fetch conversations]
B --> C[Compute unread_count]
C --> D[Compute participant_count]
D --> E[Fetch last_message]
E --> F[Return enriched list]
¶
flowchart TD
A[List conversations] --> B[Fetch conversations]
B --> C[Compute unread_count]
C --> D[Compute participant_count]
D --> E[Fetch last_message]
E --> F[Return enriched list]
Message creation flow¶
Message creation is one of the most important workflows in the app.
Main steps¶
- validate active participant access
- create the message
- attach uploaded files if attachment IDs are provided
- optionally notify other participants
- return the message and attached IDs
Mermaid flow¶
flowchart TD
A[create_message] --> B{Active participant?}
B -->|no| C[Raise permission error]
B -->|yes| D[Create Message]
D --> E{Attachment IDs provided?}
E -->|yes| F[Attach unclaimed uploads]
E -->|no| G[Skip attachment linking]
F --> H[Optionally notify participants]
G --> H
H --> I[Return message + attachment ids]
¶
flowchart TD
A[create_message] --> B{Active participant?}
B -->|no| C[Raise permission error]
B -->|yes| D[Create Message]
D --> E{Attachment IDs provided?}
E -->|yes| F[Attach unclaimed uploads]
E -->|no| G[Skip attachment linking]
F --> H[Optionally notify participants]
G --> H
H --> I[Return message + attachment ids]
Message edit flow¶
Main steps¶
1. load message visible to participant
2. ensure sender is the actor
3. update body
4. set edited_at
5. return updated message
Mermaid flow¶
flowchart TD
A[edit_message] --> B[Load message]
B --> C{Participant has access?}
C -->|no| D[Raise permission error]
C -->|yes| E{Actor is sender?}
E -->|no| F[Raise permission error]
E -->|yes| G[Update body]
G --> H[Set edited_at]
H --> I[Return updated message]
¶
flowchart TD
A[edit_message] --> B[Load message]
B --> C{Participant has access?}
C -->|no| D[Raise permission error]
C -->|yes| E{Actor is sender?}
E -->|no| F[Raise permission error]
E -->|yes| G[Update body]
G --> H[Set edited_at]
H --> I[Return updated message]
Message delete flow¶
Delete is implemented as soft delete.
Main steps¶
1. load message visible to participant
2. ensure sender is the actor
3. set deleted_at
4. return updated message
Mermaid flow¶
flowchart TD
A[delete_message] --> B[Load message]
B --> C{Participant has access?}
C -->|no| D[Raise permission error]
C -->|yes| E{Actor is sender?}
E -->|no| F[Raise permission error]
E -->|yes| G[Set deleted_at]
G --> H[Return updated message]
¶
flowchart TD
A[delete_message] --> B[Load message]
B --> C{Participant has access?}
C -->|no| D[Raise permission error]
C -->|yes| E{Actor is sender?}
E -->|no| F[Raise permission error]
E -->|yes| G[Set deleted_at]
G --> H[Return updated message]Attachment upload flow¶
Main steps¶
1. ensure user can access the conversation
2. validate upload size
3. validate content type
4. create attachment row
5. return attachment metadata
Mermaid flow¶
flowchart TD
A[upload_attachment] --> B{Can access conversation?}
B -->|no| C[Raise permission error]
B -->|yes| D[Validate upload]
D -->|invalid| E[Raise validation error]
D -->|valid| F[Create MessageAttachment]
F --> G[Return attachment]
¶
flowchart TD
A[upload_attachment] --> B{Can access conversation?}
B -->|no| C[Raise permission error]
B -->|yes| D[Validate upload]
D -->|invalid| E[Raise validation error]
D -->|valid| F[Create MessageAttachment]
F --> G[Return attachment]
Attachment linking flow¶
Main steps¶
1. receive attachment IDs during message creation
2. find unclaimed attachments uploaded by this user in this conversation
3. link them to the new message
4. return linked attachment IDs
Mermaid flow¶
flowchart TD
A[attach_attachments_to_message] --> B[Filter by conversation]
B --> C[Filter by uploaded_by]
C --> D[Filter by unclaimed uploads]
D --> E[Update message_id]
E --> F[Return linked ids]
¶
flowchart TD
A[attach_attachments_to_message] --> B[Filter by conversation]
B --> C[Filter by uploaded_by]
C --> D[Filter by unclaimed uploads]
D --> E[Update message_id]
E --> F[Return linked ids]Participant membership flow¶
Add participant¶
- load conversation
- ensure actor is active participant
- ensure target user belongs to allowed org side
- create or reactivate participant row
- return participant
Remove participant¶
- load conversation
- ensure actor is active participant
- deactivate target participant row
Mermaid flow¶
flowchart TD
A[add_participant] --> B[Load conversation]
B --> C{Actor is active participant?}
C -->|no| D[Raise permission error]
C -->|yes| E{Target user belongs to internal or customer org?}
E -->|no| F[Raise permission error]
E -->|yes| G[Create or reactivate Participant]
G --> H[Return participant]
Read-state flow¶
Main steps¶
- find active participant row
- update last_read_at
- return updated participant
Mermaid flow¶
flowchart TD
A[mark_conversation_read] --> B[Find active participant]
B -->|not found| C[Raise permission error]
B -->|found| D[Set last_read_at]
D --> E[Save participant]
E --> F[Return participant]
Shared use between REST and websocket¶
One of the main goals of the service split is reusability.
REST¶
Views call services to: • list data • create records • edit/delete records • manage read state and participants
Websocket¶
Consumers should call the same services to: • validate participant access • create messages • link attachments
This avoids having one workflow for REST and another for realtime.
Data flow¶
flowchart TD
A[View or Consumer] --> B[Service]
B --> C[Models]
B --> D[Notifications boundary]
C --> E[Updated state]
E --> F[Serializer or websocket payload]
Responsibilities by layer¶
Services layer owns¶
- conversation creation rules
- participant membership checks
- unread count logic
- message persistence workflows
- attachment linking rules
- sender-only edit/delete rules
- read-state updates
- notification integration boundary
Views layer owns¶
- HTTP request handling
- serializer invocation
- status code mapping
Consumers layer owns¶
- websocket connect/disconnect behavior
- websocket payload parsing
- group broadcasting
Serializers layer owns¶
- input validation
- response shaping
Models layer owns¶
- persistence
- relational structure
What services do not do¶
Services do not:
- parse raw websocket JSON
- return HTTP responses directly
- define serializer fields
- emit websocket group events directly
- define URL routing
- own file storage settings
Those concerns belong to:
- consumers
- views
- serializers
- routing
- storage config
Design principles¶
Thin views and consumers¶
Views and websocket consumers should orchestrate, not implement business rules.
Shared workflows across transport layers¶
REST and websocket paths should reuse the same message and attachment logic.
Participant-first authorization¶
Most chat actions depend on participant status, so access validation is centralized in services.
Upload-first attachment lifecycle¶
The app supports mobile- and frontend-friendly attachment workflows by separating upload from message creation.
Soft-delete over destructive delete¶
Messages are hidden by timestamp rather than removed immediately.
Best practices¶
- call services for all conversation and message workflows
- keep authorization checks centralized in services
- reuse message creation logic from both REST and websocket code
- treat attachments as conversation-scoped before message linking
- keep notification integration behind a service boundary
- keep unread logic based on
last_read_at, not duplicated in views
Future extensions¶
The service layer is well positioned to support:
- message reactions
- conversation archive/mute
- topic-specific conversation creation helpers
- read receipt expansion
- system messages
- typing persistence or throttling
- attachment virus scanning hooks
- conversation close/reopen workflows
- notification preferences for chat messages
Summary¶
The chat services layer is the workflow engine of the app.
It provides the business behavior for:
- conversations
- messages
- attachments
- participants
- read state
- notification hooks
This makes the chat app:
- reusable across REST and websocket paths
- easier to test
- easier to document
- much more frontend-ready