From bfd415a60dad4f4b461c23553e7fc5b3b932c456 Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Mon, 27 Oct 2025 17:09:02 -0300 Subject: [PATCH] feat: implement manga chapter download producer and consumer with RabbitMQ integration --- .../mangamochi/config/RabbitConfig.java | 6 +++++ .../controller/MangaController.java | 12 +++++++++ .../dto/MangaChapterDownloadCommand.java | 3 +++ .../queue/MangaChapterDownloadConsumer.java | 27 +++++++++++++++++++ .../queue/MangaChapterDownloadProducer.java | 20 ++++++++++++++ .../mangamochi/service/MangaService.java | 22 +++++++++++++++ 6 files changed, 90 insertions(+) create mode 100644 src/main/java/com/magamochi/mangamochi/model/dto/MangaChapterDownloadCommand.java create mode 100644 src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadConsumer.java create mode 100644 src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadProducer.java diff --git a/src/main/java/com/magamochi/mangamochi/config/RabbitConfig.java b/src/main/java/com/magamochi/mangamochi/config/RabbitConfig.java index 8c38a38..be6984f 100644 --- a/src/main/java/com/magamochi/mangamochi/config/RabbitConfig.java +++ b/src/main/java/com/magamochi/mangamochi/config/RabbitConfig.java @@ -10,12 +10,18 @@ import org.springframework.context.annotation.Configuration; @Configuration public class RabbitConfig { public static final String MANGA_DATA_UPDATE_QUEUE = "mangaDataUpdateQueue"; + public static final String MANGA_CHAPTER_DOWNLOAD_QUEUE = "mangaChapterDownloadQueue"; @Bean public Queue mangaDataUpdateQueue() { return new Queue(MANGA_DATA_UPDATE_QUEUE, false); } + @Bean + public Queue mangaChapterDownloadQueue() { + return new Queue(MANGA_CHAPTER_DOWNLOAD_QUEUE, false); + } + @Bean public Jackson2JsonMessageConverter messageConverter() { return new Jackson2JsonMessageConverter(); diff --git a/src/main/java/com/magamochi/mangamochi/controller/MangaController.java b/src/main/java/com/magamochi/mangamochi/controller/MangaController.java index cb93633..bf45649 100644 --- a/src/main/java/com/magamochi/mangamochi/controller/MangaController.java +++ b/src/main/java/com/magamochi/mangamochi/controller/MangaController.java @@ -51,6 +51,18 @@ public class MangaController { return DefaultResponseDTO.ok(mangaService.getMangaChapters(mangaProviderId)); } + @Operation( + summary = "Fetch all chapters", + description = "Fetch all not yet downloaded chapters from the provider", + tags = {"Manga Chapter"}, + operationId = "fetchAllChapters") + @PostMapping(value = "/{mangaProviderId}/fetch-all-chapters") + public DefaultResponseDTO fetchAllChapters(@PathVariable Long mangaProviderId) { + mangaService.fetchAllNotDownloadedChapters(mangaProviderId); + + 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.", diff --git a/src/main/java/com/magamochi/mangamochi/model/dto/MangaChapterDownloadCommand.java b/src/main/java/com/magamochi/mangamochi/model/dto/MangaChapterDownloadCommand.java new file mode 100644 index 0000000..3f72132 --- /dev/null +++ b/src/main/java/com/magamochi/mangamochi/model/dto/MangaChapterDownloadCommand.java @@ -0,0 +1,3 @@ +package com.magamochi.mangamochi.model.dto; + +public record MangaChapterDownloadCommand(Long chapterId) {} diff --git a/src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadConsumer.java b/src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadConsumer.java new file mode 100644 index 0000000..3a9c018 --- /dev/null +++ b/src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadConsumer.java @@ -0,0 +1,27 @@ +package com.magamochi.mangamochi.queue; + +import com.magamochi.mangamochi.config.RabbitConfig; +import com.magamochi.mangamochi.model.dto.MangaChapterDownloadCommand; +import com.magamochi.mangamochi.service.MangaChapterService; +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 MangaChapterDownloadConsumer { + private final MangaChapterService mangaChapterService; + + @RabbitListener(queues = RabbitConfig.MANGA_CHAPTER_DOWNLOAD_QUEUE) + public void receiveMangaChapterDownloadCommand(MangaChapterDownloadCommand command) { + log.info("Received manga chapter download command: {}", command); + + try { + mangaChapterService.fetchChapter(command.chapterId()); + } catch (Exception e) { + log.error("Couldn't download chapter {}. {}", command.chapterId(), e.getMessage()); + } + } +} diff --git a/src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadProducer.java b/src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadProducer.java new file mode 100644 index 0000000..2b638ae --- /dev/null +++ b/src/main/java/com/magamochi/mangamochi/queue/MangaChapterDownloadProducer.java @@ -0,0 +1,20 @@ +package com.magamochi.mangamochi.queue; + +import com.magamochi.mangamochi.config.RabbitConfig; +import com.magamochi.mangamochi.model.dto.MangaChapterDownloadCommand; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class MangaChapterDownloadProducer { + private final RabbitTemplate rabbitTemplate; + + public void sendMangaChapterDownloadCommand(MangaChapterDownloadCommand command) { + rabbitTemplate.convertAndSend(RabbitConfig.MANGA_CHAPTER_DOWNLOAD_QUEUE, command); + log.info("Sent manga chapter download command: {}", command); + } +} diff --git a/src/main/java/com/magamochi/mangamochi/service/MangaService.java b/src/main/java/com/magamochi/mangamochi/service/MangaService.java index 7b99b6f..787756c 100644 --- a/src/main/java/com/magamochi/mangamochi/service/MangaService.java +++ b/src/main/java/com/magamochi/mangamochi/service/MangaService.java @@ -9,6 +9,7 @@ import com.magamochi.mangamochi.model.entity.MangaChapter; import com.magamochi.mangamochi.model.entity.MangaProvider; import com.magamochi.mangamochi.model.repository.*; import com.magamochi.mangamochi.model.specification.MangaSpecification; +import com.magamochi.mangamochi.queue.MangaChapterDownloadProducer; import com.magamochi.mangamochi.service.providers.ContentProviderFactory; import java.net.*; import java.util.Comparator; @@ -33,6 +34,27 @@ public class MangaService { private final ContentProviderFactory contentProviderFactory; private final UserFavoriteMangaRepository userFavoriteMangaRepository; + private final MangaChapterDownloadProducer mangaChapterDownloadProducer; + + public void fetchAllNotDownloadedChapters(Long mangaProviderId) { + var mangaProvider = + mangaProviderRepository + .findById(mangaProviderId) + .orElseThrow( + () -> new NotFoundException("Manga Provider not found for ID: " + mangaProviderId)); + + var chapterIds = + mangaProvider.getMangaChapters().stream() + .filter(mangaChapter -> !mangaChapter.getDownloaded()) + .map(MangaChapter::getId) + .collect(Collectors.toSet()); + + chapterIds.forEach( + chapterId -> + mangaChapterDownloadProducer.sendMangaChapterDownloadCommand( + new MangaChapterDownloadCommand(chapterId))); + } + public Page getMangas(MangaListFilterDTO filterDTO, Pageable pageable) { var user = userService.getLoggedUser();