feat: add WebSocket notifications for new comments

- Introduced enriched comment retrieval with user information and like statistics.
- Implemented WebSocket notifications to notify users of new comments on content.
- Updated dependency injection to include `EventsGateway` and `RealtimeModule`.
This commit is contained in:
Mathis HERRIOT
2026-01-29 15:46:00 +01:00
parent ace438be6b
commit 6d80795e44
3 changed files with 53 additions and 4 deletions

View File

@@ -1,5 +1,6 @@
import { Module } from "@nestjs/common";
import { AuthModule } from "../auth/auth.module";
import { RealtimeModule } from "../realtime/realtime.module";
import { S3Module } from "../s3/s3.module";
import { CommentsController } from "./comments.controller";
import { CommentsService } from "./comments.service";
@@ -7,7 +8,7 @@ import { CommentLikesRepository } from "./repositories/comment-likes.repository"
import { CommentsRepository } from "./repositories/comments.repository";
@Module({
imports: [AuthModule, S3Module],
imports: [AuthModule, S3Module, RealtimeModule],
controllers: [CommentsController],
providers: [CommentsService, CommentsRepository, CommentLikesRepository],
exports: [CommentsService],

View File

@@ -3,6 +3,7 @@ import {
Injectable,
NotFoundException,
} from "@nestjs/common";
import { EventsGateway } from "../realtime/events.gateway";
import { S3Service } from "../s3/s3.service";
import type { CreateCommentDto } from "./dto/create-comment.dto";
import { CommentLikesRepository } from "./repositories/comment-likes.repository";
@@ -14,6 +15,7 @@ export class CommentsService {
private readonly commentsRepository: CommentsRepository,
private readonly commentLikesRepository: CommentLikesRepository,
private readonly s3Service: S3Service,
private readonly eventsGateway: EventsGateway,
) {}
async create(userId: string, contentId: string, dto: CreateCommentDto) {
@@ -24,11 +26,36 @@ export class CommentsService {
parentId: dto.parentId,
});
// Enrichir le commentaire créé (pour le retour API)
// Récupérer le commentaire avec les infos utilisateur pour le WebSocket
const enrichedComment = await this.findOneEnriched(comment.id, userId);
// Notifier les autres utilisateurs sur ce contenu
this.eventsGateway.sendToContent(contentId, "new_comment", enrichedComment);
return enrichedComment;
}
async findOneEnriched(commentId: string, currentUserId?: string) {
const comment = await this.commentsRepository.findOneEnriched(commentId);
if (!comment) return null;
const [likesCount, isLiked] = await Promise.all([
this.commentLikesRepository.countByCommentId(comment.id),
currentUserId
? this.commentLikesRepository.isLikedByUser(comment.id, currentUserId)
: Promise.resolve(false),
]);
return {
...comment,
likesCount: 0,
isLiked: false,
likesCount,
isLiked,
user: {
...comment.user,
avatarUrl: comment.user.avatarUrl
? this.s3Service.getPublicUrl(comment.user.avatarUrl)
: null,
},
};
}

View File

@@ -45,6 +45,27 @@ export class CommentsRepository {
return results[0];
}
async findOneEnriched(id: string) {
const results = await this.databaseService.db
.select({
id: comments.id,
text: comments.text,
parentId: comments.parentId,
createdAt: comments.createdAt,
updatedAt: comments.updatedAt,
user: {
uuid: users.uuid,
username: users.username,
displayName: users.displayName,
avatarUrl: users.avatarUrl,
},
})
.from(comments)
.innerJoin(users, eq(comments.userId, users.uuid))
.where(and(eq(comments.id, id), isNull(comments.deletedAt)));
return results[0];
}
async delete(id: string) {
await this.databaseService.db
.update(comments)