Merge pull request 'feat(manga): enhance ingest process with error handling' (#64) from feat/concurrency into main

Reviewed-on: #64
This commit is contained in:
rov 2026-06-24 09:00:10 -03:00
commit 3b8a8f87e2
2 changed files with 99 additions and 9 deletions

View File

@ -27,6 +27,20 @@ public class MangaIngestService {
log.info(
"Ingesting manga with mangaTitle '{}' from provider {}", mangaTitle, contentProviderId);
try {
doIngestManga(contentProviderId, mangaTitle, url);
} catch (Exception e) {
log.error(
"Unexpected error ingesting manga '{}' from provider {} at url '{}'",
mangaTitle,
contentProviderId,
url,
e);
throw e;
}
}
private void doIngestManga(long contentProviderId, String mangaTitle, String url) {
if (mangaContentProviderRepository.existsByMangaTitleIgnoreCaseAndContentProvider_Id(
mangaTitle, contentProviderId)) {
log.info(
@ -51,6 +65,17 @@ public class MangaIngestService {
return;
}
if (mangaContentProviderRepository
.findByManga_IdAndContentProvider_Id(manga.getId(), contentProviderId)
.isPresent()) {
log.info(
"MangaContentProvider already exists for manga '{}' (ID: {}) and provider '{}', skipping ingest",
manga.getTitle(),
manga.getId(),
contentProviderId);
return;
}
try {
var contentProvider = contentProviderService.find(contentProviderId);
@ -63,10 +88,11 @@ public class MangaIngestService {
.build());
} catch (Exception e) {
log.error(
"Failed to ingest manga with mangaTitle '{}' from provider '{}'",
"Failed to save manga content provider for manga '{}' from provider '{}'",
mangaTitle,
contentProviderId,
e);
return;
}
log.info(

View File

@ -1,5 +1,6 @@
package com.magamochi.catalog.service;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import com.magamochi.catalog.model.entity.Manga;
@ -9,6 +10,7 @@ import com.magamochi.catalog.queue.producer.MangaUpdateProducer;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
@Log4j2
@ -116,21 +118,21 @@ public class MangaResolutionService {
if (nonNull(aniListId)) {
var existingByAniList = mangaRepository.findByAniListId(aniListId);
if (existingByAniList.isPresent()) {
return existingByAniList.get();
return mergeExternalIds(existingByAniList.get(), canonicalTitle, aniListId, malId);
}
}
if (nonNull(malId)) {
var existingByMalId = mangaRepository.findByMalId(malId);
if (existingByMalId.isPresent()) {
return existingByMalId.get();
return mergeExternalIds(existingByMalId.get(), canonicalTitle, aniListId, malId);
}
}
if (nonNull(canonicalTitle)) {
var existingByTitle = mangaRepository.findByTitleIgnoreCase(canonicalTitle);
if (existingByTitle.isPresent()) {
return existingByTitle.get();
return mergeExternalIds(existingByTitle.get(), canonicalTitle, aniListId, malId);
}
}
@ -138,13 +140,75 @@ public class MangaResolutionService {
}
private Manga createAndNotifyManga(String title, Long aniListId, Long malId) {
var manga =
mangaRepository.save(
Manga.builder().title(title).aniListId(aniListId).malId(malId).build());
try {
var manga =
mangaRepository.save(
Manga.builder().title(title).aniListId(aniListId).malId(malId).build());
mangaUpdateProducer.sendMangaUpdateCommand(new MangaUpdateCommand(manga.getId()));
try {
mangaUpdateProducer.sendMangaUpdateCommand(new MangaUpdateCommand(manga.getId()));
} catch (Exception e) {
log.error("Failed to send manga update command for manga '{}' (ID: {})", title, manga.getId(), e);
}
return manga;
return manga;
} catch (DataIntegrityViolationException e) {
log.warn(
"Race condition while creating manga '{}' (aniListId={}, malId={}), re-querying existing",
title,
aniListId,
malId);
if (nonNull(aniListId)) {
var existing = mangaRepository.findByAniListId(aniListId);
if (existing.isPresent()) {
return mergeExternalIds(existing.get(), title, aniListId, malId);
}
}
if (nonNull(malId)) {
var existing = mangaRepository.findByMalId(malId);
if (existing.isPresent()) {
return mergeExternalIds(existing.get(), title, aniListId, malId);
}
}
if (nonNull(title)) {
var existing = mangaRepository.findByTitleIgnoreCase(title);
if (existing.isPresent()) {
return mergeExternalIds(existing.get(), title, aniListId, malId);
}
}
throw e;
}
}
private Manga mergeExternalIds(Manga existing, String canonicalTitle, Long aniListId, Long malId) {
boolean updated = false;
if (isNull(existing.getAniListId()) && nonNull(aniListId)) {
existing.setAniListId(aniListId);
updated = true;
}
if (isNull(existing.getMalId()) && nonNull(malId)) {
existing.setMalId(malId);
updated = true;
}
if (isNull(existing.getTitle()) && nonNull(canonicalTitle)) {
existing.setTitle(canonicalTitle);
updated = true;
}
if (updated) {
log.info(
"Merged external IDs into manga '{}' (ID: {}): aniListId={}, malId={}",
existing.getTitle(),
existing.getId(),
existing.getAniListId(),
existing.getMalId());
return mangaRepository.save(existing);
}
return existing;
}
private record ProviderResult(String title, Long externalId) {}