diff --git a/src/main/java/com/magamochi/catalog/model/dto/MangaDTO.java b/src/main/java/com/magamochi/catalog/model/dto/MangaDTO.java index 4cdfe51..091186a 100644 --- a/src/main/java/com/magamochi/catalog/model/dto/MangaDTO.java +++ b/src/main/java/com/magamochi/catalog/model/dto/MangaDTO.java @@ -4,9 +4,9 @@ import static java.util.Objects.isNull; import com.magamochi.catalog.model.entity.Manga; import com.magamochi.catalog.model.entity.MangaAlternativeTitle; +import com.magamochi.catalog.model.entity.MangaContentProvider; import com.magamochi.catalog.model.enumeration.MangaStatus; -import com.magamochi.model.entity.MangaChapter; -import com.magamochi.model.entity.MangaContentProvider; +import com.magamochi.content.model.entity.MangaContent; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.OffsetDateTime; @@ -59,9 +59,9 @@ public record MangaDTO( @NotNull Integer chaptersDownloaded, @NotNull Boolean supportsChapterFetch) { public static MangaProviderDTO from(MangaContentProvider mangaContentProvider) { - var chapters = mangaContentProvider.getMangaChapters(); + var chapters = mangaContentProvider.getMangaContents(); var chaptersAvailable = chapters.size(); - var chaptersDownloaded = (int) chapters.stream().filter(MangaChapter::getDownloaded).count(); + var chaptersDownloaded = (int) chapters.stream().filter(MangaContent::getDownloaded).count(); return new MangaProviderDTO( mangaContentProvider.getId(), @@ -69,7 +69,7 @@ public record MangaDTO( mangaContentProvider.getContentProvider().isActive(), chaptersAvailable, chaptersDownloaded, - mangaContentProvider.getContentProvider().getSupportsChapterFetch()); + mangaContentProvider.getContentProvider().getSupportsContentFetch()); } } } diff --git a/src/main/java/com/magamochi/catalog/model/entity/Manga.java b/src/main/java/com/magamochi/catalog/model/entity/Manga.java index 8a307ed..8662417 100644 --- a/src/main/java/com/magamochi/catalog/model/entity/Manga.java +++ b/src/main/java/com/magamochi/catalog/model/entity/Manga.java @@ -3,7 +3,6 @@ package com.magamochi.catalog.model.entity; import com.magamochi.catalog.model.enumeration.MangaState; import com.magamochi.catalog.model.enumeration.MangaStatus; import com.magamochi.image.model.entity.Image; -import com.magamochi.model.entity.MangaContentProvider; import com.magamochi.model.entity.UserFavoriteManga; import jakarta.persistence.*; import java.time.Instant; diff --git a/src/main/java/com/magamochi/model/entity/MangaContentProvider.java b/src/main/java/com/magamochi/catalog/model/entity/MangaContentProvider.java similarity index 86% rename from src/main/java/com/magamochi/model/entity/MangaContentProvider.java rename to src/main/java/com/magamochi/catalog/model/entity/MangaContentProvider.java index 9bfcc2c..cd4e150 100644 --- a/src/main/java/com/magamochi/model/entity/MangaContentProvider.java +++ b/src/main/java/com/magamochi/catalog/model/entity/MangaContentProvider.java @@ -1,6 +1,6 @@ -package com.magamochi.model.entity; +package com.magamochi.catalog.model.entity; -import com.magamochi.catalog.model.entity.Manga; +import com.magamochi.content.model.entity.MangaContent; import com.magamochi.ingestion.model.entity.ContentProvider; import jakarta.persistence.*; import java.time.Instant; @@ -34,7 +34,7 @@ public class MangaContentProvider { private String url; @OneToMany(mappedBy = "mangaContentProvider") - List mangaChapters; + List mangaContents; @CreationTimestamp private Instant createdAt; diff --git a/src/main/java/com/magamochi/catalog/model/repository/MangaContentProviderRepository.java b/src/main/java/com/magamochi/catalog/model/repository/MangaContentProviderRepository.java index de3f1bd..7632849 100644 --- a/src/main/java/com/magamochi/catalog/model/repository/MangaContentProviderRepository.java +++ b/src/main/java/com/magamochi/catalog/model/repository/MangaContentProviderRepository.java @@ -1,6 +1,6 @@ package com.magamochi.catalog.model.repository; -import com.magamochi.model.entity.MangaContentProvider; +import com.magamochi.catalog.model.entity.MangaContentProvider; import jakarta.validation.constraints.NotBlank; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/magamochi/catalog/service/LanguageService.java b/src/main/java/com/magamochi/catalog/service/LanguageService.java index 83833eb..fe8af57 100644 --- a/src/main/java/com/magamochi/catalog/service/LanguageService.java +++ b/src/main/java/com/magamochi/catalog/service/LanguageService.java @@ -11,7 +11,7 @@ import org.springframework.stereotype.Service; public class LanguageService { public final LanguageRepository languageRepository; - public Language getOrThrow(String code) { + public Language find(String code) { return languageRepository .findByCodeIgnoreCase(code) .orElseThrow(() -> new NotFoundException("Language with code " + code + " not found")); diff --git a/src/main/java/com/magamochi/catalog/service/MangaContentProviderService.java b/src/main/java/com/magamochi/catalog/service/MangaContentProviderService.java new file mode 100644 index 0000000..85aa949 --- /dev/null +++ b/src/main/java/com/magamochi/catalog/service/MangaContentProviderService.java @@ -0,0 +1,22 @@ +package com.magamochi.catalog.service; + +import com.magamochi.catalog.model.entity.MangaContentProvider; +import com.magamochi.catalog.model.repository.MangaContentProviderRepository; +import com.magamochi.common.exception.NotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MangaContentProviderService { + private final MangaContentProviderRepository mangaContentProviderRepository; + + public MangaContentProvider find(long mangaContentProviderId) { + return mangaContentProviderRepository + .findById(mangaContentProviderId) + .orElseThrow( + () -> + new NotFoundException( + "MangaContentProvider not found - ID: " + mangaContentProviderId)); + } +} diff --git a/src/main/java/com/magamochi/catalog/service/MangaIngestService.java b/src/main/java/com/magamochi/catalog/service/MangaIngestService.java index 3d26e35..5f3d919 100644 --- a/src/main/java/com/magamochi/catalog/service/MangaIngestService.java +++ b/src/main/java/com/magamochi/catalog/service/MangaIngestService.java @@ -2,11 +2,11 @@ package com.magamochi.catalog.service; import static java.util.Objects.isNull; +import com.magamochi.catalog.model.entity.MangaContentProvider; import com.magamochi.catalog.model.entity.MangaIngestReview; import com.magamochi.catalog.model.repository.MangaContentProviderRepository; import com.magamochi.catalog.model.repository.MangaIngestReviewRepository; import com.magamochi.ingestion.service.ContentProviderService; -import com.magamochi.model.entity.MangaContentProvider; import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; diff --git a/src/main/java/com/magamochi/common/config/RabbitConfig.java b/src/main/java/com/magamochi/common/config/RabbitConfig.java index 9a3ba42..83508a3 100644 --- a/src/main/java/com/magamochi/common/config/RabbitConfig.java +++ b/src/main/java/com/magamochi/common/config/RabbitConfig.java @@ -16,6 +16,9 @@ public class RabbitConfig { @Value("${queues.manga-ingest}") private String mangaIngestQueue; + @Value("${queues.manga-content-ingest}") + private String mangaContentIngestQueue; + @Value("${queues.provider-page-ingest}") private String providerPageIngestQueue; @@ -62,6 +65,11 @@ public class RabbitConfig { null); } + @Bean + public Queue mangaContentIngestQueue() { + return new Queue(mangaContentIngestQueue, false); + } + @Bean public Queue mangaIngestQueue() { return new Queue(mangaIngestQueue, false); diff --git a/src/main/java/com/magamochi/common/model/enumeration/ContentType.java b/src/main/java/com/magamochi/common/model/enumeration/ContentType.java index 79726ce..f236f37 100644 --- a/src/main/java/com/magamochi/common/model/enumeration/ContentType.java +++ b/src/main/java/com/magamochi/common/model/enumeration/ContentType.java @@ -1,5 +1,7 @@ package com.magamochi.common.model.enumeration; public enum ContentType { - MANGA_COVER + MANGA_COVER, + CHAPTER, + VOLUME } diff --git a/src/main/java/com/magamochi/common/queue/command/MangaContentIngestCommand.java b/src/main/java/com/magamochi/common/queue/command/MangaContentIngestCommand.java new file mode 100644 index 0000000..b04bd82 --- /dev/null +++ b/src/main/java/com/magamochi/common/queue/command/MangaContentIngestCommand.java @@ -0,0 +1,9 @@ +package com.magamochi.common.queue.command; + +import jakarta.validation.constraints.NotBlank; + +public record MangaContentIngestCommand( + long mangaContentProviderId, + @NotBlank String title, + @NotBlank String url, + @NotBlank String languageCode) {} diff --git a/src/main/java/com/magamochi/content/controller/ContentController.java b/src/main/java/com/magamochi/content/controller/ContentController.java new file mode 100644 index 0000000..e107044 --- /dev/null +++ b/src/main/java/com/magamochi/content/controller/ContentController.java @@ -0,0 +1,31 @@ +package com.magamochi.content.controller; + +import com.magamochi.common.model.dto.DefaultResponseDTO; +import com.magamochi.content.model.dto.MangaContentDTO; +import com.magamochi.content.service.ContentService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/content") +@RequiredArgsConstructor +public class ContentController { + private final ContentService contentService; + + @Operation( + summary = "Get the content for a specific manga/content provider combination", + description = "Retrieve the content for a specific manga/content provider combination.", + tags = {"Content"}, + operationId = "getMangaProviderContent") + @GetMapping("/{mangaContentProviderId}") + public DefaultResponseDTO> getMangaProviderContent( + @PathVariable @NotNull Long mangaContentProviderId) { + return DefaultResponseDTO.ok(contentService.getContent(mangaContentProviderId)); + } +} diff --git a/src/main/java/com/magamochi/model/dto/LanguageDTO.java b/src/main/java/com/magamochi/content/model/dto/LanguageDTO.java similarity index 92% rename from src/main/java/com/magamochi/model/dto/LanguageDTO.java rename to src/main/java/com/magamochi/content/model/dto/LanguageDTO.java index 32c409d..3f70990 100644 --- a/src/main/java/com/magamochi/model/dto/LanguageDTO.java +++ b/src/main/java/com/magamochi/content/model/dto/LanguageDTO.java @@ -1,4 +1,4 @@ -package com.magamochi.model.dto; +package com.magamochi.content.model.dto; import static java.util.Objects.isNull; diff --git a/src/main/java/com/magamochi/content/model/dto/MangaContentDTO.java b/src/main/java/com/magamochi/content/model/dto/MangaContentDTO.java new file mode 100644 index 0000000..86a52ca --- /dev/null +++ b/src/main/java/com/magamochi/content/model/dto/MangaContentDTO.java @@ -0,0 +1,21 @@ +package com.magamochi.content.model.dto; + +import com.magamochi.content.model.entity.MangaContent; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record MangaContentDTO( + @NotNull Long id, + @NotBlank String title, + @NotNull Boolean downloaded, + @NotNull Boolean isRead, + LanguageDTO language) { + public static MangaContentDTO from(MangaContent mangaContent) { + return new MangaContentDTO( + mangaContent.getId(), + mangaContent.getTitle(), + mangaContent.getDownloaded(), + false, + LanguageDTO.from(mangaContent.getLanguage())); + } +} diff --git a/src/main/java/com/magamochi/model/entity/MangaChapter.java b/src/main/java/com/magamochi/content/model/entity/MangaContent.java similarity index 67% rename from src/main/java/com/magamochi/model/entity/MangaChapter.java rename to src/main/java/com/magamochi/content/model/entity/MangaContent.java index 1e6ed63..000683e 100644 --- a/src/main/java/com/magamochi/model/entity/MangaChapter.java +++ b/src/main/java/com/magamochi/content/model/entity/MangaContent.java @@ -1,6 +1,8 @@ -package com.magamochi.model.entity; +package com.magamochi.content.model.entity; import com.magamochi.catalog.model.entity.Language; +import com.magamochi.catalog.model.entity.MangaContentProvider; +import com.magamochi.common.model.enumeration.ContentType; import jakarta.persistence.*; import java.time.Instant; import java.util.List; @@ -9,13 +11,13 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @Entity -@Table(name = "manga_chapters") +@Table(name = "manga_contents") @Builder @NoArgsConstructor @AllArgsConstructor @Getter @Setter -public class MangaChapter { +public class MangaContent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -24,22 +26,20 @@ public class MangaChapter { @JoinColumn(name = "manga_content_provider_id") private MangaContentProvider mangaContentProvider; + @Builder.Default private ContentType type = ContentType.CHAPTER; + private String title; private String url; @Builder.Default private Boolean downloaded = false; - @Builder.Default private Boolean read = false; - @CreationTimestamp private Instant createdAt; @UpdateTimestamp private Instant updatedAt; - @OneToMany(mappedBy = "mangaChapter") - private List mangaChapterImages; - - private Integer chapterNumber; + @OneToMany(mappedBy = "mangaContent") + private List mangaContentImages; @ManyToOne @JoinColumn(name = "language_id") diff --git a/src/main/java/com/magamochi/model/entity/MangaChapterImage.java b/src/main/java/com/magamochi/content/model/entity/MangaContentImage.java similarity index 75% rename from src/main/java/com/magamochi/model/entity/MangaChapterImage.java rename to src/main/java/com/magamochi/content/model/entity/MangaContentImage.java index 33cf78b..f0fc463 100644 --- a/src/main/java/com/magamochi/model/entity/MangaChapterImage.java +++ b/src/main/java/com/magamochi/content/model/entity/MangaContentImage.java @@ -1,4 +1,4 @@ -package com.magamochi.model.entity; +package com.magamochi.content.model.entity; import com.magamochi.image.model.entity.Image; import jakarta.persistence.*; @@ -8,20 +8,20 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @Entity -@Table(name = "manga_chapter_images") +@Table(name = "manga_content_images") @Builder @NoArgsConstructor @AllArgsConstructor @Getter @Setter -public class MangaChapterImage { +public class MangaContentImage { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne - @JoinColumn(name = "manga_chapter_id") - private MangaChapter mangaChapter; + @JoinColumn(name = "manga_content_id") + private MangaContent mangaContent; @OneToOne @JoinColumn(name = "image_id") diff --git a/src/main/java/com/magamochi/content/model/repository/MangaChapterImageRepository.java b/src/main/java/com/magamochi/content/model/repository/MangaChapterImageRepository.java new file mode 100644 index 0000000..a5b4659 --- /dev/null +++ b/src/main/java/com/magamochi/content/model/repository/MangaChapterImageRepository.java @@ -0,0 +1,10 @@ +package com.magamochi.content.model.repository; + +import com.magamochi.content.model.entity.MangaContent; +import com.magamochi.content.model.entity.MangaContentImage; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MangaChapterImageRepository extends JpaRepository { + List findAllByMangaContent(MangaContent mangaContent); +} diff --git a/src/main/java/com/magamochi/content/model/repository/MangaContentRepository.java b/src/main/java/com/magamochi/content/model/repository/MangaContentRepository.java new file mode 100644 index 0000000..2aadd67 --- /dev/null +++ b/src/main/java/com/magamochi/content/model/repository/MangaContentRepository.java @@ -0,0 +1,8 @@ +package com.magamochi.content.model.repository; + +import com.magamochi.content.model.entity.MangaContent; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MangaContentRepository extends JpaRepository { + boolean existsByMangaContentProvider_IdAndUrlIgnoreCase(Long mangaContentProviderId, String url); +} diff --git a/src/main/java/com/magamochi/content/queue/consumer/MangaContentIngestConsumer.java b/src/main/java/com/magamochi/content/queue/consumer/MangaContentIngestConsumer.java new file mode 100644 index 0000000..17df5bd --- /dev/null +++ b/src/main/java/com/magamochi/content/queue/consumer/MangaContentIngestConsumer.java @@ -0,0 +1,22 @@ +package com.magamochi.content.queue.consumer; + +import com.magamochi.common.queue.command.MangaContentIngestCommand; +import com.magamochi.content.service.ContentIngestService; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class MangaContentIngestConsumer { + private final ContentIngestService contentIngestService; + + @RabbitListener(queues = "${queues.manga-content-ingest}") + public void receiveMangaContentIngestCommand(MangaContentIngestCommand command) { + log.info("Received manga content ingest command: {}", command); + contentIngestService.ingest( + command.mangaContentProviderId(), command.title(), command.url(), command.languageCode()); + } +} diff --git a/src/main/java/com/magamochi/content/service/ContentIngestService.java b/src/main/java/com/magamochi/content/service/ContentIngestService.java new file mode 100644 index 0000000..06c6da1 --- /dev/null +++ b/src/main/java/com/magamochi/content/service/ContentIngestService.java @@ -0,0 +1,56 @@ +package com.magamochi.content.service; + +import com.magamochi.catalog.service.LanguageService; +import com.magamochi.catalog.service.MangaContentProviderService; +import com.magamochi.content.model.entity.MangaContent; +import com.magamochi.content.model.repository.MangaContentRepository; +import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class ContentIngestService { + private final MangaContentProviderService mangaContentProviderService; + private final LanguageService languageService; + + private final MangaContentRepository mangaContentRepository; + + public void ingest( + long mangaContentProviderId, + @NotBlank String title, + @NotBlank String url, + @NotBlank String languageCode) { + log.info("Ingesting Manga Content ({}) for provider {}", title, mangaContentProviderId); + + var mangaContentProvider = mangaContentProviderService.find(mangaContentProviderId); + + if (mangaContentRepository.existsByMangaContentProvider_IdAndUrlIgnoreCase( + mangaContentProvider.getId(), url)) { + log.info( + "Manga Content ({}) for provider {} already exists. Skipped.", + title, + mangaContentProviderId); + return; + } + + var language = languageService.find(languageCode); + + var mangaContent = + mangaContentRepository.save( + MangaContent.builder() + .mangaContentProvider(mangaContentProvider) + .title(title) + .url(url) + .language(language) + .build()); + + log.info( + "Ingested Manga Content ({}) for provider {}: {}", + title, + mangaContentProviderId, + mangaContent.getId()); + } +} diff --git a/src/main/java/com/magamochi/content/service/ContentService.java b/src/main/java/com/magamochi/content/service/ContentService.java new file mode 100644 index 0000000..86c3861 --- /dev/null +++ b/src/main/java/com/magamochi/content/service/ContentService.java @@ -0,0 +1,25 @@ +package com.magamochi.content.service; + +import com.magamochi.catalog.service.MangaContentProviderService; +import com.magamochi.content.model.dto.MangaContentDTO; +import com.magamochi.content.model.entity.MangaContent; +import jakarta.validation.constraints.NotNull; +import java.util.Comparator; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ContentService { + private final MangaContentProviderService mangaContentProviderService; + + public List getContent(@NotNull Long mangaContentProviderId) { + var mangaContentProvider = mangaContentProviderService.find(mangaContentProviderId); + + return mangaContentProvider.getMangaContents().stream() + .sorted(Comparator.comparing(MangaContent::getId)) + .map(MangaContentDTO::from) + .toList(); + } +} diff --git a/src/main/java/com/magamochi/controller/MangaController.java b/src/main/java/com/magamochi/controller/MangaController.java index 3ffdd3d..5c17b85 100644 --- a/src/main/java/com/magamochi/controller/MangaController.java +++ b/src/main/java/com/magamochi/controller/MangaController.java @@ -1,10 +1,8 @@ package com.magamochi.controller; import com.magamochi.common.model.dto.DefaultResponseDTO; -import com.magamochi.model.dto.MangaChapterDTO; import com.magamochi.service.OldMangaService; import io.swagger.v3.oas.annotations.Operation; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -14,17 +12,6 @@ import org.springframework.web.bind.annotation.*; public class MangaController { private final OldMangaService oldMangaService; - @Operation( - summary = "Get the available chapters for a specific manga/provider combination", - description = "Retrieve a list of manga chapters for a specific manga/provider combination.", - tags = {"Manga"}, - operationId = "getMangaChapters") - @GetMapping("/{mangaProviderId}/chapters") - public DefaultResponseDTO> getMangaChapters( - @PathVariable Long mangaProviderId) { - return DefaultResponseDTO.ok(oldMangaService.getMangaChapters(mangaProviderId)); - } - @Operation( summary = "Fetch all chapters", description = "Fetch all not yet downloaded chapters from the provider", @@ -37,18 +24,6 @@ public class MangaController { return DefaultResponseDTO.ok().build(); } - @Operation( - summary = "Fetch the available chapters for a specific manga/provider combination", - description = "Fetch a list of manga chapters for a specific manga/provider combination.", - tags = {"Manga"}, - operationId = "fetchMangaChapters") - @PostMapping("/{mangaProviderId}/fetch-chapters") - public DefaultResponseDTO fetchMangaChapters(@PathVariable Long mangaProviderId) { - oldMangaService.fetchMangaChapters(mangaProviderId); - - return DefaultResponseDTO.ok().build(); - } - @Operation( summary = "Follow the manga specified by its ID", description = "Follow the manga specified by its ID.", diff --git a/src/main/java/com/magamochi/ingestion/controller/IngestionController.java b/src/main/java/com/magamochi/ingestion/controller/IngestionController.java index 7b46f07..a4027dd 100644 --- a/src/main/java/com/magamochi/ingestion/controller/IngestionController.java +++ b/src/main/java/com/magamochi/ingestion/controller/IngestionController.java @@ -51,4 +51,18 @@ public class IngestionController { return DefaultResponseDTO.ok().build(); } + + @Operation( + summary = "Fetch content list from a content provider", + description = + "Triggers the ingestion process for a specific content provider, fetching content list and queuing it for processing.", + tags = {"Ingestion"}, + operationId = "fetchContentProviderContentList") + @PostMapping("/manga-content-providers/{mangaContentProviderId}/fetch") + public DefaultResponseDTO fetchContentProviderContentList( + @PathVariable Long mangaContentProviderId) { + ingestionService.fetchMangaContentProviderContentList(mangaContentProviderId); + + return DefaultResponseDTO.ok().build(); + } } diff --git a/src/main/java/com/magamochi/ingestion/model/dto/ContentInfoDTO.java b/src/main/java/com/magamochi/ingestion/model/dto/ContentInfoDTO.java new file mode 100644 index 0000000..d967cbf --- /dev/null +++ b/src/main/java/com/magamochi/ingestion/model/dto/ContentInfoDTO.java @@ -0,0 +1,6 @@ +package com.magamochi.ingestion.model.dto; + +import jakarta.validation.constraints.NotBlank; + +public record ContentInfoDTO( + @NotBlank String title, @NotBlank String url, @NotBlank String languageCode) {} diff --git a/src/main/java/com/magamochi/ingestion/model/entity/ContentProvider.java b/src/main/java/com/magamochi/ingestion/model/entity/ContentProvider.java index 85baedf..26ab447 100644 --- a/src/main/java/com/magamochi/ingestion/model/entity/ContentProvider.java +++ b/src/main/java/com/magamochi/ingestion/model/entity/ContentProvider.java @@ -1,6 +1,6 @@ package com.magamochi.ingestion.model.entity; -import com.magamochi.model.entity.MangaContentProvider; +import com.magamochi.catalog.model.entity.MangaContentProvider; import jakarta.persistence.*; import java.time.Instant; import java.util.List; @@ -24,7 +24,7 @@ public class ContentProvider { private boolean active; - private Boolean supportsChapterFetch; + private Boolean supportsContentFetch; private Boolean manualImport; diff --git a/src/main/java/com/magamochi/ingestion/model/repository/ContentProviderRepository.java b/src/main/java/com/magamochi/ingestion/model/repository/ContentProviderRepository.java index 8232dec..8f1e612 100644 --- a/src/main/java/com/magamochi/ingestion/model/repository/ContentProviderRepository.java +++ b/src/main/java/com/magamochi/ingestion/model/repository/ContentProviderRepository.java @@ -1,9 +1,6 @@ package com.magamochi.ingestion.model.repository; import com.magamochi.ingestion.model.entity.ContentProvider; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -public interface ContentProviderRepository extends JpaRepository { - Optional findByNameIgnoreCase(String name); -} +public interface ContentProviderRepository extends JpaRepository {} diff --git a/src/main/java/com/magamochi/ingestion/providers/ContentProvider.java b/src/main/java/com/magamochi/ingestion/providers/ContentProvider.java index 17eb162..45fced2 100644 --- a/src/main/java/com/magamochi/ingestion/providers/ContentProvider.java +++ b/src/main/java/com/magamochi/ingestion/providers/ContentProvider.java @@ -1,12 +1,12 @@ package com.magamochi.ingestion.providers; -import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; -import com.magamochi.model.entity.MangaContentProvider; +import com.magamochi.catalog.model.entity.MangaContentProvider; +import com.magamochi.ingestion.model.dto.ContentInfoDTO; import java.util.List; import java.util.Map; public interface ContentProvider { - List getAvailableChapters(MangaContentProvider provider); + List getAvailableChapters(MangaContentProvider provider); Map getChapterImagesUrls(String chapterUrl); } diff --git a/src/main/java/com/magamochi/ingestion/providers/impl/MangaDexProvider.java b/src/main/java/com/magamochi/ingestion/providers/impl/MangaDexProvider.java index 5a46e81..7a2bb81 100644 --- a/src/main/java/com/magamochi/ingestion/providers/impl/MangaDexProvider.java +++ b/src/main/java/com/magamochi/ingestion/providers/impl/MangaDexProvider.java @@ -3,13 +3,13 @@ package com.magamochi.ingestion.providers.impl; import static java.util.Objects.isNull; import com.google.common.util.concurrent.RateLimiter; +import com.magamochi.catalog.model.entity.MangaContentProvider; import com.magamochi.client.MangaDexClient; import com.magamochi.common.exception.UnprocessableException; +import com.magamochi.ingestion.model.dto.ContentInfoDTO; import com.magamochi.ingestion.providers.ContentProvider; import com.magamochi.ingestion.providers.ContentProviders; import com.magamochi.ingestion.providers.ManualImportContentProvider; -import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; -import com.magamochi.model.entity.MangaContentProvider; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -26,8 +26,7 @@ public class MangaDexProvider implements ContentProvider, ManualImportContentPro private final RateLimiter mangaDexRateLimiter; @Override - public List getAvailableChapters( - MangaContentProvider provider) { + public List getAvailableChapters(MangaContentProvider provider) { try { mangaDexRateLimiter.acquire(); var response = @@ -85,10 +84,9 @@ public class MangaDexProvider implements ContentProvider, ManualImportContentPro }) .map( c -> - new ContentProviderMangaChapterResponseDTO( + new ContentInfoDTO( c.attributes().chapter() + " - " + c.attributes().title(), c.id().toString(), - c.attributes().chapter(), languagesToImport.get(c.attributes().translatedLanguage()))) .toList(); } catch (Exception e) { diff --git a/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreBlogProvider.java b/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreBlogProvider.java index 431b480..e8c5e61 100644 --- a/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreBlogProvider.java +++ b/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreBlogProvider.java @@ -1,11 +1,11 @@ package com.magamochi.ingestion.providers.impl; +import com.magamochi.catalog.model.entity.MangaContentProvider; +import com.magamochi.ingestion.model.dto.ContentInfoDTO; import com.magamochi.ingestion.model.dto.MangaInfoDTO; import com.magamochi.ingestion.providers.ContentProvider; import com.magamochi.ingestion.providers.ContentProviders; import com.magamochi.ingestion.providers.PagedContentProvider; -import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; -import com.magamochi.model.entity.MangaContentProvider; import java.io.IOException; import java.util.*; import java.util.regex.Pattern; @@ -27,8 +27,7 @@ public class MangaLivreBlogProvider implements ContentProvider, PagedContentProv private final String url = "https://mangalivre.blog/manga/"; @Override - public List getAvailableChapters( - MangaContentProvider mangaContentProvider) { + public List getAvailableChapters(MangaContentProvider mangaContentProvider) { log.info( "Getting available chapters from {}, manga {}", ContentProviders.MANGA_LIVRE_BLOG, @@ -49,8 +48,8 @@ public class MangaLivreBlogProvider implements ContentProvider, PagedContentProv var chapterNumberElement = linkElement.getElementsByClass("chapter-number").getFirst(); - return new ContentProviderMangaChapterResponseDTO( - chapterNumberElement.text(), linkElement.attr("href"), null, "pt-BR"); + return new ContentInfoDTO( + chapterNumberElement.text(), linkElement.attr("href"), "pt-BR"); }) .toList(); } catch (IOException | NoSuchElementException e) { diff --git a/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreProvider.java b/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreProvider.java index 5f0be16..e73b276 100644 --- a/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreProvider.java +++ b/src/main/java/com/magamochi/ingestion/providers/impl/MangaLivreProvider.java @@ -2,13 +2,13 @@ package com.magamochi.ingestion.providers.impl; import static java.util.Objects.nonNull; +import com.magamochi.catalog.model.entity.MangaContentProvider; +import com.magamochi.ingestion.model.dto.ContentInfoDTO; import com.magamochi.ingestion.model.dto.MangaInfoDTO; import com.magamochi.ingestion.providers.ContentProvider; import com.magamochi.ingestion.providers.ContentProviders; import com.magamochi.ingestion.providers.PagedContentProvider; import com.magamochi.ingestion.service.FlareService; -import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; -import com.magamochi.model.entity.MangaContentProvider; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -26,8 +26,7 @@ public class MangaLivreProvider implements ContentProvider, PagedContentProvider private final FlareService flareService; @Override - public List getAvailableChapters( - MangaContentProvider provider) { + public List getAvailableChapters(MangaContentProvider provider) { log.info( "Getting available chapters from {}, manga {}", ContentProviders.MANGA_LIVRE_TO, @@ -49,8 +48,7 @@ public class MangaLivreProvider implements ContentProvider, PagedContentProvider var url = linkElement.attr("href"); var title = linkElement.text(); - return new ContentProviderMangaChapterResponseDTO( - title.trim(), url.trim(), null, "pt-BR"); + return new ContentInfoDTO(title.trim(), url.trim(), "pt-BR"); }) .toList(); } catch (NoSuchElementException e) { diff --git a/src/main/java/com/magamochi/ingestion/providers/impl/PinkRosaScanProvider.java b/src/main/java/com/magamochi/ingestion/providers/impl/PinkRosaScanProvider.java index d631095..0760f30 100644 --- a/src/main/java/com/magamochi/ingestion/providers/impl/PinkRosaScanProvider.java +++ b/src/main/java/com/magamochi/ingestion/providers/impl/PinkRosaScanProvider.java @@ -3,13 +3,13 @@ package com.magamochi.ingestion.providers.impl; import static java.util.Objects.isNull; import static org.apache.commons.lang3.StringUtils.isBlank; +import com.magamochi.catalog.model.entity.MangaContentProvider; +import com.magamochi.ingestion.model.dto.ContentInfoDTO; import com.magamochi.ingestion.model.dto.MangaInfoDTO; import com.magamochi.ingestion.providers.ContentProvider; import com.magamochi.ingestion.providers.ContentProviders; import com.magamochi.ingestion.providers.PagedContentProvider; import com.magamochi.ingestion.service.FlareService; -import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; -import com.magamochi.model.entity.MangaContentProvider; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -27,8 +27,7 @@ public class PinkRosaScanProvider implements ContentProvider, PagedContentProvid private final FlareService flareService; @Override - public List getAvailableChapters( - MangaContentProvider provider) { + public List getAvailableChapters(MangaContentProvider provider) { log.info( "Getting available chapters from {}, manga {}", ContentProviders.PINK_ROSA_SCAN, @@ -57,11 +56,8 @@ public class PinkRosaScanProvider implements ContentProvider, PagedContentProvid .getFirst() .getElementsByClass("text-sm truncate") .getFirst(); - return new ContentProviderMangaChapterResponseDTO( - chapterTitleElement.text().trim(), - chapterItemElement.attr("href"), - null, - "pt-BR"); + return new ContentInfoDTO( + chapterTitleElement.text().trim(), chapterItemElement.attr("href"), "pt-BR"); }) .toList(); } catch (NoSuchElementException e) { diff --git a/src/main/java/com/magamochi/ingestion/queue/producer/MangaContentIngestProducer.java b/src/main/java/com/magamochi/ingestion/queue/producer/MangaContentIngestProducer.java new file mode 100644 index 0000000..68702c8 --- /dev/null +++ b/src/main/java/com/magamochi/ingestion/queue/producer/MangaContentIngestProducer.java @@ -0,0 +1,23 @@ +package com.magamochi.ingestion.queue.producer; + +import com.magamochi.common.queue.command.MangaContentIngestCommand; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class MangaContentIngestProducer { + private final RabbitTemplate rabbitTemplate; + + @Value("${queues.manga-content-ingest}") + private String mangaContentIngestQueue; + + public void sendMangaContentIngestCommand(MangaContentIngestCommand command) { + rabbitTemplate.convertAndSend(mangaContentIngestQueue, command); + log.info("Sent manga content ingest command: {}", command); + } +} diff --git a/src/main/java/com/magamochi/ingestion/service/IngestionService.java b/src/main/java/com/magamochi/ingestion/service/IngestionService.java index fed47cf..db83167 100644 --- a/src/main/java/com/magamochi/ingestion/service/IngestionService.java +++ b/src/main/java/com/magamochi/ingestion/service/IngestionService.java @@ -1,8 +1,12 @@ package com.magamochi.ingestion.service; +import com.magamochi.catalog.service.MangaContentProviderService; +import com.magamochi.common.queue.command.MangaContentIngestCommand; import com.magamochi.common.queue.command.MangaIngestCommand; +import com.magamochi.ingestion.providers.ContentProviderFactory; import com.magamochi.ingestion.providers.PagedContentProviderFactory; import com.magamochi.ingestion.queue.command.ProviderPageIngestCommand; +import com.magamochi.ingestion.queue.producer.MangaContentIngestProducer; import com.magamochi.ingestion.queue.producer.MangaIngestProducer; import com.magamochi.ingestion.queue.producer.ProviderPageIngestProducer; import java.util.stream.IntStream; @@ -13,10 +17,14 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class IngestionService { private final ContentProviderService contentProviderService; + private final MangaContentProviderService mangaContentProviderService; + + private final ContentProviderFactory contentProviderFactory; private final PagedContentProviderFactory pagedContentProviderFactory; private final ProviderPageIngestProducer providerPageIngestProducer; private final MangaIngestProducer mangaIngestProducer; + private final MangaContentIngestProducer mangaContentIngestProducer; public void fetchContentProviderMangas(long contentProviderId) { var contentProvider = contentProviderService.find(contentProviderId); @@ -48,4 +56,23 @@ public class IngestionService { var contentProviders = contentProviderService.getProviders(null); contentProviders.providers().forEach(dto -> fetchContentProviderMangas(dto.id())); } + + public void fetchMangaContentProviderContentList(Long mangaContentProviderId) { + var mangaContentProvider = mangaContentProviderService.find(mangaContentProviderId); + + var contentProvider = + contentProviderFactory.getContentProvider( + mangaContentProvider.getContentProvider().getName()); + + var availableChapters = contentProvider.getAvailableChapters(mangaContentProvider); + + availableChapters.forEach( + content -> + mangaContentIngestProducer.sendMangaContentIngestCommand( + new MangaContentIngestCommand( + mangaContentProvider.getId(), + content.title(), + content.url(), + content.languageCode()))); + } } diff --git a/src/main/java/com/magamochi/model/dto/ContentProviderMangaChapterResponseDTO.java b/src/main/java/com/magamochi/model/dto/ContentProviderMangaChapterResponseDTO.java deleted file mode 100644 index 0ea052c..0000000 --- a/src/main/java/com/magamochi/model/dto/ContentProviderMangaChapterResponseDTO.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.magamochi.model.dto; - -import jakarta.validation.constraints.NotBlank; - -public record ContentProviderMangaChapterResponseDTO( - @NotBlank String chapterTitle, - @NotBlank String chapterUrl, - String chapter, - String languageCode) {} diff --git a/src/main/java/com/magamochi/model/dto/MangaChapterDTO.java b/src/main/java/com/magamochi/model/dto/MangaChapterDTO.java deleted file mode 100644 index 9e008b1..0000000 --- a/src/main/java/com/magamochi/model/dto/MangaChapterDTO.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.magamochi.model.dto; - -import com.magamochi.model.entity.MangaChapter; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -public record MangaChapterDTO( - @NotNull Long id, - @NotBlank String title, - @NotNull Boolean downloaded, - @NotNull Boolean isRead, - LanguageDTO language) { - public static MangaChapterDTO from(MangaChapter mangaChapter) { - return new MangaChapterDTO( - mangaChapter.getId(), - mangaChapter.getTitle(), - mangaChapter.getDownloaded(), - mangaChapter.getRead(), - LanguageDTO.from(mangaChapter.getLanguage())); - } -} diff --git a/src/main/java/com/magamochi/model/dto/MangaChapterImagesDTO.java b/src/main/java/com/magamochi/model/dto/MangaChapterImagesDTO.java index ea6bcce..cbb9cfa 100644 --- a/src/main/java/com/magamochi/model/dto/MangaChapterImagesDTO.java +++ b/src/main/java/com/magamochi/model/dto/MangaChapterImagesDTO.java @@ -1,7 +1,7 @@ package com.magamochi.model.dto; -import com.magamochi.model.entity.MangaChapter; -import com.magamochi.model.entity.MangaChapterImage; +import com.magamochi.content.model.entity.MangaContent; +import com.magamochi.content.model.entity.MangaContentImage; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.Comparator; @@ -13,14 +13,14 @@ public record MangaChapterImagesDTO( Long previousChapterId, Long nextChapterId, @NotNull List<@NotBlank String> chapterImageKeys) { - public static MangaChapterImagesDTO from(MangaChapter mangaChapter, Long prevId, Long nextId) { + public static MangaChapterImagesDTO from(MangaContent mangaContent, Long prevId, Long nextId) { return new MangaChapterImagesDTO( - mangaChapter.getId(), - mangaChapter.getTitle(), + mangaContent.getId(), + mangaContent.getTitle(), prevId, nextId, - mangaChapter.getMangaChapterImages().stream() - .sorted(Comparator.comparing(MangaChapterImage::getPosition)) + mangaContent.getMangaContentImages().stream() + .sorted(Comparator.comparing(MangaContentImage::getPosition)) .map(mangaChapterImage -> mangaChapterImage.getImage().getObjectKey()) .toList()); } diff --git a/src/main/java/com/magamochi/model/repository/MangaChapterImageRepository.java b/src/main/java/com/magamochi/model/repository/MangaChapterImageRepository.java deleted file mode 100644 index 0dd8574..0000000 --- a/src/main/java/com/magamochi/model/repository/MangaChapterImageRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.magamochi.model.repository; - -import com.magamochi.model.entity.MangaChapter; -import com.magamochi.model.entity.MangaChapterImage; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface MangaChapterImageRepository extends JpaRepository { - List findAllByMangaChapter(MangaChapter mangaChapter); -} diff --git a/src/main/java/com/magamochi/model/repository/MangaChapterRepository.java b/src/main/java/com/magamochi/model/repository/MangaChapterRepository.java deleted file mode 100644 index 3b1b9c2..0000000 --- a/src/main/java/com/magamochi/model/repository/MangaChapterRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magamochi.model.repository; - -import com.magamochi.model.entity.MangaChapter; -import com.magamochi.model.entity.MangaContentProvider; -import jakarta.validation.constraints.NotBlank; -import java.util.List; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface MangaChapterRepository extends JpaRepository { - Optional findByMangaContentProviderAndUrlIgnoreCase( - MangaContentProvider mangaContentProvider, @NotBlank String url); - - List findByMangaContentProviderId(Long mangaProvider_id); -} diff --git a/src/main/java/com/magamochi/queue/UpdateMangaFollowChapterListConsumer.java b/src/main/java/com/magamochi/queue/UpdateMangaFollowChapterListConsumer.java index d6d9168..75efa22 100644 --- a/src/main/java/com/magamochi/queue/UpdateMangaFollowChapterListConsumer.java +++ b/src/main/java/com/magamochi/queue/UpdateMangaFollowChapterListConsumer.java @@ -16,6 +16,6 @@ public class UpdateMangaFollowChapterListConsumer { @RabbitListener(queues = "${rabbit-mq.queues.manga-follow-update-chapter}") public void receiveMangaFollowUpdateChapterCommand(UpdateMangaFollowChapterListCommand command) { log.info("Received update followed manga chapter list command: {}", command); - oldMangaService.fetchFollowedMangaChapters(command.mangaProviderId()); + // oldMangaService.fetchFollowedMangaChapters(command.mangaProviderId()); } } diff --git a/src/main/java/com/magamochi/service/MangaChapterService.java b/src/main/java/com/magamochi/service/MangaChapterService.java index ccd1209..42eeebf 100644 --- a/src/main/java/com/magamochi/service/MangaChapterService.java +++ b/src/main/java/com/magamochi/service/MangaChapterService.java @@ -2,14 +2,14 @@ package com.magamochi.service; import com.google.common.util.concurrent.RateLimiter; import com.magamochi.common.exception.UnprocessableException; +import com.magamochi.content.model.entity.MangaContent; +import com.magamochi.content.model.entity.MangaContentImage; +import com.magamochi.content.model.repository.MangaChapterImageRepository; +import com.magamochi.content.model.repository.MangaContentRepository; import com.magamochi.ingestion.providers.ContentProviderFactory; import com.magamochi.model.dto.MangaChapterArchiveDTO; import com.magamochi.model.dto.MangaChapterImagesDTO; -import com.magamochi.model.entity.MangaChapter; -import com.magamochi.model.entity.MangaChapterImage; import com.magamochi.model.enumeration.ArchiveFileType; -import com.magamochi.model.repository.MangaChapterImageRepository; -import com.magamochi.model.repository.MangaChapterRepository; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryRegistry; import java.io.BufferedInputStream; @@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class MangaChapterService { - private final MangaChapterRepository mangaChapterRepository; + private final MangaContentRepository mangaContentRepository; private final MangaChapterImageRepository mangaChapterImageRepository; private final OldImageService oldImageService; @@ -104,8 +104,8 @@ public class MangaChapterService { chapterId, entry.getValue()); - return MangaChapterImage.builder() - .mangaChapter(chapter) + return MangaContentImage.builder() + .mangaContent(chapter) .position(entry.getKey()) .image(image) .build(); @@ -121,15 +121,15 @@ public class MangaChapterService { mangaChapterImageRepository.saveAll(chapterImages); chapter.setDownloaded(true); - mangaChapterRepository.save(chapter); + mangaContentRepository.save(chapter); } public MangaChapterImagesDTO getMangaChapterImages(Long chapterId) { var chapter = getMangaChapterThrowIfNotFound(chapterId); var chapters = - chapter.getMangaContentProvider().getMangaChapters().stream() - .sorted(Comparator.comparing(MangaChapter::getId)) + chapter.getMangaContentProvider().getMangaContents().stream() + .sorted(Comparator.comparing(MangaContent::getId)) .toList(); Long prevId = null; Long nextId = null; @@ -153,17 +153,18 @@ public class MangaChapterService { } public void markAsRead(Long chapterId) { - var chapter = getMangaChapterThrowIfNotFound(chapterId); - chapter.setRead(true); - - mangaChapterRepository.save(chapter); + // TODO: implement this + // var chapter = getMangaChapterThrowIfNotFound(chapterId); + // chapter.setRead(true); + // + // mangaChapterRepository.save(chapter); } public MangaChapterArchiveDTO downloadChapter(Long chapterId, ArchiveFileType archiveFileType) throws IOException { var chapter = getMangaChapterThrowIfNotFound(chapterId); - var chapterImages = mangaChapterImageRepository.findAllByMangaChapter(chapter); + var chapterImages = mangaChapterImageRepository.findAllByMangaContent(chapter); var byteArrayOutputStream = switch (archiveFileType) { @@ -177,7 +178,7 @@ public class MangaChapterService { chapter.getTitle() + ".cbz", byteArrayOutputStream.toByteArray()); } - private ByteArrayOutputStream getChapterCbzArchive(List chapterImages) + private ByteArrayOutputStream getChapterCbzArchive(List chapterImages) throws IOException { var byteArrayOutputStream = new ByteArrayOutputStream(); var bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream); @@ -202,8 +203,8 @@ public class MangaChapterService { return byteArrayOutputStream; } - private MangaChapter getMangaChapterThrowIfNotFound(Long chapterId) { - return mangaChapterRepository + private MangaContent getMangaChapterThrowIfNotFound(Long chapterId) { + return mangaContentRepository .findById(chapterId) .orElseThrow(() -> new RuntimeException("Manga Chapter not found for ID: " + chapterId)); } diff --git a/src/main/java/com/magamochi/service/MangaImportService.java b/src/main/java/com/magamochi/service/MangaImportService.java index ea64e30..510f9c2 100644 --- a/src/main/java/com/magamochi/service/MangaImportService.java +++ b/src/main/java/com/magamochi/service/MangaImportService.java @@ -378,12 +378,12 @@ // var mangaChapter = // mangaChapterRepository // .findByMangaContentProviderAndUrlIgnoreCase(mangaContentProvider, -// chapter.chapterUrl()) +// chapter.url()) // .orElseGet(MangaChapter::new); // // mangaChapter.setMangaContentProvider(mangaContentProvider); -// mangaChapter.setTitle(chapter.chapterTitle()); -// mangaChapter.setUrl(chapter.chapterUrl()); +// mangaChapter.setTitle(chapter.title()); +// mangaChapter.setUrl(chapter.url()); // // var language = languageService.getOrThrow(chapter.languageCode()); // mangaChapter.setLanguage(language); diff --git a/src/main/java/com/magamochi/service/OldMangaService.java b/src/main/java/com/magamochi/service/OldMangaService.java index b0de84c..0ddd4cf 100644 --- a/src/main/java/com/magamochi/service/OldMangaService.java +++ b/src/main/java/com/magamochi/service/OldMangaService.java @@ -1,20 +1,19 @@ package com.magamochi.service; import com.magamochi.catalog.model.entity.Manga; +import com.magamochi.catalog.model.entity.MangaContentProvider; import com.magamochi.catalog.model.repository.MangaContentProviderRepository; import com.magamochi.catalog.model.repository.MangaRepository; import com.magamochi.client.NtfyClient; import com.magamochi.common.exception.NotFoundException; +import com.magamochi.content.model.entity.MangaContent; +import com.magamochi.content.model.repository.MangaContentRepository; import com.magamochi.ingestion.providers.ContentProviderFactory; import com.magamochi.model.dto.*; -import com.magamochi.model.entity.MangaChapter; -import com.magamochi.model.entity.MangaContentProvider; import com.magamochi.model.entity.UserMangaFollow; import com.magamochi.model.repository.*; import com.magamochi.queue.MangaChapterDownloadProducer; import com.magamochi.user.service.UserService; -import java.util.Comparator; -import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -25,7 +24,6 @@ import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class OldMangaService { - // private final MangaImportService mangaImportService; private final UserService userService; private final MangaRepository mangaRepository; private final MangaContentProviderRepository mangaContentProviderRepository; @@ -35,7 +33,7 @@ public class OldMangaService { private final UserMangaFollowRepository userMangaFollowRepository; private final MangaChapterDownloadProducer mangaChapterDownloadProducer; - private final MangaChapterRepository mangaChapterRepository; + private final MangaContentRepository mangaContentRepository; private final NtfyClient ntfyClient; @@ -47,9 +45,9 @@ public class OldMangaService { () -> new NotFoundException("Manga Provider not found for ID: " + mangaProviderId)); var chapterIds = - mangaProvider.getMangaChapters().stream() + mangaProvider.getMangaContents().stream() .filter(mangaChapter -> !mangaChapter.getDownloaded()) - .map(MangaChapter::getId) + .map(MangaContent::getId) .collect(Collectors.toSet()); chapterIds.forEach( @@ -58,57 +56,39 @@ public class OldMangaService { new MangaChapterDownloadCommand(chapterId))); } - public List getMangaChapters(Long mangaProviderId) { - var mangaProvider = getMangaProviderThrowIfNotFound(mangaProviderId); - - return mangaProvider.getMangaChapters().stream() - .sorted(Comparator.comparing(MangaChapter::getId)) - .map(MangaChapterDTO::from) - .toList(); - } - - public void fetchFollowedMangaChapters(Long mangaProviderId) { - var mangaProvider = - mangaContentProviderRepository - .findById(mangaProviderId) - .orElseThrow( - () -> new NotFoundException("Manga Provider not found for ID: " + mangaProviderId)); - - var currentAvailableChapterCount = - mangaChapterRepository.findByMangaContentProviderId(mangaProviderId).size(); - - fetchMangaChapters(mangaProviderId); - mangaChapterRepository.flush(); - - var availableChapterCount = - mangaChapterRepository.findByMangaContentProviderId(mangaProviderId).size(); - - if (availableChapterCount <= currentAvailableChapterCount) { - return; - } - - log.info("New chapters found for Manga Provider {}", mangaProviderId); - - var userMangaFollows = userMangaFollowRepository.findByManga(mangaProvider.getManga()); - userMangaFollows.forEach( - umf -> - ntfyClient.notify( - new NtfyClient.Request( - "mangamochi-" + umf.getUser().getId().toString(), - umf.getManga().getTitle(), - "New chapter available on " + mangaProvider.getContentProvider().getName()))); - } - - public void fetchMangaChapters(Long mangaProviderId) { - var mangaProvider = getMangaProviderThrowIfNotFound(mangaProviderId); - - var contentProvider = - contentProviderFactory.getContentProvider(mangaProvider.getContentProvider().getName()); - var availableChapters = contentProvider.getAvailableChapters(mangaProvider); - - // availableChapters.forEach( - // chapter -> mangaImportService.persistMangaChapter(mangaProvider, chapter)); - } + // public void fetchFollowedMangaChapters(Long mangaProviderId) { + // var mangaProvider = + // mangaContentProviderRepository + // .findById(mangaProviderId) + // .orElseThrow( + // () -> new NotFoundException("Manga Provider not found for ID: " + + // mangaProviderId)); + // + // var currentAvailableChapterCount = + // mangaChapterRepository.findByMangaContentProviderId(mangaProviderId).size(); + // + // fetchMangaChapters(mangaProviderId); + // mangaChapterRepository.flush(); + // + // var availableChapterCount = + // mangaChapterRepository.findByMangaContentProviderId(mangaProviderId).size(); + // + // if (availableChapterCount <= currentAvailableChapterCount) { + // return; + // } + // + // log.info("New chapters found for Manga Provider {}", mangaProviderId); + // + // var userMangaFollows = userMangaFollowRepository.findByManga(mangaProvider.getManga()); + // userMangaFollows.forEach( + // umf -> + // ntfyClient.notify( + // new NtfyClient.Request( + // "mangamochi-" + umf.getUser().getId().toString(), + // umf.getManga().getTitle(), + // "New chapter available on " + + // mangaProvider.getContentProvider().getName()))); + // } public Manga findMangaByIdThrowIfNotFound(Long mangaId) { return mangaRepository diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 99de633..09df276 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -96,6 +96,7 @@ topics: queues: manga-ingest: ${MANGA_INGEST_QUEUE:mangaIngest} + manga-content-ingest: ${MANGA_CONTENT_INGEST_QUEUE:mangaContentIngest} provider-page-ingest: ${PROVIDER_PAGE_INGEST_QUEUE:providerPageIngest} manga-update: ${MANGA_UPDATE_QUEUE:mangaUpdate} image-fetch: ${IMAGE_FETCH_QUEUE:imageFetch} diff --git a/src/main/resources/db/migration/V0001__CREATE_TABLES.sql b/src/main/resources/db/migration/V0001__CREATE_TABLES.sql index 05db389..ca96fe7 100644 --- a/src/main/resources/db/migration/V0001__CREATE_TABLES.sql +++ b/src/main/resources/db/migration/V0001__CREATE_TABLES.sql @@ -15,8 +15,8 @@ ON CONFLICT DO NOTHING; CREATE TABLE images ( id UUID NOT NULL PRIMARY KEY, - object_key VARCHAR NOT NULL, - file_hash VARCHAR(64) UNIQUE, + object_key VARCHAR NOT NULL, + file_hash VARCHAR(64) UNIQUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -46,7 +46,7 @@ CREATE TABLE content_providers name VARCHAR NOT NULL, url VARCHAR NOT NULL, active BOOLEAN NOT NULL DEFAULT TRUE, - supports_chapter_fetch BOOLEAN NOT NULL DEFAULT TRUE, + supports_content_fetch BOOLEAN NOT NULL DEFAULT TRUE, manual_import BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP @@ -64,24 +64,23 @@ CREATE TABLE manga_content_provider UNIQUE (manga_id, content_provider_id) ); -CREATE TABLE manga_chapters +CREATE TABLE manga_contents ( - id BIGSERIAL NOT NULL PRIMARY KEY, - manga_content_provider_id BIGINT NOT NULL REFERENCES manga_content_provider (id) ON DELETE CASCADE, - title VARCHAR NOT NULL, - url VARCHAR NOT NULL, - chapter_number INTEGER, + id BIGSERIAL NOT NULL PRIMARY KEY, + manga_content_provider_id BIGINT NOT NULL REFERENCES manga_content_provider (id) ON DELETE CASCADE, + type VARCHAR(50) NOT NULL, + title VARCHAR NOT NULL, + url VARCHAR NOT NULL, language_id BIGINT REFERENCES languages (id), - downloaded BOOLEAN DEFAULT FALSE, - read BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + downloaded BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE manga_chapter_images +CREATE TABLE manga_content_images ( id BIGSERIAL NOT NULL PRIMARY KEY, - manga_chapter_id BIGINT NOT NULL REFERENCES manga_chapters (id) ON DELETE CASCADE, + manga_content_id BIGINT NOT NULL REFERENCES manga_contents (id) ON DELETE CASCADE, image_id UUID REFERENCES images (id) ON DELETE CASCADE, position INT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/src/main/resources/db/migration/V0002__CONTENT_PROVIDERS.sql b/src/main/resources/db/migration/V0002__CONTENT_PROVIDERS.sql index dce63ff..45cfb40 100644 --- a/src/main/resources/db/migration/V0002__CONTENT_PROVIDERS.sql +++ b/src/main/resources/db/migration/V0002__CONTENT_PROVIDERS.sql @@ -1,4 +1,4 @@ -INSERT INTO content_providers(name, url, active, supports_chapter_fetch, manual_import) +INSERT INTO content_providers(name, url, active, supports_content_fetch, manual_import) VALUES ('Manga Livre Blog', 'https://mangalivre.blog', TRUE, TRUE, FALSE), ('Manga Livre.to', 'https://mangalivre.to', TRUE, TRUE, FALSE), ('Pink Rosa Scan', 'https://scanpinkrosa.blogspot.com', TRUE, TRUE, FALSE); \ No newline at end of file