feat: commit before changing gitea
This commit is contained in:
parent
b05bd98f63
commit
ffceda606f
12
src/Domain/Manga/Application/Command/ImportChapter.php
Normal file
12
src/Domain/Manga/Application/Command/ImportChapter.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\Command;
|
||||
|
||||
readonly class ImportChapter
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public float $chapterNumber,
|
||||
public string $fileBinary
|
||||
) {}
|
||||
}
|
||||
16
src/Domain/Manga/Application/Command/ImportVolume.php
Normal file
16
src/Domain/Manga/Application/Command/ImportVolume.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\Command;
|
||||
|
||||
readonly class ImportVolume
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public int $volumeNumber,
|
||||
public string $fileBinary
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\ImportChapter;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
use App\Domain\Shared\Domain\Contract\MangaPathManagerInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
readonly class ImportChapterHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ChapterRepositoryInterface $chapterRepository,
|
||||
private MangaPathManagerInterface $pathManager
|
||||
) {}
|
||||
|
||||
public function handle(ImportChapter $command): void
|
||||
{
|
||||
// 1. Validate that the manga exists
|
||||
$manga = $this->mangaRepository->findById($command->mangaId);
|
||||
if (!$manga) {
|
||||
throw new MangaNotFoundException($command->mangaId);
|
||||
}
|
||||
|
||||
// 2. Validate that the file is a valid CBZ
|
||||
if (!$this->isValidCbzFile($command->fileBinary)) {
|
||||
throw new \InvalidArgumentException('The provided file is not a valid CBZ file');
|
||||
}
|
||||
|
||||
// 3. Check if chapter exists
|
||||
$existingChapter = $this->chapterRepository->findByMangaIdAndChapterNumber(
|
||||
$command->mangaId,
|
||||
$command->chapterNumber
|
||||
);
|
||||
|
||||
if (!$existingChapter) {
|
||||
throw new ChapterNotFoundException("Chapter {$command->chapterNumber} not found for manga {$command->mangaId}");
|
||||
}
|
||||
|
||||
// 4. Save the CBZ file to storage using the path manager
|
||||
$cbzPath = $this->saveCbzFile($command, $manga, $existingChapter);
|
||||
|
||||
// 5. Update existing chapter with new CBZ path
|
||||
$updatedChapter = new Chapter(
|
||||
id: new ChapterId($existingChapter->getId()),
|
||||
mangaId: $existingChapter->getMangaId(),
|
||||
number: $existingChapter->getNumber(),
|
||||
title: $existingChapter->getTitle(),
|
||||
volume: $existingChapter->getVolume(),
|
||||
isVisible: $existingChapter->isVisible(),
|
||||
cbzPath: $cbzPath,
|
||||
createdAt: $existingChapter->getCreatedAt()
|
||||
);
|
||||
$this->chapterRepository->save($updatedChapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the binary data is a valid CBZ (ZIP) file
|
||||
*/
|
||||
private function isValidCbzFile(string $fileBinary): bool
|
||||
{
|
||||
// CBZ files are ZIP archives, check for ZIP magic number
|
||||
$zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04
|
||||
|
||||
return strpos($fileBinary, $zipMagicNumber) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the CBZ file to storage and return the path
|
||||
*/
|
||||
private function saveCbzFile(ImportChapter $command, \App\Domain\Manga\Domain\Model\Manga $manga, Chapter $chapter): string
|
||||
{
|
||||
// Build the final CBZ path using the path manager (creates directories)
|
||||
$volumeNumber = $chapter->getVolume() ?? 0;
|
||||
$cbzPath = $this->pathManager->buildChapterCbzPath(
|
||||
$manga->getTitle()->getValue(),
|
||||
(string)$manga->getPublicationYear(),
|
||||
$volumeNumber,
|
||||
(string)$command->chapterNumber
|
||||
);
|
||||
|
||||
// Write the binary content directly to the CBZ path
|
||||
if (!file_put_contents($cbzPath, $command->fileBinary)) {
|
||||
throw new \RuntimeException('Failed to save CBZ file');
|
||||
}
|
||||
|
||||
return $cbzPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\ImportVolume;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
use App\Domain\Shared\Domain\Contract\MangaPathManagerInterface;
|
||||
|
||||
readonly class ImportVolumeHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ChapterRepositoryInterface $chapterRepository,
|
||||
private MangaPathManagerInterface $pathManager
|
||||
) {}
|
||||
|
||||
public function handle(ImportVolume $command): void
|
||||
{
|
||||
// 1. Validate that the manga exists
|
||||
$manga = $this->mangaRepository->findById($command->mangaId);
|
||||
if (!$manga) {
|
||||
throw new MangaNotFoundException($command->mangaId);
|
||||
}
|
||||
|
||||
// 2. Validate that the file is a valid CBZ
|
||||
if (!$this->isValidCbzFile($command->fileBinary)) {
|
||||
throw new \InvalidArgumentException('The provided file is not a valid CBZ file');
|
||||
}
|
||||
|
||||
// 3. Get all chapters for this volume
|
||||
$chapters = $this->chapterRepository->findByMangaIdAndVolume(
|
||||
$command->mangaId,
|
||||
$command->volumeNumber
|
||||
);
|
||||
|
||||
if (empty($chapters)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"No chapters found for manga {$command->mangaId} in volume {$command->volumeNumber}"
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Save the CBZ file to storage using the path manager
|
||||
$cbzPath = $this->saveCbzFile($command, $manga);
|
||||
|
||||
// 5. Update all chapters with the volume CBZ path
|
||||
foreach ($chapters as $chapter) {
|
||||
$updatedChapter = new Chapter(
|
||||
id: new ChapterId($chapter->getId()),
|
||||
mangaId: $chapter->getMangaId(),
|
||||
number: $chapter->getNumber(),
|
||||
title: $chapter->getTitle(),
|
||||
volume: $chapter->getVolume(),
|
||||
isVisible: $chapter->isVisible(),
|
||||
cbzPath: $cbzPath,
|
||||
createdAt: $chapter->getCreatedAt()
|
||||
);
|
||||
$this->chapterRepository->save($updatedChapter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the binary data is a valid CBZ (ZIP) file
|
||||
*/
|
||||
private function isValidCbzFile(string $fileBinary): bool
|
||||
{
|
||||
// CBZ files are ZIP archives, check for ZIP magic number
|
||||
$zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04
|
||||
|
||||
return strpos($fileBinary, $zipMagicNumber) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the CBZ file to storage and return the path
|
||||
*/
|
||||
private function saveCbzFile(ImportVolume $command, \App\Domain\Manga\Domain\Model\Manga $manga): string
|
||||
{
|
||||
// Build the final CBZ path using the path manager (creates directories)
|
||||
$cbzPath = $this->pathManager->buildVolumeCbzPath(
|
||||
$manga->getTitle()->getValue(),
|
||||
(string)$manga->getPublicationYear(),
|
||||
$command->volumeNumber
|
||||
);
|
||||
|
||||
// Write the binary content directly to the CBZ path
|
||||
if (!file_put_contents($cbzPath, $command->fileBinary)) {
|
||||
throw new \RuntimeException('Failed to save CBZ file');
|
||||
}
|
||||
|
||||
return $cbzPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ readonly class DownloadVolumeHandler implements QueryHandlerInterface
|
||||
$cbzPaths[] = $chapter->getCbzPath();
|
||||
}
|
||||
|
||||
$volumeName = sprintf('%s-volume-%d',
|
||||
$volumeName = sprintf('%s_vol%d',
|
||||
$manga->getSlug()->getValue(),
|
||||
$query->volume
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ interface ChapterRepositoryInterface
|
||||
{
|
||||
public function findById(string $id): ?Chapter;
|
||||
public function findVisibleById(string $id): ?Chapter;
|
||||
public function findByMangaIdAndChapterNumber(string $mangaId, float $chapterNumber): ?Chapter;
|
||||
public function save(Chapter $chapter): void;
|
||||
public function delete(Chapter $chapter): void;
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Controller;
|
||||
|
||||
use App\Domain\Manga\Application\Command\ImportChapter;
|
||||
use App\Domain\Manga\Application\CommandHandler\ImportChapterHandler;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\ImportChapterResource;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
#[AsController]
|
||||
final class ImportChapterController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImportChapterHandler $commandHandler
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
// Get form parameters
|
||||
$mangaId = $request->request->get('mangaId');
|
||||
$chapterNumber = $request->request->get('chapterNumber');
|
||||
$uploadedFile = $request->files->get('file');
|
||||
|
||||
// Validate required fields
|
||||
if (!$mangaId) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'mangaId', 'message' => 'mangaId is required']
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$chapterNumber) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'chapterNumber', 'message' => 'chapterNumber is required']
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$uploadedFile) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file']
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Validate file
|
||||
$errors = $this->validateFile($uploadedFile);
|
||||
if (!empty($errors)) {
|
||||
return $this->json($errors, 422);
|
||||
}
|
||||
|
||||
try {
|
||||
// Read file binary content
|
||||
$fileBinary = file_get_contents($uploadedFile->getPathname());
|
||||
if ($fileBinary === false) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file']
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Create the command
|
||||
$command = new ImportChapter(
|
||||
mangaId: $mangaId,
|
||||
chapterNumber: (float) $chapterNumber,
|
||||
fileBinary: $fileBinary
|
||||
);
|
||||
|
||||
// Execute the import
|
||||
$this->commandHandler->handle($command);
|
||||
|
||||
return $this->json([
|
||||
'message' => 'Chapter imported successfully',
|
||||
'mangaId' => $mangaId,
|
||||
'chapterNumber' => $chapterNumber
|
||||
], 200);
|
||||
|
||||
} catch (MangaNotFoundException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Manga not found',
|
||||
'details' => $e->getMessage()
|
||||
], 404);
|
||||
|
||||
} catch (ChapterNotFoundException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Chapter not found',
|
||||
'details' => $e->getMessage()
|
||||
], 404);
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Invalid file',
|
||||
'details' => $e->getMessage()
|
||||
], 400);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'error' => 'Import failed',
|
||||
'details' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateFile($uploadedFile): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Check if file is valid
|
||||
if (!$uploadedFile->isValid()) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage()
|
||||
];
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Check file size (500MB max)
|
||||
$maxSize = 500 * 1024 * 1024; // 500MB in bytes
|
||||
if ($uploadedFile->getSize() > $maxSize) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is too large. Allowed size is 500MB.'
|
||||
];
|
||||
}
|
||||
|
||||
// Check file extension
|
||||
$allowedExtensions = ['cbz'];
|
||||
$extension = strtolower($uploadedFile->getClientOriginalExtension());
|
||||
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'Please upload a valid CBZ file'
|
||||
];
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Controller;
|
||||
|
||||
use App\Domain\Manga\Application\Command\ImportVolume;
|
||||
use App\Domain\Manga\Application\CommandHandler\ImportVolumeHandler;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\ImportVolumeResource;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
|
||||
#[AsController]
|
||||
final class ImportVolumeController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImportVolumeHandler $commandHandler
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
// Get form parameters
|
||||
$mangaId = $request->request->get('mangaId');
|
||||
$volumeNumber = $request->request->get('volumeNumber');
|
||||
$uploadedFile = $request->files->get('file');
|
||||
|
||||
// Validate required fields
|
||||
if (!$mangaId) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'mangaId', 'message' => 'mangaId is required']
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$volumeNumber) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'volumeNumber', 'message' => 'volumeNumber is required']
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$uploadedFile) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file']
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Validate file
|
||||
$errors = $this->validateFile($uploadedFile);
|
||||
if (!empty($errors)) {
|
||||
return $this->json($errors, 422);
|
||||
}
|
||||
|
||||
try {
|
||||
// Read file binary content
|
||||
$fileBinary = file_get_contents($uploadedFile->getPathname());
|
||||
if ($fileBinary === false) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file']
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Create the command
|
||||
$command = new ImportVolume(
|
||||
mangaId: $mangaId,
|
||||
volumeNumber: (int) $volumeNumber,
|
||||
fileBinary: $fileBinary
|
||||
);
|
||||
|
||||
// Execute the import
|
||||
$this->commandHandler->handle($command);
|
||||
|
||||
return $this->json([
|
||||
'message' => 'Volume imported successfully',
|
||||
'mangaId' => $mangaId,
|
||||
'volumeNumber' => (int) $volumeNumber
|
||||
], 200);
|
||||
|
||||
} catch (MangaNotFoundException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Manga not found',
|
||||
'details' => $e->getMessage()
|
||||
], 404);
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$statusCode = str_contains($e->getMessage(), 'not found') ? 404 : 400;
|
||||
return $this->json([
|
||||
'error' => 'Invalid request',
|
||||
'details' => $e->getMessage()
|
||||
], $statusCode);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'error' => 'Import failed',
|
||||
'details' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateFile($uploadedFile): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Check if file is valid
|
||||
if (!$uploadedFile->isValid()) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage()
|
||||
];
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Check file size (500MB max)
|
||||
$maxSize = 500 * 1024 * 1024; // 500MB in bytes
|
||||
if ($uploadedFile->getSize() > $maxSize) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is too large. Allowed size is 500MB.'
|
||||
];
|
||||
}
|
||||
|
||||
// Check file extension
|
||||
$allowedExtensions = ['cbz'];
|
||||
$extension = strtolower($uploadedFile->getClientOriginalExtension());
|
||||
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'Please upload a valid CBZ file'
|
||||
];
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Controller\ImportChapterController;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ImportChapter',
|
||||
operations: [
|
||||
new Post(
|
||||
uriTemplate: '/chapters/import',
|
||||
controller: ImportChapterController::class,
|
||||
deserialize: false,
|
||||
openapiContext: [
|
||||
'summary' => 'Import a chapter from CBZ file',
|
||||
'description' => 'Imports a CBZ file for an existing chapter and stores it',
|
||||
'requestBody' => [
|
||||
'content' => [
|
||||
'multipart/form-data' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'required' => ['mangaId', 'chapterNumber', 'file'],
|
||||
'properties' => [
|
||||
'mangaId' => [
|
||||
'type' => 'string',
|
||||
'format' => 'uuid',
|
||||
'description' => 'The manga UUID'
|
||||
],
|
||||
'chapterNumber' => [
|
||||
'type' => 'number',
|
||||
'description' => 'The chapter number (e.g., 1.5)'
|
||||
],
|
||||
'file' => [
|
||||
'type' => 'string',
|
||||
'format' => 'binary',
|
||||
'description' => 'CBZ file to import (max 500MB)'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'Chapter imported successfully',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'message' => ['type' => 'string'],
|
||||
'chapterId' => ['type' => 'string']
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ImportChapterResource
|
||||
{
|
||||
public ?string $mangaId = null;
|
||||
|
||||
public ?float $chapterNumber = null;
|
||||
|
||||
public ?File $file = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Controller\ImportVolumeController;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ImportVolume',
|
||||
operations: [
|
||||
new Post(
|
||||
uriTemplate: '/volumes/import',
|
||||
controller: ImportVolumeController::class,
|
||||
deserialize: false,
|
||||
openapiContext: [
|
||||
'summary' => 'Import a volume from CBZ file',
|
||||
'description' => 'Imports a CBZ file for an existing volume and updates all chapters',
|
||||
'requestBody' => [
|
||||
'content' => [
|
||||
'multipart/form-data' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'required' => ['mangaId', 'volumeNumber', 'file'],
|
||||
'properties' => [
|
||||
'mangaId' => [
|
||||
'type' => 'string',
|
||||
'format' => 'uuid',
|
||||
'description' => 'The manga UUID'
|
||||
],
|
||||
'volumeNumber' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'The volume number'
|
||||
],
|
||||
'file' => [
|
||||
'type' => 'string',
|
||||
'format' => 'binary',
|
||||
'description' => 'CBZ file to import (max 500MB)'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'Volume imported successfully',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'message' => ['type' => 'string'],
|
||||
'mangaId' => ['type' => 'string'],
|
||||
'volumeNumber' => ['type' => 'integer']
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ImportVolumeResource
|
||||
{
|
||||
public ?string $mangaId = null;
|
||||
|
||||
public ?int $volumeNumber = null;
|
||||
|
||||
public ?File $file = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,21 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
|
||||
return $entity ? $this->toDomainModel($entity) : null;
|
||||
}
|
||||
|
||||
public function findByMangaIdAndChapterNumber(string $mangaId, float $chapterNumber): ?Chapter
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(ChapterEntity::class, 'c')
|
||||
->where('c.manga = :mangaId')
|
||||
->andWhere('c.number = :chapterNumber')
|
||||
->setParameter('mangaId', $mangaId)
|
||||
->setParameter('chapterNumber', $chapterNumber);
|
||||
|
||||
$entity = $qb->getQuery()->getOneOrNullResult();
|
||||
|
||||
return $entity ? $this->toDomainModel($entity) : null;
|
||||
}
|
||||
|
||||
public function save(Chapter $chapter): void
|
||||
{
|
||||
$entity = $this->entityManager->find(ChapterEntity::class, $chapter->getId());
|
||||
|
||||
Reference in New Issue
Block a user