feat: implement manga follow update task with RabbitMQ integration and notification system #55
@ -1,4 +1,4 @@
|
||||
package com.magamochi.model.specification;
|
||||
package com.magamochi.catalog.model.specification;
|
||||
|
||||
import static java.util.Objects.isNull;
|
||||
import static java.util.Objects.nonNull;
|
||||
@ -7,8 +7,8 @@ import com.magamochi.catalog.model.dto.MangaListDTO;
|
||||
import com.magamochi.catalog.model.dto.MangaListFilterDTO;
|
||||
import com.magamochi.catalog.model.entity.Manga;
|
||||
import com.magamochi.catalog.model.repository.MangaRepository;
|
||||
import com.magamochi.catalog.model.specification.MangaSpecification;
|
||||
import com.magamochi.common.exception.NotFoundException;
|
||||
import com.magamochi.model.specification.MangaSpecification;
|
||||
import com.magamochi.user.service.UserService;
|
||||
import com.magamochi.userinteraction.model.repository.UserFavoriteMangaRepository;
|
||||
import com.magamochi.userinteraction.model.repository.UserMangaFollowRepository;
|
||||
|
||||
@ -52,6 +52,9 @@ public class RabbitConfig {
|
||||
@Value("${queues.manga-content-download}")
|
||||
private String mangaContentDownloadQueue;
|
||||
|
||||
@Value("${queues.manga-follow-update-chapter}")
|
||||
private String mangaFollowUpdateChapterQueue;
|
||||
|
||||
@Bean
|
||||
public TopicExchange imageUpdatesExchange() {
|
||||
return new TopicExchange(imageUpdatesTopic);
|
||||
@ -220,11 +223,6 @@ public class RabbitConfig {
|
||||
return QueueBuilder.durable(mangaContentDownloadQueue + ".dlq").quorum().build();
|
||||
}
|
||||
|
||||
// TODO: remove unused queues
|
||||
|
||||
@Value("${rabbit-mq.queues.manga-follow-update-chapter}")
|
||||
private String mangaFollowUpdateChapterQueue;
|
||||
|
||||
@Bean
|
||||
public Queue mangaFollowUpdateChapterQueue() {
|
||||
return QueueBuilder.durable(mangaFollowUpdateChapterQueue)
|
||||
|
||||
@ -14,6 +14,7 @@ import com.magamochi.content.model.entity.MangaContentImage;
|
||||
import com.magamochi.content.model.repository.MangaContentImageRepository;
|
||||
import com.magamochi.content.model.repository.MangaContentRepository;
|
||||
import com.magamochi.image.service.ImageService;
|
||||
import com.magamochi.userinteraction.service.UserMangaFollowService;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -28,6 +29,7 @@ public class ContentIngestService {
|
||||
private final ContentService contentService;
|
||||
private final MangaContentProviderService mangaContentProviderService;
|
||||
private final LanguageService languageService;
|
||||
private final UserMangaFollowService userMangaFollowService;
|
||||
|
||||
private final MangaContentRepository mangaContentRepository;
|
||||
private final MangaContentImageRepository mangaContentImageRepository;
|
||||
@ -66,6 +68,8 @@ public class ContentIngestService {
|
||||
.language(language)
|
||||
.build());
|
||||
|
||||
userMangaFollowService.notifyNewMangaContent(mangaContent);
|
||||
|
||||
log.info(
|
||||
"Ingested Manga Content ({}) for provider {}: {}",
|
||||
title,
|
||||
|
||||
@ -3,8 +3,8 @@ package com.magamochi.controller;
|
||||
import com.magamochi.client.NtfyClient;
|
||||
import com.magamochi.common.model.dto.DefaultResponseDTO;
|
||||
import com.magamochi.image.task.ImageCleanupTask;
|
||||
import com.magamochi.task.MangaFollowUpdateTask;
|
||||
import com.magamochi.user.repository.UserRepository;
|
||||
import com.magamochi.userinteraction.task.MangaFollowUpdateTask;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
package com.magamochi.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Log4j2
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OldMangaService {
|
||||
// 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())));
|
||||
// }
|
||||
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
package com.magamochi.model.dto;
|
||||
package com.magamochi.userinteraction.queue.command;
|
||||
|
||||
public record UpdateMangaFollowChapterListCommand(Long mangaProviderId) {}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.magamochi.queue;
|
||||
package com.magamochi.userinteraction.queue.consumer;
|
||||
|
||||
import com.magamochi.model.dto.UpdateMangaFollowChapterListCommand;
|
||||
import com.magamochi.service.OldMangaService;
|
||||
import com.magamochi.userinteraction.queue.command.UpdateMangaFollowChapterListCommand;
|
||||
import com.magamochi.userinteraction.service.UserMangaFollowService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
@ -11,11 +11,11 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UpdateMangaFollowChapterListConsumer {
|
||||
private final OldMangaService oldMangaService;
|
||||
private final UserMangaFollowService userMangaFollowService;
|
||||
|
||||
@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());
|
||||
userMangaFollowService.checkForMangaUpdates(command.mangaProviderId());
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.magamochi.queue;
|
||||
package com.magamochi.userinteraction.queue.producer;
|
||||
|
||||
import com.magamochi.model.dto.UpdateMangaFollowChapterListCommand;
|
||||
import com.magamochi.userinteraction.queue.command.UpdateMangaFollowChapterListCommand;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
@ -2,7 +2,10 @@ package com.magamochi.userinteraction.service;
|
||||
|
||||
import com.magamochi.catalog.model.entity.Manga;
|
||||
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.ingestion.service.IngestionService;
|
||||
import com.magamochi.user.service.UserService;
|
||||
import com.magamochi.userinteraction.model.entity.UserMangaFollow;
|
||||
import com.magamochi.userinteraction.model.repository.UserMangaFollowRepository;
|
||||
@ -14,10 +17,13 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class UserMangaFollowService {
|
||||
private final UserService userService;
|
||||
private final IngestionService ingestionService;
|
||||
|
||||
private final MangaRepository mangaRepository;
|
||||
private final UserMangaFollowRepository userMangaFollowRepository;
|
||||
|
||||
private final NtfyClient ntfyClient;
|
||||
|
||||
@Transactional
|
||||
public void follow(Long mangaId) {
|
||||
var user = userService.getLoggedUserThrowIfNotFound();
|
||||
@ -50,4 +56,21 @@ public class UserMangaFollowService {
|
||||
.findById(mangaId)
|
||||
.orElseThrow(() -> new NotFoundException("Manga not found for ID: " + mangaId));
|
||||
}
|
||||
|
||||
public void checkForMangaUpdates(Long contentProviderId) {
|
||||
ingestionService.fetchMangaContentProviderContentList(contentProviderId);
|
||||
}
|
||||
|
||||
public void notifyNewMangaContent(MangaContent mangaContent) {
|
||||
var userMangaFollows =
|
||||
userMangaFollowRepository.findByManga(mangaContent.getMangaContentProvider().getManga());
|
||||
userMangaFollows.forEach(
|
||||
umf ->
|
||||
ntfyClient.notify(
|
||||
new NtfyClient.Request(
|
||||
"mangamochi-" + umf.getUser().getId().toString(),
|
||||
umf.getManga().getTitle(),
|
||||
"New content available on "
|
||||
+ mangaContent.getMangaContentProvider().getContentProvider().getName())));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
package com.magamochi.task;
|
||||
package com.magamochi.userinteraction.task;
|
||||
|
||||
import com.magamochi.catalog.model.entity.Manga;
|
||||
import com.magamochi.catalog.model.repository.MangaRepository;
|
||||
import com.magamochi.model.dto.UpdateMangaFollowChapterListCommand;
|
||||
import com.magamochi.queue.UpdateMangaFollowChapterListProducer;
|
||||
import com.magamochi.userinteraction.queue.command.UpdateMangaFollowChapterListCommand;
|
||||
import com.magamochi.userinteraction.queue.producer.UpdateMangaFollowChapterListProducer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@ -21,7 +22,7 @@ public class MangaFollowUpdateTask {
|
||||
|
||||
private final UpdateMangaFollowChapterListProducer producer;
|
||||
|
||||
// @Scheduled(cron = "${manga-follow.cron-expression}")
|
||||
@Scheduled(cron = "${manga-follow.cron-expression}")
|
||||
@Transactional
|
||||
public void updateMangaFollow() {
|
||||
if (!updateEnabled) {
|
||||
@ -82,14 +82,11 @@ queues:
|
||||
manga-cover-update: ${MANGA_COVER_UDPATE_QUEUE:mangamochi.manga.cover.update}
|
||||
file-import: ${FILE_IMPORT_QUEUE:mangamochi.file.import}
|
||||
manga-content-download: ${MANGA_CONTENT_DOWNLOAD_QUEUE:mangamochi.manga.content.download}
|
||||
manga-follow-update-chapter: ${MANGA_FOLLOW_UPDATE_CHAPTER_QUEUE:mangamochi.follow.update}
|
||||
|
||||
routing-key:
|
||||
image-update: ${IMAGE_UPDATE_ROUTING_KEY:mangamochi.image.update}
|
||||
|
||||
rabbit-mq:
|
||||
queues:
|
||||
manga-follow-update-chapter: ${MANGA_FOLLOW_UPDATE_CHAPTER_QUEUE:mangaFollowUpdateChapterQueue}
|
||||
|
||||
image-service:
|
||||
clean-up-enabled: ${IMAGE_SERVICE_CLEAN_UP_ENABLED:false}
|
||||
cron-expression: "@weekly"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user