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:
@@ -203,6 +203,13 @@ export class ConnectionRequestsService {
|
||||
id: true,
|
||||
email: true,
|
||||
avatar: true,
|
||||
userProfile: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user