feat: content download
This commit is contained in:
parent
5da02723cb
commit
f3def583ae
@ -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<byte[]> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.magamochi.content.model.enumeration;
|
||||
|
||||
public enum ContentArchiveFileType {
|
||||
CBZ,
|
||||
CBR
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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<byte[]> 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());
|
||||
}
|
||||
}
|
||||
@ -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<Image> findAll() {
|
||||
return imageRepository.findAll();
|
||||
}
|
||||
|
||||
public InputStream getStream(Image image) {
|
||||
return s3Service.getFileStream(image.getObjectKey());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package com.magamochi.model.enumeration;
|
||||
|
||||
public enum ArchiveFileType {
|
||||
CBZ,
|
||||
CBR
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user