2026-02-08 22:44:16 +05:30
|
|
|
import {
|
|
|
|
|
Injectable,
|
|
|
|
|
NotFoundException,
|
|
|
|
|
ForbiddenException,
|
|
|
|
|
BadRequestException,
|
|
|
|
|
} from '@nestjs/common';
|
2026-02-25 06:45:27 +05:30
|
|
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
2026-02-08 22:44:16 +05:30
|
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
|
import { CreateMessageDto } from './dto';
|
|
|
|
|
import { ConnectionStatus, MessageStatus, MessageType, UserRole } from '@prisma/client';
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class MessagesService {
|
2026-02-25 06:45:27 +05:30
|
|
|
constructor(
|
|
|
|
|
private readonly prisma: PrismaService,
|
|
|
|
|
private readonly eventEmitter: EventEmitter2,
|
|
|
|
|
) {}
|
2026-02-08 22:44:16 +05:30
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate if a user can message an agent (must have ACCEPTED connection)
|
|
|
|
|
*/
|
|
|
|
|
async validateCanMessage(userId: string, agentProfileId: string): Promise<boolean> {
|
|
|
|
|
const connection = await this.prisma.connectionRequest.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
userId_agentProfileId: {
|
|
|
|
|
userId,
|
|
|
|
|
agentProfileId,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return connection?.status === ConnectionStatus.ACCEPTED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get or create a conversation between user and agent
|
|
|
|
|
*/
|
|
|
|
|
async getOrCreateConversation(userId: string, agentProfileId: string) {
|
|
|
|
|
// Validate connection first
|
|
|
|
|
const canMessage = await this.validateCanMessage(userId, agentProfileId);
|
|
|
|
|
if (!canMessage) {
|
|
|
|
|
throw new ForbiddenException(
|
|
|
|
|
'You can only message agents with accepted connection requests',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if conversation already exists
|
|
|
|
|
let conversation = await this.prisma.conversation.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
userId_agentProfileId: {
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create new conversation if it doesn't exist
|
|
|
|
|
if (!conversation) {
|
|
|
|
|
conversation = await this.prisma.conversation.create({
|
|
|
|
|
data: {
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new message in a conversation
|
|
|
|
|
*/
|
|
|
|
|
async createMessage(
|
|
|
|
|
conversationId: string,
|
|
|
|
|
senderId: string,
|
|
|
|
|
dto: CreateMessageDto,
|
|
|
|
|
) {
|
|
|
|
|
// Verify conversation exists and user is part of it
|
|
|
|
|
const conversation = await this.prisma.conversation.findUnique({
|
|
|
|
|
where: { id: conversationId },
|
|
|
|
|
include: {
|
|
|
|
|
agentProfile: {
|
|
|
|
|
select: { userId: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!conversation) {
|
|
|
|
|
throw new NotFoundException('Conversation not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if sender is part of the conversation
|
|
|
|
|
const isUser = conversation.userId === senderId;
|
|
|
|
|
const isAgent = conversation.agentProfile.userId === senderId;
|
|
|
|
|
|
|
|
|
|
if (!isUser && !isAgent) {
|
|
|
|
|
throw new ForbiddenException('You are not part of this conversation');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the message
|
|
|
|
|
const message = await this.prisma.message.create({
|
|
|
|
|
data: {
|
|
|
|
|
conversationId,
|
|
|
|
|
senderId,
|
|
|
|
|
content: dto.content,
|
|
|
|
|
messageType: dto.messageType || MessageType.TEXT,
|
|
|
|
|
fileUrl: dto.fileUrl,
|
|
|
|
|
fileName: dto.fileName,
|
|
|
|
|
fileSize: dto.fileSize,
|
|
|
|
|
mimeType: dto.mimeType,
|
|
|
|
|
status: MessageStatus.SENT,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
sender: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
role: true,
|
|
|
|
|
userProfile: {
|
|
|
|
|
select: {
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
agentProfile: {
|
|
|
|
|
select: {
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update conversation with last message info and unread counts
|
|
|
|
|
const truncatedContent =
|
|
|
|
|
dto.content.length > 255 ? dto.content.substring(0, 252) + '...' : dto.content;
|
|
|
|
|
|
|
|
|
|
await this.prisma.conversation.update({
|
|
|
|
|
where: { id: conversationId },
|
|
|
|
|
data: {
|
|
|
|
|
lastMessageAt: new Date(),
|
|
|
|
|
lastMessageText: truncatedContent,
|
|
|
|
|
// Increment unread count for the other party
|
|
|
|
|
...(isUser
|
|
|
|
|
? { agentUnreadCount: { increment: 1 } }
|
|
|
|
|
: { userUnreadCount: { increment: 1 } }),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-25 06:45:27 +05:30
|
|
|
// Emit push notification event for the recipient
|
|
|
|
|
const recipientUserId = isUser
|
|
|
|
|
? conversation.agentProfile.userId
|
|
|
|
|
: conversation.userId;
|
|
|
|
|
const senderProfile =
|
|
|
|
|
message.sender?.agentProfile || message.sender?.userProfile;
|
|
|
|
|
const senderName = senderProfile
|
|
|
|
|
? `${senderProfile.firstName || ''} ${senderProfile.lastName || ''}`.trim()
|
|
|
|
|
: 'Someone';
|
|
|
|
|
|
|
|
|
|
this.eventEmitter.emit('notification.message', {
|
|
|
|
|
recipientUserId,
|
|
|
|
|
senderName,
|
|
|
|
|
messagePreview: dto.content?.substring(0, 100) || '',
|
|
|
|
|
conversationId,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-08 22:44:16 +05:30
|
|
|
return message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all conversations for a user (either as user or agent)
|
|
|
|
|
*/
|
|
|
|
|
async getConversations(userId: string, role: UserRole) {
|
|
|
|
|
let conversations;
|
|
|
|
|
|
|
|
|
|
if (role === UserRole.AGENT) {
|
|
|
|
|
// Get agent profile for this user
|
|
|
|
|
const agentProfile = await this.prisma.agentProfile.findUnique({
|
|
|
|
|
where: { userId },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!agentProfile) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conversations = await this.prisma.conversation.findMany({
|
|
|
|
|
where: { agentProfileId: agentProfile.id },
|
|
|
|
|
include: {
|
|
|
|
|
user: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
isOnline: true,
|
|
|
|
|
lastSeenAt: true,
|
|
|
|
|
userProfile: {
|
|
|
|
|
select: {
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
messages: {
|
|
|
|
|
take: 1,
|
|
|
|
|
orderBy: { createdAt: 'desc' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: { lastMessageAt: 'desc' },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Map to include unread count from agent perspective
|
|
|
|
|
return conversations.map((conv) => ({
|
|
|
|
|
...conv,
|
|
|
|
|
unreadCount: conv.agentUnreadCount,
|
|
|
|
|
otherParty: {
|
|
|
|
|
id: conv.user.id,
|
|
|
|
|
name: `${conv.user.userProfile?.firstName || ''} ${conv.user.userProfile?.lastName || ''}`.trim() || 'User',
|
|
|
|
|
avatar: conv.user.userProfile?.avatar,
|
|
|
|
|
isOnline: conv.user.isOnline,
|
|
|
|
|
lastSeenAt: conv.user.lastSeenAt,
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
// Regular user
|
|
|
|
|
conversations = await this.prisma.conversation.findMany({
|
|
|
|
|
where: { userId },
|
|
|
|
|
include: {
|
|
|
|
|
agentProfile: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
headline: true,
|
|
|
|
|
userId: true,
|
|
|
|
|
user: {
|
|
|
|
|
select: {
|
|
|
|
|
isOnline: true,
|
|
|
|
|
lastSeenAt: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
messages: {
|
|
|
|
|
take: 1,
|
|
|
|
|
orderBy: { createdAt: 'desc' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: { lastMessageAt: 'desc' },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Map to include unread count from user perspective
|
|
|
|
|
return conversations.map((conv) => ({
|
|
|
|
|
...conv,
|
|
|
|
|
unreadCount: conv.userUnreadCount,
|
|
|
|
|
otherParty: {
|
|
|
|
|
id: conv.agentProfile.id,
|
|
|
|
|
userId: conv.agentProfile.userId,
|
|
|
|
|
name: `${conv.agentProfile.firstName || ''} ${conv.agentProfile.lastName || ''}`.trim() || 'Agent',
|
|
|
|
|
avatar: conv.agentProfile.avatar,
|
|
|
|
|
headline: conv.agentProfile.headline,
|
|
|
|
|
isOnline: conv.agentProfile.user.isOnline,
|
|
|
|
|
lastSeenAt: conv.agentProfile.user.lastSeenAt,
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get messages for a conversation with pagination
|
|
|
|
|
*/
|
|
|
|
|
async getMessages(
|
|
|
|
|
conversationId: string,
|
|
|
|
|
userId: string,
|
|
|
|
|
page: number = 1,
|
|
|
|
|
limit: number = 50,
|
|
|
|
|
) {
|
|
|
|
|
// Verify user is part of the conversation
|
|
|
|
|
const conversation = await this.prisma.conversation.findUnique({
|
|
|
|
|
where: { id: conversationId },
|
|
|
|
|
include: {
|
|
|
|
|
agentProfile: {
|
|
|
|
|
select: { userId: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!conversation) {
|
|
|
|
|
throw new NotFoundException('Conversation not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isUser = conversation.userId === userId;
|
|
|
|
|
const isAgent = conversation.agentProfile.userId === userId;
|
|
|
|
|
|
|
|
|
|
if (!isUser && !isAgent) {
|
|
|
|
|
throw new ForbiddenException('You are not part of this conversation');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const skip = (page - 1) * limit;
|
|
|
|
|
|
|
|
|
|
const [messages, total] = await Promise.all([
|
|
|
|
|
this.prisma.message.findMany({
|
|
|
|
|
where: { conversationId },
|
|
|
|
|
include: {
|
|
|
|
|
sender: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
role: true,
|
|
|
|
|
userProfile: {
|
|
|
|
|
select: {
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
agentProfile: {
|
|
|
|
|
select: {
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: { createdAt: 'desc' },
|
|
|
|
|
skip,
|
|
|
|
|
take: limit,
|
|
|
|
|
}),
|
|
|
|
|
this.prisma.message.count({ where: { conversationId } }),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
messages: messages.reverse(), // Return in chronological order
|
|
|
|
|
pagination: {
|
|
|
|
|
page,
|
|
|
|
|
limit,
|
|
|
|
|
total,
|
|
|
|
|
pages: Math.ceil(total / limit),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mark messages as read in a conversation
|
|
|
|
|
*/
|
|
|
|
|
async markMessagesAsRead(conversationId: string, userId: string) {
|
|
|
|
|
const conversation = await this.prisma.conversation.findUnique({
|
|
|
|
|
where: { id: conversationId },
|
|
|
|
|
include: {
|
|
|
|
|
agentProfile: {
|
|
|
|
|
select: { userId: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!conversation) {
|
|
|
|
|
throw new NotFoundException('Conversation not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isUser = conversation.userId === userId;
|
|
|
|
|
const isAgent = conversation.agentProfile.userId === userId;
|
|
|
|
|
|
|
|
|
|
if (!isUser && !isAgent) {
|
|
|
|
|
throw new ForbiddenException('You are not part of this conversation');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update all unread messages from the other party
|
|
|
|
|
await this.prisma.message.updateMany({
|
|
|
|
|
where: {
|
|
|
|
|
conversationId,
|
|
|
|
|
senderId: { not: userId },
|
|
|
|
|
status: { not: MessageStatus.READ },
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
status: MessageStatus.READ,
|
|
|
|
|
readAt: new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Reset unread count for this user
|
|
|
|
|
await this.prisma.conversation.update({
|
|
|
|
|
where: { id: conversationId },
|
|
|
|
|
data: isUser ? { userUnreadCount: 0 } : { agentUnreadCount: 0 },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { success: true };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update user's online status
|
|
|
|
|
*/
|
|
|
|
|
async updateOnlineStatus(userId: string, isOnline: boolean) {
|
|
|
|
|
await this.prisma.user.update({
|
|
|
|
|
where: { id: userId },
|
|
|
|
|
data: {
|
|
|
|
|
isOnline,
|
|
|
|
|
lastSeenAt: isOnline ? undefined : new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mark a message as delivered
|
|
|
|
|
*/
|
|
|
|
|
async markMessageDelivered(messageId: string) {
|
|
|
|
|
return this.prisma.message.update({
|
|
|
|
|
where: { id: messageId },
|
|
|
|
|
data: {
|
|
|
|
|
status: MessageStatus.DELIVERED,
|
|
|
|
|
deliveredAt: new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get conversation by ID with full details
|
|
|
|
|
*/
|
|
|
|
|
async getConversationById(conversationId: string, userId: string) {
|
|
|
|
|
const conversation = await this.prisma.conversation.findUnique({
|
|
|
|
|
where: { id: conversationId },
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!conversation) {
|
|
|
|
|
throw new NotFoundException('Conversation not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isUser = conversation.userId === userId;
|
|
|
|
|
const isAgent = conversation.agentProfile.userId === userId;
|
|
|
|
|
|
|
|
|
|
if (!isUser && !isAgent) {
|
|
|
|
|
throw new ForbiddenException('You are not part of this conversation');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform to include otherParty based on who is requesting
|
|
|
|
|
if (isUser) {
|
|
|
|
|
// User is viewing - other party is 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,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
// Agent is viewing - other party is 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,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 04:48:15 +05:30
|
|
|
/**
|
|
|
|
|
* Get participant user IDs for a conversation (lightweight query)
|
|
|
|
|
*/
|
|
|
|
|
async getConversationParticipants(conversationId: string): Promise<{ userId: string; agentUserId: string } | null> {
|
|
|
|
|
const conversation = await this.prisma.conversation.findUnique({
|
|
|
|
|
where: { id: conversationId },
|
|
|
|
|
select: {
|
|
|
|
|
userId: true,
|
|
|
|
|
agentProfile: {
|
|
|
|
|
select: { userId: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!conversation) return null;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
userId: conversation.userId,
|
|
|
|
|
agentUserId: conversation.agentProfile.userId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 22:44:16 +05:30
|
|
|
/**
|
|
|
|
|
* Get total unread count for a user across all conversations
|
|
|
|
|
*/
|
|
|
|
|
async getTotalUnreadCount(userId: string, role: UserRole) {
|
|
|
|
|
if (role === UserRole.AGENT) {
|
|
|
|
|
const agentProfile = await this.prisma.agentProfile.findUnique({
|
|
|
|
|
where: { userId },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!agentProfile) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await this.prisma.conversation.aggregate({
|
|
|
|
|
where: { agentProfileId: agentProfile.id },
|
|
|
|
|
_sum: { agentUnreadCount: true },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result._sum.agentUnreadCount || 0;
|
|
|
|
|
} else {
|
|
|
|
|
const result = await this.prisma.conversation.aggregate({
|
|
|
|
|
where: { userId },
|
|
|
|
|
_sum: { userUnreadCount: true },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result._sum.userUnreadCount || 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|