diff --git a/src/main/java/com/espacogeek/geek/batch/MovieItemWriter.java b/src/main/java/com/espacogeek/geek/batch/MovieItemWriter.java index 68bcbe03..d42374d0 100644 --- a/src/main/java/com/espacogeek/geek/batch/MovieItemWriter.java +++ b/src/main/java/com/espacogeek/geek/batch/MovieItemWriter.java @@ -75,13 +75,13 @@ public void write(List items) { // fetch and persist alternative titles using the external API id (if present) try { - String externalId = original.getExternalReference().get(0).getReference(); + String externalId = original.getExternalReference().iterator().next().getReference(); if (externalId != null) { var alts = movieApi.getAlternativeTitles(Integer.valueOf(externalId)); if (alts != null && !alts.isEmpty()) { alts.forEach(a -> a.setMedia(persisted)); alternativeTitlesService.saveAll(alts); - persisted.setAlternativeTitles(alts); + persisted.setAlternativeTitles(new java.util.LinkedHashSet<>(alts)); } } } catch (Exception e) { diff --git a/src/main/java/com/espacogeek/geek/batch/MovieProcessor.java b/src/main/java/com/espacogeek/geek/batch/MovieProcessor.java index 66ec159e..2282d4b1 100644 --- a/src/main/java/com/espacogeek/geek/batch/MovieProcessor.java +++ b/src/main/java/com/espacogeek/geek/batch/MovieProcessor.java @@ -87,7 +87,7 @@ public MediaModel process(JSONObject json) { externalReference.setTypeReference(typeReference); externalReference.setReference(idStr); - List refs = new ArrayList<>(); + java.util.LinkedHashSet refs = new java.util.LinkedHashSet<>(); refs.add(externalReference); media.setExternalReference(refs); diff --git a/src/main/java/com/espacogeek/geek/batch/SerieItemWriter.java b/src/main/java/com/espacogeek/geek/batch/SerieItemWriter.java index 243d74bc..8c828fc6 100644 --- a/src/main/java/com/espacogeek/geek/batch/SerieItemWriter.java +++ b/src/main/java/com/espacogeek/geek/batch/SerieItemWriter.java @@ -75,7 +75,7 @@ public void write(List items) { } try { - String externalId = original.getExternalReference().get(0).getReference(); + String externalId = original.getExternalReference().iterator().next().getReference(); if (externalId != null) { List alts = tvSeriesApi.getAlternativeTitles(Integer.valueOf(externalId)); if (alts != null && !alts.isEmpty()) { @@ -83,7 +83,7 @@ public void write(List items) { alternativeTitleModel.setMedia(persisted); } alternativeTitlesService.saveAll(alts); - persisted.setAlternativeTitles(alts); + persisted.setAlternativeTitles(new java.util.LinkedHashSet<>(alts)); } } } catch (Exception e) { diff --git a/src/main/java/com/espacogeek/geek/batch/SerieProcessor.java b/src/main/java/com/espacogeek/geek/batch/SerieProcessor.java index 10fb3887..6baf27ca 100644 --- a/src/main/java/com/espacogeek/geek/batch/SerieProcessor.java +++ b/src/main/java/com/espacogeek/geek/batch/SerieProcessor.java @@ -87,7 +87,7 @@ public MediaModel process(JSONObject json) { externalReference.setTypeReference(typeReference); externalReference.setReference(idStr); - List refs = new ArrayList<>(); + java.util.LinkedHashSet refs = new java.util.LinkedHashSet<>(); refs.add(externalReference); media.setExternalReference(refs); diff --git a/src/main/java/com/espacogeek/geek/config/JwtConfig.java b/src/main/java/com/espacogeek/geek/config/JwtConfig.java index 2136db22..0fe5e335 100644 --- a/src/main/java/com/espacogeek/geek/config/JwtConfig.java +++ b/src/main/java/com/espacogeek/geek/config/JwtConfig.java @@ -79,8 +79,7 @@ private List buildRolesList(UserModel user) { if (raw != null && !raw.isBlank()) { String[] parts = raw.replaceAll("\\s", "").split(","); rolesList.addAll(Arrays.stream(parts) - .map(s -> s == null ? null : s.trim()) - .filter(s -> s != null && !s.isBlank()) + .filter(s -> !s.isBlank()) .map(s -> { if (s.startsWith("ROLE_") || s.startsWith("ID_")) return s; return "ROLE_" + s; diff --git a/src/main/java/com/espacogeek/geek/controllers/MediaController.java b/src/main/java/com/espacogeek/geek/controllers/MediaController.java index e4ce6265..7241364b 100644 --- a/src/main/java/com/espacogeek/geek/controllers/MediaController.java +++ b/src/main/java/com/espacogeek/geek/controllers/MediaController.java @@ -2,10 +2,21 @@ import lombok.RequiredArgsConstructor; import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.BatchMapping; import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.stereotype.Controller; +import com.espacogeek.geek.models.AlternativeTitleModel; +import com.espacogeek.geek.models.CompanyModel; +import com.espacogeek.geek.models.ExternalReferenceModel; +import com.espacogeek.geek.models.GenreModel; import com.espacogeek.geek.models.MediaModel; +import com.espacogeek.geek.models.PeopleModel; +import com.espacogeek.geek.models.SeasonModel; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.types.MediaPage; import com.espacogeek.geek.utils.MediaUtils; @@ -13,10 +24,18 @@ import graphql.schema.DataFetchingEnvironment; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; @Controller @RequiredArgsConstructor public class MediaController { private final MediaService mediaService; + private final SeasonRepository seasonRepository; + private final AlternativeTitlesRepository alternativeTitlesRepository; + private final ExternalReferenceRepository externalReferenceRepository; + private final MediaRepository mediaRepository; /** * Finds a MediaModel object by its ID. @@ -43,7 +62,7 @@ public MediaModel getMediaById(@Argument(name = "id") Integer id) { public MediaPage getMovie(@Argument(name = "id") Integer id, @Argument(name = "name") String name, DataFetchingEnvironment dataFetchingEnvironment) { name = name == null ? null : name.trim(); - if (name == null && id == null || name == "" && id == null) { + if (id == null && (name == null || name.isEmpty())) { return new MediaPage(); } @@ -62,7 +81,7 @@ public MediaPage getMovie(@Argument(name = "id") Integer id, @Argument(name = "n public MediaPage getSerie(@Argument(name = "id") Integer id, @Argument(name = "name") String name, DataFetchingEnvironment dataFetchingEnvironment) { name = name == null ? null : name.trim(); - if (name == null && id == null || name == "" && id == null) { + if (id == null && (name == null || name.isEmpty())) { return new MediaPage(); } @@ -81,7 +100,7 @@ public MediaPage getSerie(@Argument(name = "id") Integer id, @Argument(name = "n public MediaPage getGame(@Argument(name = "id") Integer id, @Argument(name = "name") String name, DataFetchingEnvironment dataFetchingEnvironment) { name = name == null ? null : name.trim(); - if (name == null && id == null || name == "" && id == null) { + if (id == null && (name == null || name.isEmpty())) { return new MediaPage(); } @@ -98,11 +117,10 @@ public MediaPage getGame(@Argument(name = "id") Integer id, @Argument(name = "na */ @QueryMapping(name = "vn") public MediaPage getVisualNovel(@Argument(name = "id") Integer id, @Argument(name = "name") String name, DataFetchingEnvironment dataFetchingEnvironment) { - MediaPage response = new MediaPage(); name = name == null ? null : name.trim(); - if (name == null && id == null || name == "" && id == null) { - return response; + if (id == null && (name == null || name.isEmpty())) { + return new MediaPage(); } return this.mediaService.findVisualNovelByIdOrName(id, name, MediaUtils.getPageable(dataFetchingEnvironment)); @@ -119,10 +137,143 @@ public MediaPage getVisualNovel(@Argument(name = "id") Integer id, @Argument(nam public MediaPage getAnime(@Argument(name = "id") Integer id, @Argument(name = "name") String name, DataFetchingEnvironment dataFetchingEnvironment) { name = name == null ? null : name.trim(); - if (name == null && id == null || name == "" && id == null) { + if (id == null && (name == null || name.isEmpty())) { return new MediaPage(); } return this.mediaService.findAnimeByIdOrName(id, name, MediaUtils.getPageable(dataFetchingEnvironment)); } + + /** + * Batch-loads seasons for a list of MediaModel sources, resolving the N+1 problem. + */ + @BatchMapping + public Map> season(List medias) { + Map sourceById = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + sourceById.putIfAbsent(m.getId(), m); + } + Map> result = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + result.putIfAbsent(m, new HashSet<>()); + } + for (SeasonModel season : seasonRepository.findByMediaIn(medias)) { + MediaModel source = sourceById.get(season.getMedia().getId()); + if (source != null) { + result.get(source).add(season); + } + } + return result; + } + + /** + * Batch-loads genres for a list of MediaModel sources, resolving the N+1 problem. + */ + @BatchMapping + public Map> genre(List medias) { + Map sourceById = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + sourceById.putIfAbsent(m.getId(), m); + } + Map> result = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + result.putIfAbsent(m, new HashSet<>()); + } + for (MediaModel loaded : mediaRepository.findAllWithGenreByMediaIn(medias)) { + MediaModel source = sourceById.get(loaded.getId()); + if (source != null && loaded.getGenre() != null) { + result.put(source, loaded.getGenre()); + } + } + return result; + } + + /** + * Batch-loads companies for a list of MediaModel sources, resolving the N+1 problem. + */ + @BatchMapping + public Map> company(List medias) { + Map sourceById = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + sourceById.putIfAbsent(m.getId(), m); + } + Map> result = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + result.putIfAbsent(m, new HashSet<>()); + } + for (MediaModel loaded : mediaRepository.findAllWithCompanyByMediaIn(medias)) { + MediaModel source = sourceById.get(loaded.getId()); + if (source != null && loaded.getCompany() != null) { + result.put(source, loaded.getCompany()); + } + } + return result; + } + + /** + * Batch-loads people for a list of MediaModel sources, resolving the N+1 problem. + */ + @BatchMapping + public Map> people(List medias) { + Map sourceById = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + sourceById.putIfAbsent(m.getId(), m); + } + Map> result = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + result.putIfAbsent(m, new HashSet<>()); + } + for (MediaModel loaded : mediaRepository.findAllWithPeopleByMediaIn(medias)) { + MediaModel source = sourceById.get(loaded.getId()); + if (source != null && loaded.getPeople() != null) { + result.put(source, loaded.getPeople()); + } + } + return result; + } + + /** + * Batch-loads external references for a list of MediaModel sources, resolving the N+1 problem. + */ + @BatchMapping + public Map> externalReference(List medias) { + Map sourceById = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + sourceById.putIfAbsent(m.getId(), m); + } + Map> result = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + result.putIfAbsent(m, new HashSet<>()); + } + List refs = externalReferenceRepository.findAllByMediaIn(medias); + for (ExternalReferenceModel ref : refs) { + MediaModel source = sourceById.get(ref.getMedia().getId()); + if (source != null) { + result.get(source).add(ref); + } + } + return result; + } + + /** + * Batch-loads alternative titles for a list of MediaModel sources, resolving the N+1 problem. + */ + @BatchMapping + public Map> alternativeTitles(List medias) { + Map sourceById = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + sourceById.putIfAbsent(m.getId(), m); + } + Map> result = new java.util.LinkedHashMap<>(); + for (MediaModel m : medias) { + result.putIfAbsent(m, new HashSet<>()); + } + for (AlternativeTitleModel alt : alternativeTitlesRepository.findByMediaIn(medias)) { + MediaModel source = sourceById.get(alt.getMedia().getId()); + if (source != null) { + result.get(source).add(alt); + } + } + return result; + } } diff --git a/src/main/java/com/espacogeek/geek/controllers/UserController.java b/src/main/java/com/espacogeek/geek/controllers/UserController.java index 47846f18..69b0ff4c 100644 --- a/src/main/java/com/espacogeek/geek/controllers/UserController.java +++ b/src/main/java/com/espacogeek/geek/controllers/UserController.java @@ -183,7 +183,7 @@ public String editPasswordUserLogged(Authentication authentication, @Argument(na Integer userId = UserUtils.getUserID(authentication); - UserModel userLogged = userService.findById(Integer.valueOf(userId)).get(); + UserModel userLogged = userService.findById(userId).orElseThrow(() -> new GenericException(HttpStatus.NOT_FOUND.toString())); boolean resultPassword = BCrypt.verifyer().verify(actualPassword.toCharArray(), userLogged.getPassword()).verified; if (resultPassword) { @@ -205,11 +205,11 @@ public String deleteUserLogged(Authentication authentication, @Argument(name = " Integer userId = UserUtils.getUserID(authentication); - UserModel userLogged = userService.findById(userId).get(); + UserModel userLogged = userService.findById(userId).orElseThrow(() -> new GenericException(HttpStatus.NOT_FOUND.toString())); boolean resultPassword = BCrypt.verifyer().verify(password.toCharArray(), userLogged.getPassword()).verified; if (resultPassword) { - userService.deleteById(Integer.valueOf(userId)); + userService.deleteById(userId); return HttpStatus.OK.toString(); } @@ -222,7 +222,7 @@ public String editUsernameUserLogged(Authentication authentication, @Argument(na Integer userId = UserUtils.getUserID(authentication); - UserModel userLogged = userService.findById(userId).get(); + UserModel userLogged = userService.findById(userId).orElseThrow(() -> new GenericException(HttpStatus.NOT_FOUND.toString())); boolean resultPassword = BCrypt.verifyer().verify(password.toCharArray(), userLogged.getPassword()).verified; if (resultPassword) { @@ -240,7 +240,7 @@ public String editEmailUserLogged(Authentication authentication, @Argument(name Integer userId = UserUtils.getUserID(authentication); - UserModel userLogged = userService.findById(userId).get(); + UserModel userLogged = userService.findById(userId).orElseThrow(() -> new GenericException(HttpStatus.NOT_FOUND.toString())); boolean resultPassword = BCrypt.verifyer().verify(password.toCharArray(), userLogged.getPassword()).verified; if (resultPassword) { diff --git a/src/main/java/com/espacogeek/geek/data/api/impl/GamesAndVNsApiImpl.java b/src/main/java/com/espacogeek/geek/data/api/impl/GamesAndVNsApiImpl.java index f8e3a9d5..0d7c7491 100644 --- a/src/main/java/com/espacogeek/geek/data/api/impl/GamesAndVNsApiImpl.java +++ b/src/main/java/com/espacogeek/geek/data/api/impl/GamesAndVNsApiImpl.java @@ -95,7 +95,7 @@ public MediaModel getDetails(Integer id) { genresName.add(genre.getName()); }); - media.setGenre(genreService.findAllByNames(genresName)); + media.setGenre(new java.util.LinkedHashSet<>(genreService.findAllByNames(genresName))); media.setAbout(result.getSummary()); media.setName(result.getName()); @@ -112,8 +112,8 @@ public MediaModel getDetails(Integer id) { for (proto.AlternativeName title : result.getAlternativeNamesList()) { if (!title.getName().isEmpty()) alternativeTitles.add(new AlternativeTitleModel(null, title.getName(), media)); } - media.setAlternativeTitles(alternativeTitles); - media.setExternalReference(new ArrayList<>(List.of(reference))); + media.setAlternativeTitles(new java.util.LinkedHashSet<>(alternativeTitles)); + media.setExternalReference(new java.util.LinkedHashSet<>(List.of(reference))); media.setMediaCategory(category); } } @@ -154,8 +154,8 @@ public List doSearch(String search, MediaCategoryModel mediaCategory for (proto.AlternativeName title : result.getGame().getAlternativeNamesList()) { if (!title.getName().isEmpty()) alternativeTitles.add(new AlternativeTitleModel(null, title.getName(), media)); } - media.setAlternativeTitles(alternativeTitles); - media.setExternalReference(new ArrayList<>(List.of(reference))); + media.setAlternativeTitles(new java.util.LinkedHashSet<>(alternativeTitles)); + media.setExternalReference(new java.util.LinkedHashSet<>(List.of(reference))); media.setMediaCategory(category); diff --git a/src/main/java/com/espacogeek/geek/data/api/impl/MovieAPIImpl.java b/src/main/java/com/espacogeek/geek/data/api/impl/MovieAPIImpl.java index 1e682b01..1e7c449c 100644 --- a/src/main/java/com/espacogeek/geek/data/api/impl/MovieAPIImpl.java +++ b/src/main/java/com/espacogeek/geek/data/api/impl/MovieAPIImpl.java @@ -77,7 +77,7 @@ public InputStream updateTitlesStream() { @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) @Override public MediaModel getDetails(Integer id) { - MovieDb movieDb = new MovieDb(); + MovieDb movieDb; try { movieDb = api.getDetails(id, "en-US", MovieAppendToResponse.EXTERNAL_IDS, MovieAppendToResponse.ALTERNATIVE_TITLES, MovieAppendToResponse.IMAGES, MovieAppendToResponse.VIDEOS); } catch (TmdbException e) { @@ -99,21 +99,19 @@ public MediaModel getDetails(Integer id) { movieDb.getPosterPath() == null ? null : ExternalCDN.TMDB.getUrl() + movieDb.getPosterPath(), movieDb.getBackdropPath() == null ? null : ExternalCDN.TMDB.getUrl() + movieDb.getBackdropPath(), mediaCategoryService.findById(MediaDataController.MediaType.MOVIE.getId()).get(), - externalReferences, + new java.util.LinkedHashSet<>(externalReferences), null, null, - formatGenre(movieDb.getGenres()), + new java.util.LinkedHashSet<>(formatGenre(movieDb.getGenres())), null, - formatAlternativeTitles(movieDb.getAlternativeTitles().getTitles()), + new java.util.LinkedHashSet<>(formatAlternativeTitles(movieDb.getAlternativeTitles().getTitles())), null); return serie; } public ExternalReferenceModel getTrailer(MovieDb movieDb) { - ExternalReferenceModel trailers = null; - - trailers = movieDb.getVideos().getResults().stream().filter(video -> video.getType().equals("Trailer")) + ExternalReferenceModel trailers = movieDb.getVideos().getResults().stream().filter(video -> video.getType().equals("Trailer")) .findFirst().map(video -> new ExternalReferenceModel(null, video.getKey(), null, typeReferenceService.findById(MediaDataController.ExternalReferenceType.YT.getId()).get())) .orElse(null); @@ -125,7 +123,7 @@ public ExternalReferenceModel getTrailer(MovieDb movieDb) { */ @Override public MediaModel getArtwork(Integer id) { - Images rawArtwork = new Images(); + Images rawArtwork; try { rawArtwork = api.getImages(id, "en"); } catch (TmdbException e) { @@ -160,7 +158,7 @@ public List getKeyword(Integer id) { @Override @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) public List getAlternativeTitles(Integer id) { - List rawAlternativeTitles = new ArrayList<>(); + List rawAlternativeTitles; try { rawAlternativeTitles = api.getAlternativeTitles(id, "us").getTitles(); } catch (TmdbException e) { @@ -186,7 +184,7 @@ private List formatAlternativeTitles(List getExternalReference(Integer id) { - ExternalIds rawExternalReferences = new ExternalIds(); + ExternalIds rawExternalReferences; try { rawExternalReferences = api.getExternalIds(id); } catch (TmdbException e) { @@ -214,7 +212,7 @@ private List formatExternalReference(ExternalIds rawExte @Override @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) public List getGenre(Integer id) { - MovieDb movieDb = new MovieDb(); + MovieDb movieDb; try { movieDb = api.getDetails(id, "en-US"); } catch (TmdbException e) { @@ -237,9 +235,7 @@ private List formatGenre(List rawGenres) { var genre = rawStringGenres.get(i); if (genre.contains("&")) { for (String genreDivided : genre.split("&")) { - genreDivided.replace("&", ""); - genreDivided = genreDivided.strip(); - newRawGenres.add(genreDivided); + newRawGenres.add(genreDivided.strip()); } } } diff --git a/src/main/java/com/espacogeek/geek/data/api/impl/QuoteApiImpl.java b/src/main/java/com/espacogeek/geek/data/api/impl/QuoteApiImpl.java index c624affe..27346d92 100644 --- a/src/main/java/com/espacogeek/geek/data/api/impl/QuoteApiImpl.java +++ b/src/main/java/com/espacogeek/geek/data/api/impl/QuoteApiImpl.java @@ -49,7 +49,7 @@ public void init() { @Override public QuoteModel getRandomQuote() { var client = new OkHttpClient().newBuilder().build(); - Request request = null; + Request request; try { request = new Request.Builder() .url(URL_QUOTE) @@ -62,21 +62,21 @@ public QuoteModel getRandomQuote() { throw new GenericException("Quote not found"); } - Response response = null; - try { - response = client.newCall(request).execute(); - } catch (IOException e) { - log.error("Error executing request for Quote API: {}", e.getMessage()); - throw new GenericException("Quote not found"); - } - var parser = new JSONParser(); var jsonArray = new JSONArray(); - try { - assert response.body() != null; - jsonArray = (JSONArray) parser.parse(response.body().string()); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + log.error("Quote API returned unsuccessful response: {}", response.code()); + throw new GenericException("Quote not found"); + } + okhttp3.ResponseBody body = response.body(); + if (body == null) { + log.error("Quote API returned empty body"); + throw new GenericException("Quote not found"); + } + jsonArray = (JSONArray) parser.parse(body.string()); } catch (ParseException | IOException e) { - log.error("Error parsing response from Quote API: {}", e.getMessage()); + log.error("Error executing or parsing response from Quote API: {}", e.getMessage()); throw new GenericException("Quote not found"); } diff --git a/src/main/java/com/espacogeek/geek/data/api/impl/TvSeriesApiImpl.java b/src/main/java/com/espacogeek/geek/data/api/impl/TvSeriesApiImpl.java index 0627e860..a4efa663 100644 --- a/src/main/java/com/espacogeek/geek/data/api/impl/TvSeriesApiImpl.java +++ b/src/main/java/com/espacogeek/geek/data/api/impl/TvSeriesApiImpl.java @@ -82,7 +82,7 @@ public InputStream updateTitlesStream() { @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) @Override public MediaModel getDetails(Integer id) { - TvSeriesDb rawSerieDetails = new TvSeriesDb(); + TvSeriesDb rawSerieDetails; try { rawSerieDetails = api.getDetails(id, "en-US", TvSeriesAppendToResponse.EXTERNAL_IDS, TvSeriesAppendToResponse.ALTERNATIVE_TITLES, TvSeriesAppendToResponse.IMAGES, TvSeriesAppendToResponse.VIDEOS); } catch (TmdbException e) { @@ -99,27 +99,25 @@ public MediaModel getDetails(Integer id) { MediaModel serie = new MediaModel( null, rawSerieDetails.getName(), - Optional.ofNullable(rawSerieDetails.getNumberOfEpisodes()).orElse(season.stream().map(SeasonModel::getEpisodeCount).reduce(Integer::sum).orElseGet(null)), + Optional.ofNullable(rawSerieDetails.getNumberOfEpisodes()).orElse(season.stream().map(SeasonModel::getEpisodeCount).reduce(Integer::sum).orElse(null)), rawSerieDetails.getEpisodeRunTime() == null || rawSerieDetails.getEpisodeRunTime().isEmpty() ? null : rawSerieDetails.getEpisodeRunTime().getFirst(), rawSerieDetails.getOverview(), rawSerieDetails.getPosterPath() == null ? null : ExternalCDN.TMDB.getUrl() + rawSerieDetails.getPosterPath(), rawSerieDetails.getBackdropPath() == null ? null : ExternalCDN.TMDB.getUrl() + rawSerieDetails.getBackdropPath(), mediaCategoryService.findById(MediaDataController.MediaType.SERIE.getId()).get(), - externalReferences, + new java.util.LinkedHashSet<>(externalReferences), null, null, - formatGenre(rawSerieDetails.getGenres()), + new java.util.LinkedHashSet<>(formatGenre(rawSerieDetails.getGenres())), null, - formatAlternativeTitles(rawSerieDetails.getAlternativeTitles().getResults()), - season); + new java.util.LinkedHashSet<>(formatAlternativeTitles(rawSerieDetails.getAlternativeTitles().getResults())), + new java.util.LinkedHashSet<>(season)); return serie; } public ExternalReferenceModel getTrailer(TvSeriesDb rawSerieDetails) { - ExternalReferenceModel trailers = null; - - trailers = rawSerieDetails.getVideos().getResults().stream().filter(video -> video.getType().equals("Trailer")) + ExternalReferenceModel trailers = rawSerieDetails.getVideos().getResults().stream().filter(video -> video.getType().equals("Trailer")) .findFirst().map(video -> new ExternalReferenceModel(null, video.getKey(), null, typeReferenceService.findById(MediaDataController.ExternalReferenceType.YT.getId()).get())) .orElse(null); @@ -133,7 +131,7 @@ public ExternalReferenceModel getTrailer(TvSeriesDb rawSerieDetails) { @Override @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) public MediaModel getArtwork(Integer id) { - Images rawArtwork = new Images(); + Images rawArtwork; try { rawArtwork = api.getImages(id, ""); } catch (TmdbException e) { @@ -167,7 +165,7 @@ public List getKeyword(Integer id) { @Override @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) public List getAlternativeTitles(Integer id) { - List rawAlternativeTitles = new ArrayList<>(); + List rawAlternativeTitles; try { rawAlternativeTitles = api.getAlternativeTitles(id).getResults(); } catch (TmdbException e) { @@ -192,7 +190,7 @@ private List formatAlternativeTitles(List getExternalReference(Integer id) { - ExternalIds rawExternalReferences = new ExternalIds(); + ExternalIds rawExternalReferences; try { rawExternalReferences = api.getExternalIds(id); } catch (TmdbException e) { @@ -225,7 +223,7 @@ private List formatExternalReference(ExternalIds rawExte @Override @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) public List getGenre(Integer id) { - TvSeriesDb rawSerieDetails = new TvSeriesDb(); + TvSeriesDb rawSerieDetails; try { rawSerieDetails = api.getDetails(id, "en-US"); } catch (TmdbException e) { @@ -246,9 +244,7 @@ private List formatGenre(List rawGenres) { for (String genre : rawStringGenres) { if (genre.contains("&")) { for (String genreDivided : genre.split("&")) { - genreDivided = genreDivided.replace("&", ""); - genreDivided = genreDivided.strip(); - newRawGenres.add(genreDivided); + newRawGenres.add(genreDivided.strip()); } } } diff --git a/src/main/java/com/espacogeek/geek/data/impl/GenericMediaDataControllerImpl.java b/src/main/java/com/espacogeek/geek/data/impl/GenericMediaDataControllerImpl.java index 5ade23b2..bfd22880 100644 --- a/src/main/java/com/espacogeek/geek/data/impl/GenericMediaDataControllerImpl.java +++ b/src/main/java/com/espacogeek/geek/data/impl/GenericMediaDataControllerImpl.java @@ -1,7 +1,9 @@ package com.espacogeek.geek.data.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.LinkedHashSet; import java.lang.reflect.Field; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -102,7 +104,7 @@ public MediaModel updateArtworks(MediaModel media, MediaModel result, TypeRefere MediaModel rawArtwork = new MediaModel(); if (result == null) { - List externalReferences = media.getExternalReference(); + Collection externalReferences = media.getExternalReference(); if (externalReferences == null || !Hibernate.isInitialized(externalReferences)) { externalReferences = externalReferenceService.findAll(media); } @@ -148,20 +150,20 @@ public List updateAlternativeTitles(MediaModel media, Med } } } else { - allAlternativeTitles = result.getAlternativeTitles(); + allAlternativeTitles = result.getAlternativeTitles() != null ? new ArrayList<>(result.getAlternativeTitles()) : new ArrayList<>(); } - if (CollectionUtils.isEmpty(allAlternativeTitles)) return media.getAlternativeTitles(); + if (CollectionUtils.isEmpty(allAlternativeTitles)) return media.getAlternativeTitles() != null ? new ArrayList<>(media.getAlternativeTitles()) : new ArrayList<>(); - if (media.getAlternativeTitles() == null) media.setAlternativeTitles(new ArrayList<>()); + if (media.getAlternativeTitles() == null) media.setAlternativeTitles(new LinkedHashSet<>()); for (AlternativeTitleModel title : allAlternativeTitles) { if (media.getAlternativeTitles().stream().noneMatch((alternativeTitle) -> alternativeTitle.getName().equals(title.getName()))) { media.getAlternativeTitles().add(new AlternativeTitleModel(null, title.getName(), media)); } } - alternativeTitlesService.saveAll(media.getAlternativeTitles()); - return media.getAlternativeTitles(); + alternativeTitlesService.saveAll(new ArrayList<>(media.getAlternativeTitles())); + return new ArrayList<>(media.getAlternativeTitles()); } @Override @@ -179,12 +181,12 @@ public List updateExternalReferences(MediaModel media, M } } } else { - rawExternalReferences = result.getExternalReference(); + rawExternalReferences = result.getExternalReference() != null ? new ArrayList<>(result.getExternalReference()) : new ArrayList<>(); } - if (CollectionUtils.isEmpty(rawExternalReferences)) return media.getExternalReference(); + if (CollectionUtils.isEmpty(rawExternalReferences)) return media.getExternalReference() != null ? new ArrayList<>(media.getExternalReference()) : new ArrayList<>(); - if (media.getExternalReference() == null) media.setExternalReference(new ArrayList<>()); + if (media.getExternalReference() == null) media.setExternalReference(new LinkedHashSet<>()); for (ExternalReferenceModel reference : rawExternalReferences) { if (CollectionUtils.isEmpty(media.getExternalReference()) || media.getExternalReference().stream().noneMatch((eReference) -> eReference.getReference().equals(reference.getReference()))) { reference.setMedia(media); @@ -192,9 +194,9 @@ public List updateExternalReferences(MediaModel media, M } } - externalReferenceService.saveAll(media.getExternalReference()); + externalReferenceService.saveAll(new ArrayList<>(media.getExternalReference())); - return media.getExternalReference(); + return new ArrayList<>(media.getExternalReference()); } @Override @@ -212,10 +214,14 @@ public List updateGenres(MediaModel media, MediaModel result, TypeRe } } } else { - rawGenres = result.getGenre(); + rawGenres = result.getGenre() != null ? new ArrayList<>(result.getGenre()) : new ArrayList<>(); } - if (CollectionUtils.isEmpty(rawGenres)) return media.getGenre(); + if (CollectionUtils.isEmpty(rawGenres)) return media.getGenre() != null ? new ArrayList<>(media.getGenre()) : new ArrayList<>(); + + if (media.getGenre() == null) { + media.setGenre(new LinkedHashSet<>()); + } rawGenres.forEach((rawGenre) -> { if (media.getGenre().stream().noneMatch((genre) -> genre.getId().equals(rawGenre.getId()))) { @@ -224,8 +230,8 @@ public List updateGenres(MediaModel media, MediaModel result, TypeRe } }); - genreService.saveAll(media.getGenre()); - return media.getGenre(); + genreService.saveAll(new ArrayList<>(media.getGenre())); + return new ArrayList<>(media.getGenre()); } @Override @@ -240,10 +246,14 @@ public List updateSeason(MediaModel media, MediaModel result, TypeR } } } else { - rawSeasons = result.getSeason(); + rawSeasons = result.getSeason() != null ? new ArrayList<>(result.getSeason()) : new ArrayList<>(); } - if (CollectionUtils.isEmpty(rawSeasons)) return media.getSeason(); + if (CollectionUtils.isEmpty(rawSeasons)) return media.getSeason() != null ? new ArrayList<>(media.getSeason()) : new ArrayList<>(); + + if (media.getSeason() == null) { + media.setSeason(new LinkedHashSet<>()); + } rawSeasons.forEach((rawSeason) -> { if (media.getSeason().stream().noneMatch((season) -> season.getName().equals(rawSeason.getName()))) { @@ -252,7 +262,7 @@ public List updateSeason(MediaModel media, MediaModel result, TypeR }); var savedSeasons = seasonService.saveAll(seasons); - List newSeasons = media.getSeason() == null ? new ArrayList<>() : media.getSeason(); + List newSeasons = media.getSeason() == null ? new ArrayList<>() : new ArrayList<>(media.getSeason()); newSeasons.addAll(savedSeasons == null ? new ArrayList<>() : savedSeasons); return newSeasons; @@ -264,12 +274,9 @@ public List searchMedia(String search, MediaApi mediaApi, TypeRefere var result = new ArrayList(); for (MediaModel mediaSearch : rawMediaSearchList) { - var media = new MediaModel(); - media.setMediaCategory(mediaCategory); - + MediaModel media; try { media = createMediaIfNotExistAndIfExistReturnIt(mediaSearch, typeReference); - media = mediaSearch; if (media != null) { updateExternalReferences(media, mediaSearch, typeReference, mediaApi); @@ -283,8 +290,14 @@ public List searchMedia(String search, MediaApi mediaApi, TypeRefere result.add(media); } catch (MediaAlreadyExist e) { - media = mediaService.findByReferenceAndTypeReference(mediaSearch.getExternalReference().getFirst(), typeReference).orElseThrow(); + Collection externalReferences = mediaSearch.getExternalReference(); + if (CollectionUtils.isEmpty(externalReferences)) { + throw new com.espacogeek.geek.exception.GenericException("MediaAlreadyExist thrown but no external references are available to lookup existing media"); + } + ExternalReferenceModel firstReference = externalReferences.iterator().next(); + media = mediaService.findByReferenceAndTypeReference(firstReference, typeReference) + .orElseThrow(() -> new com.espacogeek.geek.exception.GenericException("Media not found for the given external reference")); result.add(media); } } diff --git a/src/main/java/com/espacogeek/geek/data/impl/MovieControllerImpl.java b/src/main/java/com/espacogeek/geek/data/impl/MovieControllerImpl.java index 5bb3de3d..36a1fbdc 100644 --- a/src/main/java/com/espacogeek/geek/data/impl/MovieControllerImpl.java +++ b/src/main/java/com/espacogeek/geek/data/impl/MovieControllerImpl.java @@ -124,20 +124,15 @@ else if (isUndefined) media.setName(json.get("original_title").toString()); - if (externalReferenceExisted.isPresent()) { - media.setId(externalReferenceExisted.get().getMedia().getId()); - externalReference.setId(externalReferenceExisted.get().getId()); - } - var mediaSaved = mediaService.save(media); externalReference.setMedia(mediaSaved); var referenceSaved = externalReferenceService.save(externalReference); - List referenceListSaved = new ArrayList<>(); + java.util.LinkedHashSet referenceListSaved = new java.util.LinkedHashSet<>(); referenceListSaved.add(referenceSaved); mediaSaved.setExternalReference(referenceListSaved); - media.setAlternativeTitles(updateAlternativeTitles(mediaSaved, null, typeReference, movieAPI)); + media.setAlternativeTitles(new java.util.LinkedHashSet<>(updateAlternativeTitles(mediaSaved, null, typeReference, movieAPI))); } } catch (Exception e) { var json = (JSONObject) jsonArrayDailyExport.get(index); diff --git a/src/main/java/com/espacogeek/geek/data/impl/SerieControllerImpl.java b/src/main/java/com/espacogeek/geek/data/impl/SerieControllerImpl.java index 9a38936e..42154abd 100644 --- a/src/main/java/com/espacogeek/geek/data/impl/SerieControllerImpl.java +++ b/src/main/java/com/espacogeek/geek/data/impl/SerieControllerImpl.java @@ -93,20 +93,15 @@ private void updateTvSeries() { media.setName(json.get("original_name").toString()); - if (externalReferenceExisted.isPresent()) { - media.setId(externalReferenceExisted.get().getMedia().getId()); - externalReference.setId(externalReferenceExisted.get().getId()); - } - var mediaSaved = mediaService.save(media); externalReference.setMedia(mediaSaved); var referenceSaved = externalReferenceService.save(externalReference); - List referenceListSaved = new ArrayList<>(); + java.util.LinkedHashSet referenceListSaved = new java.util.LinkedHashSet<>(); referenceListSaved.add(referenceSaved); mediaSaved.setExternalReference(referenceListSaved); - media.setAlternativeTitles(updateAlternativeTitles(mediaSaved, null, typeReference, tvSeriesApi)); + media.setAlternativeTitles(new java.util.LinkedHashSet<>(updateAlternativeTitles(mediaSaved, null, typeReference, tvSeriesApi))); } } catch (Exception e) { var json = (JSONObject) jsonArrayDailyExport.get(index); diff --git a/src/main/java/com/espacogeek/geek/metrics/GraphQLMetricsInstrumentation.java b/src/main/java/com/espacogeek/geek/metrics/GraphQLMetricsInstrumentation.java index 774c8212..9f3283c3 100644 --- a/src/main/java/com/espacogeek/geek/metrics/GraphQLMetricsInstrumentation.java +++ b/src/main/java/com/espacogeek/geek/metrics/GraphQLMetricsInstrumentation.java @@ -28,7 +28,7 @@ public static String extractOperationName(String query) { } // Se não encontrou, tenta extrair a primeira palavra-chave GraphQL - pattern = java.util.regex.Pattern.compile("(\\w+)\\s*(?:\\(|\\{)"); + pattern = java.util.regex.Pattern.compile("(\\w+)\\s*[({]"); matcher = pattern.matcher(query); if (matcher.find()) { return matcher.group(1); diff --git a/src/main/java/com/espacogeek/geek/models/MediaModel.java b/src/main/java/com/espacogeek/geek/models/MediaModel.java index 95c75d18..8083536a 100644 --- a/src/main/java/com/espacogeek/geek/models/MediaModel.java +++ b/src/main/java/com/espacogeek/geek/models/MediaModel.java @@ -2,11 +2,9 @@ import java.io.Serializable; import java.util.Date; -import java.util.List; import java.util.Objects; +import java.util.Set; -import org.hibernate.annotations.Fetch; -import org.hibernate.annotations.FetchMode; import org.hibernate.annotations.UpdateTimestamp; import jakarta.persistence.*; @@ -56,32 +54,28 @@ public class MediaModel implements Serializable { private MediaCategoryModel mediaCategory; @OneToMany(mappedBy = "media", fetch = FetchType.LAZY) - @Fetch(FetchMode.SUBSELECT) - private List externalReference; + private Set externalReference; @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "medias_has_companies", joinColumns = @JoinColumn(name = "medias_id_media"), inverseJoinColumns = @JoinColumn(name = "companies_id_company")) - @Fetch(FetchMode.SUBSELECT) - private List company; + private Set company; @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "medias_has_people", joinColumns = @JoinColumn(name = "medias_id_media"), inverseJoinColumns = @JoinColumn(name = "people_id_person")) - @Fetch(FetchMode.SUBSELECT) - private List people; + private Set people; @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "medias_has_genres", joinColumns = @JoinColumn(name = "medias_id_media"), inverseJoinColumns = @JoinColumn(name = "genres_id_genre")) - @Fetch(FetchMode.SUBSELECT) - private List genre; + private Set genre; @Column(name = "update_at") @UpdateTimestamp @@ -89,12 +83,10 @@ public class MediaModel implements Serializable { private Date updateAt; @OneToMany(mappedBy = "media", fetch = FetchType.LAZY) - @Fetch(FetchMode.SUBSELECT) - private List alternativeTitles; + private Set alternativeTitles; - @OneToMany(mappedBy = "media") - @Fetch(FetchMode.SUBSELECT) - private List season; + @OneToMany(mappedBy = "media", fetch = FetchType.LAZY) + private Set season; @Override public final boolean equals(Object o) { @@ -103,7 +95,7 @@ public final boolean equals(Object o) { Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; - ExternalReferenceModel that = (ExternalReferenceModel) o; + MediaModel that = (MediaModel) o; return getId() != null && Objects.equals(getId(), that.getId()); } diff --git a/src/main/java/com/espacogeek/geek/repositories/AlternativeTitlesRepository.java b/src/main/java/com/espacogeek/geek/repositories/AlternativeTitlesRepository.java index 601f2069..2d6f9752 100644 --- a/src/main/java/com/espacogeek/geek/repositories/AlternativeTitlesRepository.java +++ b/src/main/java/com/espacogeek/geek/repositories/AlternativeTitlesRepository.java @@ -1,11 +1,17 @@ package com.espacogeek.geek.repositories; +import java.util.Collection; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.espacogeek.geek.models.AlternativeTitleModel; +import com.espacogeek.geek.models.MediaModel; @Repository -public interface AlternativeTitlesRepository extends JpaRepository { - +public interface AlternativeTitlesRepository extends JpaRepository { + + List findByMediaIn(Collection medias); + } diff --git a/src/main/java/com/espacogeek/geek/repositories/ExternalReferenceRepository.java b/src/main/java/com/espacogeek/geek/repositories/ExternalReferenceRepository.java index 37d90128..dfc233c2 100644 --- a/src/main/java/com/espacogeek/geek/repositories/ExternalReferenceRepository.java +++ b/src/main/java/com/espacogeek/geek/repositories/ExternalReferenceRepository.java @@ -1,5 +1,7 @@ package com.espacogeek.geek.repositories; +import java.util.Collection; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,12 +12,14 @@ import com.espacogeek.geek.models.TypeReferenceModel; @Repository -public interface ExternalReferenceRepository extends JpaRepository { +public interface ExternalReferenceRepository extends JpaRepository { Optional findByReferenceAndTypeReference (String reference, TypeReferenceModel typeReference); Optional findByMedia(MediaModel media); - java.util.List findAllByMedia(MediaModel media); + List findAllByMedia(MediaModel media); + + List findAllByMediaIn(Collection medias); boolean existsByMediaId(Integer id); } diff --git a/src/main/java/com/espacogeek/geek/repositories/MediaRepository.java b/src/main/java/com/espacogeek/geek/repositories/MediaRepository.java index b48e6b4d..81b84f0a 100644 --- a/src/main/java/com/espacogeek/geek/repositories/MediaRepository.java +++ b/src/main/java/com/espacogeek/geek/repositories/MediaRepository.java @@ -1,5 +1,7 @@ package com.espacogeek.geek.repositories; +import java.util.Collection; +import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -59,4 +61,34 @@ Page findMediaByNameOrAlternativeTitleAndMediaCategory( @Query("SELECT m FROM MediaModel m JOIN ExternalReferenceModel e ON e MEMBER OF m.externalReference WHERE e.reference = :reference AND e.typeReference = :typeReference") public Optional findOneMediaByExternalReferenceAndTypeReference(@Param("reference") String reference, @Param("typeReference") TypeReferenceModel typeReference); + + /** + * Batch-loads the genre association for a collection of MediaModel entities. + * Used by @BatchMapping to resolve the N+1 problem for genres. + * + * @param medias the collection of MediaModel entities to load genres for. + * @return a list of MediaModel entities with their genre collections initialized. + */ + @Query("SELECT DISTINCT m FROM MediaModel m LEFT JOIN FETCH m.genre WHERE m IN :medias") + List findAllWithGenreByMediaIn(@Param("medias") Collection medias); + + /** + * Batch-loads the company association for a collection of MediaModel entities. + * Used by @BatchMapping to resolve the N+1 problem for companies. + * + * @param medias the collection of MediaModel entities to load companies for. + * @return a list of MediaModel entities with their company collections initialized. + */ + @Query("SELECT DISTINCT m FROM MediaModel m LEFT JOIN FETCH m.company WHERE m IN :medias") + List findAllWithCompanyByMediaIn(@Param("medias") Collection medias); + + /** + * Batch-loads the people association for a collection of MediaModel entities. + * Used by @BatchMapping to resolve the N+1 problem for people. + * + * @param medias the collection of MediaModel entities to load people for. + * @return a list of MediaModel entities with their people collections initialized. + */ + @Query("SELECT DISTINCT m FROM MediaModel m LEFT JOIN FETCH m.people WHERE m IN :medias") + List findAllWithPeopleByMediaIn(@Param("medias") Collection medias); } diff --git a/src/main/java/com/espacogeek/geek/repositories/SeasonRepository.java b/src/main/java/com/espacogeek/geek/repositories/SeasonRepository.java index 8981b5e5..5b533809 100644 --- a/src/main/java/com/espacogeek/geek/repositories/SeasonRepository.java +++ b/src/main/java/com/espacogeek/geek/repositories/SeasonRepository.java @@ -1,11 +1,17 @@ package com.espacogeek.geek.repositories; +import java.util.Collection; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import com.espacogeek.geek.models.MediaModel; import com.espacogeek.geek.models.SeasonModel; @Repository public interface SeasonRepository extends JpaRepository { + List findByMediaIn(Collection medias); + } diff --git a/src/main/java/com/espacogeek/geek/services/impl/ApiKeyServiceImpl.java b/src/main/java/com/espacogeek/geek/services/impl/ApiKeyServiceImpl.java index 2a78a35a..a7345a50 100644 --- a/src/main/java/com/espacogeek/geek/services/impl/ApiKeyServiceImpl.java +++ b/src/main/java/com/espacogeek/geek/services/impl/ApiKeyServiceImpl.java @@ -31,6 +31,6 @@ public Optional findById(Integer id) { */ @Override public Optional save(ApiKeyModel apiKeyModel) { - return Optional.ofNullable(apiKeyRepository.save(apiKeyModel)); + return Optional.of(apiKeyRepository.save(apiKeyModel)); } } diff --git a/src/main/java/com/espacogeek/geek/services/impl/ExternalReferenceServiceImpl.java b/src/main/java/com/espacogeek/geek/services/impl/ExternalReferenceServiceImpl.java index 18bf8c48..0148b298 100644 --- a/src/main/java/com/espacogeek/geek/services/impl/ExternalReferenceServiceImpl.java +++ b/src/main/java/com/espacogeek/geek/services/impl/ExternalReferenceServiceImpl.java @@ -18,14 +18,12 @@ @Service public class ExternalReferenceServiceImpl implements ExternalReferenceService { - @SuppressWarnings("rawtypes") @Autowired private ExternalReferenceRepository externalReferenceRepository; /** * @see ExternalReferenceService#findAll(MediaModel) */ - @SuppressWarnings("unchecked") @Override public List findAll(MediaModel media) { return this.externalReferenceRepository.findAllByMedia(media); @@ -34,7 +32,6 @@ public List findAll(MediaModel media) { /** * @see ExternalReferenceService#findById(Integer) */ - @SuppressWarnings("unchecked") @Override public Optional findById(Integer id) { return this.externalReferenceRepository.findById(id); @@ -43,16 +40,14 @@ public Optional findById(Integer id) { /** * @see ExternalReferenceService#save(ExternalReferenceModel) */ - @SuppressWarnings("unchecked") @Override public ExternalReferenceModel save(ExternalReferenceModel externalReference) { - return (ExternalReferenceModel) this.externalReferenceRepository.save(externalReference); + return this.externalReferenceRepository.save(externalReference); } /** * @see ExternalReferenceService#saveAll(List) */ - @SuppressWarnings("unchecked") @Override public List saveAll(List externalReferences) { return this.externalReferenceRepository.saveAll(externalReferences); @@ -61,7 +56,6 @@ public List saveAll(List externa /** * @see ExternalReferenceService#findByIdAndType(Integer, TypeReferenceModel) */ - @SuppressWarnings("unchecked") @Override public Optional findByReferenceAndType(String reference, TypeReferenceModel typeReference) { return this.externalReferenceRepository.findByReferenceAndTypeReference(reference, typeReference); diff --git a/src/main/java/com/espacogeek/geek/services/impl/MediaServiceImpl.java b/src/main/java/com/espacogeek/geek/services/impl/MediaServiceImpl.java index de9c49a0..d23ef4ac 100644 --- a/src/main/java/com/espacogeek/geek/services/impl/MediaServiceImpl.java +++ b/src/main/java/com/espacogeek/geek/services/impl/MediaServiceImpl.java @@ -4,6 +4,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; @@ -45,10 +46,8 @@ public class MediaServiceImpl implements MediaService { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MediaServiceImpl.class); - @SuppressWarnings("rawtypes") private final MediaRepository mediaRepository; - @SuppressWarnings("rawtypes") private final ExternalReferenceRepository externalsRepo; private final MediaCategoryService mediaCategoryService; @@ -211,7 +210,7 @@ public Optional findByReferenceAndTypeReference(ExternalReferenceMod @Transactional public Optional findByIdEager(Integer id) { var fieldList = new ArrayList(); - MediaModel media = (MediaModel) mediaRepository.findById(id).orElseGet(null); + MediaModel media = (MediaModel) mediaRepository.findById(id).orElse(null); if (media == null) return Optional.empty(); @@ -228,8 +227,8 @@ public Optional findByIdEager(Integer id) { String getterName = "get" + capitalize(field.getName()); Method getter = media.getClass().getMethod(getterName); var fieldValue = getter.invoke(media); - if (fieldValue instanceof List) { - ((List) fieldValue).size(); // This will initialize the collection + if (fieldValue instanceof Collection) { + ((Collection) fieldValue).size(); // This will initialize the collection } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { log.error("Failed to initialize field {} for media id={}: {}", field.getName(), media.getId(), e.getMessage()); @@ -272,8 +271,7 @@ public Optional randomArtwork() { if (categoryId == null) continue; - MediaModel updated = media; - updated = switch (categoryId) { + MediaModel updated = switch (categoryId) { case 2, 3 -> MediaUtils .updateGenericMedia( List.of(media), @@ -282,7 +280,7 @@ public Optional randomArtwork() { gamesAndVNsAPI) .getFirst(); case 1 -> MediaUtils.updateMedia(List.of(media), serieController).getFirst(); - default -> updated; + default -> media; }; if (updated != null && updated.getBanner() != null && !updated.getBanner().isEmpty()) { diff --git a/src/main/java/com/espacogeek/geek/services/impl/UserDetailsServiceImpl.java b/src/main/java/com/espacogeek/geek/services/impl/UserDetailsServiceImpl.java index 80d593a1..a37e1978 100644 --- a/src/main/java/com/espacogeek/geek/services/impl/UserDetailsServiceImpl.java +++ b/src/main/java/com/espacogeek/geek/services/impl/UserDetailsServiceImpl.java @@ -36,8 +36,7 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep String[] parts = raw.replaceAll("\\s", "").split(","); // Normalize roles: if a role doesn't start with ROLE_ or ID_, prefix with ROLE_ rolesList.addAll(Arrays.stream(parts) - .map(s -> s == null ? null : s.trim()) - .filter(s -> s != null && !s.isBlank()) + .filter(s -> !s.isBlank()) .map(s -> { if (s.startsWith("ROLE_") || s.startsWith("ID_")) return s; return "ROLE_" + s; diff --git a/src/main/java/com/espacogeek/geek/utils/MediaUtils.java b/src/main/java/com/espacogeek/geek/utils/MediaUtils.java index cac3a29e..3717a9a0 100644 --- a/src/main/java/com/espacogeek/geek/utils/MediaUtils.java +++ b/src/main/java/com/espacogeek/geek/utils/MediaUtils.java @@ -40,7 +40,7 @@ public static Boolean updateMediaWhenLastTimeUpdateMoreThanOneDay(MediaModel med LocalDate mediaUpdateAt = media.getUpdateAt() == null ? null : LocalDate.ofInstant(media.getUpdateAt().toInstant(), ZoneId.systemDefault()); - if (mediaUpdateAt == null || ChronoUnit.DAYS.between(mediaUpdateAt, LocalDate.now()) > 1l) { + if (mediaUpdateAt == null || ChronoUnit.DAYS.between(mediaUpdateAt, LocalDate.now()) > 1L) { return true; } @@ -69,7 +69,7 @@ public static List updateGenericMedia(List medias, Media : media); } - return medias; + return updatedMedias; } /** @@ -89,7 +89,7 @@ public static List updateMedia(List medias, MediaDataCon : media); } - return medias; + return updatedMedias; } /** diff --git a/src/main/resources/graphql/entity/Company.graphqls b/src/main/resources/graphql/entity/Company.graphqls new file mode 100644 index 00000000..ed18195f --- /dev/null +++ b/src/main/resources/graphql/entity/Company.graphqls @@ -0,0 +1,4 @@ +type Company { + id: ID + name: String +} diff --git a/src/main/resources/graphql/entity/Media.graphqls b/src/main/resources/graphql/entity/Media.graphqls index 68d6bae1..8525211c 100644 --- a/src/main/resources/graphql/entity/Media.graphqls +++ b/src/main/resources/graphql/entity/Media.graphqls @@ -20,6 +20,10 @@ type Media { mediaCategory: MediaCategory "List of genres" genre: [Genre] + "Companies involved in production" + company: [Company] + "People involved (cast, crew, etc.)" + people: [People] "External references (IMDB, IGDB, VNDB, etc.)" externalReference: [ExternalReference] "Alternative titles in different languages" diff --git a/src/main/resources/graphql/entity/People.graphqls b/src/main/resources/graphql/entity/People.graphqls new file mode 100644 index 00000000..c431df85 --- /dev/null +++ b/src/main/resources/graphql/entity/People.graphqls @@ -0,0 +1,5 @@ +type People { + id: ID + name: String + typePerson: TypePerson +} diff --git a/src/test/java/com/espacogeek/geek/cors/BrowserCorsRequestTest.java b/src/test/java/com/espacogeek/geek/cors/BrowserCorsRequestTest.java index 859ca7a9..2265f926 100644 --- a/src/test/java/com/espacogeek/geek/cors/BrowserCorsRequestTest.java +++ b/src/test/java/com/espacogeek/geek/cors/BrowserCorsRequestTest.java @@ -19,6 +19,10 @@ import com.espacogeek.geek.controllers.DailyQuoteArtworkController; import com.espacogeek.geek.controllers.MediaController; import com.espacogeek.geek.models.DailyQuoteArtworkModel; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.DailyQuoteArtworkService; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.services.impl.UserDetailsServiceImpl; @@ -88,6 +92,18 @@ RuntimeWiringConfigurer dateScalarConfigurer() { @MockitoBean private MediaService mediaService; + @MockitoBean + private SeasonRepository seasonRepository; + + @MockitoBean + private AlternativeTitlesRepository alternativeTitlesRepository; + + @MockitoBean + private ExternalReferenceRepository externalReferenceRepository; + + @MockitoBean + private MediaRepository mediaRepository; + @MockitoBean private UserDetailsServiceImpl userDetailsService; diff --git a/src/test/java/com/espacogeek/geek/query/media/AnimeQueryTest.java b/src/test/java/com/espacogeek/geek/query/media/AnimeQueryTest.java index 2cfc2067..f8e0447c 100644 --- a/src/test/java/com/espacogeek/geek/query/media/AnimeQueryTest.java +++ b/src/test/java/com/espacogeek/geek/query/media/AnimeQueryTest.java @@ -20,6 +20,10 @@ import com.espacogeek.geek.controllers.MediaController; import com.espacogeek.geek.data.MediaDataController; import com.espacogeek.geek.models.MediaModel; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.MediaCategoryService; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.services.TypeReferenceService; @@ -48,6 +52,18 @@ public class AnimeQueryTest { @MockitoBean private MediaCategoryService mediaCategoryService; + @MockitoBean + private SeasonRepository seasonRepository; + + @MockitoBean + private AlternativeTitlesRepository alternativeTitlesRepository; + + @MockitoBean + private ExternalReferenceRepository externalReferenceRepository; + + @MockitoBean + private MediaRepository mediaRepository; + @Test void anime_NoParameters_ShouldReturnEmptyPage() { // When & Then diff --git a/src/test/java/com/espacogeek/geek/query/media/GameQueryTest.java b/src/test/java/com/espacogeek/geek/query/media/GameQueryTest.java index 1ed142ef..aef8a130 100644 --- a/src/test/java/com/espacogeek/geek/query/media/GameQueryTest.java +++ b/src/test/java/com/espacogeek/geek/query/media/GameQueryTest.java @@ -21,6 +21,10 @@ import com.espacogeek.geek.models.MediaCategoryModel; import com.espacogeek.geek.models.MediaModel; import com.espacogeek.geek.models.TypeReferenceModel; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.MediaCategoryService; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.services.TypeReferenceService; @@ -51,6 +55,18 @@ class GameQueryTest { @MockitoBean private MediaCategoryService mediaCategoryService; + @MockitoBean + private SeasonRepository seasonRepository; + + @MockitoBean + private AlternativeTitlesRepository alternativeTitlesRepository; + + @MockitoBean + private ExternalReferenceRepository externalReferenceRepository; + + @MockitoBean + private MediaRepository mediaRepository; + @Test void game_NoParameters_ShouldReturnEmptyPage() { // When & Then diff --git a/src/test/java/com/espacogeek/geek/query/media/MediaQueryTest.java b/src/test/java/com/espacogeek/geek/query/media/MediaQueryTest.java index 60afed3f..d89b78f3 100644 --- a/src/test/java/com/espacogeek/geek/query/media/MediaQueryTest.java +++ b/src/test/java/com/espacogeek/geek/query/media/MediaQueryTest.java @@ -18,6 +18,10 @@ import com.espacogeek.geek.data.api.MediaApi; import com.espacogeek.geek.models.MediaCategoryModel; import com.espacogeek.geek.models.MediaModel; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.MediaCategoryService; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.services.TypeReferenceService; @@ -47,6 +51,18 @@ class MediaQueryTest { @MockitoBean private MediaCategoryService mediaCategoryService; + @MockitoBean + private SeasonRepository seasonRepository; + + @MockitoBean + private AlternativeTitlesRepository alternativeTitlesRepository; + + @MockitoBean + private ExternalReferenceRepository externalReferenceRepository; + + @MockitoBean + private MediaRepository mediaRepository; + @Test void media_NotFound_ShouldReturnError() { // Given diff --git a/src/test/java/com/espacogeek/geek/query/media/MovieQueryTest.java b/src/test/java/com/espacogeek/geek/query/media/MovieQueryTest.java index 75c499de..ce6b2f92 100644 --- a/src/test/java/com/espacogeek/geek/query/media/MovieQueryTest.java +++ b/src/test/java/com/espacogeek/geek/query/media/MovieQueryTest.java @@ -21,6 +21,10 @@ import com.espacogeek.geek.data.MediaDataController; import com.espacogeek.geek.data.api.MediaApi; import com.espacogeek.geek.models.MediaModel; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.MediaCategoryService; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.services.TypeReferenceService; @@ -48,6 +52,18 @@ public class MovieQueryTest { @MockitoBean private MediaCategoryService mediaCategoryService; + @MockitoBean + private SeasonRepository seasonRepository; + + @MockitoBean + private AlternativeTitlesRepository alternativeTitlesRepository; + + @MockitoBean + private ExternalReferenceRepository externalReferenceRepository; + + @MockitoBean + private MediaRepository mediaRepository; + @Test void movie_NoParameters_ShouldReturnEmptyPage() { // When & Then diff --git a/src/test/java/com/espacogeek/geek/query/media/TvSerieQueryTest.java b/src/test/java/com/espacogeek/geek/query/media/TvSerieQueryTest.java index cbba1765..7632d942 100644 --- a/src/test/java/com/espacogeek/geek/query/media/TvSerieQueryTest.java +++ b/src/test/java/com/espacogeek/geek/query/media/TvSerieQueryTest.java @@ -18,6 +18,10 @@ import com.espacogeek.geek.controllers.MediaController; import com.espacogeek.geek.data.MediaDataController; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.MediaCategoryService; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.services.TypeReferenceService; @@ -46,6 +50,18 @@ class TvSerieQueryTest { @MockitoBean private MediaCategoryService mediaCategoryService; + @MockitoBean + private SeasonRepository seasonRepository; + + @MockitoBean + private AlternativeTitlesRepository alternativeTitlesRepository; + + @MockitoBean + private ExternalReferenceRepository externalReferenceRepository; + + @MockitoBean + private MediaRepository mediaRepository; + private MediaPage stubMediaPage() { MediaSimplefied item = new MediaSimplefied(); item.setId(1); diff --git a/src/test/java/com/espacogeek/geek/query/media/VisualNovelQueryTest.java b/src/test/java/com/espacogeek/geek/query/media/VisualNovelQueryTest.java index 082a952d..8eb72070 100644 --- a/src/test/java/com/espacogeek/geek/query/media/VisualNovelQueryTest.java +++ b/src/test/java/com/espacogeek/geek/query/media/VisualNovelQueryTest.java @@ -21,6 +21,10 @@ import com.espacogeek.geek.models.MediaCategoryModel; import com.espacogeek.geek.models.MediaModel; import com.espacogeek.geek.models.TypeReferenceModel; +import com.espacogeek.geek.repositories.AlternativeTitlesRepository; +import com.espacogeek.geek.repositories.ExternalReferenceRepository; +import com.espacogeek.geek.repositories.MediaRepository; +import com.espacogeek.geek.repositories.SeasonRepository; import com.espacogeek.geek.services.MediaCategoryService; import com.espacogeek.geek.services.MediaService; import com.espacogeek.geek.services.TypeReferenceService; @@ -51,6 +55,18 @@ class VisualNovelQueryTest { @MockitoBean private MediaCategoryService mediaCategoryService; + @MockitoBean + private SeasonRepository seasonRepository; + + @MockitoBean + private AlternativeTitlesRepository alternativeTitlesRepository; + + @MockitoBean + private ExternalReferenceRepository externalReferenceRepository; + + @MockitoBean + private MediaRepository mediaRepository; + @Test void vn_NoParameters_ShouldReturnEmptyPage() { // When & Then diff --git a/src/test/java/com/espacogeek/geek/services/MediaServiceImplTest.java b/src/test/java/com/espacogeek/geek/services/MediaServiceImplTest.java index 303ef0ac..bee26767 100644 --- a/src/test/java/com/espacogeek/geek/services/MediaServiceImplTest.java +++ b/src/test/java/com/espacogeek/geek/services/MediaServiceImplTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import jakarta.validation.ValidationException; @@ -63,7 +64,7 @@ void save_WithInMemoryExternalReference_ShouldPersist() { MediaModel media = new MediaModel(); media.setName("Inception"); ExternalReferenceModel ref = new ExternalReferenceModel(); - media.setExternalReference(List.of(ref)); + media.setExternalReference(new HashSet<>(List.of(ref))); when(mediaRepository.save(media)).thenReturn(media); // When @@ -94,7 +95,7 @@ void save_WithEmptyExternalReferenceListAndNoId_ShouldThrowValidationException() // Given MediaModel media = new MediaModel(); media.setName("Unknown"); - media.setExternalReference(new ArrayList<>()); + media.setExternalReference(new HashSet<>()); // When & Then assertThatThrownBy(() -> mediaService.save(media)) @@ -146,11 +147,11 @@ void saveAll_AllWithExternalReferences_ShouldSaveAll() { // Given MediaModel media1 = new MediaModel(); media1.setName("Movie A"); - media1.setExternalReference(List.of(new ExternalReferenceModel())); + media1.setExternalReference(new HashSet<>(List.of(new ExternalReferenceModel()))); MediaModel media2 = new MediaModel(); media2.setName("Movie B"); - media2.setExternalReference(List.of(new ExternalReferenceModel())); + media2.setExternalReference(new HashSet<>(List.of(new ExternalReferenceModel()))); List input = List.of(media1, media2); when(mediaRepository.saveAll(input)).thenReturn(input); @@ -168,7 +169,7 @@ void saveAll_SomeWithoutExternalReferences_ShouldSkipInvalidAndSaveValid() { // Given MediaModel valid = new MediaModel(); valid.setName("Valid Movie"); - valid.setExternalReference(List.of(new ExternalReferenceModel())); + valid.setExternalReference(new HashSet<>(List.of(new ExternalReferenceModel()))); MediaModel invalid = new MediaModel(); invalid.setName("No Ref Movie"); @@ -194,7 +195,7 @@ void saveAll_AllWithoutExternalReferences_ShouldReturnEmptyListWithoutCallingRep MediaModel media2 = new MediaModel(); media2.setName("No Ref B"); - media2.setExternalReference(new ArrayList<>()); + media2.setExternalReference(new HashSet<>()); // When List result = mediaService.saveAll(List.of(media1, media2));