From 22a905220e805481c5cbc6f6df47a9159d8063a2 Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Sat, 23 May 2026 20:14:57 -0300 Subject: [PATCH] feat: add manga content image deletion functionality with RabbitMQ integration --- .../magamochi/common/config/RabbitConfig.java | 32 +++++++++++++++++++ .../MangaContentImageDeleteCommand.java | 3 ++ .../MangaContentImageDeleteConsumer.java | 22 +++++++++++++ .../content/service/ContentIngestService.java | 6 ++++ .../queue/consumer/ImageFetchConsumer.java | 10 +++++- .../queue/producer/ImageUpdateProducer.java | 10 ++++++ src/main/resources/application.yml | 2 ++ 7 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/magamochi/common/queue/command/MangaContentImageDeleteCommand.java create mode 100644 src/main/java/com/magamochi/content/queue/consumer/MangaContentImageDeleteConsumer.java diff --git a/src/main/java/com/magamochi/common/config/RabbitConfig.java b/src/main/java/com/magamochi/common/config/RabbitConfig.java index 5f3e057..f71a000 100644 --- a/src/main/java/com/magamochi/common/config/RabbitConfig.java +++ b/src/main/java/com/magamochi/common/config/RabbitConfig.java @@ -40,6 +40,9 @@ public class RabbitConfig { @Value("${queues.image-fetch}") private String imageFetchQueue; + @Value("${queues.manga-content-image-delete}") + private String mangaContentImageDeleteQueue; + @Value("${queues.file-import}") private String fileImportQueue; @@ -49,6 +52,9 @@ public class RabbitConfig { @Value("${routing-key.image-update}") private String imageUpdateRoutingKey; + @Value("${routing-key.image-delete}") + private String imageDeleteRoutingKey; + @Value("${queues.manga-content-download}") private String mangaContentDownloadQueue; @@ -102,6 +108,20 @@ public class RabbitConfig { return QueueBuilder.durable(mangaContentImageUpdateQueue + ".dlq").quorum().build(); } + @Bean + public Queue mangaContentImageDeleteQueue() { + return QueueBuilder.durable(mangaContentImageDeleteQueue) + .quorum() + .deadLetterExchange("") + .deadLetterRoutingKey(mangaContentImageDeleteQueue + ".dlq") + .build(); + } + + @Bean + public Queue mangaContentImageDeleteDlq() { + return QueueBuilder.durable(mangaContentImageDeleteQueue + ".dlq").quorum().build(); + } + @Bean public Queue mangaCoverUpdateQueue() { return QueueBuilder.durable(mangaCoverUpdateQueue) @@ -153,6 +173,18 @@ public class RabbitConfig { null); } + @Bean + public Binding bindingMangaContentImageDeleteQueue( + Queue mangaContentImageDeleteQueue, TopicExchange imageUpdatesExchange) { + return new Binding( + mangaContentImageDeleteQueue.getName(), + Binding.DestinationType.QUEUE, + imageUpdatesExchange.getName(), + String.format( + imageDeleteRoutingKey + ".%s", ContentType.CONTENT_IMAGE.name().toLowerCase()), + null); + } + @Bean public Queue mangaContentIngestQueue() { return QueueBuilder.durable(mangaContentIngestQueue) diff --git a/src/main/java/com/magamochi/common/queue/command/MangaContentImageDeleteCommand.java b/src/main/java/com/magamochi/common/queue/command/MangaContentImageDeleteCommand.java new file mode 100644 index 0000000..e96580c --- /dev/null +++ b/src/main/java/com/magamochi/common/queue/command/MangaContentImageDeleteCommand.java @@ -0,0 +1,3 @@ +package com.magamochi.common.queue.command; + +public record MangaContentImageDeleteCommand(long entityId) {} diff --git a/src/main/java/com/magamochi/content/queue/consumer/MangaContentImageDeleteConsumer.java b/src/main/java/com/magamochi/content/queue/consumer/MangaContentImageDeleteConsumer.java new file mode 100644 index 0000000..5dd356a --- /dev/null +++ b/src/main/java/com/magamochi/content/queue/consumer/MangaContentImageDeleteConsumer.java @@ -0,0 +1,22 @@ +package com.magamochi.content.queue.consumer; + +import com.magamochi.common.queue.command.MangaContentImageDeleteCommand; +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 MangaContentImageDeleteConsumer { + private final ContentIngestService contentIngestService; + + @RabbitListener(queues = "${queues.manga-content-image-delete}") + public void receiveMangaContentImageDeleteCommand(MangaContentImageDeleteCommand command) { + log.info("Received manga content image delete command: {}", command); + + contentIngestService.deleteMangaContentImage(command.entityId()); + } +} diff --git a/src/main/java/com/magamochi/content/service/ContentIngestService.java b/src/main/java/com/magamochi/content/service/ContentIngestService.java index b1b0dc2..c74631e 100644 --- a/src/main/java/com/magamochi/content/service/ContentIngestService.java +++ b/src/main/java/com/magamochi/content/service/ContentIngestService.java @@ -119,4 +119,10 @@ public class ContentIngestService { var image = imageService.find(imageId); mangaContentImage.setImage(image); } + + @Transactional + public void deleteMangaContentImage(long mangaContentImageId) { + mangaContentImageRepository.deleteById(mangaContentImageId); + log.info("Deleted Manga Content Image entity with ID {}", mangaContentImageId); + } } diff --git a/src/main/java/com/magamochi/image/queue/consumer/ImageFetchConsumer.java b/src/main/java/com/magamochi/image/queue/consumer/ImageFetchConsumer.java index 8fab861..6bfc5bf 100644 --- a/src/main/java/com/magamochi/image/queue/consumer/ImageFetchConsumer.java +++ b/src/main/java/com/magamochi/image/queue/consumer/ImageFetchConsumer.java @@ -3,7 +3,9 @@ package com.magamochi.image.queue.consumer; import com.magamochi.common.exception.ExcludedImageException; import com.magamochi.common.queue.command.ImageFetchCommand; import com.magamochi.common.queue.command.ImageUpdateCommand; +import com.magamochi.common.queue.command.MangaContentImageDeleteCommand; import com.magamochi.image.queue.producer.ImageUpdateProducer; +import com.magamochi.common.model.enumeration.ContentType; import com.magamochi.image.service.ImageFetchService; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -17,7 +19,7 @@ public class ImageFetchConsumer { private final ImageFetchService imageFetchService; private final ImageUpdateProducer imageUpdateProducer; - @RabbitListener(queues = "${queues.image-fetch}") + @RabbitListener(queues = "${queues.image-fetch}", concurrency = "${queues.image-fetch-concurrency:5-10}") public void receiveImageFetchCommand(ImageFetchCommand command) { log.info("Received image fetch command: {}", command); @@ -28,6 +30,12 @@ public class ImageFetchConsumer { new ImageUpdateCommand(command.entityId(), imageId), command.contentType()); } catch (ExcludedImageException e) { log.info("Image at URL {} is excluded from upload, skipping.", command.url()); + + if (ContentType.CONTENT_IMAGE.equals(command.contentType())) { + log.info("Deleting excluded manga content image entity for ID {}.", command.entityId()); + imageUpdateProducer.publishImageDeleteCommand( + new MangaContentImageDeleteCommand(command.entityId()), command.contentType()); + } } catch (Exception e) { log.error("Failed to fetch image from URL.", e); } diff --git a/src/main/java/com/magamochi/image/queue/producer/ImageUpdateProducer.java b/src/main/java/com/magamochi/image/queue/producer/ImageUpdateProducer.java index e5540f4..9be1b45 100644 --- a/src/main/java/com/magamochi/image/queue/producer/ImageUpdateProducer.java +++ b/src/main/java/com/magamochi/image/queue/producer/ImageUpdateProducer.java @@ -2,6 +2,7 @@ package com.magamochi.image.queue.producer; import com.magamochi.common.model.enumeration.ContentType; import com.magamochi.common.queue.command.ImageUpdateCommand; +import com.magamochi.common.queue.command.MangaContentImageDeleteCommand; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -20,9 +21,18 @@ public class ImageUpdateProducer { @Value("${routing-key.image-update}") private String imageUpdateRoutingKey; + @Value("${routing-key.image-delete}") + private String imageDeleteRoutingKey; + public void publishImageUpdateCommand(ImageUpdateCommand command, ContentType contentType) { var routingKey = String.format(imageUpdateRoutingKey + ".%s", contentType.name().toLowerCase()); rabbitTemplate.convertAndSend(imageUpdatesTopic, routingKey, command); } + + public void publishImageDeleteCommand(MangaContentImageDeleteCommand command, ContentType contentType) { + var routingKey = String.format(imageDeleteRoutingKey + ".%s", contentType.name().toLowerCase()); + + rabbitTemplate.convertAndSend(imageUpdatesTopic, routingKey, command); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 797fcb9..3e0fade 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -77,6 +77,7 @@ queues: manga-content-ingest: ${MANGA_CONTENT_INGEST_QUEUE:mangamochi.manga.content.ingest} manga-content-image-ingest: ${MANGA_CONTENT_IMAGE_INGEST_QUEUE:mangamochi.manga.content.image.ingest} manga-content-image-update: ${MANGA_CONTENT_IMAGE_UPDATE_QUEUE:mangamochi.manga.content.image.update} + manga-content-image-delete: ${MANGA_CONTENT_IMAGE_DELETE_QUEUE:mangamochi.manga.content.image.delete} provider-page-ingest: ${PROVIDER_PAGE_INGEST_QUEUE:mangamochi.provider.page.ingest} image-fetch: ${IMAGE_FETCH_QUEUE:mangamochi.image.fetch} manga-cover-update: ${MANGA_COVER_UDPATE_QUEUE:mangamochi.manga.cover.update} @@ -86,6 +87,7 @@ queues: routing-key: image-update: ${IMAGE_UPDATE_ROUTING_KEY:mangamochi.image.update} + image-delete: ${IMAGE_DELETE_ROUTING_KEY:mangamochi.image.delete} image-service: clean-up-enabled: ${IMAGE_SERVICE_CLEAN_UP_ENABLED:false}