feat: implement manga chapter download producer and consumer with RabbitMQ integration

This commit is contained in:
Rodrigo Verdiani 2025-10-27 17:09:02 -03:00
parent 581f436c5f
commit bfd415a60d
6 changed files with 90 additions and 0 deletions

View File

@ -10,12 +10,18 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class RabbitConfig { public class RabbitConfig {
public static final String MANGA_DATA_UPDATE_QUEUE = "mangaDataUpdateQueue"; public static final String MANGA_DATA_UPDATE_QUEUE = "mangaDataUpdateQueue";
public static final String MANGA_CHAPTER_DOWNLOAD_QUEUE = "mangaChapterDownloadQueue";
@Bean @Bean
public Queue mangaDataUpdateQueue() { public Queue mangaDataUpdateQueue() {
return new Queue(MANGA_DATA_UPDATE_QUEUE, false); return new Queue(MANGA_DATA_UPDATE_QUEUE, false);
} }
@Bean
public Queue mangaChapterDownloadQueue() {
return new Queue(MANGA_CHAPTER_DOWNLOAD_QUEUE, false);
}
@Bean @Bean
public Jackson2JsonMessageConverter messageConverter() { public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter(); return new Jackson2JsonMessageConverter();

View File

@ -51,6 +51,18 @@ public class MangaController {
return DefaultResponseDTO.ok(mangaService.getMangaChapters(mangaProviderId)); 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<Void> fetchAllChapters(@PathVariable Long mangaProviderId) {
mangaService.fetchAllNotDownloadedChapters(mangaProviderId);
return DefaultResponseDTO.ok().build();
}
@Operation( @Operation(
summary = "Fetch the available chapters for a specific manga/provider combination", summary = "Fetch the available chapters for a specific manga/provider combination",
description = "Fetch a list of manga chapters for a specific manga/provider combination.", description = "Fetch a list of manga chapters for a specific manga/provider combination.",

View File

@ -0,0 +1,3 @@
package com.magamochi.mangamochi.model.dto;
public record MangaChapterDownloadCommand(Long chapterId) {}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -9,6 +9,7 @@ import com.magamochi.mangamochi.model.entity.MangaChapter;
import com.magamochi.mangamochi.model.entity.MangaProvider; import com.magamochi.mangamochi.model.entity.MangaProvider;
import com.magamochi.mangamochi.model.repository.*; import com.magamochi.mangamochi.model.repository.*;
import com.magamochi.mangamochi.model.specification.MangaSpecification; import com.magamochi.mangamochi.model.specification.MangaSpecification;
import com.magamochi.mangamochi.queue.MangaChapterDownloadProducer;
import com.magamochi.mangamochi.service.providers.ContentProviderFactory; import com.magamochi.mangamochi.service.providers.ContentProviderFactory;
import java.net.*; import java.net.*;
import java.util.Comparator; import java.util.Comparator;
@ -33,6 +34,27 @@ public class MangaService {
private final ContentProviderFactory contentProviderFactory; private final ContentProviderFactory contentProviderFactory;
private final UserFavoriteMangaRepository userFavoriteMangaRepository; 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<MangaListDTO> getMangas(MangaListFilterDTO filterDTO, Pageable pageable) { public Page<MangaListDTO> getMangas(MangaListFilterDTO filterDTO, Pageable pageable) {
var user = userService.getLoggedUser(); var user = userService.getLoggedUser();