diff --git a/.gitignore b/.gitignore index a7076f44..fb8e02bc 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,29 @@ docker\.env bin/ geek.log + +# Environment & Secrets (AIOS) +.env +.env.local +.env.*.local +*.key +*.pem + +# Dependencies (AIOS) +node_modules/ +node_modules + +# Build & Logs (AIOS) +*.log +logs/ + +# IDE & OS (AIOS) +.DS_Store +Thumbs.db +.idea/ +*.swp + +# AIOS Local (AIOS) +.aios-core/local/ +.claude/settings.local.json +.aios/install-log.txt diff --git a/src/main/java/com/espacogeek/geek/controllers/BatchController.java b/src/main/java/com/espacogeek/geek/controllers/BatchController.java index 52ab3a75..b4c098ba 100644 --- a/src/main/java/com/espacogeek/geek/controllers/BatchController.java +++ b/src/main/java/com/espacogeek/geek/controllers/BatchController.java @@ -66,7 +66,7 @@ public ResponseEntity runJob(@PathVariable Long executionId) { } @GetMapping("/stop/{executionId}") - @PreAuthorize("hasRole('aadmin')") + @PreAuthorize("hasRole('admin')") public ResponseEntity stopJob(@PathVariable Long executionId) { try { boolean result = jobOperator.stop(executionId); diff --git a/src/main/java/com/espacogeek/geek/controllers/MediaController.java b/src/main/java/com/espacogeek/geek/controllers/MediaController.java index b8d095dc..e4ce6265 100644 --- a/src/main/java/com/espacogeek/geek/controllers/MediaController.java +++ b/src/main/java/com/espacogeek/geek/controllers/MediaController.java @@ -43,7 +43,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 (name == null && id == null || name == "" && id == null) { return new MediaPage(); } @@ -62,7 +62,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 (name == null && id == null || name == "" && id == null) { return new MediaPage(); } @@ -81,7 +81,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 (name == null && id == null || name == "" && id == null) { return new MediaPage(); } @@ -101,7 +101,7 @@ public MediaPage getVisualNovel(@Argument(name = "id") Integer id, @Argument(nam MediaPage response = new MediaPage(); name = name == null ? null : name.trim(); - if (name == null & id == null || name == "" & id == null) { + if (name == null && id == null || name == "" && id == null) { return response; } @@ -119,7 +119,7 @@ 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 (name == null && id == null || name == "" && id == null) { return new MediaPage(); } diff --git a/src/main/java/com/espacogeek/geek/data/api/MediaApi.java b/src/main/java/com/espacogeek/geek/data/api/MediaApi.java index a0f4903a..02a43b66 100644 --- a/src/main/java/com/espacogeek/geek/data/api/MediaApi.java +++ b/src/main/java/com/espacogeek/geek/data/api/MediaApi.java @@ -1,6 +1,7 @@ package com.espacogeek.geek.data.api; import java.util.List; +import java.io.InputStream; import lombok.Getter; import org.json.simple.JSONArray; @@ -45,6 +46,10 @@ default JSONArray updateTitles() { throw new UnsupportedOperationException(); } + default InputStream updateTitlesStream() { + throw new UnsupportedOperationException(); + } + default MediaModel getDetails(Integer id) { throw new UnsupportedOperationException(); } 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 4642216f..1e682b01 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 @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.io.InputStream; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -64,6 +65,12 @@ public JSONArray updateTitles() { return DataJumpUtils.getDataJumpTMDBArray(DataJumpTypeTMDB.MOVIE); } + @Override + @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) + public InputStream updateTitlesStream() { + return DataJumpUtils.getDataJumpTMDBStream(DataJumpTypeTMDB.MOVIE); + } + /** * @see MediaApi#updateTitles( */ 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 005ec7ba..0627e860 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 @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.io.InputStream; import info.movito.themoviedbapi.model.core.NamedIdElement; import lombok.RequiredArgsConstructor; @@ -69,6 +70,12 @@ public JSONArray updateTitles() { return DataJumpUtils.getDataJumpTMDBArray(DataJumpTypeTMDB.SERIES); } + @Override + @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) + public InputStream updateTitlesStream() { + return DataJumpUtils.getDataJumpTMDBStream(DataJumpTypeTMDB.SERIES); + } + /** * @see MediaApi#updateTitles() */ 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 8f41c6f5..57474aa3 100644 --- a/src/main/java/com/espacogeek/geek/services/impl/MediaServiceImpl.java +++ b/src/main/java/com/espacogeek/geek/services/impl/MediaServiceImpl.java @@ -106,7 +106,10 @@ public List saveAll(List medias) { public MediaPage findSerieByIdOrName(Integer id, String name, Pageable pageable) { Page results; if (id != null) { - results = (Page) this.mediaRepository.findById(id).orElseGet(null); + var medias = new ArrayList(); + this.mediaRepository.findById(id).ifPresent(medias::add); + Pageable safePageable = pageable != null ? pageable : Pageable.unpaged(); + results = new PageImpl<>(medias, safePageable, medias.size()); } else { results = this.mediaRepository.findMediaByNameOrAlternativeTitleAndMediaCategory(name, name, mediaCategoryService.findById(MediaDataController.MediaType.SERIE.getId()).get().getId(), pageable); } @@ -139,7 +142,10 @@ public MediaPage findSerieByIdOrName(Integer id, String name, Map results; if (id != null) { - results = (Page) this.mediaRepository.findById(id).orElseGet(null); + var medias = new ArrayList(); + this.mediaRepository.findById(id).ifPresent(medias::add); + Pageable safePageable = pageable != null ? pageable : Pageable.unpaged(); + results = new PageImpl<>(medias, safePageable, medias.size()); } else { results = this.mediaRepository.findMediaByNameOrAlternativeTitleAndMediaCategory(name, name, mediaCategoryService.findById(MediaDataController.MediaType.GAME.getId()).get().getId(), pageable); } @@ -147,11 +153,15 @@ public MediaPage findGameByIdOrName(Integer id, String name, Pageable pageable) } @Override + @SuppressWarnings("unchecked") public MediaPage findVisualNovelByIdOrName(Integer id, String name, Pageable pageable) { Page results; if(id != null) { - results = (Page) this.mediaRepository.findById(id).orElseGet(null); + var medias = new ArrayList(); + this.mediaRepository.findById(id).ifPresent(medias::add); + Pageable safePageable = pageable != null ? pageable : Pageable.unpaged(); + results = new PageImpl<>(medias, safePageable, medias.size()); } else { results = this.mediaRepository.findMediaByNameOrAlternativeTitleAndMediaCategory(name, name, mediaCategoryService.findById(MediaDataController.MediaType.VN.getId()).get().getId(), pageable); } @@ -278,15 +288,15 @@ public MediaPage findMovieByIdOrName(Integer id, String name, Map results; if (id != null) { - results = (Page) this.mediaRepository.findById(id).orElseGet(null); + var medias = new ArrayList(); + this.mediaRepository.findById(id).ifPresent(medias::add); + Pageable safePageable = pageable != null ? pageable : Pageable.unpaged(); + results = new PageImpl<>(medias, safePageable, medias.size()); } else { results = (Page) this.mediaRepository.findMediaByNameOrAlternativeTitleAndMediaCategory(name, name, mediaCategoryService.findById(MediaDataController.MediaType.ANIME_SERIE.getId()).get().getId(), pageable); } @@ -301,7 +311,10 @@ public MediaPage findAnimeByIdOrName(Integer id, String name, Pageable pageable) public MediaPage findMovieByIdOrName(Integer id, String name, Pageable pageable) { Page results; if (id != null) { - results = (Page) this.mediaRepository.findById(id).orElseGet(null); + var medias = new ArrayList(); + this.mediaRepository.findById(id).ifPresent(medias::add); + Pageable safePageable = pageable != null ? pageable : Pageable.unpaged(); + results = new PageImpl<>(medias, safePageable, medias.size()); } else { results = (Page) this.mediaRepository.findMediaByNameOrAlternativeTitleAndMediaCategory(name, name, mediaCategoryService.findById(MediaDataController.MediaType.MOVIE.getId()).get().getId(), pageable); } @@ -330,16 +343,21 @@ private MediaPage mountMediaPage(Page medias) { var mediasList = medias.getContent(); for (MediaModel mediaModel : mediasList) { - switch (mediaModel.getMediaCategory().getId()) { - case 5: - case 1: - serieController.updateArtworks(mediaModel, null); - case 7: - case 4: - genericMediaDataController.updateArtworks(mediaModel, null, typeReferenceService.findById(MediaDataController.ExternalReferenceType.TMDB.getId()).get(), movieAPI); - case 2: - case 3: - genericMediaDataController.updateArtworks(mediaModel, null, typeReferenceService.findById(MediaDataController.ExternalReferenceType.IGDB.getId()).get(), gamesAndVNsAPI); + if (MediaUtils.updateMediaWhenLastTimeUpdateMoreThanOneDay(mediaModel)) { + switch (mediaModel.getMediaCategory().getId()) { + case 5: + case 1: + serieController.updateArtworks(mediaModel, null); + break; + case 7: + case 4: + genericMediaDataController.updateArtworks(mediaModel, null, typeReferenceService.findById(MediaDataController.ExternalReferenceType.TMDB.getId()).get(), movieAPI); + break; + case 2: + case 3: + genericMediaDataController.updateArtworks(mediaModel, null, typeReferenceService.findById(MediaDataController.ExternalReferenceType.IGDB.getId()).get(), gamesAndVNsAPI); + break; + } } } diff --git a/src/main/java/com/espacogeek/geek/utils/DataJumpUtils.java b/src/main/java/com/espacogeek/geek/utils/DataJumpUtils.java index 42410612..e6e15921 100644 --- a/src/main/java/com/espacogeek/geek/utils/DataJumpUtils.java +++ b/src/main/java/com/espacogeek/geek/utils/DataJumpUtils.java @@ -1,11 +1,14 @@ package com.espacogeek.geek.utils; -import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.text.MessageFormat; import java.time.LocalDateTime; import java.util.zip.GZIPInputStream; +import lombok.Getter; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; @@ -21,7 +24,8 @@ public final class DataJumpUtils { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DataJumpUtils.class); - public static enum DataJumpTypeTMDB { + @Getter + public enum DataJumpTypeTMDB { MOVIE("movie"), SERIES("tv_series"), PERSON("person"); private final String value; @@ -30,22 +34,15 @@ public static enum DataJumpTypeTMDB { this.value = value; } - public String getValue() { - return value; - } } /** * - * This function get the daily datajump available by tmdb + * This function get the daily datajump available by tmdb as a stream * - * @return a JSON Array with all serie titles - * @throws IOException - * @throws ParseException + * @return an uncompressed GZIP InputStream with all titles */ - @SuppressWarnings({ "unchecked", "null" }) - @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) - public static JSONArray getDataJumpTMDBArray(final @NotNull DataJumpTypeTMDB type) { + public static InputStream getDataJumpTMDBStream(final @NotNull DataJumpTypeTMDB type) { var now = LocalDateTime.now(); // formatting the date to do request as tmdb pattern @@ -54,7 +51,7 @@ public static JSONArray getDataJumpTMDBArray(final @NotNull DataJumpTypeTMDB typ var year = String.valueOf(now.getYear()).replace(".", ""); var client = new OkHttpClient().newBuilder().build(); - Request request = null; + Request request; try { request = new Request.Builder() .url(MessageFormat.format("http://files.tmdb.org/p/exports/{3}_ids_{0}_{1}_{2}.json.gz", month, day, year, type.getValue())) @@ -68,29 +65,43 @@ public static JSONArray getDataJumpTMDBArray(final @NotNull DataJumpTypeTMDB typ Response response = null; try { response = client.newCall(request).execute(); + if (!response.isSuccessful()) { + log.error("Failed to fetch TMDB daily export: {}", response.message()); + response.close(); + throw new com.espacogeek.geek.exception.RequestException(); + } + assert response.body() != null; + return new GZIPInputStream(response.body().byteStream()); } catch (IOException e) { log.error(e.getMessage(), e); + if (response != null) response.close(); + throw new com.espacogeek.geek.exception.RequestException(); } + } - GZIPInputStream inputStream = null; - String[] json = null; - try { - inputStream = new GZIPInputStream(new ByteArrayInputStream(response.body().bytes())); - json = new String(inputStream.readAllBytes()).split("\n"); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - + /** + * + * This function get the daily datajump available by tmdb + * + * @return a JSON Array with all serie titles + */ + @SuppressWarnings({ "unchecked" }) + @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 2000), retryFor = com.espacogeek.geek.exception.RequestException.class) + public static JSONArray getDataJumpTMDBArray(final @NotNull DataJumpTypeTMDB type) { JSONArray jsonArray = new JSONArray(); - for (var item : json) { + try (InputStream is = getDataJumpTMDBStream(type); + BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + String line; JSONParser parser = new JSONParser(); - JSONObject jsonObject = null; - try { - jsonObject = (JSONObject) parser.parse(item); - } catch (ParseException e) { - log.error(e.getMessage(), e); + while ((line = reader.readLine()) != null) { + try { + jsonArray.add(parser.parse(line)); + } catch (ParseException e) { + log.error("Error parsing TMDB JSON line: {}", e.getMessage()); + } } - jsonArray.add(jsonObject); + } catch (Exception e) { + log.error("Error reading TMDB array: {}", e.getMessage()); } return jsonArray; } diff --git a/src/main/java/com/espacogeek/geek/utils/MediaUtils.java b/src/main/java/com/espacogeek/geek/utils/MediaUtils.java index dcc56972..cac3a29e 100644 --- a/src/main/java/com/espacogeek/geek/utils/MediaUtils.java +++ b/src/main/java/com/espacogeek/geek/utils/MediaUtils.java @@ -34,7 +34,7 @@ public abstract class MediaUtils { * update was more than one day ago * or if the update date is null), false otherwise */ - private static Boolean updateMediaWhenLastTimeUpdateMoreThanOneDay(MediaModel media) { + public static Boolean updateMediaWhenLastTimeUpdateMoreThanOneDay(MediaModel media) { if (media == null) return false;