-
Notifications
You must be signed in to change notification settings - Fork 0
feat: GraphQL query for user media library (findUserMediaLibrary) #88
Changes from all commits
727b01c
4cc070c
d49719a
0472eb5
5b311a0
8e5623c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.espacogeek.geek.controllers; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.springframework.graphql.data.method.annotation.Argument; | ||
| import org.springframework.graphql.data.method.annotation.QueryMapping; | ||
| import org.springframework.security.access.prepost.PreAuthorize; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.stereotype.Controller; | ||
|
|
||
| import com.espacogeek.geek.exception.GenericException; | ||
| import com.espacogeek.geek.models.CategoryType; | ||
| import com.espacogeek.geek.models.UserMediaListModel; | ||
| import com.espacogeek.geek.services.UserMediaListService; | ||
| import com.espacogeek.geek.services.UserService; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Controller | ||
| @RequiredArgsConstructor | ||
| public class UserMediaListController { | ||
|
|
||
| private final UserMediaListService userMediaListService; | ||
| private final UserService userService; | ||
|
|
||
| /** | ||
| * Returns the authenticated user's media library, optionally filtered by | ||
| * status, statusId, category, categoryName, genre, genreId, mediaId, media name, | ||
| * or alternative title. | ||
| * | ||
| * @param status optional user tracking status string filter (e.g. "watching") | ||
| * @param statusId optional media production status ID filter (MediaStatusModel.id) | ||
| * @param categoryId optional media category ID filter | ||
| * @param categoryName optional media category name filter (e.g. "ANIME") | ||
| * @param genreId optional genre ID filter | ||
| * @param genreName optional genre name filter | ||
| * @param mediaId optional media ID filter | ||
| * @param mediaName optional media name filter (partial match) | ||
| * @param altTitle optional alternative title filter (partial match) | ||
| * @param authentication the currently authenticated user (required) | ||
| * @return list of matching library entries belonging to the user | ||
| */ | ||
| @QueryMapping(name = "findUserMediaLibrary") | ||
| @PreAuthorize("hasRole('user')") | ||
| public List<UserMediaListModel> findUserMediaLibrary( | ||
| @Argument(name = "status") String status, | ||
| @Argument(name = "statusId") Integer statusId, | ||
| @Argument(name = "categoryId") Integer categoryId, | ||
| @Argument(name = "categoryName") String categoryName, | ||
| @Argument(name = "genreId") Integer genreId, | ||
| @Argument(name = "genreName") String genreName, | ||
| @Argument(name = "mediaId") Integer mediaId, | ||
| @Argument(name = "mediaName") String mediaName, | ||
| @Argument(name = "altTitle") String altTitle, | ||
|
Comment on lines
+46
to
+54
|
||
| Authentication authentication) { | ||
| var user = userService.findUserByEmail(authentication.getName()) | ||
| .orElseThrow(() -> new GenericException("User not found")); | ||
| status = status == null ? null : status.trim(); | ||
| genreName = genreName == null ? null : genreName.trim(); | ||
| mediaName = mediaName == null ? null : mediaName.trim(); | ||
| altTitle = altTitle == null ? null : altTitle.trim(); | ||
| String categoryNameTrimmed = categoryName == null ? null : categoryName.trim(); | ||
| CategoryType categoryType = null; | ||
| if (categoryNameTrimmed != null) { | ||
| try { | ||
| categoryType = CategoryType.valueOf(categoryNameTrimmed.toUpperCase()); | ||
| } catch (IllegalArgumentException e) { | ||
| throw new GenericException("Invalid categoryName: " + categoryNameTrimmed); | ||
| } | ||
| } | ||
| return userMediaListService.findByUserIdWithFilters( | ||
| user.getId(), status, statusId, categoryId, categoryType, genreId, genreName, mediaId, mediaName, altTitle); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.espacogeek.geek.repositories; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import com.espacogeek.geek.models.MediaStatusModel; | ||
|
|
||
| @Repository | ||
| public interface MediaStatusRepository extends JpaRepository<MediaStatusModel, Integer> { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,55 @@ | ||
| package com.espacogeek.geek.repositories; | ||
|
|
||
| import java.util.List; | ||
| import java.util.UUID; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
| import com.espacogeek.geek.models.CategoryType; | ||
| import com.espacogeek.geek.models.UserMediaListModel; | ||
|
|
||
| public interface UserMediaListRepository extends JpaRepository<UserMediaListModel, UUID> { | ||
|
|
||
| /** | ||
| * Fetches library entries for a given user with optional filters on: | ||
| * status string, status ID (media's production status), media category ID, | ||
| * category name, genre ID, genre name, media ID, media name, and alternative title. | ||
| * | ||
| * @param userId the ID of the authenticated user (required) | ||
| * @param status optional user tracking status string filter (case-insensitive exact match) | ||
| * @param statusId optional media production status ID filter (MediaStatusModel.id, Integer) | ||
| * @param categoryId optional media category ID filter | ||
| * @param categoryName optional media category name filter (enum exact match) | ||
| * @param genreId optional genre ID filter | ||
| * @param genreName optional genre name filter (case-insensitive exact match) | ||
| * @param mediaId optional media ID filter | ||
| * @param mediaName optional media name filter (case-insensitive partial match) | ||
| * @param altTitle optional alternative title filter (case-insensitive partial match) | ||
| * @return a distinct list of matching library entries | ||
| */ | ||
| @Query("SELECT DISTINCT u FROM UserMediaListModel u " + | ||
| "JOIN u.media m " + | ||
| "WHERE u.user.id = :userId " + | ||
| "AND (:status IS NULL OR LOWER(u.status) = LOWER(:status)) " + | ||
| "AND (:statusId IS NULL OR (m.mediaStatus IS NOT NULL AND m.mediaStatus.id = :statusId)) " + | ||
| "AND (:categoryId IS NULL OR m.mediaCategory.id = :categoryId) " + | ||
| "AND (:categoryName IS NULL OR m.mediaCategory.name = :categoryName) " + | ||
| "AND (:genreId IS NULL OR EXISTS (SELECT 1 FROM GenreModel g WHERE g MEMBER OF m.genre AND g.id = :genreId)) " + | ||
|
Comment on lines
+37
to
+39
|
||
| "AND (:genreName IS NULL OR EXISTS (SELECT 1 FROM GenreModel g WHERE g MEMBER OF m.genre AND LOWER(g.name) = LOWER(:genreName))) " + | ||
| "AND (:mediaId IS NULL OR m.id = :mediaId) " + | ||
| "AND (:mediaName IS NULL OR LOWER(m.name) LIKE LOWER(CONCAT('%', :mediaName, '%'))) " + | ||
| "AND (:altTitle IS NULL OR EXISTS (SELECT 1 FROM AlternativeTitleModel a WHERE a.media = m AND LOWER(a.name) LIKE LOWER(CONCAT('%', :altTitle, '%'))))") | ||
| List<UserMediaListModel> findByUserIdWithFilters( | ||
| @Param("userId") Integer userId, | ||
| @Param("status") String status, | ||
| @Param("statusId") Integer statusId, | ||
| @Param("categoryId") Integer categoryId, | ||
|
Comment on lines
+35
to
+48
|
||
| @Param("categoryName") CategoryType categoryName, | ||
| @Param("genreId") Integer genreId, | ||
| @Param("genreName") String genreName, | ||
| @Param("mediaId") Integer mediaId, | ||
| @Param("mediaName") String mediaName, | ||
| @Param("altTitle") String altTitle); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.espacogeek.geek.services; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import com.espacogeek.geek.models.MediaStatusModel; | ||
|
|
||
| /** | ||
| * Interface for the MediaStatusService, which provides methods for managing MediaStatusModel objects. | ||
| */ | ||
| public interface MediaStatusService { | ||
| /** | ||
| * Retrieves all MediaStatusModel objects. | ||
| * | ||
| * @return A list of all MediaStatusModel objects. | ||
| */ | ||
| List<MediaStatusModel> findAll(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.espacogeek.geek.services; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import com.espacogeek.geek.models.CategoryType; | ||
| import com.espacogeek.geek.models.UserMediaListModel; | ||
|
|
||
| /** | ||
| * Interface for the UserMediaListService, which provides methods for managing | ||
| * the user's personal media library. | ||
| */ | ||
| public interface UserMediaListService { | ||
|
|
||
| /** | ||
| * Retrieves all library entries for the given user, with optional filters. | ||
| * | ||
| * @param userId the ID of the authenticated user | ||
| * @param status optional user tracking status string filter (e.g. "watching", "completed") | ||
| * @param statusId optional media production status ID filter (MediaStatusModel.id) | ||
| * @param categoryId optional media category ID filter | ||
| * @param categoryName optional media category name filter (e.g. {@link CategoryType#ANIME}) | ||
| * @param genreId optional genre ID filter | ||
| * @param genreName optional genre name filter | ||
| * @param mediaId optional media ID filter | ||
| * @param mediaName optional media name filter (partial match) | ||
| * @param altTitle optional alternative title filter (partial match) | ||
| * @return a list of matching {@link UserMediaListModel} entries | ||
| */ | ||
| List<UserMediaListModel> findByUserIdWithFilters( | ||
| Integer userId, | ||
| String status, | ||
| Integer statusId, | ||
|
vitorhugo-java marked this conversation as resolved.
|
||
| Integer categoryId, | ||
| CategoryType categoryName, | ||
| Integer genreId, | ||
| String genreName, | ||
| Integer mediaId, | ||
| String mediaName, | ||
| String altTitle); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.espacogeek.geek.services.impl; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import com.espacogeek.geek.models.MediaStatusModel; | ||
| import com.espacogeek.geek.repositories.MediaStatusRepository; | ||
| import com.espacogeek.geek.services.MediaStatusService; | ||
|
|
||
| /** | ||
| * An Implementation class of MediaStatusService @see MediaStatusService | ||
| */ | ||
| @Service | ||
| public class MediaStatusServiceImpl implements MediaStatusService { | ||
|
|
||
| @Autowired | ||
| private MediaStatusRepository mediaStatusRepository; | ||
|
|
||
| /** | ||
| * @see MediaStatusService#findAll() | ||
| */ | ||
| @Override | ||
| public List<MediaStatusModel> findAll() { | ||
| return mediaStatusRepository.findAll(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.espacogeek.geek.services.impl; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import com.espacogeek.geek.models.CategoryType; | ||
| import com.espacogeek.geek.models.UserMediaListModel; | ||
| import com.espacogeek.geek.repositories.UserMediaListRepository; | ||
| import com.espacogeek.geek.services.UserMediaListService; | ||
|
|
||
| /** | ||
| * An implementation class of UserMediaListService @see UserMediaListService | ||
| */ | ||
| @Service | ||
| public class UserMediaListServiceImpl implements UserMediaListService { | ||
|
|
||
| @Autowired | ||
| private UserMediaListRepository userMediaListRepository; | ||
|
|
||
| /** | ||
| * @see UserMediaListService#findByUserIdWithFilters(Integer, String, Integer, Integer, CategoryType, Integer, String, Integer, String, String) | ||
| */ | ||
| @Override | ||
| public List<UserMediaListModel> findByUserIdWithFilters( | ||
| Integer userId, | ||
|
Comment on lines
+22
to
+27
|
||
| String status, | ||
| Integer statusId, | ||
| Integer categoryId, | ||
| CategoryType categoryName, | ||
| Integer genreId, | ||
| String genreName, | ||
| Integer mediaId, | ||
| String mediaName, | ||
| String altTitle) { | ||
| return userMediaListRepository.findByUserIdWithFilters( | ||
| userId, status, statusId, categoryId, categoryName, genreId, genreName, mediaId, mediaName, altTitle); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| -- Drop the foreign key before altering column types | ||
| ALTER TABLE `medias` DROP FOREIGN KEY `fk_medias_media_status`; | ||
|
|
||
| -- Change media_status.id from BIGINT to INT | ||
| ALTER TABLE `media_status` MODIFY COLUMN `id` int NOT NULL AUTO_INCREMENT; | ||
|
|
||
| -- Change medias.status_id from BIGINT to INT | ||
| ALTER TABLE `medias` MODIFY COLUMN `status_id` int DEFAULT NULL; | ||
|
|
||
| -- Re-add the foreign key | ||
| ALTER TABLE `medias` | ||
| ADD CONSTRAINT `fk_medias_media_status` FOREIGN KEY (`status_id`) REFERENCES `media_status` (`id`); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| type MediaStatus { | ||
| id: ID | ||
| name: String | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| """ | ||
| An entry in the authenticated user's media library. | ||
| """ | ||
| type UserMediaList { | ||
| "Unique identifier for this library entry" | ||
| id: ID | ||
| "The media item" | ||
| media: Media | ||
| "User-defined status (e.g. watching, completed, dropped)" | ||
| status: String | ||
| "User score for this media" | ||
| score: Float | ||
| "Current progress (e.g. episodes watched)" | ||
| progress: Int | ||
| "Date the user started the media" | ||
| startDate: Date | ||
| "Date the user finished the media" | ||
| finishDate: Date | ||
| "Total time spent in minutes" | ||
| timeSpent: Int | ||
| "User note or review" | ||
| note: String | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.