feat: add manga content image deletion functionality with RabbitMQ integration

This commit is contained in:
Rodrigo Verdiani 2026-05-23 20:14:57 -03:00
parent b388bf3740
commit 22a905220e
7 changed files with 84 additions and 1 deletions

View File

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

View File

@ -0,0 +1,3 @@
package com.magamochi.common.queue.command;
public record MangaContentImageDeleteCommand(long entityId) {}

View File

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

View File

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

View File

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

View File

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

View File

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