From 960cdd8d48a78564a3e611336ae5d8ed8b7ee72d Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sun, 15 Mar 2026 00:28:14 +0530 Subject: [PATCH] feat: Allow both users and agents to initiate conversations and dynamically display the other party based on the caller's role, while also enhancing user profile data in connection requests. --- .../connection-requests.service.ts | 7 + src/messages/dto/start-conversation.dto.ts | 16 +- src/messages/messages.controller.ts | 7 +- src/messages/messages.service.ts | 179 ++++++++++-------- 4 files changed, 120 insertions(+), 89 deletions(-) diff --git a/src/connection-requests/connection-requests.service.ts b/src/connection-requests/connection-requests.service.ts index 096117b..d2c72ce 100644 --- a/src/connection-requests/connection-requests.service.ts +++ b/src/connection-requests/connection-requests.service.ts @@ -203,6 +203,13 @@ export class ConnectionRequestsService { id: true, email: true, avatar: true, + userProfile: { + select: { + firstName: true, + lastName: true, + avatar: true, + }, + }, }, }, }, diff --git a/src/messages/dto/start-conversation.dto.ts b/src/messages/dto/start-conversation.dto.ts index cbe322d..780f228 100644 --- a/src/messages/dto/start-conversation.dto.ts +++ b/src/messages/dto/start-conversation.dto.ts @@ -1,10 +1,16 @@ -import { IsString, IsNotEmpty, IsUUID } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty, IsUUID, IsOptional } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class StartConversationDto { - @ApiProperty({ description: 'Agent profile ID to start conversation with' }) + @ApiProperty({ description: 'Agent profile ID to start conversation with (required for users)' }) @IsString() - @IsNotEmpty() + @IsOptional() @IsUUID() - agentProfileId: string; + agentProfileId?: string; + + @ApiPropertyOptional({ description: 'User ID to start conversation with (required for agents)' }) + @IsString() + @IsOptional() + @IsUUID() + userId?: string; } diff --git a/src/messages/messages.controller.ts b/src/messages/messages.controller.ts index 3c7577f..76178e5 100644 --- a/src/messages/messages.controller.ts +++ b/src/messages/messages.controller.ts @@ -70,7 +70,7 @@ export class MessagesController { // ========================================== @Post('conversations') @HttpCode(HttpStatus.OK) - @ApiOperation({ summary: 'Start or get existing conversation with an agent' }) + @ApiOperation({ summary: 'Start or get existing conversation with a user or agent' }) @ApiResponse({ status: 200, description: 'Conversation created or retrieved', @@ -80,10 +80,11 @@ export class MessagesController { description: 'Connection not accepted', }) async startConversation( - @CurrentUser('id') userId: string, + @CurrentUser('id') callerUserId: string, + @CurrentUser('role') role: UserRole, @Body() dto: StartConversationDto, ) { - return this.messagesService.getOrCreateConversation(userId, dto.agentProfileId); + return this.messagesService.getOrCreateConversation(callerUserId, role, dto); } // ========================================== diff --git a/src/messages/messages.service.ts b/src/messages/messages.service.ts index b9e8bc5..a3f15bb 100644 --- a/src/messages/messages.service.ts +++ b/src/messages/messages.service.ts @@ -33,17 +33,80 @@ export class MessagesService { } /** - * Get or create a conversation between user and agent + * Get or create a conversation between user and agent. + * Supports both user-initiated (provides agentProfileId) and agent-initiated (provides userId). */ - async getOrCreateConversation(userId: string, agentProfileId: string) { + async getOrCreateConversation( + callerUserId: string, + callerRole: UserRole, + dto: { agentProfileId?: string; userId?: string }, + ) { + let userId: string; + let agentProfileId: string; + + if (callerRole === UserRole.AGENT) { + // Agent is starting the conversation — they provide the target userId + if (!dto.userId) { + throw new BadRequestException('userId is required for agents to start a conversation'); + } + const agentProfile = await this.prisma.agentProfile.findUnique({ + where: { userId: callerUserId }, + }); + if (!agentProfile) { + throw new BadRequestException('Agent profile not found'); + } + userId = dto.userId; + agentProfileId = agentProfile.id; + } else { + // User is starting the conversation — they provide the target agentProfileId + if (!dto.agentProfileId) { + throw new BadRequestException('agentProfileId is required for users to start a conversation'); + } + userId = callerUserId; + agentProfileId = dto.agentProfileId; + } + // Validate connection first const canMessage = await this.validateCanMessage(userId, agentProfileId); if (!canMessage) { throw new ForbiddenException( - 'You can only message agents with accepted connection requests', + 'You can only message users with accepted connection requests', ); } + const conversationInclude = { + agentProfile: { + select: { + id: true, + firstName: true, + lastName: true, + avatar: true, + headline: true, + userId: true, + user: { + select: { + isOnline: true, + lastSeenAt: true, + }, + }, + }, + }, + user: { + select: { + id: true, + isOnline: true, + lastSeenAt: true, + userProfile: { + select: { + firstName: true, + lastName: true, + avatar: true, + }, + }, + }, + }, + }; + // Check if conversation already exists let conversation = await this.prisma.conversation.findUnique({ where: { @@ -52,38 +115,7 @@ export class MessagesService { agentProfileId, }, }, - include: { - agentProfile: { - select: { - id: true, - firstName: true, - lastName: true, - avatar: true, - headline: true, - userId: true, - user: { - select: { - isOnline: true, - lastSeenAt: true, - }, - }, - }, - }, - user: { - select: { - id: true, - isOnline: true, - lastSeenAt: true, - userProfile: { - select: { - firstName: true, - lastName: true, - avatar: true, - }, - }, - }, - }, - }, + include: conversationInclude, }); // Create new conversation if it doesn't exist @@ -93,55 +125,40 @@ export class MessagesService { userId, agentProfileId, }, - include: { - agentProfile: { - select: { - id: true, - firstName: true, - lastName: true, - avatar: true, - headline: true, - userId: true, - user: { - select: { - isOnline: true, - lastSeenAt: true, - }, - }, - }, - }, - user: { - select: { - id: true, - isOnline: true, - lastSeenAt: true, - userProfile: { - select: { - firstName: true, - lastName: true, - avatar: true, - }, - }, - }, - }, - }, + include: conversationInclude, }); } - // Transform to include otherParty (for the user, other party is the agent) - return { - ...conversation, - unreadCount: conversation.userUnreadCount, - otherParty: { - id: conversation.agentProfile.id, - userId: conversation.agentProfile.userId, - name: `${conversation.agentProfile.firstName || ''} ${conversation.agentProfile.lastName || ''}`.trim() || 'Agent', - avatar: conversation.agentProfile.avatar, - headline: conversation.agentProfile.headline, - isOnline: conversation.agentProfile.user?.isOnline || false, - lastSeenAt: conversation.agentProfile.user?.lastSeenAt || null, - }, - }; + // Transform to include otherParty based on caller role + if (callerRole === UserRole.AGENT) { + // Agent is viewing — other party is the user + return { + ...conversation, + unreadCount: conversation.agentUnreadCount, + otherParty: { + id: conversation.user.id, + name: `${conversation.user.userProfile?.firstName || ''} ${conversation.user.userProfile?.lastName || ''}`.trim() || 'User', + avatar: conversation.user.userProfile?.avatar, + isOnline: conversation.user.isOnline, + lastSeenAt: conversation.user.lastSeenAt, + }, + }; + } else { + // User is viewing — other party is the agent + return { + ...conversation, + unreadCount: conversation.userUnreadCount, + otherParty: { + id: conversation.agentProfile.id, + userId: conversation.agentProfile.userId, + name: `${conversation.agentProfile.firstName || ''} ${conversation.agentProfile.lastName || ''}`.trim() || 'Agent', + avatar: conversation.agentProfile.avatar, + headline: conversation.agentProfile.headline, + isOnline: conversation.agentProfile.user?.isOnline || false, + lastSeenAt: conversation.agentProfile.user?.lastSeenAt || null, + }, + }; + } } /**