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