-
Notifications
You must be signed in to change notification settings - Fork 0
feat: GraphQL query for user media library (findUserMediaLibrary) #88
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
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.