import { ForbiddenException, forwardRef, Inject, Injectable, NotFoundException, } from "@nestjs/common"; import { ContentsRepository } from "../contents/repositories/contents.repository"; 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"; import { CommentsRepository } from "./repositories/comments.repository"; @Injectable() export class CommentsService { constructor( private readonly commentsRepository: CommentsRepository, private readonly commentLikesRepository: CommentLikesRepository, @Inject(forwardRef(() => ContentsRepository)) private readonly contentsRepository: ContentsRepository, private readonly s3Service: S3Service, private readonly eventsGateway: EventsGateway, ) {} async create(userId: string, contentId: string, dto: CreateCommentDto) { const comment = await this.commentsRepository.create({ userId, contentId, text: dto.text, parentId: dto.parentId, }); // Récupérer le commentaire avec les infos utilisateur pour le WebSocket const enrichedComment = await this.findOneEnriched(comment.id, userId); if (!enrichedComment) return null; // Notifier les autres utilisateurs sur ce contenu (room de contenu) this.eventsGateway.sendToContent(contentId, "new_comment", enrichedComment); // Notifications ciblées try { // 1. Notifier l'auteur du post const content = await this.contentsRepository.findOne(contentId); if (content && content.userId !== userId) { this.eventsGateway.sendToUser(content.userId, "notification", { type: "comment", userId: userId, username: enrichedComment.user.username, contentId: contentId, commentId: comment.id, text: `a commenté votre post : "${dto.text.substring(0, 30)}${dto.text.length > 30 ? "..." : ""}"`, }); } // 2. Si c'est une réponse, notifier l'auteur du commentaire parent if (dto.parentId) { const parentComment = await this.commentsRepository.findOne(dto.parentId); if ( parentComment && parentComment.userId !== userId && (!content || parentComment.userId !== content.userId) ) { this.eventsGateway.sendToUser(parentComment.userId, "notification", { type: "reply", userId: userId, username: enrichedComment.user.username, contentId: contentId, commentId: comment.id, text: `a répondu à votre commentaire : "${dto.text.substring(0, 30)}${dto.text.length > 30 ? "..." : ""}"`, }); } } } catch (error) { console.error("Failed to send notification:", error); } 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, isLiked, user: { ...comment.user, avatarUrl: comment.user.avatarUrl ? this.s3Service.getPublicUrl(comment.user.avatarUrl) : null, }, }; } async findAllByContentId(contentId: string, userId?: string) { const comments = await this.commentsRepository.findAllByContentId(contentId); return Promise.all( comments.map(async (comment) => { const [likesCount, isLiked] = await Promise.all([ this.commentLikesRepository.countByCommentId(comment.id), userId ? this.commentLikesRepository.isLikedByUser(comment.id, userId) : Promise.resolve(false), ]); return { ...comment, likesCount, isLiked, user: { ...comment.user, avatarUrl: comment.user.avatarUrl ? this.s3Service.getPublicUrl(comment.user.avatarUrl) : null, }, }; }), ); } async remove(userId: string, commentId: string, isAdmin = false) { const comment = await this.commentsRepository.findOne(commentId); if (!comment) { throw new NotFoundException("Comment not found"); } if (!isAdmin && comment.userId !== userId) { throw new ForbiddenException("You cannot delete this comment"); } await this.commentsRepository.delete(commentId); } async like(userId: string, commentId: string) { const comment = await this.commentsRepository.findOne(commentId); if (!comment) { throw new NotFoundException("Comment not found"); } await this.commentLikesRepository.addLike(commentId, userId); // Notifier l'auteur du commentaire if (comment.userId !== userId) { try { const liker = await this.findOneEnriched(commentId, userId); this.eventsGateway.sendToUser(comment.userId, "notification", { type: "like_comment", userId: userId, username: liker?.user.username, contentId: comment.contentId, commentId: commentId, text: "a aimé votre commentaire", }); } catch (error) { console.error("Failed to send like notification:", error); } } } async unlike(userId: string, commentId: string) { const comment = await this.commentsRepository.findOne(commentId); if (!comment) { throw new NotFoundException("Comment not found"); } await this.commentLikesRepository.removeLike(commentId, userId); } }