Skip to main content
Version: MyBestDealNow

Chat System

Authors

  • Sanket Mal
  • Ayan Ghosh
  • Ribhu Gautam

Last Updated Date

2025-10-15


SRS References


Version History

VersionDateChangesAuthor
1.02025-10-15Initial draft - Polling-based chat systemSanket Mal, Ayan Ghosh, Ribhu Gautam

Feature Overview

Objective:
Implement a bidirectional chat system that enables real-time communication between auction creators (customers) and participating dealers. The system allows dealers to communicate with customers regarding specific auctions, and customers to manage multiple dealer conversations per auction in an organized manner.

Scope:

  • Phase 1 (Current): Polling-based messaging system with REST APIs
  • Conversation management with status-based access control
  • Message history with pagination and reply functionality
  • Auction-based chat room organization for customers
  • Individual conversation views for dealers
  • Future Phase 2: SignalR integration for real-time push notifications

Dependencies:

  • AuctionEngineService - For auction status and bid acceptance logic
  • IAMService - For user authentication and authorization
  • CustomerManagementService - For customer profile information
  • DealerManagementService - For dealer profile information
  • PostgreSQL Database - For data persistence
  • MassTransit/RabbitMQ - For event-driven conversation status updates

Requirements

Functional Requirements

  1. Dealer Side Requirements

    • Dealers must see a paginated list of all auction conversations they're participating in
    • Each conversation must display auction details, last message, unread count, and who sent the last message
    • Dealers must be able to send messages to auction creators
    • Dealers must be able to view full conversation history with pagination
    • Dealers must be able to reply to specific messages
    • Dealers must see conversation status (Active/Disabled)
    • When a dealer's bid is accepted, their conversation remains active
    • When a dealer's bid is rejected or another dealer wins, their conversation becomes disabled
    • Disabled conversations must be read-only (no new messages allowed)
  2. Customer Side Requirements

    • Customers must see auction-based chat rooms for all their auctions
    • Each auction room must show participating dealer count and total unread messages
    • Customers must expand an auction room to see all participating dealers
    • Each dealer conversation must show unread count and who sent the last message
    • Customers must be able to communicate with all participating dealers before accepting a bid
    • After accepting a dealer's bid, customers can only continue chatting with that specific dealer
    • All other dealer conversations for that auction must become disabled
    • Customers must see dealer information and conversation status for each dealer
  3. Message Requirements

    • Messages must support text content (1-5000 characters)
    • Messages must support optional attachments (URLs)
    • Messages must support reply functionality (threading)
    • Messages must track delivery and read status
    • Messages must display sender information (name, type, timestamp, profile image)
    • Messages must display entity information (dealer/customer business name and logo)
    • Messages must support soft delete functionality
  4. Status Management Requirements

    • Conversations must automatically disable when:
      • Auction is cancelled
      • Dealer's bid is rejected
      • Another dealer's bid is accepted (for losing dealers)
      • Auction is completed without dealer winning
    • System must prevent messaging in disabled conversations
    • Status changes must be reflected in real-time (via polling)

Non-Functional Requirements

  1. Performance

    • API response time must be under 500ms for list endpoints
    • API response time must be under 300ms for message sending
    • Support pagination for large datasets (50+ conversations, 1000+ messages)
    • Efficient database queries with proper indexing
  2. Security

    • All endpoints must require authentication
    • Users must only access conversations they're authorized for
    • Message content must be sanitized against XSS attacks
    • Rate limiting: 10 messages per minute per user
  3. Scalability

    • Design must support 10,000+ concurrent users (Phase 2 with SignalR)
    • Database must handle millions of messages
    • Cursor-based pagination for efficient data retrieval
  4. Reliability

    • 99.9% uptime for chat service
    • Message delivery guarantee (at-least-once)
    • Data consistency across conversation status updates
  5. Usability

    • Polling interval: 3-5 seconds for active conversations
    • Clear visual indicators for unread messages
    • Intuitive conversation status display

Design Specifications

UI/UX Design

(Refer to attached wireframe image showing dealer and customer chat interfaces)

Dealer Interface:

  • Left sidebar: List of auction conversations with preview
  • Main panel: Selected conversation message thread
  • Message composer at bottom with reply and attachment support

Customer Interface:

  • Collapsible auction chat rooms with auction details
  • Expanded view shows participating dealers as conversation cards
  • Click dealer to open conversation in main panel
  • Visual indicators for winning dealer and disabled conversations

Data Models

Database Schema

Entity Relationship Diagram:

Chat Database Schema

-- Conversation Table
CREATE TABLE IF NOT EXISTS conversation (
conversationid UUID PRIMARY KEY,
auctionid UUID NOT NULL,
dealerid UUID NOT NULL,
customerid UUID NOT NULL,
conversationname VARCHAR(200),
conversationtype VARCHAR(50),
iswon BOOLEAN DEFAULT FALSE,
status VARCHAR(20) DEFAULT 'Active', -- 'Active', 'Disabled'
createdat TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedat TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
closedby UUID,
closedat TIMESTAMP,
isdeleted BOOLEAN DEFAULT FALSE,
CONSTRAINT uq_auction_dealer UNIQUE (auctionid, dealerid),
CONSTRAINT fk_conversation_auction FOREIGN KEY (auctionid) REFERENCES auctionmaster(auctionid),
CONSTRAINT fk_conversation_dealer FOREIGN KEY (dealerid) REFERENCES dealermaster(dealerid),
CONSTRAINT fk_conversation_customer FOREIGN KEY (customerid) REFERENCES customermaster(customerid)
);

-- Indexes for conversation
CREATE INDEX idx_conversation_auctionid ON conversation(auctionid);
CREATE INDEX idx_conversation_dealerid ON conversation(dealerid);
CREATE INDEX idx_conversation_customerid ON conversation(customerid);
CREATE INDEX idx_conversation_status ON conversation(status);
CREATE INDEX idx_conversation_auction_status ON conversation(auctionid, status);

-- Message Table
CREATE TABLE IF NOT EXISTS message (
messageid UUID PRIMARY KEY,
conversationid UUID NOT NULL,
senderid UUID NOT NULL,
sendername VARCHAR(200),
sendertype VARCHAR(20) NOT NULL, -- 'Dealer', 'Customer'
senderentityid UUID NOT NULL,
messagetext TEXT NOT NULL,
replytomessageid UUID,
attachments TEXT, -- JSON array of attachment URLs
isedited BOOLEAN DEFAULT FALSE,
isdelivered BOOLEAN DEFAULT TRUE, -- For polling, always true initially
isseen BOOLEAN DEFAULT FALSE,
isdeleted BOOLEAN DEFAULT FALSE,
deletedby UUID,
deletedat TIMESTAMP,
deliveredat TIMESTAMP,
seenat TIMESTAMP,
createdat TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedat TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_message_conversation FOREIGN KEY (conversationid) REFERENCES conversation(conversationid),
CONSTRAINT fk_message_reply FOREIGN KEY (replytomessageid) REFERENCES message(messageid)
);

-- Indexes for message
CREATE INDEX idx_message_conversationid ON message(conversationid);
CREATE INDEX idx_message_conversation_created ON message(conversationid, createdat DESC);
CREATE INDEX idx_message_createdat ON message(createdat);
CREATE INDEX idx_message_senderid ON message(senderid);
CREATE INDEX idx_message_replytomessageid ON message(replytomessageid);

C# Contract Models

// Request Models
public record SendMessageRequest
{
[Required(ErrorMessage = "Session ID is required")]
public Guid ConversationId { get; init; }

[Required(ErrorMessage = "Message is required")]
[StringLength(5000, MinimumLength = 1, ErrorMessage = "Message must be between 1 and 5000 characters")]
public string Message { get; init; } = string.Empty;

public Guid? ReplyToMessageId { get; init; }

public List<string>? Attachments { get; init; }
}
public record DealerConversation
{
public Guid ConversationId { get; init; }
public string ConversationName { get; init; } = string.Empty;
public Guid CustomerId { get; init; }
public Guid DealerId { get; init; }
public Guid AuctionId { get; init; }
public string AuctionCode { get; init; } = string.Empty;
public string AuctionTitle { get; init; } = string.Empty;
public string? CustomerName { get; init; }
public AuctionStatus AuctionStatus { get; init; }
public ConversationStatus Status { get; init; } // Chat Status Active/Disable
public string Image { get; init; } = string.Empty;
public bool IsSeen { get; init; } // Todo with socket
public bool IsWon { get; init; }
public string LastMessage { get; init; } = string.Empty;
public int UnreadCount { get; init; } // Number of unread messages for the dealer
public string LastMessageSentBy { get; init; } = string.Empty; // User name who sent the last message (e.g., "John Doe")
public Guid LastMessageSentByEntityId { get; init; }
public UserType LastMessageSentBySenderType {get; init;}
public DateTime UpdatedAt { get; init; }

}
public record CustomerConversation
{
public Guid ConversationId { get; init; }
public Guid DealerId { get; init; }
public string DealerName { get; init; } = string.Empty;
public string DealerCode { get; init; } = string.Empty;
public Guid AuctionId { get; init; }
public string AuctionCode { get; init; } = string.Empty;
public string AuctionTitle { get; init; } = string.Empty;
public string Image { get; init; } = string.Empty;
public ConversationStatus Status { get; init; }
public bool IsSeen { get; init; } // Todo with socket
public bool IsWon { get; init; }
public string LastMessage { get; init; } = string.Empty;
public int UnreadCount { get; init; } // Number of unread messages for the dealer
public string LastMessageSentBy { get; init; } = string.Empty; // User name who sent the last message (e.g., "John Doe")
public Guid LastMessageSentByEntityId { get; init; }
public UserType LastMessageSentBySenderType {get; init;}
public DateTime UpdatedAt { get; init; }
}

public record AuctionChatRoom
{
public Guid AuctionId { get; init; }
public string AuctionTitle { get; init; } = string.Empty;
public string AuctionCode { get; init; } = string.Empty;
public string Image { get; init; } = string.Empty;
public int DealerCount { get; init; }
public AuctionStatus AuctionStatus { get; init; }
public DateTime UpdatedAt { get; init; }
public DateTime CreatedAt { get; init; }
}
public record ReplyToMessage
{
public Guid MessageId { get; init; }
public string? MessageText { get; init; }
public string? SenderName { get; init; }
public List<string>? Attachments { get; init; }
}
public record Message
{
public Guid MessageId { get; init; }
public Guid ConversationId { get; init; }
public Guid SenderId { get; init; }
public string SenderName { get; init; } = string.Empty;
public string SenderImage { get; init; } = string.Empty; // newly added
public Guid SenderEntityId { get; init; }
public string EntityName { get; init; } = string.Empty; // newly added
public string EntityImage { get; init; } = string.Empty; // newly added
public UserType SenderType { get; init; }
public string MessageText { get; init; } = string.Empty;
public ReplyToMessage ReplyToMessage { get; init; } = new ReplyToMessage();
public List<string>? Attachments { get; init; }
public bool IsEdited { get; init; }
public bool IsDeleted { get; init; }
public bool IsDelivered { get; init; }
public bool IsSeen { get; init; }
public DateTime CreatedAt { get; init; }
public DateTime UpdatedAt { get; init; }
}
public record CursorPaginationFilter
{
// Use one of these: NextCursor to get items AFTER the given cursor,
// or PreviousCursor to get items BEFORE the given cursor.
public string? NextCursor { get; set; } // fetch next page (older/newer depending on sort)
public string? PreviousCursor { get; set; } // fetch previous page

// Page size
public int Limit { get; set; } = 20;
}
public record DealerConversationFilter : CursorPaginationFilter
{
public ConversationStatus Status { get; init; }
public AuctionStatus AuctionStatus { get; init; }
public string? SearchKeyword { get; init; }
}
public record CustomerConversationFilter : CursorPaginationFilter
{
public ConversationStatus Status { get; init; }
public string? SearchKeyword { get; init; }
}
public record AuctionChatRoomFilter : CursorPaginationFilter
{
public ConversationStatus Status { get; init; }
public AuctionStatus AuctionStatus { get; init; }
public string? SearchKeyword { get; init; }
}
public class CursorPaginatedData<T>
{
public List<T> Data { get; set; } = [];
public string? NextCursor { get; set; }
public string? PreviousCursor { get; set; }
public bool HasNextPage { get; set; }
public bool HasPreviousPage { get; set; }
public int? TotalCount { get; set; }
public int Limit { get; set; }
}
public record CommonResponse
{
public int Status { get; init; }
public string? Message { get; init; }
}

// Enums
public enum ConversationStatus
{
Active = 1,
Disable
}

public enum AuctionStatus
{
Active = 1,
Completed,
Canceled,
Expired
}

public enum UserType
{
Admin = 1,
Dealer = 2,
Customer = 4
}

API Interfaces

EndpointMethodParametersResponseResponse Status Codes
/api/chat/messagePOSTSendMessageRequestCommonResponse200, 400, 403, 500
/api/chat/dealer/conversationsGETDealerConversationFilterCursorPaginatedData<DealerConversation>200, 401, 500
/api/chat/customer/auction-roomsGETAuctionChatRoomFilterCursorPaginatedData<AuctionChatRoom>200, 401, 500
/api/chat/customer/auction-rooms/{auctionId}/dealersGETCustomerConversationFilterCursorPaginatedData<CustomerConversation>200, 401, 403, 404, 500
/api/chat/conversations/{conversationId}/messagesGETMessageFilterCursorPaginatedData<Message>200, 401, 403, 404, 500
/api/chat/conversations/{conversationId}/mark-readPUTNone (conversationId in URL)CommonResponse200, 401, 403, 404, 500

API Request & Response Examples

1. Send Message API

Endpoint: POST /api/chat/message

Request Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

Request Body (SendMessageRequest):

{
"conversationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "Hello! I'm interested in this vehicle. What's the service history?",
"replyToMessageId": null,
"attachments": []
}

Success Response (200 OK - CommonResponse):

{
"status": 0,
"message": "Message sent successfully"
}

Error Response (400 Bad Request):

{
"status": 400,
"message": "Message must be between 1 and 5000 characters"
}

Error Response (403 Forbidden):

{
"status": 403,
"message": "This conversation is disabled. You cannot send messages."
}

2. Get Dealer Conversations API

Endpoint: GET /api/chat/dealer/conversations

Request Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Query Parameters (DealerConversationFilter):

?status=1&auctionStatus=1&searchKeyword=toyota&limit=20&nextCursor=&nextCursor=MjAyNS0xMC0xNVQxNjo0NTowMFp8NmI1YTRjM2QtMmUxZi00YjlhLTdjNmQtNWU0ZjNhMmIxYzBk

Success Response (200 OK - CursorPaginatedData<DealerConversation>):

{
"data": [
{
"conversationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"conversationName": "2015 Toyota Camry LE",
"customerId": "9d8e7f6a-5b4c-4e3d-8c7b-6a5f4e3d2c1b",
"dealerId": "2e4f6a8b-9c7d-4e5f-8a6b-3c1d2e4f5a6b",
"auctionId": "8b2e9c7d-4f5a-4e3b-9c8d-7e6f5a4b3c2d",
"auctionCode": "AUC-2025-001234",
"auctionTitle": "2015 Toyota Camry LE - Low Mileage",
"customerName": "Jane Smith",
"auctionStatus": 1,
"status": 1,
"image": "https://cdn.example.com/auctions/toyota-camry-2015.jpg",
"isSeen": false,
"isWon": true,
"lastMessage": "What's your best price?",
"unreadCount": 3,
"lastMessageSentBy": "Jane Smith",
"lastMessageSentByEntityId": "9d8e7f6a-5b4c-4e3d-8c7b-6a5f4e3d2c1b",
"lastMessageSentBySenderType": 4,
"updatedAt": "2025-10-16T10:25:00Z"
},
{
"conversationId": "7fb95f64-6828-5673-c4gd-3d074g77bgb7",
"conversationName": "2018 Honda Accord Sport",
"customerId": "0e9f8g7b-6c5d-5f4e-9d8c-7b6g5f4e3d2c",
"dealerId": "2e4f6a8b-9c7d-4e5f-8a6b-3c1d2e4f5a6b",
"auctionId": "9c3f0e8e-5g6b-5f4c-9d9e-8f7g6b5c4d3e",
"auctionCode": "AUC-2025-001235",
"auctionTitle": "2018 Honda Accord Sport - Certified Pre-Owned",
"customerName": "Mike Johnson",
"auctionStatus": 1,
"status": 1,
"image": "https://cdn.example.com/auctions/honda-accord-2018.jpg",
"isSeen": true,
"isWon": false,
"lastMessage": "I can offer better warranty terms",
"unreadCount": 0,
"lastMessageSentBy": "John Dealer",
"lastMessageSentByEntityId": "2e4f6a8b-9c7d-4e5f-8a6b-3c1d2e4f5a6b",
"lastMessageSentBySenderType": 2,
"updatedAt": "2025-10-16T09:15:00Z"
}
],
"nextCursor": "MjAyNS0xMC0xNVQxNjo0NTowMFp8NGRjODZlNzUtNTcxOC00NDUxLWEzZWItMmI4NTJkNTVhYWM2",
"previousCursor": null,
"hasNextPage": true,
"hasPreviousPage": false,
"totalCount": 45,
"limit": 20
}

Empty Result Response (200 OK):

{
"data": [],
"nextCursor": null,
"previousCursor": null,
"hasNextPage": false,
"hasPreviousPage": false,
"totalCount": 0,
"limit": 20
}

3. Get Customer Auction Rooms API

Endpoint: GET /api/chat/customer/auction-rooms

Request Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Query Parameters (AuctionChatRoomFilter):

?status=1&auctionStatus=1&limit=2&nextCursor=MjAyNS0xMC0xNVQxNjo0NTowMFp8NmI1YTRjM2QtMmUxZi00YjlhLTdjNmQtNWU0ZjNhMmIxYzBk

Success Response (200 OK - CursorPaginatedData(AuctionChatRoom)):

{
"data": [
{
"auctionId": "8b2e9c7d-4f5a-4e3b-9c8d-7e6f5a4b3c2d",
"auctionTitle": "2015 Toyota Camry LE",
"auctionCode": "AUC-2025-001234",
"image": "https://cdn.example.com/auctions/toyota-camry-2015.jpg",
"dealerCount": 5,
"auctionStatus": 1,
"updatedAt": "2025-10-16T10:25:00Z",
"createdAt": "2025-10-10T08:00:00Z"
},
{
"auctionId": "8b2e9c7d-4f5a-4e3b-9c8d-7e6f5a4b3c2d",
"auctionTitle": "2015 Toyota Camry LE",
"auctionCode": "AUC-2025-001234",
"image": "https://cdn.example.com/auctions/toyota-camry-2015.jpg",
"dealerCount": 5,
"auctionStatus": 1,
"updatedAt": "2025-10-16T10:25:00Z",
"createdAt": "2025-10-10T08:00:00Z"
}
],
"nextCursor": "MjAyNS0xMC0xNVQxNjo0NTowMFp8NmI1YTRjM2QtMmUxZi00YjlhLTdjNmQtNWU0ZjNhMmIxYzBk",
"previousCursor": null,
"hasNextPage": true,
"hasPreviousPage": false,
"totalCount": 12,
"limit": 20
}

4. Get Dealers in Auction Room API

Endpoint: GET /api/chat/customer/auction-rooms/{auctionId}/dealers

Request Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

URL Parameters:

  • auctionId: 8b2e9c7d-4f5a-4e3b-9c8d-7e6f5a4b3c2d

Query Parameters (CustomerConversationFilter):

?status=1&limit=2&nextCursor=MjAyNS0xMC0xNVQxNjo0NTowMFp8NmI1YTRjM2QtMmUxZi00YjlhLTdjNmQtNWU0ZjNhMmIxYzBk

Success Response (200 OK - CursorPaginatedData<CustomerConversation>):

{
"data": [
{
"conversationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"dealerId": "2e4f6a8b-9c7d-4e5f-8a6b-3c1d2e4f5a6b",
"dealerName": "ABC Motors",
"dealerCode": "DLR-001234",
"auctionId": "8b2e9c7d-4f5a-4e3b-9c8d-7e6f5a4b3c2d",
"auctionCode": "AUC-2025-001234",
"auctionTitle": "2015 Toyota Camry LE - Low Mileage",
"image": "https://cdn.example.com/dealers/abc-motors-logo.jpg",
"status": 1,
"isSeen": false,
"isWon": false,
"lastMessage": "What's your best price?",
"unreadCount": 5,
"lastMessageSentBy": "John Dealer",
"lastMessageSentByEntityId": "2e4f6a8b-9c7d-4e5f-8a6b-3c1d2e4f5a6b",
"lastMessageSentBySenderType": 2,
"updatedAt": "2025-10-16T10:25:00Z"
},
{
"conversationId": "4gb96g75-7939-6784-d5he-4e185h88chc8",
"dealerId": "3f5g7b9c-0d8e-5g6f-9b7c-4d2e3f5g6b7c",
"dealerName": "XYZ Auto Group",
"dealerCode": "DLR-001235",
"auctionId": "8b2e9c7d-4f5a-4e3b-9c8d-7e6f5a4b3c2d",
"auctionCode": "AUC-2025-001234",
"auctionTitle": "2015 Toyota Camry LE - Low Mileage",
"image": "https://cdn.example.com/dealers/xyz-auto-logo.jpg",
"status": 1,
"isSeen": true,
"isWon": true,
"lastMessage": "Thank you for accepting my bid!",
"unreadCount": 0,
"lastMessageSentBy": "Jane Smith",
"lastMessageSentByEntityId": "9d8e7f6a-5b4c-4e3d-8c7b-6a5f4e3d2c1b",
"lastMessageSentBySenderType": 4,
"updatedAt": "2025-10-16T11:30:00Z"
}
],
"nextCursor": "MjAyNS0xMC0xNVQxNjo0NTowMFp8NmI1YTRjM2QtMmUxZi00YjlhLTdjNmQtNWU0ZjNhMmIxYzBk",
"previousCursor": null,
"hasNextPage": false,
"hasPreviousPage": false,
"totalCount": 5,
"limit": 2
}

5. Get Conversation Messages API

Endpoint: GET /api/chat/conversations/{conversationId}/messages

Request Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

URL Parameters:

  • conversationId: 3fa85f64-5717-4562-b3fc-2c963f66afa6

Query Parameters (MessageFilter - First Page):

?limit=2&&nextCursor=MjAyNS0xMC0xNVQxNjo0NTowMFp8NmI1YTRjM2QtMmUxZi00YjlhLTdjNmQtNWU0ZjNhMmIxYzBk

Success Response (200 OK - CursorPaginatedData<Message>):

{
"data": [
{
"messageId": "7d3e9a21-4c5b-4e7a-9b3c-8f2e1d4a5b6c",
"conversationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"senderId": "9c7b8a45-3e2d-4f1a-8b6c-7d5e4f3a2b1c",
"senderName": "John Dealer",
"senderImage": "https://cdn.example.com/users/john-dealer-avatar.jpg",
"senderEntityId": "2e4f6a8b-9c7d-4e5f-8a6b-3c1d2e4f5a6b",
"entityName": "ABC Motors",
"entityImage": "https://cdn.example.com/dealers/abc-motors-logo.jpg",
"senderType": 2,
"messageText": "What's your best price?",
"replyToMessage": {
"messageId": "6c2d8b20-3b4a-4d6a-8a2b-7e1d3c4a5b6c",
"messageText": "The vehicle is in excellent condition.",
"senderName": "Jane Smith",
"attachments": []
},
"attachments": [],
"isEdited": false,
"isDeleted": false,
"isDelivered": true,
"isSeen": false,
"createdAt": "2025-10-16T10:25:00Z",
"updatedAt": "2025-10-16T10:25:00Z"
},
{
"messageId": "6c2d8b20-3b4a-4d6a-8a2b-7e1d3c4a5b6c",
"conversationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"senderId": "5f8c9d6e-4a3b-5c2d-7e6f-9a8b7c6d5e4f",
"senderName": "Jane Smith",
"senderImage": "https://cdn.example.com/users/jane-smith-avatar.jpg",
"senderEntityId": "9d8e7f6a-5b4c-4e3d-8c7b-6a5f4e3d2c1b",
"entityName": "Jane Smith",
"entityImage": "https://cdn.example.com/customers/jane-smith-profile.jpg",
"senderType": 4,
"messageText": "The vehicle is in excellent condition.",
"replyToMessage": {
"messageId": null,
"messageText": null,
"senderName": null,
"attachments": null
},
"attachments": [
"https://cdn.example.com/attachments/vehicle-inspection-report.pdf"
],
"isEdited": false,
"isDeleted": false,
"isDelivered": true,
"isSeen": true,
"createdAt": "2025-10-16T09:15:00Z",
"updatedAt": "2025-10-16T09:15:00Z"
}
],
"nextCursor": "MjAyNS0xMC0xNlQwOToxNTowMFp8NmMyZDhiMjAtM2I0YS00ZDZhLThhMmItN2UxZDNjNGE1YjZj",
"previousCursor": null,
"hasNextPage": true,
"hasPreviousPage": false,
"totalCount": 25,
"limit": 2
}

6. Mark Messages as Read API

Endpoint: PUT /api/chat/conversations/{conversationId}/mark-read

Request Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

URL Parameters:

  • conversationId: 3fa85f64-5717-4562-b3fc-2c963f66afa6

Request Body:

No request body required (empty body {} is acceptable but not necessary).

Success Response (200 OK - MarkAsReadResponse):

{
"status": 0,
"message": "Messages marked as read successfully"
}

Response Details:

  • status: 0 for success, error code otherwise
  • message: Marked as read successfully

Error Response (403 Forbidden - User Not Participant):

{
"status": 403,
"message": "You do not have access to this conversation"
}

Error Response (404 Not Found - Conversation Doesn't Exist):

{
"status": 404,
"message": "Conversation not found"
}

Idempotency Response (200 OK - Already Read):

{
"status": 0,
"message": "Messages marked as read successfully"
}

Business Logic:

  1. System extracts userId and userType from JWT token
  2. System queries conversation table to get dealerId and customerId
  3. System determines current user's entity ID based on user type:
    • If Dealer: Verify user's dealerId matches conversation.dealerId
    • If Customer: Verify user's customerId matches conversation.customerId
  4. System identifies the "other" participant's entity ID
  5. System marks all unread messages sent by the other participant as isSeen = true
  6. System sets seenAt = CURRENT_TIMESTAMP for marked messages
  7. System returns count of marked messages and remaining unread count

Third-Party Integrations

  • None required for Phase 1 (polling-based)
  • Phase 2 will integrate SignalR for WebSocket connections

Workflow

1. Dealer Sends Message to Customer

1. Dealer clicks on a conversation from their conversation list
2. GET /api/chat/conversations/{conversationId}/messages (load message history)
3. System validates dealer has access and conversation is active
4. Dealer types message and clicks send
5. POST /api/chat/messages with message payload
6. System validates:
- Conversation exists
- Dealer is participant
- Conversation status is Active
- Message content is valid
7. System saves message to database with:
- sendertype = 'Dealer'
- senderid = dealer's user ID
- senderentityid = dealer's entity ID
- isdelivered = true
8. System updates conversation.updatedat timestamp
9. System returns created message
10. Frontend polls for new messages every 3-5 seconds
11. Customer sees new message when polling detects updates

2. Customer Accepts Dealer Bid - Conversation Status Update

1. Customer accepts a dealer's bid in the auction system
2. AuctionEngineService publishes BidAcceptedEvent via MassTransit:
{
"AuctionId": "uuid",
"WinningDealerId": "uuid",
"CustomerId": "uuid",
"AcceptedAt": "timestamp"
}
3. ChatService consumes BidAcceptedEvent
4. ChatService executes transaction:
a. UPDATE conversation SET iswon=true, status='Active' WHERE auctionid=X AND dealerid=WinningDealerId
b. UPDATE conversation SET status='Disabled', updatedat=NOW() WHERE auctionid=X AND dealerid!=WinningDealerId
5. System logs status change
6. Next time dealers poll their conversation list, losing dealers see status='Disabled'
7. Losing dealers cannot send new messages (API returns 403 Forbidden)
8. Customer can only chat with winning dealer for this auction

3. Customer Views Auction Chat Room

1. Customer logs in to portal
2. GET /api/chat/customer/auction-rooms (load all auction chat rooms)
3. System queries:
- All auctions created by customer
- Groups conversations by auction
- Aggregates dealer count per auction
- Calculates total unread messages per auction
- Gets latest message across all dealers per auction
4. Customer sees list of auction cards with:
- Auction name, code, image
- Number of participating dealers
- Total unread count badge
- Last message preview
5. Customer clicks expand on an auction room
6. GET /api/chat/customer/auction-rooms/{auctionId}/dealers
7. System returns list of dealers with:
- Dealer info
- Conversation status
- Unread count per dealer
- Last message per dealer
- Winning indicator if applicable
8. Customer clicks on a dealer to open conversation
9. GET /api/chat/conversations/{conversationId}/messages (load message history)
10. PUT /api/chat/conversations/{conversationId}/mark-read (mark customer's unread messages as seen)
11. Customer can send message if conversation is active

4. User Marks Conversation as Read

1. User opens a conversation (dealer or customer)
2. GET /api/chat/conversations/{conversationId}/messages (load messages)
3. Frontend calls: PUT /api/chat/conversations/{conversationId}/mark-read
4. System extracts userId and userType from JWT token
5. System queries conversation to get dealerId and customerId
6. System determines which entity the current user is:
- If Dealer: currentEntityId = dealerId, otherEntityId = customerId
- If Customer: currentEntityId = customerId, otherEntityId = dealerId
7. System marks all unread messages where senderEntityId = otherEntityId as isseen = true
8. System returns markedCount and remainingUnreadCount
9. Frontend updates unread badge to 0 for this conversation
10. Next time user polls conversation list, unread count reflects the update

5. Polling Strategy for Real-Time Feel

Frontend Implementation:

A. Conversation List Polling (Every 5-10 seconds):
- Store last known updatedat timestamp
- Poll endpoint with same filters
- Compare timestamps to detect changes
- Update UI only for changed conversations
- Show notification badge for new unread messages

B. Message Thread Polling (Every 3-5 seconds when conversation open):
- Store last message ID in current conversation
- Poll: GET /api/chat/conversations/{chatId}/messages?direction=after&cursor={lastMessageId}
- If new messages returned, append to UI
- Auto-scroll to bottom if user was at bottom
- Play notification sound for new messages
- Call mark-as-read API to update read status

C. Optimization:
- Stop polling when tab is inactive (use Page Visibility API)
- Increase polling interval if no activity for 2 minutes
- Resume aggressive polling when user returns
- Cache responses to reduce unnecessary re-renders

5. Conversation Automatic Creation

1. Dealer places first bid on an auction (in AuctionEngineService)
2. AuctionEngineService publishes DealerBidPlacedEvent:
{
"AuctionId": "uuid",
"DealerId": "uuid",
"CustomerId": "uuid",
"BidAmount": 15000
}
3. ChatService consumes event
4. ChatService checks if conversation exists (auctionid + dealerid)
5. If not exists, creates new conversation:
INSERT INTO conversation (
conversationid, auctionid, dealerid, customerid,
conversationname, status, createdat, updatedat
) VALUES (
gen_uuid(), auctionid, dealerid, customerid,
'{auction_name}', 'Active', NOW(), NOW()
)
6. Conversation is now ready for messaging
7. Dealer sees new conversation in their list
8. Customer sees new dealer in auction chat room

Development Tasks & Estimates

NoTask NameEstimate (Hours)DependenciesNotes
1Database Schema Design & Implementation4 hoursNoneCreate Conversation and Message tables with indexes and foreign keys
2Feature Documentation4 hoursTask 3Mnetion all request/response DTOs, enums, and setup ChatService project structure
3Feature Planing8 hours
4When dealer place a bid aginst ant auction, immediately insert an entry in conversation table with the associate auction data, dealer data and customer data2 hours
5API: Send Message (POST /api/chat/message)7 hoursImplement endpoint with validation, sanitization, rate limiting, error handling, unit tests
6API: Get Dealer Conversations (GET /api/chat/dealer/conversations)7 hoursImplement endpoint with filters, cursor pagination, authorization, mapping, tests
7API: Get Customer Auction Rooms (GET /api/chat/customer/auction-rooms)7 hoursImplement endpoint with grouping, aggregation, pagination, authorization, tests
8API: Get Dealers in Room (GET /api/chat/customer/auction-rooms/{id}/dealers)7 hoursImplement endpoint with filters, pagination, authorization, 404 handling, tests
9API: Get Conversation Messages (GET /api/chat/conversations/{id}/messages)7 hoursImplement endpoint with cursor pagination, reply threading, authorization, tests
10API: Mark Messages as Read (PUT /api/chat/conversations/{id}/mark-read)7 hoursImplement endpoint with JWT-based authorization, entity resolution, SQL update, tests
11When customer accept any bid then mark ConversationStatus as disable all the conversations with other dealer for that particular auction2 hours
12When customer accept any bid then mark the conversatioin with that particular dealerid, customer and auctionid IsWon as true1 hours
13Overall Integration Testing Testing10 hours
Grand Total73 hours

Testing & Quality Assurance

Integration Test Scenarios:

  • All API endpoints with valid and invalid requests
  • Authorization checks (dealer can't access customer endpoints, etc.)
  • Pagination edge cases (first page, last page, empty results)
  • Concurrent message sending
  • Status updates via event consumers
  • Database transaction rollbacks on errors

Testing Tools

  • xUnit - Unit and integration testing framework
  • FluentAssertions - Readable test assertions
  • Moq - Mocking framework for unit tests
  • WebApplicationFactory - Integration testing for APIs
  • Testcontainers - PostgreSQL container for integration tests
  • Bogus - Fake data generation for tests
  • Postman/Swagger - Manual API testing
  • k6 or JMeter - Load testing
  • SonarQube - Code quality and coverage analysis

Acceptance Criteria

Feature Complete When:

  1. ✅ Dealers can view all their auction conversations with correct status
  2. ✅ Dealers can send messages to active conversations
  3. ✅ Dealers cannot send messages to disabled conversations
  4. ✅ Customers can view all their auction chat rooms grouped by auction
  5. ✅ Customers can expand auction room to see all participating dealers
  6. ✅ Customers can chat with all dealers before accepting a bid
  7. ✅ When bid is accepted, only winner's conversation remains active
  8. ✅ All other dealer conversations for that auction are disabled
  9. ✅ Messages display with sender info, timestamp, and reply threading
  10. ✅ Unread message counts are accurate
  11. ✅ Users can mark conversations as read and unread count updates correctly
  12. ✅ Mark-as-read API only marks messages from the other participant (not own messages)
  13. ✅ Pagination works correctly for all list endpoints
  14. ✅ Polling updates UI within 5 seconds of new activity
  15. ✅ Message sending is rate limited to 10/minute
  16. ✅ All APIs respond within required time limits (mark-as-read < 200ms)
  17. ✅ Unit test coverage > 85%
  18. ✅ All integration tests pass
  19. ✅ UAT feedback incorporated and resolved

Deployment Considerations

Configuration Changes

Database Migration:

  • Run migration script 000024-CreateChatTables.sql
  • Add suggested indexes for performance
  • Verify foreign key constraints are valid

Risks & Mitigations

RiskImpactLikelihoodMitigation Strategy
Polling creates high server loadHighMediumImplement efficient queries with indexes; Use ETag/If-Modified-Since headers; Monitor and adjust intervals
Frontend polling continues when user logs outLowHighClear all intervals on logout; Use single polling manager; Implement proper cleanup in React useEffect

Review & Approval

(Include a section for review and approval by stakeholders.)

  • Reviewer:
    Abhishak Kumar Roy

  • Approval Date:
    2025-10-21


Notes

Future Enhancements (Phase 2 - SignalR)

  1. Replace polling with WebSocket connections via SignalR
  2. Implement real-time push notifications for:
    • New messages
    • Conversation status changes
    • Typing indicators
    • Online/offline presence
  3. Reduce server load by eliminating constant polling
  4. Improve user experience with instant updates
  5. Add message reactions and read receipts
  6. Implement message editing and deletion (soft delete UI)
  7. Implement Unread count calculation

Technical Debt to Address

  • Current polling approach may not scale beyond 10K concurrent users
  • Attachment handling needs dedicated service (not just URLs)

Dependencies on Other Services

  • AuctionEngineService: Must publish events for bid acceptance, rejection, cancellation
  • IAMService: Must provide user authentication and role information
  • CustomerManagementService: Must expose customer profile API
  • DealerManagementService: Must expose dealer profile API