diff --git a/src/modules/comments/comments.controller.ts b/src/modules/comments/comments.controller.ts index c9a4b6239..f24f3535e 100644 --- a/src/modules/comments/comments.controller.ts +++ b/src/modules/comments/comments.controller.ts @@ -1,3 +1,5 @@ +import { UserPayload } from './../user/interfaces/user-payload.interface'; +import { User } from './../user/entities/user.entity'; import { Controller, Body, Post, Request, Get, Param, Delete } from '@nestjs/common'; import { CommentsService } from './comments.service'; import { CreateCommentDto } from './dtos/create-comment.dto'; @@ -15,7 +17,7 @@ export class CommentsController { @ApiResponse({ status: 400, description: 'Bad Request.' }) @ApiResponse({ status: 500, description: 'Internal Server Error.' }) async addComment(@Body() createCommentDto: CreateCommentDto, @Request() req): Promise { - const { userId } = req.user; + const userId = req.user.id; return await this.commentsService.addComment(createCommentDto, userId); } @@ -26,10 +28,20 @@ export class CommentsController { return await this.commentsService.getAComment(id); } + @ApiOperation({ summary: 'Dislike a comment' }) + @ApiResponse({ status: 200, description: 'Dislike updated successfully' }) + @ApiResponse({ status: 404, description: 'Comment not found' }) + @Post(':id/dislike') + async dislikeComment(@Param('id') id: string, @Request() req) { + const userId = req.user.id; + // console.log('User ID:', userId); debug + return await this.commentsService.dislikeComment(id, userId); + } + @ApiOperation({ summary: 'Delete a comment' }) @ApiResponse({ status: 200, description: 'The comment has been deleted successfully.' }) @Delete(':id/delete') async deleteAComment(@Param('id') id: string, @Request() req): Promise { - return await this.commentsService.deleteAComment(id, req.user.userId); + return await this.commentsService.deleteAComment(id, req.user.id); } } diff --git a/src/modules/comments/comments.service.ts b/src/modules/comments/comments.service.ts index c6cec2f78..075f9d652 100644 --- a/src/modules/comments/comments.service.ts +++ b/src/modules/comments/comments.service.ts @@ -66,7 +66,7 @@ export class CommentsService { throw new CustomHttpException('You are not authorized to delete this comment', HttpStatus.FORBIDDEN); } - await this.commentRepository.delete(comment); + await this.commentRepository.delete(comment.id); return { message: 'Comment deleted successfully!', @@ -74,4 +74,35 @@ export class CommentsService { data: { comment }, }; } + + async dislikeComment(commentId: string, userId: string): Promise<{ message: string; dislikeCount: number }> { + const comment = await this.commentRepository + .createQueryBuilder('comment') + .where('comment.id = :id', { id: commentId }) + .getOne(); + + if (!comment) { + throw new CustomHttpException('Comment not found', HttpStatus.NOT_FOUND); + } + + if (!comment.dislikedBy) { + comment.dislikedBy = []; + } + + // Check if the user has already disliked the comment + if (comment.dislikedBy.includes(userId)) { + throw new CustomHttpException('You have already disliked this comment', HttpStatus.BAD_REQUEST); + } + + // Add the user to the dislikedBy array and increment dislikes + comment.dislikedBy.push(userId); + comment.dislikes = comment.dislikedBy.length; + + await this.commentRepository.save(comment); + + return { + message: 'Dislike updated successfully', + dislikeCount: comment.dislikes, + }; + } } diff --git a/src/modules/comments/dtos/dislike-comment.dto b/src/modules/comments/dtos/dislike-comment.dto new file mode 100644 index 000000000..d7dae8e97 --- /dev/null +++ b/src/modules/comments/dtos/dislike-comment.dto @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsUUID } from 'class-validator'; + +export class DislikeCommentDto { + @ApiProperty({ description: 'Comment ID to dislike' }) + @IsNotEmpty() + @IsUUID() + commentId: string; +} \ No newline at end of file diff --git a/src/modules/comments/entities/comments.entity.ts b/src/modules/comments/entities/comments.entity.ts index 8d4f22611..72ea1352c 100644 --- a/src/modules/comments/entities/comments.entity.ts +++ b/src/modules/comments/entities/comments.entity.ts @@ -19,4 +19,10 @@ export class Comment extends AbstractBaseEntity { @Column({ nullable: true }) model_type: string; + + @Column({ type: 'int', default: 0 }) // Add dislikes column + dislikes: number; + + @Column('simple-array', { nullable: true }) // Store user IDs as an array + dislikedBy: string[]; } diff --git a/src/modules/comments/tests/comments.service.spec.ts b/src/modules/comments/tests/comments.service.spec.ts index 403ce827c..1b1078d0e 100644 --- a/src/modules/comments/tests/comments.service.spec.ts +++ b/src/modules/comments/tests/comments.service.spec.ts @@ -8,11 +8,17 @@ import { User } from '../../user/entities/user.entity'; import { CustomHttpException } from '@shared/helpers/custom-http-filter'; import { HttpStatus } from '@nestjs/common'; +const mockQueryBuilder = { + where: jest.fn().mockReturnThis(), + getOne: jest.fn(), +}; + const mockCommentRepository = () => ({ create: jest.fn(), save: jest.fn(), findOne: jest.fn(), delete: jest.fn(), + createQueryBuilder: jest.fn(() => mockQueryBuilder), }); const mockUserRepository = () => ({ @@ -105,9 +111,9 @@ describe('CommentsService', () => { it('should delete a comment successfully', async () => { const commentId = 'comment-id'; const userId = 'user-id'; - const mockUser = { id: 'user-id' }; + const mockUser = { id: userId }; const mockComment = { - id: 'comment-id', + id: commentId, model_id: '1', model_type: 'post', comment: 'A valid comment', @@ -115,14 +121,14 @@ describe('CommentsService', () => { }; commentRepository.findOne.mockResolvedValue(mockComment); - commentRepository.delete.mockResolvedValue(mockComment); + commentRepository.delete.mockResolvedValue({ affected: 1 }); console.log(await commentRepository.findOne({ where: { id: commentId }, relations: ['user'] })); // Debugging const result = await service.deleteAComment(commentId, userId); expect(commentRepository.findOne).toHaveBeenCalledWith({ where: { id: commentId }, relations: ['user'] }); - expect(commentRepository.delete).toHaveBeenCalledWith(mockComment); + expect(commentRepository.delete).toHaveBeenCalledWith(commentId); expect(result).toEqual({ message: 'Comment deleted successfully!', status: HttpStatus.OK, @@ -131,4 +137,39 @@ describe('CommentsService', () => { }); }); }); + + describe('CommentsService - dislikeComment', () => { + it('should throw CustomHttpException if comment is not found', async () => { + commentRepository.findOne.mockResolvedValue(null); + + await expect(service.dislikeComment('comment-id', 'user-id')).rejects.toThrow(CustomHttpException); + await expect(service.dislikeComment('comment-id', 'user-id')).rejects.toMatchObject({ + message: 'Comment not found', + status: HttpStatus.NOT_FOUND, + }); + }); + + it('should increase the dislike count successfully', async () => { + const mockComment = { + id: 'comment-id', + dislikes: 2, + dislikedBy: ['user1', 'user2'], // Ensure this is initialized + }; + + // Mock `getOne()` from `createQueryBuilder` + mockQueryBuilder.getOne.mockResolvedValue(mockComment); + commentRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder); + + commentRepository.save.mockResolvedValue({ ...mockComment, dislikes: 3 }); + + const result = await service.dislikeComment('comment-id', 'user-id'); + + expect(result).toEqual({ + message: 'Dislike updated successfully', + dislikeCount: 3, + }); + + expect(commentRepository.save).toHaveBeenCalledWith({ ...mockComment, dislikes: 3 }); + }); + }); }); diff --git a/src/modules/products/tests/mocks/comment.mock.ts b/src/modules/products/tests/mocks/comment.mock.ts index 53006b1b6..3b8e85b15 100644 --- a/src/modules/products/tests/mocks/comment.mock.ts +++ b/src/modules/products/tests/mocks/comment.mock.ts @@ -9,6 +9,8 @@ export const mockComment: Comment = { model_type: 'Product', user: mockUser, product: productMock, + dislikes: 0, + dislikedBy: [], created_at: new Date(), updated_at: new Date(), };