refactor: enhance manga import job handling with error tracking and new endpoint

This commit is contained in:
Rodrigo Verdiani 2026-03-27 18:24:18 -03:00
parent 348911c4ef
commit 1acdebc3fe
9 changed files with 83 additions and 12 deletions

View File

@ -1,11 +1,7 @@
package com.magamochi.content.controller;
import com.magamochi.common.model.dto.DefaultResponseDTO;
import com.magamochi.content.model.dto.FileImportRequestDTO;
import com.magamochi.content.model.dto.MangaContentDTO;
import com.magamochi.content.model.dto.MangaContentImagesDTO;
import com.magamochi.content.model.dto.PresignedImportRequestDTO;
import com.magamochi.content.model.dto.PresignedImportResponseDTO;
import com.magamochi.content.model.dto.*;
import com.magamochi.content.model.enumeration.ContentArchiveFileType;
import com.magamochi.content.service.ContentDownloadService;
import com.magamochi.content.service.ContentImportService;
@ -107,4 +103,14 @@ public class ContentController {
@RequestBody PresignedImportRequestDTO request) {
return DefaultResponseDTO.ok(contentImportService.requestPresignedImport(request));
}
@Operation(
summary = "Get a list of manga import jobs",
description = "Returns a list of manga import jobs.",
tags = {"Content"},
operationId = "getMangaImportJobs")
@GetMapping(value = "/import/jobs")
public DefaultResponseDTO<List<MangaImportJobDTO>> requestPresignedImport() {
return DefaultResponseDTO.ok(contentImportService.getImportJobs());
}
}

View File

@ -0,0 +1,31 @@
package com.magamochi.content.model.dto;
import com.magamochi.content.model.entity.MangaImportJob;
import com.magamochi.content.model.enumeration.ImportJobStatus;
import java.time.Instant;
public record MangaImportJobDTO(
Long id,
ImportJobStatus status,
Long malId,
Long aniListId,
String filename,
String s3Key,
Instant createdAt,
Instant updatedAt,
String errorMessage,
String errorStackTrace) {
public static MangaImportJobDTO from(MangaImportJob mangaImportJob) {
return new MangaImportJobDTO(
mangaImportJob.getId(),
mangaImportJob.getStatus(),
mangaImportJob.getMalId(),
mangaImportJob.getAniListId(),
mangaImportJob.getOriginalFilename(),
mangaImportJob.getS3FileKey(),
mangaImportJob.getCreatedAt(),
mangaImportJob.getUpdatedAt(),
mangaImportJob.getErrorMessage(),
mangaImportJob.getErrorStacktrace());
}
}

View File

@ -30,6 +30,10 @@ public class MangaImportJob {
@Enumerated(EnumType.STRING)
private ImportJobStatus status;
private String errorMessage;
private String errorStacktrace;
@CreationTimestamp private Instant createdAt;
@UpdateTimestamp private Instant updatedAt;

View File

@ -2,8 +2,11 @@ package com.magamochi.content.queue.consumer;
import static java.util.Objects.nonNull;
import com.magamochi.content.model.enumeration.ImportJobStatus;
import com.magamochi.content.queue.command.FileImportCommand;
import com.magamochi.content.service.ContentImportService;
import java.io.PrintWriter;
import java.io.StringWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
@ -24,14 +27,16 @@ public class FileImportConsumer {
if (nonNull(command.mangaImportJobId())) {
contentImportService.updateJobStatus(
command.mangaImportJobId(),
com.magamochi.content.model.enumeration.ImportJobStatus.SUCCESS);
command.mangaImportJobId(), ImportJobStatus.SUCCESS, null, null);
}
} catch (Exception e) {
if (nonNull(command.mangaImportJobId())) {
var sw = new StringWriter();
var pw = new PrintWriter(sw);
e.printStackTrace(pw);
contentImportService.updateJobStatus(
command.mangaImportJobId(),
com.magamochi.content.model.enumeration.ImportJobStatus.FAILED);
command.mangaImportJobId(), ImportJobStatus.FAILED, e.getMessage(), sw.toString());
}
throw e;

View File

@ -8,6 +8,7 @@ import com.magamochi.catalog.service.MangaContentProviderService;
import com.magamochi.catalog.service.MangaResolutionService;
import com.magamochi.common.exception.UnprocessableException;
import com.magamochi.common.model.enumeration.ContentType;
import com.magamochi.content.model.dto.MangaImportJobDTO;
import com.magamochi.content.model.dto.PresignedImportRequestDTO;
import com.magamochi.content.model.dto.PresignedImportResponseDTO;
import com.magamochi.content.model.entity.MangaContent;
@ -119,12 +120,15 @@ public class ContentImportService {
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateJobStatus(Long jobId, ImportJobStatus status) {
public void updateJobStatus(
Long jobId, ImportJobStatus status, String errorMessage, String errorStacktrace) {
mangaImportJobRepository
.findById(jobId)
.ifPresent(
job -> {
job.setStatus(status);
job.setErrorMessage(errorMessage);
job.setErrorStacktrace(errorStacktrace);
mangaImportJobRepository.save(job);
});
}
@ -202,4 +206,8 @@ public class ContentImportService {
.build());
}
}
public List<MangaImportJobDTO> getImportJobs() {
return mangaImportJobRepository.findAll().stream().map(MangaImportJobDTO::from).toList();
}
}

View File

@ -8,6 +8,8 @@ import com.magamochi.content.queue.command.FileImportCommand;
import com.magamochi.content.queue.producer.FileImportProducer;
import com.magamochi.image.service.S3Service;
import com.magamochi.ingestion.service.ContentProviderService;
import java.io.PrintWriter;
import java.io.StringWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Scheduled;
@ -47,8 +49,14 @@ public class PendingImportScannerTask {
fileImportProducer.sendFileImportCommand(
new FileImportCommand(mangaContentProvider.getId(), job.getS3FileKey(), job.getId()));
} catch (Exception e) {
var sw = new StringWriter();
var pw = new PrintWriter(sw);
e.printStackTrace(pw);
log.error("Failed to enqueue job {}", job.getId(), e);
job.setStatus(ImportJobStatus.FAILED);
job.setErrorMessage(e.getMessage());
job.setErrorStacktrace(sw.toString());
mangaImportJobRepository.save(job);
}
}

View File

@ -11,9 +11,13 @@ public record ContentProviderListDTO(@NotNull List<ContentProviderDTO> providers
contentProviders.stream().map(ContentProviderDTO::from).toList());
}
public record ContentProviderDTO(long id, @NotBlank String name) {
public record ContentProviderDTO(long id, @NotBlank String name, String url, boolean active) {
public static ContentProviderDTO from(ContentProvider contentProvider) {
return new ContentProviderDTO(contentProvider.getId(), contentProvider.getName());
return new ContentProviderDTO(
contentProvider.getId(),
contentProvider.getName(),
contentProvider.getUrl(),
contentProvider.isActive());
}
}
}

View File

@ -22,6 +22,8 @@ public class ContentProvider {
private String name;
private String url;
private boolean active;
private Boolean supportsContentFetch;

View File

@ -0,0 +1,3 @@
ALTER TABLE manga_import_job
ADD COLUMN error_message VARCHAR,
ADD COLUMN error_stacktrace VARCHAR;