From f882a70343b01a021e55772cb4f86da63939682a Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:20:18 +0100 Subject: [PATCH] feat: add read receipt handling based on user preferences - Integrated `UsersService` into `MessagesService` for retrieving user preferences. - Updated `markAsRead` functionality to respect `showReadReceipts` preference. - Enhanced real-time read receipt notifications via `EventsGateway`. - Added `markAsRead` method to the frontend message service. --- backend/src/messages/messages.service.spec.ts | 7 +++ backend/src/messages/messages.service.ts | 44 +++++++++++++++++-- frontend/src/services/message.service.ts | 4 ++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/backend/src/messages/messages.service.spec.ts b/backend/src/messages/messages.service.spec.ts index f4814f2..14cc6d4 100644 --- a/backend/src/messages/messages.service.spec.ts +++ b/backend/src/messages/messages.service.spec.ts @@ -1,6 +1,7 @@ import { ForbiddenException } from "@nestjs/common"; import { Test, TestingModule } from "@nestjs/testing"; import { EventsGateway } from "../realtime/events.gateway"; +import { UsersService } from "../users/users.service"; import { MessagesService } from "./messages.service"; import { MessagesRepository } from "./repositories/messages.repository"; @@ -16,6 +17,7 @@ describe("MessagesService", () => { createMessage: jest.fn(), findAllConversations: jest.fn(), isParticipant: jest.fn(), + getParticipants: jest.fn(), findMessagesByConversationId: jest.fn(), markAsRead: jest.fn(), countUnreadMessages: jest.fn(), @@ -25,12 +27,17 @@ describe("MessagesService", () => { sendToUser: jest.fn(), }; + const mockUsersService = { + findOne: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ MessagesService, { provide: MessagesRepository, useValue: mockMessagesRepository }, { provide: EventsGateway, useValue: mockEventsGateway }, + { provide: UsersService, useValue: mockUsersService }, ], }).compile(); diff --git a/backend/src/messages/messages.service.ts b/backend/src/messages/messages.service.ts index 0d98d3d..4568f98 100644 --- a/backend/src/messages/messages.service.ts +++ b/backend/src/messages/messages.service.ts @@ -1,5 +1,6 @@ import { ForbiddenException, Injectable } from "@nestjs/common"; import { EventsGateway } from "../realtime/events.gateway"; +import { UsersService } from "../users/users.service"; import type { CreateMessageDto } from "./dto/create-message.dto"; import { MessagesRepository } from "./repositories/messages.repository"; @@ -8,6 +9,7 @@ export class MessagesService { constructor( private readonly messagesRepository: MessagesRepository, private readonly eventsGateway: EventsGateway, + private readonly usersService: UsersService, ) {} async sendMessage(senderId: string, dto: CreateMessageDto) { @@ -62,8 +64,24 @@ export class MessagesService { throw new ForbiddenException("You are not part of this conversation"); } - // Marquer comme lus - await this.messagesRepository.markAsRead(conversationId, userId); + // Récupérer les préférences de l'utilisateur actuel + const user = await this.usersService.findOne(userId); + + // Marquer comme lus seulement si l'utilisateur l'autorise + if (user?.showReadReceipts) { + await this.messagesRepository.markAsRead(conversationId, userId); + + // Notifier l'expéditeur que les messages ont été lus + const participants = + await this.messagesRepository.getParticipants(conversationId); + const otherParticipant = participants.find((p) => p.userId !== userId); + if (otherParticipant) { + this.eventsGateway.sendToUser(otherParticipant.userId, "messages_read", { + conversationId, + readerId: userId, + }); + } + } return this.messagesRepository.findMessagesByConversationId(conversationId); } @@ -76,6 +94,26 @@ export class MessagesService { if (!isParticipant) { throw new ForbiddenException("You are not part of this conversation"); } - return this.messagesRepository.markAsRead(conversationId, userId); + + const user = await this.usersService.findOne(userId); + if (!user?.showReadReceipts) return; + + const result = await this.messagesRepository.markAsRead( + conversationId, + userId, + ); + + // Notifier l'autre participant + const participants = + await this.messagesRepository.getParticipants(conversationId); + const otherParticipant = participants.find((p) => p.userId !== userId); + if (otherParticipant) { + this.eventsGateway.sendToUser(otherParticipant.userId, "messages_read", { + conversationId, + readerId: userId, + }); + } + + return result; } } diff --git a/frontend/src/services/message.service.ts b/frontend/src/services/message.service.ts index 73295a9..6024ab8 100644 --- a/frontend/src/services/message.service.ts +++ b/frontend/src/services/message.service.ts @@ -55,4 +55,8 @@ export const MessageService = { }); return data; }, + + async markAsRead(conversationId: string): Promise { + await api.patch(`/messages/conversations/${conversationId}/read`); + }, };