Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request to join events 6222 #6807

Merged
merged 21 commits into from
Jan 15, 2025
Merged

Request to join events 6222 #6807

merged 21 commits into from
Jan 15, 2025

Conversation

0lia
Copy link
Contributor

@0lia 0lia commented Jan 10, 2024

GreenCity PR

Issue Link

#6222

Added

  • send request to join closed event
  • delete request to join closed event
  • get all users requested for event

image
image
image

Summary by CodeRabbit

  • New Features

    • Added event request functionality, allowing users to request to join events.
    • Implemented event request management for event organizers, including approval and decline actions.
    • Created new notification types for event requests and invitations, enhancing user feedback.
    • Enhanced user management with new methods for handling event requests.
    • Introduced a new Data Transfer Object (DTO) for user information representation.
    • Added new error messages for various event request scenarios.
  • Bug Fixes

    • Enhanced security configurations for event-related endpoints.
    • Improved error handling for event request scenarios.
  • Documentation

    • Added email notification messages for event request statuses.
    • Updated localization properties for event request notifications.
  • Tests

    • Comprehensive test coverage for new event request methods.
    • Added test scenarios for various event request interactions.

Copy link

@holotsvan
Copy link
Collaborator

@coderabbitai review

Copy link

coderabbitai bot commented Jan 13, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

coderabbitai bot commented Jan 13, 2025

Walkthrough

This pull request introduces comprehensive event request management functionality to the application. The changes span multiple layers of the system, including security configuration, controller endpoints, database schema, and service implementations. The new feature allows users to request joining events, with organizers having the ability to approve or decline these requests. The implementation includes robust error handling, notification mechanisms, and pagination support for requested users.

Changes

File Change Summary
core/src/main/java/greencity/config/SecurityConfig.java Added authorization rules for new event request endpoints.
core/src/main/java/greencity/controller/EventController.java Introduced methods for adding/removing event requests, retrieving requesters, and approving/declining requests.
dao/src/main/java/greencity/entity/event/Event.java, dao/src/main/java/greencity/entity/User.java Added requesters relationship and updated equality/hash code logic.
dao/src/main/java/greencity/enums/NotificationType.java Added new notification types for event requests and invites.
service-api/src/main/java/greencity/service/EventService.java, service/src/main/java/greencity/service/EventServiceImpl.java Implemented methods for managing event requests with comprehensive validation.
service/src/main/java/greencity/constant/EmailNotificationMessagesConstants.java Added constants for email notifications related to event requests.
service/src/main/java/greencity/constant/ErrorMessage.java Introduced new error message constants for event request handling.
service/src/test/java/greencity/service/EventServiceImplTest.java Added extensive tests for event request management functionality.
dao/src/main/resources/db/changelog/db.changelog-master.xml, dao/src/main/resources/db/changelog/logs/ch-add-table-events-requesters-Pitsyk.xml Updated database changelog for new events_requesters table.
service/src/main/resources/notification.properties, service/src/main/resources/notification_ua.properties Added new notification entries for event requests and invitations.
service/src/test/java/greencity/ModelUtils.java Updated functionality to include requesters in event management.

Sequence Diagram

sequenceDiagram
    participant User
    participant EventController
    participant EventService
    participant EventRepository
    participant NotificationService
    
    User->>EventController: Request to join event
    EventController->>EventService: addToRequested(eventId, userEmail)
    EventService->>EventRepository: Verify event and user
    EventService->>EventRepository: Add user to requesters
    EventService->>NotificationService: Notify event organizer
    EventController->>User: Confirm request added
Loading

Poem

🎉 Event Requests, a Dance of Delight

Clicking "Join" with hopeful might,
Organizers watch with careful eye,
Approve or decline, decisions fly!
A digital gathering takes its flight 🚀

Finishing Touches

  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • 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 for this PR. (Beta)
  • @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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (20)
service-api/src/main/java/greencity/enums/NotificationType.java (1)

32-35: LGTM! Consider adding documentation for the new notification types.

The new notification types are well-named and align perfectly with the event request management feature. To improve maintainability, consider adding Javadoc comments describing when each notification type is triggered.

Example documentation:

+    /** Notification sent when a user's request to join an event is accepted by the organizer */
     EVENT_REQUEST_ACCEPTED,
+    /** Notification sent when a user's request to join an event is declined by the organizer */
     EVENT_REQUEST_DECLINED,
+    /** Notification sent when a user is invited to join an event */
     EVENT_INVITE;
service-api/src/main/java/greencity/dto/user/UserForListDto.java (3)

17-22: Consider refining @EqualsAndHashCode implementation

The current implementation includes all fields in equals and hashCode calculations. Consider using @EqualsAndHashCode(of = {"id", "email"}) to:

  • Improve performance by using only essential fields
  • Prevent potential issues with mutable fields in hash-based collections
  • Maintain object identity based on business keys

32-37: Consider adding temporal validation for registration date

The dateOfRegistration field might benefit from additional validation:

  • Add @PastOrPresent to ensure logical temporal consistency
  • Consider making it non-nullable since every user should have a registration date
-    private LocalDateTime dateOfRegistration;
+    @NotNull
+    @PastOrPresent
+    private LocalDateTime dateOfRegistration;

44-45: Add size constraint for userCredo field

Consider adding a maximum length constraint to prevent potential abuse through extremely long credo text.

-    private String userCredo;
+    @Size(max = ServiceValidationConstants.MAX_CREDO_LENGTH)
+    private String userCredo;
service-api/src/main/java/greencity/constant/EmailNotificationMessagesConstants.java (1)

30-37: LGTM! Clear and user-friendly notification messages.

The messages are well-crafted, particularly the declined request message which maintains a positive tone by suggesting alternative engagement. Consider adding the event title to the declined message for better context.

     public static final String JOIN_REQUEST_DECLINED_MESSAGE =
-        "While we can't confirm your attendance this time, there's another way to stay connected! Join our "
-            + "organizer's friend list for future updates and opportunities.";
+        "While we can't confirm your attendance for %s this time, there's another way to stay connected! Join our "
+            + "organizer's friend list for future updates and opportunities.";
service-api/src/main/java/greencity/service/EventService.java (1)

214-251: Add parameter descriptions in Javadoc.

The methods are well-documented, but consider adding @param descriptions for better clarity. For example:

     /**
      * Method for approving request for joining the event.
      *
+     * @param eventId - ID of the event
+     * @param email - email of the user approving the request
+     * @param userId - ID of the user whose request is being approved
      * @author Olha Pitsyk.
      */
     void approveRequest(Long eventId, String email, Long userId);
core/src/test/java/greencity/controller/EventControllerTest.java (4)

707-715: Enhance test coverage for addToRequested endpoint

While the happy path is covered, consider adding test cases for:

  • Invalid event ID
  • Already requested event
  • Non-existent user
 @Test
 @SneakyThrows
 void addToRequestedTest() {
     Long eventId = 1L;
     mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/addToRequested/{eventId}", eventId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).addToRequested(eventId, principal.getName());
+    
+    // Test invalid event ID
+    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/addToRequested/{eventId}", "invalid")
+        .principal(principal))
+        .andExpect(status().isBadRequest());
+    
+    // Test already requested event
+    doThrow(new BadRequestException(ErrorMessage.USER_HAS_ALREADY_ADDED_EVENT_TO_REQUESTED))
+        .when(eventService).addToRequested(eventId, principal.getName());
+    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/addToRequested/{eventId}", eventId)
+        .principal(principal))
+        .andExpect(status().isBadRequest());
 }

717-725: Add error scenarios for removeFromRequested endpoint

Similar to the previous test, consider adding test cases for:

  • Event not found
  • Event not in requested list
 @Test
 @SneakyThrows
 void removeFromRequestedTest() {
     Long eventId = 1L;
     mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/removeFromRequested/{eventId}", eventId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).removeFromRequested(eventId, principal.getName());
+    
+    // Test event not found
+    doThrow(new NotFoundException(ErrorMessage.EVENT_NOT_FOUND))
+        .when(eventService).removeFromRequested(eventId, principal.getName());
+    mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/removeFromRequested/{eventId}", eventId)
+        .principal(principal))
+        .andExpect(status().isNotFound());
 }

727-736: Improve pagination testing in getRequestedUsers endpoint

The current test should validate pagination behavior:

  • Different page sizes
  • Page navigation
  • Empty result handling
 @Test
 @SneakyThrows
 void getRequestedUsersTest() {
     Long eventId = 1L;
     Pageable pageable = PageRequest.of(0, 20);
     mockMvc.perform(get(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users", eventId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).getRequestedUsers(eventId, principal.getName(), pageable);
+    
+    // Test different page sizes
+    mockMvc.perform(get(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users", eventId)
+        .param("size", "10")
+        .param("page", "1")
+        .principal(principal))
+        .andExpect(status().isOk());
+    
+    // Test empty result
+    when(eventService.getRequestedUsers(eventId, principal.getName(), pageable))
+        .thenReturn(new PageableDto<>(Collections.emptyList(), 0L, 0, 0));
+    mockMvc.perform(get(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users", eventId)
+        .principal(principal))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.content").isEmpty());
 }

749-758: Reduce duplication in request handling tests

Consider extracting common test setup and validation logic for approve/decline endpoints:

+private void testRequestHandling(String endpoint, ThrowingConsumer<MockHttpServletRequestBuilder> action) throws Exception {
+    Long eventId = 1L;
+    Long userId = 1L;
+    MockHttpServletRequestBuilder request = post(EVENTS_CONTROLLER_LINK + 
+        "/{eventId}/requested-users/{userId}/" + endpoint, eventId, userId)
+        .principal(principal);
+    
+    action.accept(request);
+}

 @Test
 @SneakyThrows
 void declineRequest() {
-    Long eventId = 1L;
-    Long userId = 1L;
-    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/decline", eventId, userId)
-        .principal(principal))
-        .andExpect(status().isOk());
-    verify(eventService).declineRequest(eventId, principal.getName(), userId);
+    testRequestHandling("decline", request -> 
+        mockMvc.perform(request)
+            .andExpect(status().isOk()));
 }
core/src/main/java/greencity/controller/EventController.java (4)

498-515: Enhance API documentation and response handling

Consider the following improvements:

  1. Add more detailed API documentation including request/response examples
  2. Return a more informative response
 @Operation(summary = "Add an event to requested")
 @ApiResponses(value = {
     @ApiResponse(responseCode = "200", description = HttpStatuses.OK),
     @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
     @ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED),
-    @ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
+    @ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
+        content = @Content(examples = @ExampleObject(value = "{\"message\": \"Event not found\"}"))),
+    @ApiResponse(responseCode = "409", description = "Event already requested",
+        content = @Content(examples = @ExampleObject(value = "{\"message\": \"Already requested\"}")))
 })
 @PostMapping("/addToRequested/{eventId}")
 public ResponseEntity<Object> addToRequested(@PathVariable Long eventId,
     @Parameter(hidden = true) Principal principal) {
     eventService.addToRequested(eventId, principal.getName());
-    return ResponseEntity.status(HttpStatus.OK).build();
+    return ResponseEntity.status(HttpStatus.OK)
+        .body(Map.of("message", "Successfully added to requested events"));
 }

517-534: Improve RESTful design of request endpoints

The current path structure could be more RESTful:

-@DeleteMapping("/removeFromRequested/{eventId}")
+@DeleteMapping("/{eventId}/requests")
 public ResponseEntity<Object> removeFromRequested(@PathVariable Long eventId,
     @Parameter(hidden = true) Principal principal) {
     eventService.removeFromRequested(eventId, principal.getName());
-    return ResponseEntity.status(HttpStatus.OK).build();
+    return ResponseEntity.status(HttpStatus.OK)
+        .body(Map.of("message", "Successfully removed from requested events"));
 }

Consider restructuring the request-related endpoints to follow RESTful conventions:

  • POST /events/{eventId}/requests (add request)
  • DELETE /events/{eventId}/requests (remove request)
  • GET /events/{eventId}/requests (get requests)

557-575: Add request validation and improve response content

Enhance the endpoint with:

  1. Parameter validation
  2. Meaningful response body
  3. More specific error documentation
 @PostMapping("/{eventId}/requested-users/{userId}/approve")
 public ResponseEntity<Object> approveRequest(
-    @PathVariable Long eventId,
-    @PathVariable Long userId,
+    @PathVariable @Positive Long eventId,
+    @PathVariable @Positive Long userId,
     @Parameter(hidden = true) Principal principal) {
     eventService.approveRequest(eventId, principal.getName(), userId);
-    return ResponseEntity.status(HttpStatus.OK).build();
+    return ResponseEntity.status(HttpStatus.OK)
+        .body(Map.of(
+            "message", "Request approved successfully",
+            "eventId", eventId,
+            "userId", userId
+        ));
 }

577-595: Reduce duplication in request handling endpoints

Consider extracting common logic for approve/decline endpoints:

+private ResponseEntity<Object> handleRequestResponse(
+    Long eventId,
+    Long userId,
+    String action,
+    Consumer<Long> serviceMethod) {
+    serviceMethod.accept(eventId);
+    return ResponseEntity.status(HttpStatus.OK)
+        .body(Map.of(
+            "message", String.format("Request %s successfully", action),
+            "eventId", eventId,
+            "userId", userId
+        ));
+}

 @PostMapping("/{eventId}/requested-users/{userId}/decline")
 public ResponseEntity<Object> declineRequest(
-    @PathVariable Long eventId,
-    @PathVariable Long userId,
+    @PathVariable @Positive Long eventId,
+    @PathVariable @Positive Long userId,
     @Parameter(hidden = true) Principal principal) {
-    eventService.declineRequest(eventId, principal.getName(), userId);
-    return ResponseEntity.status(HttpStatus.OK).build();
+    return handleRequestResponse(
+        eventId, userId, "declined",
+        id -> eventService.declineRequest(id, principal.getName(), userId));
 }
service/src/test/java/greencity/service/EventServiceImplTest.java (2)

1586-1718: Good test coverage for request management functionality.

The test methods thoroughly cover the core scenarios for managing event requests. The tests validate both successful operations and error cases.

However, consider adding these test cases to improve coverage:

  • Test concurrent request handling
  • Test request limits per event
  • Test request state transitions

1719-1784: Enhance request approval test coverage.

The approval tests validate basic functionality but could be more comprehensive.

Consider adding tests for:

@Test
void approveRequestWithConcurrentModification() {
    // Test concurrent approval of the same request
}

@Test
void approveRequestWithCapacityLimit() {
    // Test approval when event reaches capacity
}
dao/src/main/resources/db/changelog/logs/ch-add-table-events-requesters-Pitsyk.xml (1)

17-29: Consider adding an index for request lookups.

While the foreign key constraints are properly defined, consider adding an index to optimize request lookups.

<createIndex tableName="events_requesters" indexName="idx_events_requesters_user">
    <column name="user_id"/>
</createIndex>
dao/src/main/java/greencity/repository/UserRepo.java (1)

903-914: LGTM! Consider enhancing query readability.

The implementation is correct and efficient. The native query properly joins the required tables and supports pagination.

Consider formatting the SQL query for better readability:

-    @Query(nativeQuery = true, value = "SELECT users.* FROM users "
-        + "JOIN events_requesters ON users.id = events_requesters.user_id "
-        + "WHERE events_requesters.event_id = :eventId")
+    @Query(nativeQuery = true, value = """
+        SELECT users.*
+        FROM users
+        JOIN events_requesters ON users.id = events_requesters.user_id
+        WHERE events_requesters.event_id = :eventId
+        """)
service/src/main/java/greencity/service/EventServiceImpl.java (2)

889-906: Consider simplifying the stream operation.

The implementation is correct, but the stream operation to filter requesters can be simplified.

-        event.setRequesters(event.getRequesters()
-            .stream()
-            .filter(user -> !user.getId().equals(currentUser.getId()))
-            .collect(Collectors.toSet()));
+        event.getRequesters().remove(currentUser);

908-930: Consider adding database index for performance.

The implementation correctly handles permissions and pagination. For better performance at scale:

Consider adding a composite index on the events_requesters table:

CREATE INDEX idx_event_requesters ON events_requesters(event_id, user_id);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6c769ab and 629c31d.

📒 Files selected for processing (20)
  • core/src/main/java/greencity/config/SecurityConfig.java (3 hunks)
  • core/src/main/java/greencity/controller/EventController.java (1 hunks)
  • core/src/test/java/greencity/controller/EventControllerTest.java (1 hunks)
  • dao/src/main/java/greencity/entity/User.java (2 hunks)
  • dao/src/main/java/greencity/entity/event/Event.java (3 hunks)
  • dao/src/main/java/greencity/enums/NotificationType.java (1 hunks)
  • dao/src/main/java/greencity/repository/UserRepo.java (1 hunks)
  • dao/src/main/resources/db/changelog/db.changelog-master.xml (1 hunks)
  • dao/src/main/resources/db/changelog/logs/ch-add-table-events-requesters-Pitsyk.xml (1 hunks)
  • service-api/src/main/java/greencity/constant/EmailNotificationMessagesConstants.java (1 hunks)
  • service-api/src/main/java/greencity/constant/ErrorMessage.java (1 hunks)
  • service-api/src/main/java/greencity/dto/user/UserForListDto.java (1 hunks)
  • service-api/src/main/java/greencity/enums/NotificationType.java (1 hunks)
  • service-api/src/main/java/greencity/service/EventService.java (2 hunks)
  • service/src/main/java/greencity/service/EventServiceImpl.java (4 hunks)
  • service/src/main/java/greencity/service/NotificationServiceImpl.java (2 hunks)
  • service/src/main/resources/notification.properties (1 hunks)
  • service/src/main/resources/notification_ua.properties (1 hunks)
  • service/src/test/java/greencity/ModelUtils.java (1 hunks)
  • service/src/test/java/greencity/service/EventServiceImplTest.java (1 hunks)
🔇 Additional comments (20)
service-api/src/main/java/greencity/dto/user/UserForListDto.java (2)

1-16: Well-organized imports and package structure!

The imports are logically grouped and include all necessary dependencies for validation, enums, and Lombok functionality.


23-31: Verify username length constraints

The validation looks good, but let's verify the constant values being used for username length constraints.

✅ Verification successful

✓ Username length constraints are appropriate and consistently implemented

The validation constants (min=6, max=30 characters) are well-chosen and consistently applied across all user-related DTOs in the codebase. These constraints strike a good balance between usability and security.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for USERNAME length constants
rg "USERNAME_[A-Z_]*LENGTH\s*=\s*\d+" -A 1

Length of output: 469


Script:

#!/bin/bash
# Search for other username validations or size constraints
rg "username|userName" -i --type java -A 2 -B 2

Length of output: 35890

dao/src/main/java/greencity/enums/NotificationType.java (1)

32-35: LGTM! Well-structured notification types for event requests.

The new notification types are well-named and align perfectly with the event request management functionality.

dao/src/main/java/greencity/entity/event/Event.java (1)

36-36: LGTM! Well-implemented many-to-many relationship.

The implementation follows JPA best practices with:

  • Appropriate lazy loading
  • Proper initialization of the Set
  • Consistent naming of the join table
  • Correct exclusion from equals/hashCode

Also applies to: 75-80

dao/src/main/java/greencity/entity/User.java (2)

193-194: Well-structured annotation updates!

Good practice excluding the new requestedEvents collection from equals/hashCode and toString methods to prevent potential recursion issues.

Also applies to: 197-198


323-324: Properly configured bidirectional relationship!

The ManyToMany relationship is well-defined with appropriate lazy loading strategy. Let's verify the corresponding mapping in the Event entity.

✅ Verification successful

✅ Bidirectional relationship verified and correctly implemented!

The Event entity properly defines the owner side of the relationship with:

  • Correct join table configuration (events_requesters)
  • Matching fetch strategy (LAZY)
  • Proper initialization of the collection
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the corresponding "requesters" mapping in Event entity
ast-grep --pattern 'class Event {
  $$$
  @ManyToMany
  $$$
  private Set<User> requesters;
  $$$
}'

Length of output: 102


Script:

#!/bin/bash
# First find the Event class file
fd -e java "Event.java"

# Then examine its content if found
fd -e java "Event.java" -x cat {}

Length of output: 3616

service-api/src/main/java/greencity/constant/ErrorMessage.java (1)

178-182: Clear and comprehensive error messages!

The new error messages are well-defined and follow the existing pattern. They cover all necessary scenarios for the event request functionality:

  • Already requested events
  • Events not in requested state
  • User not requesting the event
  • Event comment not found
service/src/main/java/greencity/service/NotificationServiceImpl.java (2)

272-272: Good categorization of the new notification type!

EVENT_INVITE is appropriately added to the invites list, maintaining consistency with the notification system's design.


281-283: Well-organized notification types!

The new event request notification types are correctly categorized as system notifications, maintaining a clean separation of concerns.

core/src/main/java/greencity/config/SecurityConfig.java (3)

272-272: Appropriate role-based access control!

The GET endpoint for requested users is properly secured with the correct roles, maintaining consistent access control.


311-313: Well-structured endpoint security!

The POST endpoints for approving, declining, and adding event requests are properly secured with appropriate roles.


409-410: Consistent security configuration!

The DELETE endpoint for removing requests is properly secured, maintaining consistency with other similar endpoints in the application.

service/src/test/java/greencity/service/EventServiceImplTest.java (1)

1785-1851: Well-structured decline request tests.

The decline request tests effectively validate permissions and state transitions.

service/src/test/java/greencity/ModelUtils.java (1)

1801-1801: LGTM! Test data properly initialized.

The requesters field is correctly initialized with test data, maintaining consistency with the Event model changes.

dao/src/main/resources/db/changelog/logs/ch-add-table-events-requesters-Pitsyk.xml (1)

8-15: Well-structured table definition with appropriate constraints.

The table definition follows best practices with proper column types and NOT NULL constraints.

service/src/main/resources/notification.properties (1)

52-57: Clear and user-friendly notification messages.

The notification messages are well-written, consistent with existing messages, and provide clear communication to users.

dao/src/main/resources/db/changelog/db.changelog-master.xml (1)

263-263: LGTM! Properly structured changelog include.

The new changelog file for events requesters is correctly included and follows the established naming convention.

service/src/main/java/greencity/service/EventServiceImpl.java (2)

870-887: LGTM! Method handles edge cases properly.

The implementation includes proper validation and error handling. Good practice sending notifications to the event organizer.


932-958: LGTM! Comprehensive validation and proper notification handling.

The implementation includes all necessary checks and properly handles the state transition from requester to attender.

service/src/main/resources/notification_ua.properties (1)

32-37: LGTM! Clear and consistent notification messages.

The notification messages are well-written, properly encoded, and maintain consistency with the existing notification style.

Comment on lines +38 to +43
@NotNull
private UserStatus userStatus;

@NotNull
private Role role;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Architectural and Consistency Issues in Enum Definitions

While the @NotNull validation is appropriate, I've identified some concerning issues in the codebase:

  • Duplicate enum definitions exist in both dao and service-api modules for UserStatus and Role
  • The Role enum values are inconsistent between modules:
    • service-api: includes ROLE_UBS_EMPLOYEE
    • dao: missing ROLE_UBS_EMPLOYEE

Consider:

  1. Consolidating enum definitions into a shared module to prevent duplication
  2. Ensuring consistent enum values across all modules to prevent potential runtime issues
🔗 Analysis chain

Verify enum completeness for UserStatus and Role

The validation looks good. Let's verify that all necessary status and role values are covered in the enums.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for UserStatus and Role enum definitions
echo "=== UserStatus enum values ==="
ast-grep --pattern 'enum UserStatus {
  $$$
}'

echo "=== Role enum values ==="
ast-grep --pattern 'enum Role {
  $$$
}'

Length of output: 221


Script:

#!/bin/bash
# First find relevant enum files
echo "=== Finding enum files ==="
fd -e java "UserStatus|Role"

echo -e "\n=== Searching for UserStatus enum ==="
rg "enum\s+UserStatus" -A 10

echo -e "\n=== Searching for Role enum ==="
rg "enum\s+Role" -A 10

Length of output: 2210


Script:

#!/bin/bash
# Check which Role enum is imported in UserForListDto
echo "=== Checking Role import in UserForListDto ==="
rg "import.*Role" service-api/src/main/java/greencity/dto/user/UserForListDto.java

Length of output: 216

Comment on lines 738 to 747
@Test
@SneakyThrows
void approveRequest() {
Long eventId = 1L;
Long userId = 1L;
mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/approve", eventId, userId)
.principal(principal))
.andExpect(status().isOk());
verify(eventService).approveRequest(eventId, principal.getName(), userId);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add security validation tests for approveRequest endpoint

Add test cases to verify:

  • Only event organizer can approve requests
  • Unauthorized access handling
  • Invalid user ID handling
 @Test
 @SneakyThrows
 void approveRequest() {
     Long eventId = 1L;
     Long userId = 1L;
     mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/approve", eventId, userId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).approveRequest(eventId, principal.getName(), userId);
+    
+    // Test unauthorized access
+    doThrow(new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION))
+        .when(eventService).approveRequest(eventId, principal.getName(), userId);
+    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/approve", eventId, userId)
+        .principal(principal))
+        .andExpect(status().isForbidden());
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 960 to 985
@Override
public void declineRequest(Long eventId, String email, Long userId) {
UserVO userVO = restClient.findByEmail(email);
User currentUser = modelMapper.map(userVO, User.class);
Event event = eventRepo.findById(eventId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));

if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
}
if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
}
User userToJoin =
userRepo.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID + userId));

event
.setRequesters(event.getRequesters().stream().filter(u -> !Objects.equals(u.getId(), userId))
.collect(Collectors.toSet()));

eventRepo.save(event);

userNotificationService.createNotification(userVO, modelMapper.map(userToJoin, UserVO.class),
NotificationType.EVENT_REQUEST_DECLINED, eventId, event.getTitle());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract common validation logic.

The implementation duplicates validation logic from approveRequest method.

Consider extracting common validation into a private method:

private void validateEventRequestOperation(Event event, User currentUser, Long userId) {
    if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
        throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
    }
    if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
        throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
    }
}

@ChernenkoVitaliy ChernenkoVitaliy self-requested a review January 14, 2025 11:33
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
service/src/test/java/greencity/service/EventServiceImplTest.java (4)

1587-1630: Enhance assertions in request addition tests.

While the tests cover the main scenarios, they could be more robust with additional assertions:

  • Verify the state of the event's requesters list after addition
  • Assert the exact exception messages in error cases

Example enhancement for addToRequestedTest:

 @Test
 void addToRequestedTest() {
     Event event = ModelUtils.getEvent();
     User user = ModelUtils.getUser();
     user.setId(2L);
 
     when(eventRepo.findById(any())).thenReturn(Optional.of(event));
     when(userRepo.findByEmail(TestConst.EMAIL)).thenReturn(Optional.of(user));
     when(eventRepo.save(event)).thenReturn(event);
 
     eventService.addToRequested(1L, TestConst.EMAIL);
 
+    assertTrue(event.getRequesters().contains(user), "User should be in requesters list");
     verify(eventRepo).findById(any());
     verify(userRepo).findByEmail(TestConst.EMAIL);
     verify(eventRepo).save(event);
 }

1632-1675: Add state verification in removal tests.

The removal tests could be strengthened by:

  • Verifying the event's requesters list is empty after removal
  • Adding specific exception message assertions

Example enhancement for removeFromRequestedTest:

 @Test
 void removeFromRequestedTest() {
     Event event = ModelUtils.getEvent();
     User user = ModelUtils.getUser();
+    event.getRequesters().add(user);
 
     when(eventRepo.findById(any())).thenReturn(Optional.of(event));
     when(userRepo.findByEmail(TestConst.EMAIL)).thenReturn(Optional.of(user));
     when(eventRepo.save(event)).thenReturn(event);
 
     eventService.removeFromRequested(1L, TestConst.EMAIL);
 
+    assertFalse(event.getRequesters().contains(user), "User should not be in requesters list");
     verify(eventRepo).findById(any());
     verify(userRepo).findByEmail(TestConst.EMAIL);
     verify(eventRepo).save(event);
 }

1677-1717: Add edge cases to requested users retrieval tests.

Consider adding tests for:

  • Empty page results
  • Multiple pages of results
  • Different page sizes
  • Invalid page numbers

Example additional test:

@Test
void getRequestedUsersEmptyPageTest() {
    Event event = ModelUtils.getEvent();
    Pageable pageable = PageRequest.of(0, 20);
    Page<User> emptyPage = new PageImpl<>(Collections.emptyList(), pageable, 0);
    
    when(userRepo.findByEmail(anyString())).thenReturn(Optional.of(ModelUtils.getUser()));
    when(eventRepo.findById(any())).thenReturn(Optional.of(event));
    when(userRepo.findUsersByRequestedEvents(any(), any(Pageable.class)))
        .thenReturn(emptyPage);
    
    Page<User> result = eventService.getRequestedUsers(1L, "", pageable);
    assertEquals(0, result.getTotalElements());
    assertTrue(result.getContent().isEmpty());
}

1586-1852: Consider adding test utilities and improving test organization.

The test suite could benefit from:

  1. Creating test utility methods for common setup code
  2. Using @BeforeEach for common mock setup
  3. Grouping related tests using nested classes

Example refactoring:

@Nested
class EventRequestTests {
    @BeforeEach
    void setUp() {
        // Common mock setup for request-related tests
    }
    
    @Nested
    class AddRequest {
        // Add request tests
    }
    
    @Nested
    class RemoveRequest {
        // Remove request tests
    }
    
    // Other nested classes
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 629c31d and f4057fd.

📒 Files selected for processing (1)
  • service/src/test/java/greencity/service/EventServiceImplTest.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build

Comment on lines +1719 to +1784
@Test
void approveRequestTest() {
UserVO userVO = ModelUtils.getUserVO();
User user = ModelUtils.getUser();
User userToJoin = User.builder().build();
Event event = ModelUtils.getEvent();
event.getRequesters().add(userToJoin);

when(restClient.findByEmail(anyString())).thenReturn(userVO);
when(modelMapper.map(userVO, User.class)).thenReturn(user);
when(eventRepo.findById(any())).thenReturn(Optional.of(event));
when(userRepo.findById(any()))
.thenReturn(Optional.of(userToJoin));
when(eventRepo.save(any())).thenReturn(null);

eventService.approveRequest(event.getId(), userVO.getEmail(), userToJoin.getId());

verify(restClient).findByEmail(anyString());
verify(modelMapper).map(userVO, User.class);
verify(eventRepo).findById(any());
verify(userRepo).findById(any());
verify(eventRepo).save(any());
}

@Test
void approveRequestUserHasNoAccessTest() {
UserVO userVO = ModelUtils.getUserVO();
User user = ModelUtils.getTestUser();
Event event = ModelUtils.getEvent();

when(restClient.findByEmail(anyString())).thenReturn(userVO);
when(modelMapper.map(userVO, User.class)).thenReturn(user);
when(eventRepo.findById(any())).thenReturn(Optional.of(event));

Long eventId = event.getId();
String email = userVO.getEmail();
assertThrows(UserHasNoPermissionToAccessException.class,
() -> eventService.approveRequest(eventId, email, null));

verify(restClient).findByEmail(anyString());
verify(modelMapper).map(userVO, User.class);
verify(eventRepo).findById(any());
}

@Test
void approveRequestUserDidNotRequestTest() {
UserVO userVO = ModelUtils.getUserVO();
User user = ModelUtils.getUser();
User userToJoin = User.builder().build();
Event event = ModelUtils.getEvent();
event.getRequesters().remove(userToJoin);

when(restClient.findByEmail(anyString())).thenReturn(userVO);
when(modelMapper.map(userVO, User.class)).thenReturn(user);
when(eventRepo.findById(any())).thenReturn(Optional.of(event));

Long eventId = event.getId();
Long userToJoinId = userToJoin.getId();
String email = userVO.getEmail();
assertThrows(BadRequestException.class,
() -> eventService.approveRequest(eventId, email, userToJoinId));

verify(restClient).findByEmail(anyString());
verify(modelMapper).map(userVO, User.class);
verify(eventRepo).findById(any());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance state verification in approval tests.

The approval tests should verify:

  • User is removed from requesters list
  • User is added to attenders list
  • Notification service interactions

Example enhancement for approveRequestTest:

 @Test
 void approveRequestTest() {
     UserVO userVO = ModelUtils.getUserVO();
     User user = ModelUtils.getUser();
     User userToJoin = User.builder().build();
     Event event = ModelUtils.getEvent();
     event.getRequesters().add(userToJoin);
 
     when(restClient.findByEmail(anyString())).thenReturn(userVO);
     when(modelMapper.map(userVO, User.class)).thenReturn(user);
     when(eventRepo.findById(any())).thenReturn(Optional.of(event));
     when(userRepo.findById(any())).thenReturn(Optional.of(userToJoin));
     when(eventRepo.save(any())).thenReturn(event);
 
     eventService.approveRequest(event.getId(), userVO.getEmail(), userToJoin.getId());
 
+    assertFalse(event.getRequesters().contains(userToJoin), "User should be removed from requesters");
+    assertTrue(event.getAttenders().contains(userToJoin), "User should be added to attenders");
     verify(restClient).findByEmail(anyString());
     verify(modelMapper).map(userVO, User.class);
     verify(eventRepo).findById(any());
     verify(userRepo).findById(any());
     verify(eventRepo).save(any());
+    verify(userNotificationService).notifyUser(any());
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1786 to +1851
@Test
void declineRequestTest() {
UserVO userVO = ModelUtils.getUserVO();
User user = ModelUtils.getUser();
User userToJoin = User.builder().build();
Event event = ModelUtils.getEvent();
event.getRequesters().add(userToJoin);

when(restClient.findByEmail(anyString())).thenReturn(userVO);
when(modelMapper.map(userVO, User.class)).thenReturn(user);
when(eventRepo.findById(any())).thenReturn(Optional.of(event));
when(userRepo.findById(any()))
.thenReturn(Optional.of(userToJoin));
when(eventRepo.save(any())).thenReturn(null);

eventService.declineRequest(event.getId(), userVO.getEmail(), userToJoin.getId());

verify(restClient).findByEmail(anyString());
verify(modelMapper).map(userVO, User.class);
verify(eventRepo).findById(any());
verify(userRepo).findById(any());
verify(eventRepo).save(any());
}

@Test
void declineRequestUserHasNoAccessTest() {
UserVO userVO = ModelUtils.getUserVO();
User user = ModelUtils.getTestUser();
Event event = ModelUtils.getEvent();

when(restClient.findByEmail(anyString())).thenReturn(userVO);
when(modelMapper.map(userVO, User.class)).thenReturn(user);
when(eventRepo.findById(any())).thenReturn(Optional.of(event));

Long eventId = event.getId();
String email = userVO.getEmail();
assertThrows(UserHasNoPermissionToAccessException.class,
() -> eventService.declineRequest(eventId, email, null));

verify(restClient).findByEmail(anyString());
verify(modelMapper).map(userVO, User.class);
verify(eventRepo).findById(any());
}

@Test
void declineRequestUserDidNotRequestTest() {
UserVO userVO = ModelUtils.getUserVO();
User user = ModelUtils.getUser();
User userToJoin = User.builder().build();
Event event = ModelUtils.getEvent();
event.getRequesters().remove(userToJoin);

when(restClient.findByEmail(anyString())).thenReturn(userVO);
when(modelMapper.map(userVO, User.class)).thenReturn(user);
when(eventRepo.findById(any())).thenReturn(Optional.of(event));

Long eventId = event.getId();
Long userToJoinId = userToJoin.getId();
String email = userVO.getEmail();
assertThrows(BadRequestException.class,
() -> eventService.declineRequest(eventId, email, userToJoinId));

verify(restClient).findByEmail(anyString());
verify(modelMapper).map(userVO, User.class);
verify(eventRepo).findById(any());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Strengthen decline request test assertions.

The decline tests should verify:

  • User is removed from requesters list
  • User is not added to attenders list
  • Notification service interactions

Example enhancement for declineRequestTest:

 @Test
 void declineRequestTest() {
     UserVO userVO = ModelUtils.getUserVO();
     User user = ModelUtils.getUser();
     User userToJoin = User.builder().build();
     Event event = ModelUtils.getEvent();
     event.getRequesters().add(userToJoin);
 
     when(restClient.findByEmail(anyString())).thenReturn(userVO);
     when(modelMapper.map(userVO, User.class)).thenReturn(user);
     when(eventRepo.findById(any())).thenReturn(Optional.of(event));
     when(userRepo.findById(any())).thenReturn(Optional.of(userToJoin));
     when(eventRepo.save(any())).thenReturn(event);
 
     eventService.declineRequest(event.getId(), userVO.getEmail(), userToJoin.getId());
 
+    assertFalse(event.getRequesters().contains(userToJoin), "User should be removed from requesters");
+    assertFalse(event.getAttenders().contains(userToJoin), "User should not be added to attenders");
     verify(restClient).findByEmail(anyString());
     verify(modelMapper).map(userVO, User.class);
     verify(eventRepo).findById(any());
     verify(userRepo).findById(any());
     verify(eventRepo).save(any());
+    verify(userNotificationService).notifyUser(any());
 }

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@ChernenkoVitaliy ChernenkoVitaliy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except from the comments in the code, here is what can be improved:
According to jacoco report, there are missed unit tests for EventServiceImpl class
addToRequested() method => line 876 when thrown NotFoundException if user not found .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));
removeFromRequested() method => line 895 when thrown NotFoundException if user not found .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));
removeFromRequested() method => line 903 .filter(user -> !user.getId().equals(currentUser.getId()))
getRequestedUsers() method => line 914 when thrown NotFoundException if event not found .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));
approveRequest() method => line 938 when thrown NotFoundException if event not found .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));
approveRequest() method => line 947 when thrown NotFoundException if user not found .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID + userId));
declineRequest() method => line 965 when thrown NotFoundException if event not found .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));


When trying to delete a request that does not belong to the user received "This event is not in requested" But should be something like 403 with message "Current user has no permission for this action"

Users can request to join their own events. It not supposed to work this way. The user Should receive 400 status and an error message that he can`t request to join his own event.

@holotsvan
Copy link
Collaborator

@coderabbitai pause

Copy link

coderabbitai bot commented Jan 15, 2025

✅ Actions performed

Reviews paused.

@holotsvan
Copy link
Collaborator

@coderabbitai resume

Copy link

coderabbitai bot commented Jan 15, 2025

✅ Actions performed

Reviews resumed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
service/src/main/java/greencity/service/EventServiceImpl.java (1)

889-903: Consider notifying organizer about request withdrawal.

The implementation efficiently removes the request, but consider adding a notification to inform the event organizer when a user withdraws their request.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 342b4b5 and bb5d76e.

📒 Files selected for processing (4)
  • core/src/main/java/greencity/config/SecurityConfig.java (3 hunks)
  • core/src/main/java/greencity/controller/EventController.java (2 hunks)
  • core/src/test/java/greencity/controller/EventControllerTest.java (1 hunks)
  • service/src/main/java/greencity/service/EventServiceImpl.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/test/java/greencity/controller/EventControllerTest.java
🔇 Additional comments (7)
core/src/main/java/greencity/config/SecurityConfig.java (2)

272-272: Security configuration for event request endpoints looks good!

The new endpoints are properly secured with appropriate roles and follow consistent authorization patterns.

Also applies to: 311-313


409-410: Security configuration for delete endpoint is appropriate.

The endpoint is properly secured and follows the established pattern for delete operations.

core/src/main/java/greencity/controller/EventController.java (3)

499-519: Implementation follows REST best practices.

The endpoint is well-documented with comprehensive API responses and follows consistent error handling patterns.


521-540: Consistent implementation with addToRequested endpoint.

The endpoint maintains consistency in error handling and response patterns.


542-562: 🛠️ Refactor suggestion

Add parameter validation and improve type safety.

Consider adding validation annotations and using a more specific return type:

 @GetMapping("/{eventId}/requested-users")
-public ResponseEntity<PageableDto<UserForListDto>> getRequestedUsers(
+public ResponseEntity<PageableDto<UserForListDto>> getRequestedUsers(
-    @PathVariable Long eventId,
+    @PathVariable @Positive Long eventId,
     @Parameter(hidden = true) Principal principal,
-    @Parameter(hidden = true) Pageable pageable) {
+    @Parameter(hidden = true) @Valid Pageable pageable) {

Likely invalid or redundant comment.

service/src/main/java/greencity/service/EventServiceImpl.java (2)

870-887: Service implementation follows best practices.

The method properly handles error cases and includes appropriate notification logic.


905-927: Well-structured implementation with proper permission checks.

The method correctly handles pagination and access control.

Comment on lines +584 to +602
/**
* Method for declining join request.
*
* @author Olha Pitsyk.
*/
@Operation(summary = "Decline join request")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED),
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
})
@PostMapping("/{eventId}/requested-users/{userId}/decline")
public ResponseEntity<Object> declineRequest(@PathVariable Long eventId, @PathVariable Long userId,
@Parameter(hidden = true) Principal principal) {
eventService.declineRequest(eventId, principal.getName(), userId);
return ResponseEntity.status(HttpStatus.OK).build();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add parameter validation consistent with approveRequest.

Apply the same validation pattern as suggested for approveRequest:

 @PostMapping("/{eventId}/requested-users/{userId}/decline")
 public ResponseEntity<Object> declineRequest(
-    @PathVariable Long eventId,
-    @PathVariable Long userId,
+    @PathVariable @Positive Long eventId,
+    @PathVariable @Positive Long userId,
     @Parameter(hidden = true) Principal principal) {
📝 Committable suggestion

‼️ 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.

Suggested change
/**
* Method for declining join request.
*
* @author Olha Pitsyk.
*/
@Operation(summary = "Decline join request")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED),
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
})
@PostMapping("/{eventId}/requested-users/{userId}/decline")
public ResponseEntity<Object> declineRequest(@PathVariable Long eventId, @PathVariable Long userId,
@Parameter(hidden = true) Principal principal) {
eventService.declineRequest(eventId, principal.getName(), userId);
return ResponseEntity.status(HttpStatus.OK).build();
}
/**
* Method for declining join request.
*
* @author Olha Pitsyk.
*/
@Operation(summary = "Decline join request")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED),
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
})
@PostMapping("/{eventId}/requested-users/{userId}/decline")
public ResponseEntity<Object> declineRequest(
@PathVariable @Positive Long eventId,
@PathVariable @Positive Long userId,
@Parameter(hidden = true) Principal principal) {
eventService.declineRequest(eventId, principal.getName(), userId);
return ResponseEntity.status(HttpStatus.OK).build();
}

Comment on lines +564 to +582
/**
* Method for approving join request.
*
* @author Olha Pitsyk.
*/
@Operation(summary = "Approve join request")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED),
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
})
@PostMapping("/{eventId}/requested-users/{userId}/approve")
public ResponseEntity<Object> approveRequest(@PathVariable Long eventId, @PathVariable Long userId,
@Parameter(hidden = true) Principal principal) {
eventService.approveRequest(eventId, principal.getName(), userId);
return ResponseEntity.status(HttpStatus.OK).build();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add parameter validation for request parameters.

Consider adding validation annotations to ensure data integrity:

 @PostMapping("/{eventId}/requested-users/{userId}/approve")
 public ResponseEntity<Object> approveRequest(
-    @PathVariable Long eventId,
-    @PathVariable Long userId,
+    @PathVariable @Positive Long eventId,
+    @PathVariable @Positive Long userId,
     @Parameter(hidden = true) Principal principal) {
📝 Committable suggestion

‼️ 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.

Suggested change
/**
* Method for approving join request.
*
* @author Olha Pitsyk.
*/
@Operation(summary = "Approve join request")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED),
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
})
@PostMapping("/{eventId}/requested-users/{userId}/approve")
public ResponseEntity<Object> approveRequest(@PathVariable Long eventId, @PathVariable Long userId,
@Parameter(hidden = true) Principal principal) {
eventService.approveRequest(eventId, principal.getName(), userId);
return ResponseEntity.status(HttpStatus.OK).build();
}
/**
* Method for approving join request.
*
* @author Olha Pitsyk.
*/
@Operation(summary = "Approve join request")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED),
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND)
})
@PostMapping("/{eventId}/requested-users/{userId}/approve")
public ResponseEntity<Object> approveRequest(
@PathVariable @Positive Long eventId,
@PathVariable @Positive Long userId,
@Parameter(hidden = true) Principal principal) {
eventService.approveRequest(eventId, principal.getName(), userId);
return ResponseEntity.status(HttpStatus.OK).build();
}

Comment on lines +929 to +953
@Override
public void approveRequest(Long eventId, String email, Long userId) {
UserVO userVO = restClient.findByEmail(email);
User currentUser = modelMapper.map(userVO, User.class);

Event event = eventRepo.findById(eventId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));

if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
}
if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
}
User userToJoin = userRepo.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID + userId));

event.getRequesters().remove(userToJoin);
event.getAttenders().add(userToJoin);

eventRepo.save(event);

userNotificationService.createNotification(modelMapper.map(userToJoin, UserVO.class), userVO,
NotificationType.EVENT_REQUEST_ACCEPTED, eventId, event.getTitle());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize user lookup and extract common validation.

Consider these improvements:

  1. Replace restClient with direct repository access
  2. Extract common validation logic
-        UserVO userVO = restClient.findByEmail(email);
-        User currentUser = modelMapper.map(userVO, User.class);
+        User currentUser = userRepo.findByEmail(email)
+            .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

+        validateEventRequestOperation(event, currentUser, userId);
-        if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
-            throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
-        }
-        if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
-            throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
-        }

Add this private method:

private void validateEventRequestOperation(Event event, User currentUser, Long userId) {
    if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
        throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
    }
    if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
        throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
    }
}

Comment on lines +955 to +977
@Override
public void declineRequest(Long eventId, String email, Long userId) {
UserVO userVO = restClient.findByEmail(email);
User currentUser = modelMapper.map(userVO, User.class);
Event event = eventRepo.findById(eventId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));

if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
}
if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
}
User userToJoin =
userRepo.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID + userId));

event.getRequesters().remove(userToJoin);
eventRepo.save(event);

userNotificationService.createNotification(modelMapper.map(userToJoin, UserVO.class), userVO,
NotificationType.EVENT_REQUEST_DECLINED, eventId, event.getTitle());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Apply the same optimizations as approveRequest.

Use the same improvements suggested for approveRequest:

  1. Replace restClient with userRepo
  2. Use the extracted validation method
-        UserVO userVO = restClient.findByEmail(email);
-        User currentUser = modelMapper.map(userVO, User.class);
+        User currentUser = userRepo.findByEmail(email)
+            .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

+        validateEventRequestOperation(event, currentUser, userId);
-        if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
-            throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
-        }
-        if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
-            throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
-        }

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
core/src/main/java/greencity/controller/EventController.java (4)

500-519: Enhance input validation and REST compliance.

Consider these improvements:

  1. Add @positive validation for eventId
  2. Return 201 Created instead of 200 OK for resource creation
  3. Include more detailed API documentation with request/response examples
-@PostMapping("/{eventId}/addToRequested")
-public ResponseEntity<Object> addToRequested(@PathVariable Long eventId,
+@PostMapping("/{eventId}/addToRequested")
+public ResponseEntity<Object> addToRequested(
+    @PathVariable @Positive(message = "Event ID must be positive") Long eventId,
     @Parameter(hidden = true) Principal principal) {
     eventService.addToRequested(eventId, principal.getName());
-    return ResponseEntity.status(HttpStatus.OK).build();
+    return ResponseEntity.status(HttpStatus.CREATED).build();
 }

521-540: Add input validation for removeFromRequested endpoint.

Add @positive validation for eventId parameter to ensure valid input:

 @DeleteMapping("/{eventId}/removeFromRequested")
-public ResponseEntity<Object> removeFromRequested(@PathVariable Long eventId,
+public ResponseEntity<Object> removeFromRequested(
+    @PathVariable @Positive(message = "Event ID must be positive") Long eventId,
     @Parameter(hidden = true) Principal principal) {
     eventService.removeFromRequested(eventId, principal.getName());
     return ResponseEntity.status(HttpStatus.OK).build();
 }

564-582: Add validation and improve error handling for approveRequest endpoint.

Add validation annotations and consider more specific error responses:

 @PostMapping("/{eventId}/requested-users/{userId}/approve")
 public ResponseEntity<Object> approveRequest(
-    @PathVariable Long eventId, @PathVariable Long userId,
+    @PathVariable @Positive(message = "Event ID must be positive") Long eventId,
+    @PathVariable @Positive(message = "User ID must be positive") Long userId,
     @Parameter(hidden = true) Principal principal) {
     eventService.approveRequest(eventId, principal.getName(), userId);
     return ResponseEntity.status(HttpStatus.OK).build();
 }

584-602: Add validation for declineRequest endpoint parameters.

Add validation annotations for path variables:

 @PostMapping("/{eventId}/requested-users/{userId}/decline")
 public ResponseEntity<Object> declineRequest(
-    @PathVariable Long eventId, @PathVariable Long userId,
+    @PathVariable @Positive(message = "Event ID must be positive") Long eventId,
+    @PathVariable @Positive(message = "User ID must be positive") Long userId,
     @Parameter(hidden = true) Principal principal) {
     eventService.declineRequest(eventId, principal.getName(), userId);
     return ResponseEntity.status(HttpStatus.OK).build();
 }
service/src/main/java/greencity/service/EventServiceImpl.java (4)

870-887: Simplify collection operations and extract validation.

Consider these improvements:

  1. Use direct collection methods instead of streams
  2. Extract validation logic to a separate method
+private void validateEventRequest(Event event, User user) {
+    if (event.getRequesters().contains(user)) {
+        throw new BadRequestException(ErrorMessage.USER_HAS_ALREADY_ADDED_EVENT_TO_REQUESTED);
+    }
+}

 @Override
 public void addToRequested(Long eventId, String email) {
     Event event = eventRepo.findById(eventId)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND_BY_ID + eventId));

     User currentUser = userRepo.findByEmail(email)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

-    if (event.getRequesters().contains(currentUser)) {
-        throw new BadRequestException(ErrorMessage.USER_HAS_ALREADY_ADDED_EVENT_TO_REQUESTED);
-    }
+    validateEventRequest(event, currentUser);

     event.getRequesters().add(currentUser);
     eventRepo.save(event);

889-903: Simplify collection operations in removeFromRequested.

Use direct collection methods for better readability and performance:

 @Override
 public void removeFromRequested(Long eventId, String email) {
     Event event = eventRepo.findById(eventId)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND_BY_ID + eventId));

     User currentUser = userRepo.findByEmail(email)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

     if (!event.getRequesters().contains(currentUser)) {
         throw new BadRequestException(ErrorMessage.EVENT_IS_NOT_IN_REQUESTED);
     }

-    event.getRequesters().remove(currentUser);
+    if (!event.getRequesters().remove(currentUser)) {
+        throw new BadRequestException(ErrorMessage.EVENT_IS_NOT_IN_REQUESTED);
+    }
     eventRepo.save(event);
 }

905-927: Extract authorization check and simplify mapping.

Consider these improvements:

  1. Extract authorization check to a reusable method
  2. Use direct mapping for the user list
+private void validateEventOrganizer(Event event, User user) {
+    if (!user.equals(event.getOrganizer())) {
+        throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
+    }
+}

 @Override
 public PageableDto<UserForListDto> getRequestedUsers(Long eventId, String email, Pageable pageable) {
     User user = userRepo.findByEmail(email)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

     Event event = eventRepo.findById(eventId)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));

-    if (!user.equals(event.getOrganizer())) {
-        throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
-    }
+    validateEventOrganizer(event, user);

     Page<User> usersPage = userRepo.findUsersByRequestedEvents(eventId, pageable);
-    List<UserForListDto> userList = usersPage.stream()
-        .map(users -> modelMapper.map(users, UserForListDto.class))
-        .collect(Collectors.toList());
+    List<UserForListDto> userList = modelMapper.map(usersPage.getContent(),
+        new TypeToken<List<UserForListDto>>() {}.getType());

955-977: Optimize service calls and reuse validation logic.

Similar to approveRequest, optimize the implementation:

 @Override
 public void declineRequest(Long eventId, String email, Long userId) {
-    UserVO userVO = restClient.findByEmail(email);
-    User currentUser = modelMapper.map(userVO, User.class);
+    User currentUser = userRepo.findByEmail(email)
+        .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

     Event event = eventRepo.findById(eventId)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));

-    if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
-        throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
-    }
-    if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
-        throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
-    }
+    validateEventRequestOperation(event, currentUser, userId);

     User userToJoin = userRepo.findById(userId)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID + userId));

     event.getRequesters().remove(userToJoin);
     eventRepo.save(event);

     userNotificationService.createNotification(modelMapper.map(userToJoin, UserVO.class),
-        userVO, NotificationType.EVENT_REQUEST_DECLINED, eventId, event.getTitle());
+        modelMapper.map(currentUser, UserVO.class),
+        NotificationType.EVENT_REQUEST_DECLINED, eventId, event.getTitle());
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 342b4b5 and bb5d76e.

📒 Files selected for processing (4)
  • core/src/main/java/greencity/config/SecurityConfig.java (3 hunks)
  • core/src/main/java/greencity/controller/EventController.java (2 hunks)
  • core/src/test/java/greencity/controller/EventControllerTest.java (1 hunks)
  • service/src/main/java/greencity/service/EventServiceImpl.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/main/java/greencity/config/SecurityConfig.java
🔇 Additional comments (6)
core/src/test/java/greencity/controller/EventControllerTest.java (4)

749-758: 🛠️ Refactor suggestion

Add error handling tests for declineRequest endpoint.

Similar to approveRequest, add test cases for:

  • Non-organizer access attempts
  • Invalid user/event ID handling
  • User not in requested list
  • Error responses (400, 401, 403, 404)
 @Test
 @SneakyThrows
 void declineRequestTest() {
     Long eventId = 1L;
     Long userId = 1L;
     mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/decline", eventId, userId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).declineRequest(eventId, principal.getName(), userId);
 }
+
+@Test
+@SneakyThrows
+void declineRequestUnauthorizedTest() {
+    Long eventId = 1L;
+    Long userId = 1L;
+    doThrow(new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION))
+        .when(eventService)
+        .declineRequest(eventId, principal.getName(), userId);
+    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/decline", eventId, userId)
+        .principal(principal))
+        .andExpect(status().isForbidden());
+}

Likely invalid or redundant comment.


738-747: 🛠️ Refactor suggestion

Improve test coverage for approveRequest endpoint.

Add test cases for:

  • Non-organizer access attempts
  • Invalid user/event ID handling
  • User not in requested list
  • Error responses (400, 401, 403, 404)
 @Test
 @SneakyThrows
 void approveRequestTest() {
     Long eventId = 1L;
     Long userId = 1L;
     mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/approve", eventId, userId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).approveRequest(eventId, principal.getName(), userId);
 }
+
+@Test
+@SneakyThrows
+void approveRequestNotRequestedUserTest() {
+    Long eventId = 1L;
+    Long userId = 1L;
+    doThrow(new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId))
+        .when(eventService)
+        .approveRequest(eventId, principal.getName(), userId);
+    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users/{userId}/approve", eventId, userId)
+        .principal(principal))
+        .andExpect(status().isBadRequest());
+}

Likely invalid or redundant comment.


727-736: 🛠️ Refactor suggestion

Expand test coverage for getRequestedUsers endpoint.

Add tests to verify:

  • Pagination parameter validation
  • Authorization checks for non-organizer access
  • Invalid event ID handling
  • Response content verification
 @Test
 @SneakyThrows
 void getRequestedUsersTest() {
     Long eventId = 1L;
     Pageable pageable = PageRequest.of(0, 20);
     mockMvc.perform(get(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users", eventId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).getRequestedUsers(eventId, principal.getName(), pageable);
 }
+
+@Test
+@SneakyThrows
+void getRequestedUsersUnauthorizedTest() {
+    Long eventId = 1L;
+    doThrow(new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION))
+        .when(eventService)
+        .getRequestedUsers(eq(eventId), anyString(), any(Pageable.class));
+    mockMvc.perform(get(EVENTS_CONTROLLER_LINK + "/{eventId}/requested-users", eventId)
+        .principal(principal))
+        .andExpect(status().isForbidden());
+}

Likely invalid or redundant comment.


707-715: 🛠️ Refactor suggestion

Enhance test coverage for addToRequested endpoint.

While the happy path is covered, consider adding tests for:

  • Invalid event ID handling
  • Unauthorized access
  • Case when user has already requested to join
  • Error responses (400, 401, 403, 404)
 @Test
 @SneakyThrows
 void addToRequestedTest() {
     Long eventId = 1L;
     mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/addToRequested", eventId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).addToRequested(eventId, principal.getName());
 }
+
+@Test
+@SneakyThrows
+void addToRequestedBadRequestTest() {
+    String notValidId = "id";
+    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/addToRequested", notValidId)
+        .principal(principal))
+        .andExpect(status().isBadRequest());
+}
+
+@Test
+@SneakyThrows
+void addToRequestedAlreadyRequestedTest() {
+    Long eventId = 1L;
+    doThrow(new BadRequestException(ErrorMessage.USER_HAS_ALREADY_ADDED_EVENT_TO_REQUESTED))
+        .when(eventService)
+        .addToRequested(eventId, principal.getName());
+    mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/{eventId}/addToRequested", eventId)
+        .principal(principal))
+        .andExpect(status().isBadRequest());
+}

Likely invalid or redundant comment.

core/src/main/java/greencity/controller/EventController.java (1)

542-562: Improve type safety and validation in getRequestedUsers endpoint.

The current implementation could benefit from:

  1. Specific return type instead of Object
  2. Validation annotations for parameters
 @GetMapping("/{eventId}/requested-users")
 public ResponseEntity<PageableDto<UserForListDto>> getRequestedUsers(
-    @PathVariable Long eventId,
+    @PathVariable @Positive(message = "Event ID must be positive") Long eventId,
     @Parameter(hidden = true) Principal principal,
-    @Parameter(hidden = true) Pageable pageable) {
+    @Parameter(hidden = true) @Valid Pageable pageable) {
     return ResponseEntity.status(HttpStatus.OK)
         .body(eventService.getRequestedUsers(eventId, principal.getName(), pageable));
 }
service/src/main/java/greencity/service/EventServiceImpl.java (1)

929-953: Extract common validation logic and optimize service calls.

The implementation duplicates validation logic and could be optimized:

+private void validateEventRequestOperation(Event event, User currentUser, Long userId) {
+    if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
+        throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
+    }
+    if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
+        throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
+    }
+}

 @Override
 public void approveRequest(Long eventId, String email, Long userId) {
-    UserVO userVO = restClient.findByEmail(email);
-    User currentUser = modelMapper.map(userVO, User.class);
+    User currentUser = userRepo.findByEmail(email)
+        .orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

     Event event = eventRepo.findById(eventId)
         .orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND));

-    if (!Objects.equals(currentUser.getId(), event.getOrganizer().getId())) {
-        throw new UserHasNoPermissionToAccessException(ErrorMessage.USER_HAS_NO_PERMISSION);
-    }
-    if (event.getRequesters().stream().noneMatch(u -> Objects.equals(u.getId(), userId))) {
-        throw new BadRequestException(ErrorMessage.USER_DID_NOT_REQUEST_FOR_EVENT + userId);
-    }
+    validateEventRequestOperation(event, currentUser, userId);

Comment on lines +717 to +725
@Test
@SneakyThrows
void removeFromRequestedTest() {
Long eventId = 1L;
mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/{eventId}/removeFromRequested", eventId)
.principal(principal))
.andExpect(status().isOk());
verify(eventService).removeFromRequested(eventId, principal.getName());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling tests for removeFromRequested endpoint.

Similar to addToRequested, enhance test coverage for error scenarios:

  • Invalid event ID handling
  • Unauthorized access
  • Case when event is not in requested list
  • Error responses (400, 401, 403, 404)
 @Test
 @SneakyThrows
 void removeFromRequestedTest() {
     Long eventId = 1L;
     mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/{eventId}/removeFromRequested", eventId)
         .principal(principal))
         .andExpect(status().isOk());
     verify(eventService).removeFromRequested(eventId, principal.getName());
 }
+
+@Test
+@SneakyThrows
+void removeFromRequestedNotFoundTest() {
+    Long eventId = 1L;
+    doThrow(new NotFoundException(ErrorMessage.EVENT_NOT_FOUND))
+        .when(eventService)
+        .removeFromRequested(eventId, principal.getName());
+    mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/{eventId}/removeFromRequested", eventId)
+        .principal(principal))
+        .andExpect(status().isNotFound());
+}
📝 Committable suggestion

‼️ 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.

Suggested change
@Test
@SneakyThrows
void removeFromRequestedTest() {
Long eventId = 1L;
mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/{eventId}/removeFromRequested", eventId)
.principal(principal))
.andExpect(status().isOk());
verify(eventService).removeFromRequested(eventId, principal.getName());
}
@Test
@SneakyThrows
void removeFromRequestedTest() {
Long eventId = 1L;
mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/{eventId}/removeFromRequested", eventId)
.principal(principal))
.andExpect(status().isOk());
verify(eventService).removeFromRequested(eventId, principal.getName());
}
@Test
@SneakyThrows
void removeFromRequestedNotFoundTest() {
Long eventId = 1L;
doThrow(new NotFoundException(ErrorMessage.EVENT_NOT_FOUND))
.when(eventService)
.removeFromRequested(eventId, principal.getName());
mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/{eventId}/removeFromRequested", eventId)
.principal(principal))
.andExpect(status().isNotFound());
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants