diff --git a/src/main/java/com/magamochi/content/controller/ContentController.java b/src/main/java/com/magamochi/content/controller/ContentController.java index 7b2984a..de4088f 100644 --- a/src/main/java/com/magamochi/content/controller/ContentController.java +++ b/src/main/java/com/magamochi/content/controller/ContentController.java @@ -3,21 +3,28 @@ package com.magamochi.content.controller; import com.magamochi.common.model.dto.DefaultResponseDTO; import com.magamochi.content.model.dto.MangaContentDTO; import com.magamochi.content.model.dto.MangaContentImagesDTO; +import com.magamochi.content.model.enumeration.ContentArchiveFileType; +import com.magamochi.content.service.ContentDownloadService; import com.magamochi.content.service.ContentService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.constraints.NotNull; +import java.io.IOException; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/content") @RequiredArgsConstructor public class ContentController { private final ContentService contentService; + private final ContentDownloadService contentDownloadService; @Operation( summary = "Get the content for a specific manga/content provider combination", @@ -41,4 +48,32 @@ public class ContentController { @PathVariable Long mangaContentId) { return DefaultResponseDTO.ok(contentService.getContentImages(mangaContentId)); } + + @Operation( + summary = "Download content archive", + description = "Download content as a compressed file by its ID.", + tags = {"Content"}, + operationId = "downloadContentArchive") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "Successful download", + content = + @Content( + mediaType = "application/octet-stream", + schema = @Schema(type = "string", format = "binary"))), + }) + @PostMapping( + value = "/{mangaContentId}/download", + produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public ResponseEntity downloadContentArchive( + @PathVariable Long mangaContentId, + @RequestParam ContentArchiveFileType contentArchiveFileType) + throws IOException { + var response = contentDownloadService.downloadContent(mangaContentId, contentArchiveFileType); + + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=\"" + response.filename() + "\"") + .body(response.content()); + } } diff --git a/src/main/java/com/magamochi/model/dto/MangaChapterArchiveDTO.java b/src/main/java/com/magamochi/content/model/dto/MangaContentArchiveDTO.java similarity index 54% rename from src/main/java/com/magamochi/model/dto/MangaChapterArchiveDTO.java rename to src/main/java/com/magamochi/content/model/dto/MangaContentArchiveDTO.java index 30c7500..30628b5 100644 --- a/src/main/java/com/magamochi/model/dto/MangaChapterArchiveDTO.java +++ b/src/main/java/com/magamochi/content/model/dto/MangaContentArchiveDTO.java @@ -1,6 +1,6 @@ -package com.magamochi.model.dto; +package com.magamochi.content.model.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -public record MangaChapterArchiveDTO(@NotBlank String filename, @NotNull byte[] content) {} +public record MangaContentArchiveDTO(@NotBlank String filename, @NotNull byte[] content) {} diff --git a/src/main/java/com/magamochi/content/model/enumeration/ContentArchiveFileType.java b/src/main/java/com/magamochi/content/model/enumeration/ContentArchiveFileType.java new file mode 100644 index 0000000..4084102 --- /dev/null +++ b/src/main/java/com/magamochi/content/model/enumeration/ContentArchiveFileType.java @@ -0,0 +1,6 @@ +package com.magamochi.content.model.enumeration; + +public enum ContentArchiveFileType { + CBZ, + CBR +} diff --git a/src/main/java/com/magamochi/service/MangaChapterService.java b/src/main/java/com/magamochi/content/service/ContentDownloadService.java similarity index 56% rename from src/main/java/com/magamochi/service/MangaChapterService.java rename to src/main/java/com/magamochi/content/service/ContentDownloadService.java index 8e89f2f..9e370de 100644 --- a/src/main/java/com/magamochi/service/MangaChapterService.java +++ b/src/main/java/com/magamochi/content/service/ContentDownloadService.java @@ -1,12 +1,10 @@ -package com.magamochi.service; +package com.magamochi.content.service; import com.magamochi.common.exception.UnprocessableException; -import com.magamochi.content.model.entity.MangaContent; +import com.magamochi.content.model.dto.MangaContentArchiveDTO; import com.magamochi.content.model.entity.MangaContentImage; -import com.magamochi.content.model.repository.MangaContentImageRepository; -import com.magamochi.content.model.repository.MangaContentRepository; -import com.magamochi.model.dto.MangaChapterArchiveDTO; -import com.magamochi.model.enumeration.ArchiveFileType; +import com.magamochi.content.model.enumeration.ContentArchiveFileType; +import com.magamochi.image.service.ImageService; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -21,27 +19,24 @@ import org.springframework.stereotype.Service; @Log4j2 @Service @RequiredArgsConstructor -public class MangaChapterService { - private final MangaContentRepository mangaContentRepository; - private final MangaContentImageRepository mangaContentImageRepository; +public class ContentDownloadService { + private final ContentService contentService; + private final ImageService imageService; - private final OldImageService oldImageService; - - public MangaChapterArchiveDTO downloadChapter(Long chapterId, ArchiveFileType archiveFileType) - throws IOException { - var chapter = getMangaChapterThrowIfNotFound(chapterId); - - var chapterImages = mangaContentImageRepository.findAllByMangaContent(chapter); + public MangaContentArchiveDTO downloadContent( + Long mangaContentId, ContentArchiveFileType contentArchiveFileType) throws IOException { + var chapter = contentService.find(mangaContentId); + var chapterImages = chapter.getMangaContentImages(); var byteArrayOutputStream = - switch (archiveFileType) { + switch (contentArchiveFileType) { case CBZ -> getChapterCbzArchive(chapterImages); default -> throw new UnprocessableException( - "Unsupported archive file type: " + archiveFileType.name()); + "Unsupported archive file type: " + contentArchiveFileType.name()); }; - return new MangaChapterArchiveDTO( + return new MangaContentArchiveDTO( chapter.getTitle() + ".cbz", byteArrayOutputStream.toByteArray()); } @@ -60,7 +55,7 @@ public class MangaChapterService { var paddedFileName = String.format("%0" + paddingLength + "d.jpg", imgSrc.getPosition()); zipOutputStream.putNextEntry(new ZipEntry(paddedFileName)); - IOUtils.copy(oldImageService.getImageStream(imgSrc.getImage()), zipOutputStream); + IOUtils.copy(imageService.getStream(imgSrc.getImage()), zipOutputStream); zipOutputStream.closeEntry(); } @@ -69,10 +64,4 @@ public class MangaChapterService { IOUtils.closeQuietly(zipOutputStream); return byteArrayOutputStream; } - - private MangaContent getMangaChapterThrowIfNotFound(Long chapterId) { - return mangaContentRepository - .findById(chapterId) - .orElseThrow(() -> new RuntimeException("Manga Chapter not found for ID: " + chapterId)); - } } diff --git a/src/main/java/com/magamochi/controller/MangaChapterController.java b/src/main/java/com/magamochi/controller/MangaChapterController.java deleted file mode 100644 index 1e98783..0000000 --- a/src/main/java/com/magamochi/controller/MangaChapterController.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.magamochi.controller; - -import com.magamochi.model.enumeration.ArchiveFileType; -import com.magamochi.service.MangaChapterService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import java.io.IOException; -import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/mangas/chapters") -@RequiredArgsConstructor -public class MangaChapterController { - private final MangaChapterService mangaChapterService; - - @Operation( - summary = "Download chapter archive", - description = "Download a chapter as a compressed file by its ID.", - tags = {"Manga Chapter"}, - operationId = "downloadChapterArchive") - @ApiResponses({ - @ApiResponse( - responseCode = "200", - description = "Successful download", - content = - @Content( - mediaType = "application/octet-stream", - schema = @Schema(type = "string", format = "binary"))), - }) - @PostMapping(value = "/{chapterId}/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public ResponseEntity downloadChapterArchive( - @PathVariable Long chapterId, @RequestParam ArchiveFileType archiveFileType) - throws IOException { - - var response = mangaChapterService.downloadChapter(chapterId, archiveFileType); - return ResponseEntity.ok() - .header("Content-Disposition", "attachment; filename=\"" + response.filename() + "\"") - .body(response.content()); - } -} diff --git a/src/main/java/com/magamochi/image/service/ImageService.java b/src/main/java/com/magamochi/image/service/ImageService.java index e3c19a8..d53e9d3 100644 --- a/src/main/java/com/magamochi/image/service/ImageService.java +++ b/src/main/java/com/magamochi/image/service/ImageService.java @@ -3,6 +3,7 @@ package com.magamochi.image.service; import com.magamochi.common.exception.NotFoundException; import com.magamochi.image.model.entity.Image; import com.magamochi.image.model.repository.ImageRepository; +import java.io.InputStream; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -51,4 +52,8 @@ public class ImageService { public List findAll() { return imageRepository.findAll(); } + + public InputStream getStream(Image image) { + return s3Service.getFileStream(image.getObjectKey()); + } } diff --git a/src/main/java/com/magamochi/image/service/S3Service.java b/src/main/java/com/magamochi/image/service/S3Service.java index 9cebd2f..2adb5a1 100644 --- a/src/main/java/com/magamochi/image/service/S3Service.java +++ b/src/main/java/com/magamochi/image/service/S3Service.java @@ -2,6 +2,7 @@ package com.magamochi.image.service; import static java.util.Objects.nonNull; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -103,4 +104,10 @@ public class S3Service { } } } + + public InputStream getFileStream(String key) { + var request = GetObjectRequest.builder().bucket(bucket).key(key).build(); + + return s3Client.getObject(request); + } } diff --git a/src/main/java/com/magamochi/model/enumeration/ArchiveFileType.java b/src/main/java/com/magamochi/model/enumeration/ArchiveFileType.java deleted file mode 100644 index 4a26d3f..0000000 --- a/src/main/java/com/magamochi/model/enumeration/ArchiveFileType.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.magamochi.model.enumeration; - -public enum ArchiveFileType { - CBZ, - CBR -} diff --git a/src/main/java/com/magamochi/service/OldImageService.java b/src/main/java/com/magamochi/service/OldImageService.java deleted file mode 100644 index fe7ba46..0000000 --- a/src/main/java/com/magamochi/service/OldImageService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.magamochi.service; - -import com.magamochi.image.model.entity.Image; -import java.io.InputStream; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.stereotype.Service; - -@Log4j2 -@Service -@RequiredArgsConstructor -public class OldImageService { - private final OldS3Service oldS3Service; - - public InputStream getImageStream(Image image) { - return oldS3Service.getFile(image.getObjectKey()); - } -} diff --git a/src/main/java/com/magamochi/service/OldS3Service.java b/src/main/java/com/magamochi/service/OldS3Service.java deleted file mode 100644 index d8cdc8d..0000000 --- a/src/main/java/com/magamochi/service/OldS3Service.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.magamochi.service; - -import java.io.InputStream; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.*; - -@Service -@RequiredArgsConstructor -public class OldS3Service { - @Value("${minio.bucket}") - private String bucket; - - private final S3Client s3Client; - - public InputStream getFile(String key) { - var request = GetObjectRequest.builder().bucket(bucket).key(key).build(); - - return s3Client.getObject(request); - } -}