Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/conversations/conversations.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,45 @@ export class ConversationsController {
};
}

@Get('/unseen/:conversationId')
@UseGuards(JwtAuthGuard)
@ApiCookieAuth()
@ApiOperation({
summary: 'Get count of unseen messages in a specific conversation for the authenticated user',
description: 'Retrieves the total number of unseen messages across all conversations',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Unseen messages count retrieved successfully',
schema: {
example: {
status: 'success',
unseenCount: 5,
},
},
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: 'Unauthorized - Token missing or invalid',
schema: ErrorResponseDto.schemaExample(
'Authentication token is missing or invalid',
'Unauthorized',
),
})
async getConversationUnseenMessagesCount(
@CurrentUser() user: AuthenticatedUser,
@Param('conversationId', ParseIntPipe) conversationId: number,
) {
const unseenCount = await this.conversationsService.getConversationUnseenMessagesCount(
conversationId,
user.id,
);
return {
status: 'success',
unseenCount,
};
}

@Get('/:conversationId')
@UseGuards(JwtAuthGuard)
@ApiCookieAuth()
Expand Down
51 changes: 50 additions & 1 deletion src/conversations/conversations.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConflictException, Inject, Injectable } from '@nestjs/common';
import { CreateConversationDto } from './dto/create-conversation.dto';
import { PrismaService } from 'src/prisma/prisma.service';
import { Services } from 'src/utils/constants';
import { getUnseenMessageCountWhere } from './helpers/unseen-message.helper';

@Injectable()
export class ConversationsService {
Expand Down Expand Up @@ -173,9 +174,18 @@ export class ConversationsService {
}),
]);

// Fetch unseen counts for all conversations
const unseenCounts = await Promise.all(
conversations.map((conv) =>
this.prismaService.message.count({
where: getUnseenMessageCountWhere(conv.id, userId, userId === conv.User1.id),
}),
),
);

// Transform Messages to messages and filter based on user
const transformedConversations = conversations.map(
({ Messages, User1, User2, ...conversation }) => {
({ Messages, User1, User2, ...conversation }, index) => {
const isUser1 = userId === User1.id;

// Find the first message that's not deleted for this user
Expand All @@ -185,6 +195,7 @@ export class ConversationsService {

return {
...conversation,
unseenCount: unseenCounts[index],
lastMessage: lastVisibleMessage
? {
id: lastVisibleMessage.id,
Expand Down Expand Up @@ -325,10 +336,16 @@ export class ConversationsService {
isUser1 ? !msg.isDeletedU1 : !msg.isDeletedU2,
);

// Fetch exact unseen count from database
const unseenCount = await this.prismaService.message.count({
where: getUnseenMessageCountWhere(conversationId, userId, isUser1),
});

const transformedConversation = {
id: conversation.id,
updatedAt: conversation.updatedAt,
createdAt: conversation.createdAt,
unseenCount,
lastMessage: lastVisibleMessage
? {
id: lastVisibleMessage.id,
Expand Down Expand Up @@ -356,4 +373,36 @@ export class ConversationsService {

return { data: transformedConversation };
}

async getConversationUnseenMessagesCount(conversationId: number, userId: number) {
const conversation = await this.prismaService.conversation.findUnique({
where: { id: conversationId },
select: {
User1: {
select: {
id: true,
},
},
User2: {
select: {
id: true,
},
},
},
});

if (!conversation) {
throw new ConflictException('Conversation not found');
}

const isUser1 = userId === conversation.User1.id;

if (!isUser1 && userId !== conversation.User2.id) {
throw new ConflictException('You are not part of this conversation');
}

return this.prismaService.message.count({
where: getUnseenMessageCountWhere(conversationId, userId, isUser1),
});
}
}
15 changes: 15 additions & 0 deletions src/conversations/helpers/unseen-message.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function getUnseenMessageCountWhere(
conversationId: number,
userId: number,
isUser1: boolean,
) {
return {
conversationId,
isSeen: false,
senderId: {
not: userId,
},
isDeletedU1: isUser1 ? false : undefined,
isDeletedU2: isUser1 ? undefined : false,
};
}
3 changes: 2 additions & 1 deletion src/messages/messages.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect
const recipientId =
userId === participants.user1Id ? participants.user2Id : participants.user1Id;

const message = await this.messagesService.create(createMessageDto);
const { message, unseenCount } = await this.messagesService.create(createMessageDto);

// Emit to conversation room
this.server
Expand Down Expand Up @@ -218,6 +218,7 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect
return {
status: 'success',
data: message,
unseenCount,
};
} catch (error) {
console.error(`Error creating message: ${error.message}`);
Expand Down
20 changes: 19 additions & 1 deletion src/messages/messages.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { UpdateMessageDto } from './dto/update-message.dto';
import { PrismaService } from '../prisma/prisma.service';
import { RemoveMessageDto } from './dto/remove-message.dto';
import { Services } from 'src/utils/constants';
import { getUnseenMessageCountWhere } from 'src/conversations/helpers/unseen-message.helper';

@Injectable()
export class MessagesService {
Expand All @@ -31,8 +32,23 @@ export class MessagesService {
throw new Error('Conversation not found');
}

const isUser1 = senderId === conversation.user1Id;

if (!isUser1 && senderId !== conversation.user2Id) {
throw new ForbiddenException('You are not part of this conversation');
}

// invert sender to get unseen count at receiver side
const unseenCount = await this.prismaService.message.count({
where: getUnseenMessageCountWhere(
createMessageDto.conversationId,
isUser1 ? conversation.user2Id : conversation.user1Id,
!isUser1,
),
});

// Create the message and update conversation timestamp in a transaction
return this.prismaService.$transaction(async (prisma) => {
const message = await this.prismaService.$transaction(async (prisma) => {
await prisma.conversation.update({
where: { id: conversationId },
data: {}, // Empty update triggers @updatedAt
Expand All @@ -54,6 +70,8 @@ export class MessagesService {
},
});
});

return { message, unseenCount };
}

async getConversationUsers(
Expand Down
Loading