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.

This commit is contained in:
pradeepkumar
2026-03-15 00:28:14 +05:30
parent 2c0d0f0aeb
commit 960cdd8d48
4 changed files with 120 additions and 89 deletions

View File

@@ -203,6 +203,13 @@ export class ConnectionRequestsService {
id: true, id: true,
email: true, email: true,
avatar: true, avatar: true,
userProfile: {
select: {
firstName: true,
lastName: true,
avatar: true,
},
},
}, },
}, },
}, },

View File

@@ -1,10 +1,16 @@
import { IsString, IsNotEmpty, IsUUID } from 'class-validator'; import { IsString, IsNotEmpty, IsUUID, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class StartConversationDto { 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() @IsString()
@IsNotEmpty() @IsOptional()
@IsUUID() @IsUUID()
agentProfileId: string; agentProfileId?: string;
@ApiPropertyOptional({ description: 'User ID to start conversation with (required for agents)' })
@IsString()
@IsOptional()
@IsUUID()
userId?: string;
} }

View File

@@ -70,7 +70,7 @@ export class MessagesController {
// ========================================== // ==========================================
@Post('conversations') @Post('conversations')
@HttpCode(HttpStatus.OK) @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({ @ApiResponse({
status: 200, status: 200,
description: 'Conversation created or retrieved', description: 'Conversation created or retrieved',
@@ -80,10 +80,11 @@ export class MessagesController {
description: 'Connection not accepted', description: 'Connection not accepted',
}) })
async startConversation( async startConversation(
@CurrentUser('id') userId: string, @CurrentUser('id') callerUserId: string,
@CurrentUser('role') role: UserRole,
@Body() dto: StartConversationDto, @Body() dto: StartConversationDto,
) { ) {
return this.messagesService.getOrCreateConversation(userId, dto.agentProfileId); return this.messagesService.getOrCreateConversation(callerUserId, role, dto);
} }
// ========================================== // ==========================================

View File

@@ -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 // Validate connection first
const canMessage = await this.validateCanMessage(userId, agentProfileId); const canMessage = await this.validateCanMessage(userId, agentProfileId);
if (!canMessage) { if (!canMessage) {
throw new ForbiddenException( 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 // Check if conversation already exists
let conversation = await this.prisma.conversation.findUnique({ let conversation = await this.prisma.conversation.findUnique({
where: { where: {
@@ -52,38 +115,7 @@ export class MessagesService {
agentProfileId, agentProfileId,
}, },
}, },
include: { include: 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,
},
},
},
},
},
}); });
// Create new conversation if it doesn't exist // Create new conversation if it doesn't exist
@@ -93,55 +125,40 @@ export class MessagesService {
userId, userId,
agentProfileId, agentProfileId,
}, },
include: { include: 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,
},
},
},
},
},
}); });
} }
// Transform to include otherParty (for the user, other party is the agent) // Transform to include otherParty based on caller role
return { if (callerRole === UserRole.AGENT) {
...conversation, // Agent is viewing — other party is the user
unreadCount: conversation.userUnreadCount, return {
otherParty: { ...conversation,
id: conversation.agentProfile.id, unreadCount: conversation.agentUnreadCount,
userId: conversation.agentProfile.userId, otherParty: {
name: `${conversation.agentProfile.firstName || ''} ${conversation.agentProfile.lastName || ''}`.trim() || 'Agent', id: conversation.user.id,
avatar: conversation.agentProfile.avatar, name: `${conversation.user.userProfile?.firstName || ''} ${conversation.user.userProfile?.lastName || ''}`.trim() || 'User',
headline: conversation.agentProfile.headline, avatar: conversation.user.userProfile?.avatar,
isOnline: conversation.agentProfile.user?.isOnline || false, isOnline: conversation.user.isOnline,
lastSeenAt: conversation.agentProfile.user?.lastSeenAt || null, 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,
},
};
}
} }
/** /**