Repository: IT-Academy-BCN/ita-challenges-backend
Pull request #826: Feature/#181 user favorites endpoint
Author: login: paumaneja
Created: 2025-04-01T16:49:29Z | Merged: 2025-04-03T09:22:04Z

Create a new GET endpoint in the User microservice, which, given a user, returns a list of ChallengeId (ChallengeDto) that the user has saved as favorites.


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a9ba4540..d0078e07b 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,8 @@
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 
-                                         
+
+* Issue #826: Added GET endpoint to retrieve the list of challenges marked as favorites by a user.                                          
 * Issue #763: Modified POST endpoint for adding new challenge (in Challenge micro)  
               Created language image attribute in LanguageDocument and LanguageDto.
               Added language image's URL to the database and updated Challenge tests.
diff --git a/itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java b/itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
index 15691d7d8..6a77c5921 100755
--- a/itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
+++ b/itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
@@ -22,6 +22,9 @@
 import org.springframework.web.bind.annotation.*;
 import reactor.core.publisher.Mono;
 
+import java.util.Set;
+import java.util.UUID;
+
 @RestController
 @Validated
 @RequestMapping(value = "/itachallenge/api/v1/user")
@@ -32,6 +35,8 @@ public class UserController {
     public static final String X_FAVORITE_DELETED = "X-Favorite-Deleted";
     public static final String X_FAVORITE_MESSAGE = "X-Favorite-Message";
     public static final String X_VALIDATION_STATUS = "X-Validation-Status";
+    public static final String X_ERROR_MESSAGE = "X-Error-Message";
+    public static final String X_GITHUB_USERNAME ="X-Github-Username";
     public static final String FALSE = "False";
 
     private final UserService userService;
@@ -87,7 +92,7 @@ public Mono<ResponseEntity<UserDocument>> getUser(@PathVariable @ValidGithubUser
                     log.info("User found: {} (Role: {})", githubUsername, user.getRole());
                     return ResponseEntity.ok()
                             .header(X_VALIDATION_STATUS, "Success")
-                            .header("X-Github-Username", githubUsername)
+                            .header(X_GITHUB_USERNAME, githubUsername)
                             .body(user);
                 })
                 .switchIfEmpty(Mono.fromCallable(() -> {
@@ -95,14 +100,14 @@ public Mono<ResponseEntity<UserDocument>> getUser(@PathVariable @ValidGithubUser
                     return ResponseEntity
                             .status(HttpStatus.NOT_FOUND)
                             .header(X_VALIDATION_STATUS, "Error")
-                            .header("X-Error-Message", "User not found")
+                            .header(X_ERROR_MESSAGE, "User not found")
                             .body(null);
                 }))
                 .onErrorResume(e -> {
                     log.error("Error retrieving user '{}': {}", githubUsername, e.getMessage());
                     return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                             .header(X_VALIDATION_STATUS, "Error")
-                            .header("X-Error-Message", "An error occurred retrieving user.")
+                            .header(X_ERROR_MESSAGE, "An error occurred retrieving user.")
                             .body(null));
                 });
     }
@@ -304,4 +309,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
 
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "Set of favorite challengeIds by user"),
+                    @ApiResponse(responseCode = "404", description = "User not found"),
+                    @ApiResponse(responseCode = "400", description = "Invalid UUID format"),
+                    @ApiResponse(responseCode = "500", description = "Unexpected error")
+            }
+    )
+
+    @GetMapping("/users/{userId}/favorites")
+    public Mono<ResponseEntity<Set<UUID>>> getUserFavorites(@PathVariable String userId) {
+        return userService.getUserFavorites(userId)
+                .map(favorites -> {
+                    log.info("Retrieved {} favorite challenges for user {}", favorites.size(), userId);
+                    return ResponseEntity.ok()
+                            .header(X_VALIDATION_STATUS, "Success")
+                            .body(favorites);
+                })
+                .onErrorResume(throwable -> {
+                    if (throwable instanceof NotFoundException) {
+                        log.warn("User not found with id: {}", userId);
+                        return Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND)
+                                .header(X_VALIDATION_STATUS, "Error")
+                                .header(X_ERROR_MESSAGE, "User not found")
+                                .body(null));
+                    }
+                    if (throwable instanceof BadUUIDException) {
+                        log.error("Invalid UUID provided: {}", userId);
+                        return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST)
+                                .header(X_VALIDATION_STATUS, "Error")
+                                .header(X_ERROR_MESSAGE, "Invalid UUID format")
+                                .body(null));
+                    }
+                    log.error("Unexpected error getting favorites from user {}: {}", userId, throwable.getMessage());
+                    return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+                            .header(X_VALIDATION_STATUS, "Error")
+                            .header(X_ERROR_MESSAGE, "Unexpected server error")
+                            .body(null));
+                });
+    }
+
 }
\ No newline at end of file
diff --git a/itachallenge-user/src/main/java/com/itachallenge/user/service/UserService.java b/itachallenge-user/src/main/java/com/itachallenge/user/service/UserService.java
index b65a30d4a..4112dd649 100644
--- a/itachallenge-user/src/main/java/com/itachallenge/user/service/UserService.java
+++ b/itachallenge-user/src/main/java/com/itachallenge/user/service/UserService.java
@@ -3,6 +3,9 @@
 import com.itachallenge.user.document.UserDocument;
 import reactor.core.publisher.Mono;
 
+import java.util.Set;
+import java.util.UUID;
+
 public interface UserService {
     Mono<UserDocument> getUser(String githubUsername);
 
@@ -10,4 +13,8 @@ public interface UserService {
 
     Mono<Boolean> deleteChallengeFromFavorites(String userId, String challengeId);
 
+    Mono<Set<UUID>> getUserFavorites(String userId);
+
+
+
 }
diff --git a/itachallenge-user/src/main/java/com/itachallenge/user/service/UserServiceImpl.java b/itachallenge-user/src/main/java/com/itachallenge/user/service/UserServiceImpl.java
index cf4e981d8..220fb2875 100644
--- a/itachallenge-user/src/main/java/com/itachallenge/user/service/UserServiceImpl.java
+++ b/itachallenge-user/src/main/java/com/itachallenge/user/service/UserServiceImpl.java
@@ -94,4 +94,14 @@ private Mono<UUID> parseAndValidateUUID(String id) {
         }
     }
 
+    @Override
+    public Mono<Set<UUID>> getUserFavorites(String userId) {
+        return parseAndValidateUUID(userId)
+                .flatMap(userUuid ->
+                        userRepository.findById(userUuid)
+                                .switchIfEmpty(Mono.error(new NotFoundException("User not found with id: " + userId)))
+                                .map(user -> Optional.ofNullable(user.getFavoriteChallenges()).orElseGet(HashSet::new))
+                );
+    }
+
 }
diff --git a/itachallenge-user/src/test/java/com/itachallenge/user/controller/UserControllerTest.java b/itachallenge-user/src/test/java/com/itachallenge/user/controller/UserControllerTest.java
index 256f9fb7d..473135431 100644
--- a/itachallenge-user/src/test/java/com/itachallenge/user/controller/UserControllerTest.java
+++ b/itachallenge-user/src/test/java/com/itachallenge/user/controller/UserControllerTest.java
@@ -7,6 +7,7 @@
 import com.itachallenge.user.service.UserService;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -15,6 +16,7 @@
 import org.springframework.test.web.reactive.server.WebTestClient;
 import reactor.core.publisher.Mono;
 
+import java.util.Set;
 import java.util.UUID;
 
 import static org.mockito.Mockito.*;
@@ -285,4 +287,81 @@ void deleteFromFavorites_WhenUnexpectedError_Returns500() {
         verify(userService, times(1)).deleteChallengeFromFavorites(userId, challengeId);
     }
 
+    @Test
+    @DisplayName("GET /users/{userId}/favorites returns favorite challenges")
+    void getUserFavorites_returnsFavorites() {
+        UUID userId = UUID.randomUUID();
+        Set<UUID> expectedFavorites = Set.of(UUID.randomUUID(), UUID.randomUUID());
+
+        when(userService.getUserFavorites(userId.toString())).thenReturn(Mono.just(expectedFavorites));
+
+        webTestClient.get()
+                .uri("/itachallenge/api/v1/user/users/{userId}/favorites", userId)
+                .exchange()
+                .expectStatus().isOk()
+                .expectHeader().valueEquals("X-Validation-Status", "Success")
+                .expectBodyList(UUID.class)
+                .hasSize(expectedFavorites.size())
+                .contains(expectedFavorites.toArray(new UUID[0]));
+
+        verify(userService, times(1)).getUserFavorites(userId.toString());
+    }
+
+    @Test
+    @DisplayName("GET /users/{userId}/favorites returns 404 if user not found")
+    void getUserFavorites_returns404IfUserNotFound() {
+        UUID userId = UUID.randomUUID();
+
+        when(userService.getUserFavorites(userId.toString()))
+                .thenReturn(Mono.error(new NotFoundException("User not found")));
+
+        webTestClient.get()
+                .uri("/itachallenge/api/v1/user/users/{userId}/favorites", userId)
+                .exchange()
+                .expectStatus().isNotFound()
+                .expectHeader().valueEquals("X-Validation-Status", "Error")
+                .expectHeader().valueEquals("X-Error-Message", "User not found")
+                .expectBody().isEmpty();
+
+        verify(userService, times(1)).getUserFavorites(userId.toString());
+    }
+
+    @Test
+    @DisplayName("GET /users/{userId}/favorites returns 400 if UUID is invalid")
+    void getUserFavorites_returns400IfInvalidUUID() {
+        String invalidUserId = "invalid-uuid";
+
+        when(userService.getUserFavorites(invalidUserId))
+                .thenReturn(Mono.error(new BadUUIDException("Invalid UUID format")));
+
+        webTestClient.get()
+                .uri("/itachallenge/api/v1/user/users/{userId}/favorites", invalidUserId)
+                .exchange()
+                .expectStatus().isEqualTo(HttpStatus.BAD_REQUEST)
+                .expectHeader().valueEquals("X-Validation-Status", "Error")
+                .expectHeader().valueEquals("X-Error-Message", "Invalid UUID format")
+                .expectBody().isEmpty();
+
+        verify(userService, times(1)).getUserFavorites(invalidUserId);
+    }
+
+    @Test
+    @DisplayName("GET /users/{userId}/favorites returns 500 if there is an internal error")
+    void getUserFavorites_returns500IfUnexpectedError() {
+        UUID userId = UUID.randomUUID();
+
+        when(userService.getUserFavorites(userId.toString()))
+                .thenReturn(Mono.error(new RuntimeException("Unexpected error")));
+
+        webTestClient.get()
+                .uri("/itachallenge/api/v1/user/users/{userId}/favorites", userId)
+                .exchange()
+                .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
+                .expectHeader().valueEquals("X-Validation-Status", "Error")
+                .expectHeader().valueEquals("X-Error-Message", "Unexpected server error")
+                .expectBody().isEmpty();
+
+        verify(userService, times(1)).getUserFavorites(userId.toString());
+    }
+
 }
diff --git a/itachallenge-user/src/test/java/com/itachallenge/user/service/UserServiceImplTest.java b/itachallenge-user/src/test/java/com/itachallenge/user/service/UserServiceImplTest.java
index 748b35220..57e775ca6 100644
--- a/itachallenge-user/src/test/java/com/itachallenge/user/service/UserServiceImplTest.java
+++ b/itachallenge-user/src/test/java/com/itachallenge/user/service/UserServiceImplTest.java
@@ -7,6 +7,7 @@
 import com.itachallenge.user.repository.UserRepository;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -363,4 +364,69 @@ void deleteChallengeFromFavorites_ShouldThrowBadRequestException_WhenChallengeUu
         verify(userRepository, times(0)).save(any());
     }
 
+    @Test
+    @DisplayName("getUserFavorites returns favorite challenges when the user exists and has challenges")
+    void getUserFavorites_WhenUserExistsWithFavorites_ReturnsSet() {
+        UUID userId = UUID.randomUUID();
+        UUID challengeId1 = UUID.randomUUID();
+        UUID challengeId2 = UUID.randomUUID();
+
+        Set<UUID> favorites = Set.of(challengeId1, challengeId2);
+        UserDocument user = new UserDocument();
+        user.setUuid(userId);
+        user.setFavoriteChallenges(favorites);
+
+        when(userRepository.findById(userId)).thenReturn(Mono.just(user));
+
+        userService.getUserFavorites(userId.toString())
+                .as(StepVerifier::create)
+                .expectNextMatches(result -> result.size() == 2 && result.contains(challengeId1))
+                .verifyComplete();
+    }
+
+    @Test
+    @DisplayName("getUserFavorites returns an empty set when the user has no challenges marked.")
+    void getUserFavorites_WhenUserHasNoFavorites_ReturnsEmptySet() {
+        UUID userId = UUID.randomUUID();
+
+        UserDocument user = new UserDocument();
+        user.setUuid(userId);
+        user.setFavoriteChallenges(null); // explícitament null
+
+        when(userRepository.findById(userId)).thenReturn(Mono.just(user));
+
+        userService.getUserFavorites(userId.toString())
+                .as(StepVerifier::create)
+                .expectNextMatches(Set::isEmpty)
+                .verifyComplete();
+    }
+
+    @Test
+    @DisplayName("getUserFavorites returns NotFoundException error when the user does not exist")
+    void getUserFavorites_WhenUserNotFound_ReturnsError() {
+        UUID userId = UUID.randomUUID();
+
+        when(userRepository.findById(userId)).thenReturn(Mono.empty());
+
+        userService.getUserFavorites(userId.toString())
+                .as(StepVerifier::create)
+                .expectErrorMatches(error ->
+                        error instanceof NotFoundException &&
+                                error.getMessage().equals("User not found with id: " + userId))
+                .verify();
+    }
+
+    @Test
+    @DisplayName("getUserFavorites throws BadUUIDException when the UUID format is invalid")
+    void getUserFavorites_WhenInvalidUUID_ReturnsBadUUIDException() {
+        String invalidUUID = "invalid-uuid";
+
+        userService.getUserFavorites(invalidUUID)
+                .as(StepVerifier::create)
+                .expectErrorMatches(error ->
+                        error instanceof BadUUIDException &&
+                                error.getMessage().equals("Invalid ID format"))
+                .verify();
+    }
+
 }


Reviews:
user: login: Almami679
body: 
Clear and short PR, not like mine ahha
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T17:40:16Z
user: login: paumaneja
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T09:17:39Z
user: login: paumaneja
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T09:20:26Z
user: login: paumaneja
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T09:23:57Z
user: login: Almami679
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T11:32:22Z
user: login: Almami679
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T11:33:46Z
user: login: ivilarop
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T11:56:28Z
user: login: paumaneja
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T07:17:22Z
user: login: ivilarop
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-03T09:19:36Z

Review comments:
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
+                    @ApiResponse(responseCode = "404", description = "User not found"),
+                    @ApiResponse(responseCode = "400", description = "Invalid UUID format"),
+                    @ApiResponse(responseCode = "500", description = "Unexpected error")
+            }
+    )
+
+    @GetMapping("/users/{userId}/favorites")
+    public Mono<ResponseEntity<Set<UUID>>> getUserFavorites(@PathVariable String userId) {
+        return userService.getUserFavorites(userId)
+                .map(favorites -> {
+                    log.info("Retrieved {} favorite challenges for user {}", favorites.size(), userId);
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: Almami679
body: It's fine to use logs in the development environment, but when doing PR I would delete them.
created_at: 2025-04-01T17:14:50Z
author_association: COLLABORATOR
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: Almami679
body: 
I would make it clearer that the return is Set<UUID>
created_at: 2025-04-01T17:17:59Z
author_association: COLLABORATOR
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
+                    @ApiResponse(responseCode = "404", description = "User not found"),
+                    @ApiResponse(responseCode = "400", description = "Invalid UUID format"),
+                    @ApiResponse(responseCode = "500", description = "Unexpected error")
+            }
+    )
+
+    @GetMapping("/users/{userId}/favorites")
+    public Mono<ResponseEntity<Set<UUID>>> getUserFavorites(@PathVariable String userId) {
+        return userService.getUserFavorites(userId)
+                .map(favorites -> {
+                    log.info("Retrieved {} favorite challenges for user {}", favorites.size(), userId);
+                    return ResponseEntity.ok()
+                            .header(X_VALIDATION_STATUS, "Success")
+                            .body(favorites);
+                })
+                .onErrorResume(throwable -> {
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: Almami679
body: Shouldn't these cases be handled from the exception handler? Maybe you should use the buildErrorResponse() method.
created_at: 2025-04-01T17:37:40Z
author_association: COLLABORATOR
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
+                    @ApiResponse(responseCode = "404", description = "User not found"),
+                    @ApiResponse(responseCode = "400", description = "Invalid UUID format"),
+                    @ApiResponse(responseCode = "500", description = "Unexpected error")
+            }
+    )
+
+    @GetMapping("/users/{userId}/favorites")
+    public Mono<ResponseEntity<Set<UUID>>> getUserFavorites(@PathVariable String userId) {
+        return userService.getUserFavorites(userId)
+                .map(favorites -> {
+                    log.info("Retrieved {} favorite challenges for user {}", favorites.size(), userId);
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: paumaneja
body: You're right, but I left the logs out of consistency with the latest changes applied to everything related to Favorites that the previous colleague had implemented. They could be deleted but all the methods that had already been implemented and also those that @Alexandra is implementing related to Bookmarks should also be changed.
created_at: 2025-04-02T09:17:39Z
author_association: COLLABORATOR
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: paumaneja
body: updated
created_at: 2025-04-02T09:20:26Z
author_association: COLLABORATOR
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
+                    @ApiResponse(responseCode = "404", description = "User not found"),
+                    @ApiResponse(responseCode = "400", description = "Invalid UUID format"),
+                    @ApiResponse(responseCode = "500", description = "Unexpected error")
+            }
+    )
+
+    @GetMapping("/users/{userId}/favorites")
+    public Mono<ResponseEntity<Set<UUID>>> getUserFavorites(@PathVariable String userId) {
+        return userService.getUserFavorites(userId)
+                .map(favorites -> {
+                    log.info("Retrieved {} favorite challenges for user {}", favorites.size(), userId);
+                    return ResponseEntity.ok()
+                            .header(X_VALIDATION_STATUS, "Success")
+                            .body(favorites);
+                })
+                .onErrorResume(throwable -> {
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: paumaneja
body: The same as the change you mention in the logs. I implemented it like this for consistency with what was already implemented. If Ivan thinks it's better as you mention, it should be applied to the rest of the Favorites and Bookmarks methods.
created_at: 2025-04-02T09:23:57Z
author_association: COLLABORATOR
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
+                    @ApiResponse(responseCode = "404", description = "User not found"),
+                    @ApiResponse(responseCode = "400", description = "Invalid UUID format"),
+                    @ApiResponse(responseCode = "500", description = "Unexpected error")
+            }
+    )
+
+    @GetMapping("/users/{userId}/favorites")
+    public Mono<ResponseEntity<Set<UUID>>> getUserFavorites(@PathVariable String userId) {
+        return userService.getUserFavorites(userId)
+                .map(favorites -> {
+                    log.info("Retrieved {} favorite challenges for user {}", favorites.size(), userId);
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: Almami679
body: perfect, I didn't have any context for everything 
created_at: 2025-04-02T11:32:22Z
author_association: COLLABORATOR
diff_hunk: @@ -275,4 +280,55 @@ public Mono<ResponseEntity<Boolean>> deleteFromFavorites(@PathVariable String us
                 });
     }
 
+    @Operation(
+            summary = "Gets challenges marked as favorites by a user",
+            description = "Returns a set of challenge IDs that the specified user has marked as favorites",
+            parameters = {
+                    @Parameter(
+                            name = "userId",
+                            description = "UUID of the user",
+                            required = true,
+                            in = ParameterIn.PATH
+                    )
+            },
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "List of favorite challengeIds by user"),
+                    @ApiResponse(responseCode = "404", description = "User not found"),
+                    @ApiResponse(responseCode = "400", description = "Invalid UUID format"),
+                    @ApiResponse(responseCode = "500", description = "Unexpected error")
+            }
+    )
+
+    @GetMapping("/users/{userId}/favorites")
+    public Mono<ResponseEntity<Set<UUID>>> getUserFavorites(@PathVariable String userId) {
+        return userService.getUserFavorites(userId)
+                .map(favorites -> {
+                    log.info("Retrieved {} favorite challenges for user {}", favorites.size(), userId);
+                    return ResponseEntity.ok()
+                            .header(X_VALIDATION_STATUS, "Success")
+                            .body(favorites);
+                })
+                .onErrorResume(throwable -> {
path: itachallenge-user/src/main/java/com/itachallenge/user/controller/UserController.java
user: login: Almami679
body: 
Ok, Ivan's turn.
created_at: 2025-04-02T11:33:46Z
author_association: COLLABORATOR
diff_hunk: @@ -94,4 +94,14 @@ private Mono<UUID> parseAndValidateUUID(String id) {
         }
     }
 
+    @Override
+    public Mono<Set<UUID>> getUserFavorites(String userId) {
+        return parseAndValidateUUID(userId)
+                .flatMap(userUuid ->
+                        userRepository.findById(userUuid)
+                                .switchIfEmpty(Mono.error(new NotFoundException("User not found.")))
path: itachallenge-user/src/main/java/com/itachallenge/user/service/UserServiceImpl.java
user: login: ivilarop
body: for more info:new NotFoundException("User not found with id: " + userId))
created_at: 2025-04-02T11:54:29Z
author_association: COLLABORATOR
diff_hunk: @@ -94,4 +94,14 @@ private Mono<UUID> parseAndValidateUUID(String id) {
         }
     }
 
+    @Override
+    public Mono<Set<UUID>> getUserFavorites(String userId) {
+        return parseAndValidateUUID(userId)
+                .flatMap(userUuid ->
+                        userRepository.findById(userUuid)
+                                .switchIfEmpty(Mono.error(new NotFoundException("User not found.")))
path: itachallenge-user/src/main/java/com/itachallenge/user/service/UserServiceImpl.java
user: login: paumaneja
body: updated
created_at: 2025-04-03T07:17:22Z
author_association: COLLABORATOR

PR comments:
user: login: ivilarop
created_at: 2025-04-02T11:57:54Z
body: add the issue to changelog.md
author_association: COLLABORATOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-03T08:24:45Z
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=IT-Academy-BCN_ita-challenges-backend&pullRequest=826) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=IT-Academy-BCN_ita-challenges-backend&pullRequest=826&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=IT-Academy-BCN_ita-challenges-backend&pullRequest=826&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=IT-Academy-BCN_ita-challenges-backend&pullRequest=826&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=IT-Academy-BCN_ita-challenges-backend&pullRequest=826&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=IT-Academy-BCN_ita-challenges-backend&pullRequest=826&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=IT-Academy-BCN_ita-challenges-backend&pullRequest=826)


author_association: NONE
user: login: ivilarop
created_at: 2025-04-03T09:19:21Z
body: > add the issue to changelog.md

Done!
author_association: COLLABORATOR