- Added a null check for `enrichedComment` to prevent processing invalid data and potential runtime errors.
178 lines
5.4 KiB
TypeScript
178 lines
5.4 KiB
TypeScript
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);
|
|
}
|
|
}
|