Repository: ita-social-projects/GreenCity
Pull request #8267: Added likeV2/dislikeV2 methods with response body. Fixed CommentDtoMapper
Author: login: Andrii-Danylenko
Created: 2025-03-20T21:30:52Z | Merged: 2025-03-24T11:41:36Z

## Summary of change
I have updated the like and dislike methods to return responses with the correct comment information, enabling the frontend to utilize them. Additionally, I fixed CommentDtoMapper, which previously generated an incorrect object mapping.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced new endpoints for liking and disliking comments, enhancing user interaction capabilities.
	- Updated methods now return enriched response data, including updated like counts and user interaction details.
- **Bug Fixes**
	- Improved handling of user interactions with comments to ensure accurate tracking of likes and dislikes.
- **Refactor**
	- Streamlined and modularized comment reaction logic for better maintainability and clarity.
	- Simplified method signatures for liking and disliking comments.
- **Tests**
	- Added tests for the new like/dislike functionality to ensure reliable performance and correct behavior.
	- Updated existing tests to reflect changes in method signatures and parameters.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/core/src/main/java/greencity/config/SecurityConfig.java b/core/src/main/java/greencity/config/SecurityConfig.java
index 5cf629f69d..6fb0cfef9d 100644
--- a/core/src/main/java/greencity/config/SecurityConfig.java
+++ b/core/src/main/java/greencity/config/SecurityConfig.java
@@ -350,6 +350,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
                     ECO_NEWS + "/{ecoNewsId}/favorites",
                     "/habit/assign/{habitId}/invite",
                     "place/v2/save",
+                    EVENTS + COMMENTS + "/dislikeV2" + COMMENT_ID,
+                    EVENTS + COMMENTS + "/likeV2" + COMMENT_ID,
                     LOGS)
                 .hasAnyRole(USER, ADMIN, MODERATOR, UBS_EMPLOYEE)
                 .requestMatchers(HttpMethod.PUT,
diff --git a/core/src/main/java/greencity/controller/EcoNewsCommentController.java b/core/src/main/java/greencity/controller/EcoNewsCommentController.java
index e88a93f8a8..b6f38dbf77 100644
--- a/core/src/main/java/greencity/controller/EcoNewsCommentController.java
+++ b/core/src/main/java/greencity/controller/EcoNewsCommentController.java
@@ -219,8 +219,9 @@ public void update(
     @PostMapping("/comments/like")
     public void like(
         @RequestParam("commentId") Long commentId,
-        @Parameter(hidden = true) @CurrentUser UserVO userVO) {
-        commentService.like(commentId, userVO, null);
+        @Parameter(hidden = true) @CurrentUser UserVO userVO,
+        @ValidLanguage Locale locale) {
+        commentService.like(commentId, userVO, locale);
     }
 
     /**
@@ -242,7 +243,7 @@ public void like(
     public void dislike(
         @RequestParam("commentId") Long commentId,
         @Parameter(hidden = true) @CurrentUser UserVO userVO) {
-        commentService.dislike(commentId, userVO, null);
+        commentService.dislike(commentId, userVO);
     }
 
     /**
diff --git a/core/src/main/java/greencity/controller/EventCommentController.java b/core/src/main/java/greencity/controller/EventCommentController.java
index 8e35b16826..40ae494331 100644
--- a/core/src/main/java/greencity/controller/EventCommentController.java
+++ b/core/src/main/java/greencity/controller/EventCommentController.java
@@ -282,6 +282,52 @@ public void like(
     public void dislike(
         @PathVariable Long commentId,
         @Parameter(hidden = true) @CurrentUser UserVO user) {
-        commentService.dislike(commentId, user, null);
+        commentService.dislike(commentId, user);
+    }
+
+    /**
+     * Method to like/unlike certain {@link CommentDto} specified by id.
+     *
+     * @param commentId of {@link CommentDto} to like/dislike
+     * @return {@link CommentDto} with updated amount of likes
+     */
+    @Operation(summary = "Like/unlike comment.")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = HttpStatuses.OK),
+        @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,
+            content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
+        @ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED,
+            content = @Content(examples = @ExampleObject(HttpStatuses.UNAUTHORIZED))),
+        @ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
+            content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
+    })
+    @PostMapping("/comments/likeV2/{commentId}")
+    public ResponseEntity<CommentDto> likeV2(
+        @PathVariable Long commentId,
+        @Parameter(hidden = true) @CurrentUser UserVO user,
+        @Parameter @ValidLanguage Locale locale) {
+        return ResponseEntity.ok(commentService.likeV2(commentId, user, locale));
+    }
+
+    /**
+     * Method to dislike certain {@link CommentDto} specified by id.
+     *
+     * @param commentId of {@link CommentDto} to like/dislike
+     */
+    @Operation(summary = "Dislike/remove dislike on comment.")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = HttpStatuses.OK),
+        @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,
+            content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
+        @ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED,
+            content = @Content(examples = @ExampleObject(HttpStatuses.UNAUTHORIZED))),
+        @ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
+            content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
+    })
+    @PostMapping("/comments/dislikeV2/{commentId}")
+    public ResponseEntity<CommentDto> dislikeV2(
+        @PathVariable Long commentId,
+        @Parameter(hidden = true) @CurrentUser UserVO user) {
+        return ResponseEntity.ok(commentService.dislikeV2(commentId, user));
     }
 }
diff --git a/core/src/main/java/greencity/controller/HabitCommentController.java b/core/src/main/java/greencity/controller/HabitCommentController.java
index da0a679ef6..4a5cd7ce30 100644
--- a/core/src/main/java/greencity/controller/HabitCommentController.java
+++ b/core/src/main/java/greencity/controller/HabitCommentController.java
@@ -224,9 +224,8 @@ public void like(@RequestParam("commentId") Long commentId,
     })
     @PostMapping("/comments/dislike")
     public void dislike(@RequestParam("commentId") Long commentId,
-        @Parameter(hidden = true) @CurrentUser UserVO user,
-        @Parameter(hidden = true) @ValidLanguage Locale locale) {
-        commentService.dislike(commentId, user, locale);
+        @Parameter(hidden = true) @CurrentUser UserVO user) {
+        commentService.dislike(commentId, user);
     }
 
     /**
diff --git a/core/src/test/java/greencity/controller/EcoNewsCommentControllerTest.java b/core/src/test/java/greencity/controller/EcoNewsCommentControllerTest.java
index 28e7ce75b6..d689408955 100644
--- a/core/src/test/java/greencity/controller/EcoNewsCommentControllerTest.java
+++ b/core/src/test/java/greencity/controller/EcoNewsCommentControllerTest.java
@@ -296,7 +296,7 @@ void likeTest() {
             .principal(principal))
             .andExpect(status().isOk());
 
-        verify(commentService).like(numericCommentId, userVO, null);
+        verify(commentService).like(numericCommentId, userVO, Locale.ENGLISH);
     }
 
     @Test
@@ -323,7 +323,7 @@ void likeNotFoundTest() {
 
         doThrow(new NotFoundException(errorMessage))
             .when(commentService)
-            .like(commentId, userVO, null);
+            .like(commentId, userVO, Locale.ENGLISH);
 
         Assertions.assertThatThrownBy(
             () -> mockMvc.perform(post(ECONEWS_LINK + "/comments/like")
@@ -384,7 +384,7 @@ void dislikeTest() {
             .principal(principal))
             .andExpect(status().isOk());
 
-        verify(commentService).dislike(numericCommentId, userVO, null);
+        verify(commentService).dislike(numericCommentId, userVO);
     }
 
     @Test
@@ -411,7 +411,7 @@ void dislikeNotFoundTest() {
 
         doThrow(new NotFoundException(errorMessage))
             .when(commentService)
-            .dislike(commentId, userVO, null);
+            .dislike(commentId, userVO);
 
         Assertions.assertThatThrownBy(
             () -> mockMvc.perform(post(ECONEWS_LINK + "/comments/dislike")
diff --git a/core/src/test/java/greencity/controller/EventCommentControllerTest.java b/core/src/test/java/greencity/controller/EventCommentControllerTest.java
index 19266dcf6c..9a57265a60 100644
--- a/core/src/test/java/greencity/controller/EventCommentControllerTest.java
+++ b/core/src/test/java/greencity/controller/EventCommentControllerTest.java
@@ -13,6 +13,7 @@
 import greencity.service.UserService;
 import lombok.SneakyThrows;
 import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -42,6 +43,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.doThrow;
@@ -70,6 +72,12 @@ class EventCommentControllerTest {
     @Mock
     private CommentService commentService;
     private final Principal principal = getPrincipal();
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    @BeforeAll
+    static void setUp() {
+        objectMapper.findAndRegisterModules();
+    }
 
     @BeforeEach
     void setup() {
@@ -114,9 +122,8 @@ void save() {
             .content(content))
             .andExpect(status().isCreated());
 
-        ObjectMapper mapper = new ObjectMapper();
         AddCommentDtoRequest addCommentDtoRequest =
-            mapper.readValue(content, AddCommentDtoRequest.class);
+            objectMapper.readValue(content, AddCommentDtoRequest.class);
 
         verify(userService).findByEmail("test@gmail.com");
         verify(commentService).save(eq(ArticleType.EVENT),
@@ -234,7 +241,6 @@ void findAllReplies() {
         Pageable pageable = PageRequest.of(pageNumber, pageSize);
         PageableDto<CommentDto> commentReplies = getPageableCommentDtos();
 
-        ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
         String expectedJson = objectMapper.writeValueAsString(commentReplies);
 
         when(commentService.getAllActiveReplies(pageable, parentCommentId, userVO))
@@ -390,7 +396,7 @@ void dislikeTest() {
             .principal(principal))
             .andExpect(status().isOk());
 
-        verify(commentService).dislike(commentId, userVO, null);
+        verify(commentService).dislike(commentId, userVO);
     }
 
     @Test
@@ -415,7 +421,7 @@ void dislikeNotFoundTest() {
 
         doThrow(new NotFoundException(errorMessage))
             .when(commentService)
-            .dislike(commentId, userVO, null);
+            .dislike(commentId, userVO);
 
         Assertions.assertThatThrownBy(
             () -> mockMvc.perform(post(EVENT_COMMENTS_CONTROLLER_LINK + "/dislike/" + commentId)
@@ -423,4 +429,38 @@ void dislikeNotFoundTest() {
                 .andExpect(status().isNotFound()))
             .hasCause(new NotFoundException(errorMessage));
     }
+
+    @Test
+    @SneakyThrows
+    void getLikeV2ResponseTest() {
+        long commentId = 1L;
+        CommentDto commentDto = getPageableCommentDtos().getPage().getFirst();
+        UserVO user = getUserVO();
+        String expectedJson = objectMapper.writeValueAsString(commentDto);
+        when(userService.findByEmail(anyString())).thenReturn(user);
+        when(commentService.likeV2(commentId, user, Locale.ENGLISH)).thenReturn(commentDto);
+        mockMvc.perform(post(EVENT_COMMENTS_CONTROLLER_LINK + "/likeV2/" + commentId)
+            .principal(principal)
+            .accept(MediaType.APPLICATION_JSON))
+            .andExpect(content().json(expectedJson))
+            .andExpect(status().isOk());
+        verify(commentService, times(1)).likeV2(commentId, user, Locale.ENGLISH);
+    }
+
+    @Test
+    @SneakyThrows
+    void getDislikeV2ResponseTest() {
+        long commentId = 1L;
+        CommentDto commentDto = getPageableCommentDtos().getPage().getFirst();
+        UserVO user = getUserVO();
+        String expectedJson = objectMapper.writeValueAsString(commentDto);
+        when(userService.findByEmail(anyString())).thenReturn(user);
+        when(commentService.dislikeV2(commentId, user)).thenReturn(commentDto);
+        mockMvc.perform(post(EVENT_COMMENTS_CONTROLLER_LINK + "/dislikeV2/" + commentId)
+            .principal(principal)
+            .accept(MediaType.APPLICATION_JSON))
+            .andExpect(content().json(expectedJson))
+            .andExpect(status().isOk());
+        verify(commentService, times(1)).dislikeV2(commentId, user);
+    }
 }
diff --git a/core/src/test/java/greencity/controller/HabitCommentControllerTest.java b/core/src/test/java/greencity/controller/HabitCommentControllerTest.java
index 6f15563834..c3620e257a 100644
--- a/core/src/test/java/greencity/controller/HabitCommentControllerTest.java
+++ b/core/src/test/java/greencity/controller/HabitCommentControllerTest.java
@@ -383,7 +383,7 @@ void dislikeTest() {
             .principal(principal))
             .andExpect(status().isOk());
 
-        verify(commentService).dislike(numericCommentId, userVO, Locale.ENGLISH);
+        verify(commentService).dislike(numericCommentId, userVO);
     }
 
     @Test
@@ -410,7 +410,7 @@ void dislikeNotFoundTest() {
 
         doThrow(new NotFoundException(errorMessage))
             .when(commentService)
-            .dislike(commentId, userVO, Locale.ENGLISH);
+            .dislike(commentId, userVO);
 
         Assertions.assertThatThrownBy(
             () -> mockMvc.perform(post(HABIT_LINK + "/comments/dislike")
diff --git a/service-api/src/main/java/greencity/service/CommentService.java b/service-api/src/main/java/greencity/service/CommentService.java
index 56b1f1e819..a0a9f82b71 100644
--- a/service-api/src/main/java/greencity/service/CommentService.java
+++ b/service-api/src/main/java/greencity/service/CommentService.java
@@ -110,7 +110,7 @@ AddCommentDtoResponse save(ArticleType articleType, Long articleId, AddCommentDt
      * @param commentId id of {@link CommentVO} to dislike.
      * @param userVO    current {@link UserVO} that wants to dislike.
      */
-    void dislike(Long commentId, UserVO userVO, Locale locale);
+    void dislike(Long commentId, UserVO userVO);
 
     /**
      * Method to change the existing {@link CommentVO}.
@@ -137,4 +137,22 @@ AddCommentDtoResponse save(ArticleType articleType, Long articleId, AddCommentDt
      * @author Anton Bondar
      */
     void searchUsers(UserSearchDto searchUsers);
+
+    /**
+     * Method to like or unlike {@link CommentVO} specified by id.
+     *
+     * @param commentId id of {@link CommentVO} to like/unlike.
+     * @param userVO    current {@link UserVO} that wants to like/unlike.
+     * @return {@link CommentDto} comment with updated data.
+     */
+    CommentDto likeV2(Long commentId, UserVO userVO, Locale locale);
+
+    /**
+     * Method to dislike or remove dislike {@link CommentVO} specified by id.
+     *
+     * @param commentId id of {@link CommentVO} to dislike.
+     * @param userVO    current {@link UserVO} that wants to dislike.
+     * @return {@link CommentDto} comment with updated data.
+     */
+    CommentDto dislikeV2(Long commentId, UserVO userVO);
 }
diff --git a/service/src/main/java/greencity/mapping/CommentDtoMapper.java b/service/src/main/java/greencity/mapping/CommentDtoMapper.java
index 58bffccc6b..0bb11c8c87 100644
--- a/service/src/main/java/greencity/mapping/CommentDtoMapper.java
+++ b/service/src/main/java/greencity/mapping/CommentDtoMapper.java
@@ -1,5 +1,6 @@
 package greencity.mapping;
 
+import greencity.dto.comment.CommentAuthorDto;
 import greencity.dto.comment.CommentDto;
 import greencity.entity.Comment;
 import greencity.entity.CommentImages;
@@ -23,7 +24,6 @@ public class CommentDtoMapper extends AbstractConverter<Comment, CommentDto> {
     protected CommentDto convert(Comment comment) {
         CommentDto commentDto = new CommentDto();
         commentDto.setId(comment.getId());
-        commentDto.setText(comment.getText());
         commentDto.setCreatedDate(comment.getCreatedDate());
         commentDto.setModifiedDate(comment.getModifiedDate());
         if (comment.getParentComment() != null) {
@@ -34,6 +34,15 @@ protected CommentDto convert(Comment comment) {
         if (comment.getAdditionalImages() != null) {
             commentDto.setAdditionalImages(comment.getAdditionalImages().stream().map(CommentImages::getLink).toList());
         }
+        commentDto.setCurrentUserLiked(comment.isCurrentUserLiked());
+        commentDto.setCurrentUserDisliked(comment.isCurrentUserDisliked());
+        commentDto.setLikes(comment.getUsersLiked().size());
+        commentDto.setDislikes(comment.getUsersDisliked().size());
+        commentDto.setAuthor(
+            CommentAuthorDto.builder()
+                .id(comment.getUser().getId())
+                .name(comment.getUser().getName())
+                .profilePicturePath(comment.getUser().getProfilePicturePath()).build());
         return commentDto;
     }
 }
diff --git a/service/src/main/java/greencity/service/CommentServiceImpl.java b/service/src/main/java/greencity/service/CommentServiceImpl.java
index bf50d13821..488e8577fe 100644
--- a/service/src/main/java/greencity/service/CommentServiceImpl.java
+++ b/service/src/main/java/greencity/service/CommentServiceImpl.java
@@ -523,58 +523,15 @@ private CommentDto convertToCommentDto(Comment comment, UserVO user) {
      */
     @Override
     public void like(Long commentId, UserVO userVO, Locale locale) {
-        Comment comment = commentRepo.findByIdAndStatusNot(commentId, CommentStatus.DELETED)
-            .orElseThrow(() -> new NotFoundException(ErrorMessage.COMMENT_NOT_FOUND_BY_ID + commentId));
-
-        boolean isAuthor = comment.getUser().getId().equals(userVO.getId());
-
-        if (isAuthor) {
-            throw new BadRequestException(ErrorMessage.USER_HAS_NO_PERMISSION);
-        }
-
-        if (removeLikeIfExists(comment, userVO)) {
-            return;
-        }
-
-        removeDislikeIfExists(comment, userVO);
-
-        User mappedUser = modelMapper.map(userVO, User.class);
-        if (mappedUser.equals(comment.getUser())) {
-            return;
-        }
-
-        comment.getUsersLiked().add(mappedUser);
-        achievementCalculation.calculateAchievement(userVO,
-            AchievementCategoryType.LIKE_COMMENT_OR_REPLY, AchievementAction.ASSIGN);
-        ratingCalculation.ratingCalculation(ratingPointsRepo.findByNameOrThrow("LIKE_COMMENT_OR_REPLY"), userVO);
-        createCommentLikeNotification(comment.getArticleType(), comment.getArticleId(), comment, userVO, locale);
-
-        commentRepo.save(comment);
+        likeHelper(commentId, userVO, locale);
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
-    public void dislike(Long commentId, UserVO userVO, Locale locale) {
-        Comment comment = commentRepo.findByIdAndStatusNot(commentId, CommentStatus.DELETED)
-            .orElseThrow(() -> new NotFoundException(ErrorMessage.COMMENT_NOT_FOUND_BY_ID + commentId));
-
-        boolean isAuthor = comment.getUser().getId().equals(userVO.getId());
-
-        if (isAuthor) {
-            throw new BadRequestException(ErrorMessage.USER_HAS_NO_PERMISSION);
-        }
-
-        removeLikeIfExists(comment, userVO);
-
-        if (removeDislikeIfExists(comment, userVO)) {
-            return;
-        }
-
-        comment.getUsersDisliked().add(modelMapper.map(userVO, User.class));
-
-        commentRepo.save(comment);
+    public void dislike(Long commentId, UserVO userVO) {
+        dislikeHelper(commentId, userVO);
     }
 
     /**
@@ -723,7 +680,7 @@ private boolean removeLikeIfExists(Comment comment, UserVO userVO) {
 
             userNotificationService.removeActionUserFromNotification(modelMapper.map(comment.getUser(), UserVO.class),
                 userVO, comment.getId(), getNotificationType(comment.getArticleType(), CommentActionType.COMMENT_LIKE));
-
+            comment.setCurrentUserLiked(false);
             return true;
         }
         return false;
@@ -746,8 +703,88 @@ private boolean removeDislikeIfExists(Comment comment, UserVO userVO) {
 
             userNotificationService.removeActionUserFromNotification(modelMapper.map(comment.getUser(), UserVO.class),
                 userVO, comment.getId(), getNotificationType(comment.getArticleType(), CommentActionType.COMMENT_LIKE));
+            comment.setCurrentUserDisliked(false);
             return true;
         }
         return false;
     }
+
+    /**
+     * This helper method provides liking logic, similar both to like and likeV2.
+     *
+     * @param commentId - id of a comment to like
+     * @param userVO    - current user
+     * @param locale    - language of content (if needed)
+     */
+    private CommentDto likeHelper(Long commentId, UserVO userVO, Locale locale) {
+        Comment comment = commentRepo.findByIdAndStatusNot(commentId, CommentStatus.DELETED)
+            .orElseThrow(() -> new NotFoundException(ErrorMessage.COMMENT_NOT_FOUND_BY_ID + commentId));
+
+        boolean isAuthor = comment.getUser().getId().equals(userVO.getId());
+        if (isAuthor) {
+            throw new BadRequestException(ErrorMessage.USER_HAS_NO_PERMISSION);
+        }
+
+        if (removeLikeIfExists(comment, userVO)) {
+            return modelMapper.map(comment, CommentDto.class);
+        }
+        removeDislikeIfExists(comment, userVO);
+
+        User mappedUser = modelMapper.map(userVO, User.class);
+        if (mappedUser.equals(comment.getUser())) {
+            return modelMapper.map(comment, CommentDto.class);
+        }
+
+        comment.getUsersLiked().add(mappedUser);
+        comment.setCurrentUserLiked(true);
+        achievementCalculation.calculateAchievement(userVO,
+            AchievementCategoryType.LIKE_COMMENT_OR_REPLY, AchievementAction.ASSIGN);
+        ratingCalculation.ratingCalculation(ratingPointsRepo.findByNameOrThrow("LIKE_COMMENT_OR_REPLY"), userVO);
+        createCommentLikeNotification(comment.getArticleType(), comment.getArticleId(), comment, userVO, locale);
+
+        return modelMapper.map(commentRepo.save(comment), CommentDto.class);
+    }
+
+    /**
+     * This helper method provides disliking logic, similar both to dislike and
+     * dislikeV2.
+     *
+     * @param commentId - id of a comment to like
+     * @param userVO    - current user
+     */
+    private CommentDto dislikeHelper(Long commentId, UserVO userVO) {
+        Comment comment = commentRepo.findByIdAndStatusNot(commentId, CommentStatus.DELETED)
+            .orElseThrow(() -> new NotFoundException(ErrorMessage.COMMENT_NOT_FOUND_BY_ID + commentId));
+        boolean isAuthor = comment.getUser().getId().equals(userVO.getId());
+
+        if (isAuthor) {
+            throw new BadRequestException(ErrorMessage.USER_HAS_NO_PERMISSION);
+        }
+
+        removeLikeIfExists(comment, userVO);
+
+        if (removeDislikeIfExists(comment, userVO)) {
+            return modelMapper.map(comment, CommentDto.class);
+        }
+
+        comment.getUsersDisliked().add(modelMapper.map(userVO, User.class));
+        comment.setCurrentUserDisliked(true);
+        return modelMapper.map(commentRepo.save(comment), CommentDto.class);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CommentDto likeV2(Long commentId, UserVO userVO, Locale locale) {
+        return likeHelper(commentId, userVO, locale);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CommentDto dislikeV2(Long commentId, UserVO userVO) {
+        return dislikeHelper(commentId, userVO);
+    }
 }
diff --git a/service/src/test/java/greencity/service/CommentServiceImplTest.java b/service/src/test/java/greencity/service/CommentServiceImplTest.java
index 0f0817be0a..35438d17df 100644
--- a/service/src/test/java/greencity/service/CommentServiceImplTest.java
+++ b/service/src/test/java/greencity/service/CommentServiceImplTest.java
@@ -1243,7 +1243,7 @@ void dislikeTest() {
         when(commentRepo.findByIdAndStatusNot(1L, CommentStatus.DELETED)).thenReturn(Optional.of(comment));
         when(userRepo.findById(user.getId())).thenReturn(Optional.of(user));
 
-        commentService.dislike(1L, userVO, null);
+        commentService.dislike(1L, userVO);
 
         verify(commentRepo).save(comment);
         assertEquals(1L, comment.getUsersDisliked().size());
@@ -1258,7 +1258,7 @@ void testDislikeOwn() {
 
         when(commentRepo.findByIdAndStatusNot(1L, CommentStatus.DELETED)).thenReturn(Optional.of(comment));
 
-        assertThrows(BadRequestException.class, () -> commentService.dislike(1L, userVO, Locale.ENGLISH));
+        assertThrows(BadRequestException.class, () -> commentService.dislike(1L, userVO));
     }
 
     @Test
@@ -1273,7 +1273,7 @@ void givenEventLikedByUser_whenDislikedByUser_shouldRemoveLikeAndAddDislike() {
         when(commentRepo.findByIdAndStatusNot(1L, CommentStatus.DELETED)).thenReturn(Optional.of(comment));
         when(userRepo.findById(user.getId())).thenReturn(Optional.of(user));
 
-        commentService.dislike(1L, userVO, null);
+        commentService.dislike(1L, userVO);
 
         assertEquals(0, comment.getUsersLiked().size());
         assertEquals(1, comment.getUsersDisliked().size());
@@ -1443,4 +1443,52 @@ void getArticleEcoNewsTitleNotFoundTest() {
 
         verify(econewsRepo).findById(articleId);
     }
+
+    @Test
+    void likeV2Test() {
+        Long commentId = 1L;
+        UserVO userVO = getUserVONotCommentOwner();
+        User user = getUserNotCommentOwner();
+        Comment comment = getComment();
+        RatingPoints ratingPoints = RatingPoints.builder().id(1L).name("LIKE_COMMENT_OR_REPLY").points(1).build();
+        Long articleId = 10L;
+        Habit habit = getHabit();
+        habit.setUserId(user.getId());
+        HabitTranslation habitTranslation = getHabitTranslation();
+
+        when(userRepo.findById(user.getId())).thenReturn(Optional.of(user));
+        when(habitRepo.findById(articleId)).thenReturn(Optional.of(habit));
+        when(habitTranslationRepo.findByHabitAndLanguageCode(habit, Locale.of("en").getLanguage()))
+            .thenReturn(Optional.ofNullable(habitTranslation));
+        when(ratingPointsRepo.findByNameOrThrow("LIKE_COMMENT_OR_REPLY")).thenReturn(ratingPoints);
+        when(commentRepo.findByIdAndStatusNot(commentId, CommentStatus.DELETED)).thenReturn(Optional.of(comment));
+        when(modelMapper.map(userVO, User.class)).thenReturn(user);
+        doNothing().when(userNotificationService).createNotification(
+            any(UserVO.class), any(UserVO.class), any(NotificationType.class),
+            anyLong(), anyString(), anyLong(), anyString());
+
+        commentService.likeV2(commentId, userVO, Locale.ENGLISH);
+
+        assertTrue(comment.getUsersLiked().contains(user));
+
+        verify(commentRepo).findByIdAndStatusNot(commentId, CommentStatus.DELETED);
+        verify(modelMapper).map(userVO, User.class);
+    }
+
+    @Test
+    void dislikeV2Test() {
+        UserVO userVO = getUserVO();
+        User user = getUser();
+        Comment comment = getComment();
+        comment.getUser().setId(2L);
+        comment.setUsersDisliked(new HashSet<>());
+
+        when(commentRepo.findByIdAndStatusNot(1L, CommentStatus.DELETED)).thenReturn(Optional.of(comment));
+        when(userRepo.findById(user.getId())).thenReturn(Optional.of(user));
+
+        commentService.dislikeV2(1L, userVO);
+
+        verify(commentRepo).save(comment);
+        assertEquals(1L, comment.getUsersDisliked().size());
+    }
 }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>service/src/main/java/greencity/mapping/CommentDtoMapper.java (1)</summary><blockquote>

`37-46`: **Good enhancement of DTO properties with interaction data!**

The addition of like/dislike status and counts, along with author information, provides a more complete representation of the comment. This will be valuable for frontend rendering and user interaction feedback.

However, there's a potential null safety issue with the author mapping. Consider adding a null check for `comment.getUser()` to prevent NullPointerExceptions if a comment somehow exists without a user.

```diff
  commentDto.setLikes(comment.getUsersLiked().size());
  commentDto.setDislikes(comment.getUsersDisliked().size());
- commentDto.setAuthor(
-     CommentAuthorDto.builder()
-         .id(comment.getUser().getId())
-         .name(comment.getUser().getName())
-         .profilePicturePath(comment.getUser().getProfilePicturePath()).build());
+ if (comment.getUser() != null) {
+     commentDto.setAuthor(
+         CommentAuthorDto.builder()
+             .id(comment.getUser().getId())
+             .name(comment.getUser().getName())
+             .profilePicturePath(comment.getUser().getProfilePicturePath()).build());
+ }
```

</blockquote></details>
<details>
<summary>service-api/src/main/java/greencity/service/CommentService.java (1)</summary><blockquote>

`141-158`: **Good addition of V2 methods that return the updated comment data**

The new `likeV2` and `dislikeV2` methods enhance the API by returning the updated comment DTO, which aligns with the PR objective of providing more complete information to the frontend after interaction.

However, the Javadoc comments could be more explicit about the toggle behavior. It currently mentions "like or unlike" and "dislike or remove dislike", but it might be clearer to specifically state that these methods toggle the status.

Consider updating the Javadoc to make the toggle behavior more explicit:

```diff
/**
- * Method to like or unlike {@link CommentVO} specified by id.
+ * Method to toggle like status for {@link CommentVO} specified by id.
+ * If the comment is already liked by the user, the like will be removed.
+ * If the comment is not liked, a like will be added.
 *
 * @param commentId id of {@link CommentVO} to like/unlike.
 * @param userVO    current {@link UserVO} that wants to like/unlike.
 * @return {@link CommentDto} comment with updated data.
 */
CommentDto likeV2(Long commentId, UserVO userVO, Locale locale);

/**
- * Method to dislike or remove dislike {@link CommentVO} specified by id.
+ * Method to toggle dislike status for {@link CommentVO} specified by id.
+ * If the comment is already disliked by the user, the dislike will be removed.
+ * If the comment is not disliked, a dislike will be added.
 *
 * @param commentId id of {@link CommentVO} to dislike.
 * @param userVO    current {@link UserVO} that wants to dislike.
 * @return {@link CommentDto} comment with updated data.
 */
CommentDto dislikeV2(Long commentId, UserVO userVO, Locale locale);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 11a1546284a3bd3b553bf1c99143672cbf787ab6 and 9ea854d495540d41bbc02f08195fa6af8347ec8a.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `core/src/main/java/greencity/config/SecurityConfig.java` (1 hunks)
* `core/src/main/java/greencity/controller/EcoNewsCommentController.java` (1 hunks)
* `core/src/main/java/greencity/controller/EventCommentController.java` (1 hunks)
* `service-api/src/main/java/greencity/service/CommentService.java` (1 hunks)
* `service/src/main/java/greencity/mapping/CommentDtoMapper.java` (2 hunks)
* `service/src/main/java/greencity/service/CommentServiceImpl.java` (3 hunks)
* `service/src/test/java/greencity/service/CommentServiceImplTest.java` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>core/src/main/java/greencity/config/SecurityConfig.java (1)</summary>

`353-354`: **Security configuration properly updated for new V2 endpoints**

The addition of the new request matchers for likeV2 and dislikeV2 endpoints ensures that these actions are properly secured, allowing only authenticated users with appropriate roles to access them.

</details>
<details>
<summary>service/src/test/java/greencity/service/CommentServiceImplTest.java (2)</summary>

`1447-1476`: **Well-structured tests for the new likeV2 method!**

The test properly validates that the `likeV2` method adds a user to the comment's liked users list and verifies the appropriate service method calls. Good job on maintaining consistency with the existing test patterns while covering the new functionality.

---

`1478-1493`: **Good coverage of the dislikeV2 functionality.**

The test effectively verifies that the comment is saved and that the user is added to the disliked users set. The test setup and verification follow best practices and match the pattern used in other similar tests.

</details>
<details>
<summary>core/src/main/java/greencity/controller/EventCommentController.java (1)</summary>

`288-310`: **Well-documented likeV2 method with appropriate return type.**

The new endpoint is clearly documented with proper API annotations. The implementation correctly wraps the response in ResponseEntity for consistent HTTP status handling.

</details>
<details>
<summary>service/src/main/java/greencity/service/CommentServiceImpl.java (4)</summary>

`526-535`: **Good refactoring of existing methods.**

The original `like` and `dislike` methods now leverage the helper methods, promoting code reuse while maintaining backward compatibility. This approach will make the code easier to maintain in the future.

---

`712-746`: **Well-structured helper method with comprehensive logic.**

The `likeHelper` method effectively encapsulates all the like-related logic, including permission checks, handling existing likes/dislikes, and notification creation. The code is well-organized and thoroughly documented.

---

`748-774`: **Comprehensive dislike helper implementation.**

The `dislikeHelper` method properly handles the dislike functionality, including removing existing likes, checking permissions, and updating the comment state. The method follows the same pattern as `likeHelper` for consistency.

---

`779-790`: **Clean implementation of V2 methods.**

The `likeV2` and `dislikeV2` methods elegantly leverage the helper methods to provide the enhanced functionality with minimal code duplication. The implementation is simple, maintainable, and follows good software design practices.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T21:34:05Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>service/src/main/java/greencity/service/CommentServiceImpl.java (1)</summary><blockquote>

`734-736`: **Redundant user check in likeHelper method**

There's a redundant check here since you've already verified if the user is the author at lines 723-726 and thrown an exception if so. This code block will never be executed because of the earlier check.

```diff
-        if (mappedUser.equals(comment.getUser())) {
-            return modelMapper.map(comment, CommentDto.class);
-        }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9ea854d495540d41bbc02f08195fa6af8347ec8a and 80e286f6f2d067fe8dc55970360f84f84814b2ae.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `service/src/main/java/greencity/service/CommentServiceImpl.java` (3 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>service/src/main/java/greencity/service/CommentServiceImpl.java (5)</summary>

`525-535`: **Good refactoring to remove code duplication**

The refactoring of the `like` and `dislike` methods to delegate to helper methods improves code organization and maintainability. This approach allows for code reuse between the original methods and the new V2 versions.

---

`683-683`: **Appropriate enhancement to update user interaction flags**

Adding the `setCurrentUserLiked(false)` and `setCurrentUserDisliked(false)` calls ensures that the comment object properly reflects the user's interaction status when removing likes or dislikes. This is crucial for accurate frontend display of the user's interaction state.



Also applies to: 706-706

---

`711-746`: **Well-implemented helper method with comprehensive logic**

The `likeHelper` method effectively handles all scenarios:
- Checks for comment existence and throws appropriate exceptions
- Verifies user permissions (preventing self-likes)
- Handles removing existing likes/dislikes
- Updates user achievements and ratings
- Creates notifications
- Returns the updated comment data

This implementation aligns well with the PR objective of providing response bodies with correct comment information.

---

`748-774`: **Well-structured dislikeHelper method**

The `dislikeHelper` method follows the same pattern as `likeHelper`, ensuring consistent handling of user interactions. The proper removal of existing interactions before adding new ones prevents data inconsistencies.

---

`779-790`: **Clean implementation of V2 methods**

The new `likeV2` and `dislikeV2` methods adhere to the DRY principle by reusing the helper methods. This implementation provides the enhanced functionality mentioned in the PR objectives while maintaining a clean codebase.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T21:40:14Z
user: login: maks741
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T10:06:00Z
user: login: oleksandra-bulhakova
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T10:34:03Z
user: login: Cr1stal423
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T11:11:30Z

Review comments:
diff_hunk: @@ -241,8 +241,9 @@ public void like(
     @PostMapping("/comments/dislike")
     public void dislike(
         @RequestParam("commentId") Long commentId,
-        @Parameter(hidden = true) @CurrentUser UserVO userVO) {
-        commentService.dislike(commentId, userVO, null);
+        @Parameter(hidden = true) @CurrentUser UserVO userVO,
+        @Parameter(hidden = true) @ValidLanguage Locale locale) {
+        commentService.dislike(commentId, userVO, locale);
     }
path: core/src/main/java/greencity/controller/EcoNewsCommentController.java
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Inconsistency between like and dislike method signatures**

The `dislike` method has been updated to include the locale parameter, but the corresponding `like` method (lines 220-224) still uses null for the locale parameter. This inconsistency could lead to confusion or unexpected behavior.

Update the `like` method to also accept and use the locale parameter:

```diff
@PostMapping("/comments/like")
public void like(
    @RequestParam("commentId") Long commentId,
-   @Parameter(hidden = true) @CurrentUser UserVO userVO) {
-   commentService.like(commentId, userVO, null);
+   @Parameter(hidden = true) @CurrentUser UserVO userVO,
+   @Parameter(hidden = true) @ValidLanguage Locale locale) {
+   commentService.like(commentId, userVO, locale);
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
@PostMapping("/comments/like")
public void like(
    @RequestParam("commentId") Long commentId,
    @Parameter(hidden = true) @CurrentUser UserVO userVO,
    @Parameter(hidden = true) @ValidLanguage Locale locale) {
    commentService.like(commentId, userVO, locale);
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-20T21:34:05Z
author_association: NONE
diff_hunk: @@ -284,4 +284,51 @@ public void dislike(
         @Parameter(hidden = true) @CurrentUser UserVO user) {
         commentService.dislike(commentId, user, null);
     }
+
+    /**
+     * Method to like/unlike certain {@link CommentDto} specified by id.
+     *
+     * @param commentId of {@link CommentDto} to like/dislike
+     * @return {@link CommentDto} with updated amount of likes
+     */
+    @Operation(summary = "Like/unlike comment.")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = HttpStatuses.OK),
+        @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,
+            content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
+        @ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED,
+            content = @Content(examples = @ExampleObject(HttpStatuses.UNAUTHORIZED))),
+        @ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
+            content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
+    })
+    @PostMapping("/comments/likeV2/{commentId}")
+    public ResponseEntity<CommentDto> likeV2(
+        @PathVariable Long commentId,
+        @Parameter(hidden = true) @CurrentUser UserVO user,
+        @Parameter @ValidLanguage Locale locale) {
+        return ResponseEntity.ok(commentService.likeV2(commentId, user, locale));
+    }
+
+    /**
+     * Method to dislike certain {@link CommentDto} specified by id.
+     *
+     * @param commentId of {@link CommentDto} to like/dislike
+     */
+    @Operation(summary = "Dislike/remove dislike on comment.")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = HttpStatuses.OK),
+        @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,
+            content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
+        @ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED,
+            content = @Content(examples = @ExampleObject(HttpStatuses.UNAUTHORIZED))),
+        @ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
+            content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
+    })
+    @PostMapping("/comments/dislikeV2/{commentId}")
+    public CommentDto dislikeV2(
+        @PathVariable Long commentId,
+        @Parameter(hidden = true) @CurrentUser UserVO user,
+        @Parameter @ValidLanguage Locale locale) {
+        return commentService.dislikeV2(commentId, user, locale);
+    }
path: core/src/main/java/greencity/controller/EventCommentController.java
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Inconsistent return type in dislikeV2 compared to likeV2.**

While the implementation is functionally correct, the `dislikeV2` method returns `CommentDto` directly, whereas `likeV2` returns `ResponseEntity<CommentDto>`. This inconsistency may cause issues in frontend integration and API documentation.


For consistency, consider modifying the return type to match the `likeV2` method:

```diff
-    public CommentDto dislikeV2(
+    public ResponseEntity<CommentDto> dislikeV2(
         @PathVariable Long commentId,
         @Parameter(hidden = true) @CurrentUser UserVO user,
         @Parameter @ValidLanguage Locale locale) {
-        return commentService.dislikeV2(commentId, user, locale);
+        return ResponseEntity.ok(commentService.dislikeV2(commentId, user, locale));
     }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    /**
     * Method to dislike certain {@link CommentDto} specified by id.
     *
     * @param commentId of {@link CommentDto} to like/dislike
     */
    @Operation(summary = "Dislike/remove dislike on comment.")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = HttpStatuses.OK),
        @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,
            content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
        @ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED,
            content = @Content(examples = @ExampleObject(HttpStatuses.UNAUTHORIZED))),
        @ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
            content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
    })
    @PostMapping("/comments/dislikeV2/{commentId}")
    public ResponseEntity<CommentDto> dislikeV2(
        @PathVariable Long commentId,
        @Parameter(hidden = true) @CurrentUser UserVO user,
        @Parameter @ValidLanguage Locale locale) {
        return ResponseEntity.ok(commentService.dislikeV2(commentId, user, locale));
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-20T21:34:05Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-20T21:31:00Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request adds new endpoints for liking and disliking event comments using a version 2 format. The security configuration is updated with new POST request matchers, and controller methods are introduced or modified to handle the new endpoints. The service layer is refactored to utilize helper methods for processing likes and dislikes, and the DTO mapping logic is enhanced to include additional interaction details. Moreover, new tests are added to validate the updated functionality.

## Changes

| File(s)                                            | Change Summary                                                                                                                                                   |
|----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.../config/SecurityConfig.java`                   | Added two new POST request matchers for endpoints: `/dislikeV2/{commentId}` and `/likeV2/{commentId}` with defined role permissions.                                 |
| `.../controller/EcoNewsCommentController.java` <br> `.../controller/EventCommentController.java` | Updated the `like` method in EcoNewsCommentController to include a `locale` parameter and added new endpoints (`likeV2` and `dislikeV2`) in EventCommentController with API response annotations. |
| `.../service/CommentService.java` <br> `.../service/CommentServiceImpl.java`          | Introduced new methods `likeV2` and `dislikeV2` in the interface and refactored the implementation using helper methods (`likeHelper` and `dislikeHelper`) that now return a `CommentDto`.  |
| `.../mapping/CommentDtoMapper.java`                | Modified the DTO conversion logic to remove the comment text assignment and add fields for user interaction (like/dislike flags and counts) plus author information.                       |
| `.../service/CommentServiceImplTest.java`          | Added test methods `likeV2Test` and `dislikeV2Test` to verify the proper functionality of the updated like/dislike operations.                                    |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant Client
    participant Controller as EventCommentController
    participant Service as CommentServiceImpl
    participant Helper as likeHelper/dislikeHelper
    participant Mapper as CommentDtoMapper

    Client->>Controller: POST /comments/likeV2/{commentId} (with locale, user)
    Controller->>Service: likeV2(commentId, user, locale)
    Service->>Helper: likeHelper(commentId, user, locale)
    Helper-->>Service: Return CommentDto
    Service->>Controller: Return CommentDto
    Controller->>Client: Response with updated CommentDto

    Client->>Controller: POST /comments/dislikeV2/{commentId} (with locale, user)
    Controller->>Service: dislikeV2(commentId, user, locale)
    Service->>Helper: dislikeHelper(commentId, user, locale)
    Helper-->>Service: Return CommentDto
    Service->>Controller: Return CommentDto
    Controller->>Client: Response with updated CommentDto
```

## Possibly related PRs
- **ita-social-projects/GreenCity#8187**: The changes in the main PR, which add new request matchers for liking and disliking comments, are directly related to the new `likeV2` and `dislikeV2` methods introduced in the retrieved PR, as they both handle similar functionalities for comments and events respectively.
- **ita-social-projects/GreenCity#8257**: The changes in the main PR are related to the addition of new request matchers for liking and disliking comments, which aligns with the modifications in the retrieved PR that also introduces new endpoint paths for these actions in the `SecurityConfig` class.
- **ita-social-projects/GreenCity#8046**: The changes in the main PR, which add new request matchers for liking and disliking comments, are related to the retrieved PR as both involve modifications to the handling of user interactions (likes/dislikes) with comments and events, specifically through the introduction of new methods in the controllers that manage these interactions.

## Suggested labels
`back-end`, `java`

## Suggested reviewers
- KizerovDmitriy
- holotsvan
- maks741

## Poem
> In our code garden, new endpoints bloom,  
> Like and dislike, dispelling the gloom.  
> Controllers and services now sing in tune,  
> Testing the flow from morning till noon.  
> Here's to clean code and progress on display 🚀!

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7e484303801dc4e3350b2885a189712324f24d4b and 08d64403dc6d1efa4b9b578a11ed71741a185ae3.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `core/src/test/java/greencity/controller/EventCommentControllerTest.java` (6 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* core/src/test/java/greencity/controller/EventCommentControllerTest.java

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ita-social-projects/GreenCity&utm_content=8267):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-03-23T12:26:24Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ita-social-projects-green-city&pullRequest=8267) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=ita-social-projects-green-city&pullRequest=8267&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ita-social-projects-green-city&pullRequest=8267&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ita-social-projects-green-city&pullRequest=8267&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [93.3% Coverage on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects-green-city&pullRequest=8267&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects-green-city&pullRequest=8267&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ita-social-projects-green-city&pullRequest=8267)


author_association: NONE