feat: ajout de la fonctionnalité de conversion de fichiers de bande dessinée, permettant la conversion de fichiers CBR en CBZ. Intégration d'un service de conversion, d'une API pour gérer les téléchargements, et mise en place de validations pour les fichiers uploadés. Tests unitaires ajoutés pour garantir le bon fonctionnement de cette nouvelle fonctionnalité.
This commit is contained in:
parent
b4bfa48d00
commit
7a05934116
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Conversion\Infrastructure\ApiPlatform\Controller;
|
||||
|
||||
use App\Domain\Conversion\Application\Command\ConvertFileCommand;
|
||||
use App\Domain\Conversion\Application\CommandHandler\ConvertFileCommandHandler;
|
||||
use App\Domain\Conversion\Domain\Exception\ConversionException;
|
||||
use App\Domain\Conversion\Infrastructure\ApiPlatform\Resource\ConvertFileResource;
|
||||
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 ConvertFileController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ConvertFileCommandHandler $commandHandler
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$uploadedFile = $request->files->get('file');
|
||||
if (!$uploadedFile) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file']
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Validation manuelle pour éviter les règles automatiques de Symfony
|
||||
$errors = $this->validateFile($uploadedFile);
|
||||
if (!empty($errors)) {
|
||||
return $this->json($errors, 422);
|
||||
}
|
||||
|
||||
try {
|
||||
// Créer la commande
|
||||
$command = new ConvertFileCommand(
|
||||
filePath: $uploadedFile->getPathname(),
|
||||
originalFilename: $uploadedFile->getClientOriginalName(),
|
||||
fileSize: $uploadedFile->getSize()
|
||||
);
|
||||
|
||||
// Exécuter la conversion
|
||||
$response = $this->commandHandler->handle($command);
|
||||
|
||||
// Retourner le fichier converti
|
||||
$fileContent = file_get_contents($response->convertedFilePath);
|
||||
|
||||
return new Response(
|
||||
content: $fileContent,
|
||||
status: 200,
|
||||
headers: [
|
||||
'Content-Type' => 'application/x-cbz',
|
||||
'Content-Disposition' => sprintf('attachment; filename=%s', $response->outputFilename),
|
||||
]
|
||||
);
|
||||
|
||||
} catch (ConversionException $e) {
|
||||
return $this->json(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateFile($uploadedFile): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Vérifier si le fichier est valide
|
||||
if (!$uploadedFile->isValid()) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage()
|
||||
];
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Vérifier la taille (150MB max)
|
||||
$maxSize = 150 * 1024 * 1024; // 150MB en bytes
|
||||
if ($uploadedFile->getSize() > $maxSize) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is too large. Allowed size is 150MB.'
|
||||
];
|
||||
}
|
||||
|
||||
// Vérifier l'extension
|
||||
$allowedExtensions = ['cbr', 'cbz', 'zip', 'rar'];
|
||||
$extension = strtolower($uploadedFile->getClientOriginalExtension());
|
||||
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'Please upload a valid CBR or CBZ file'
|
||||
];
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Conversion\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model;
|
||||
use App\Domain\Conversion\Infrastructure\ApiPlatform\Controller\ConvertFileController;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'Conversion',
|
||||
operations: [
|
||||
new Post(
|
||||
uriTemplate: '/conversions/convert',
|
||||
controller: ConvertFileController::class,
|
||||
deserialize: false,
|
||||
openapiContext: [
|
||||
'summary' => 'Convert comic book file to CBZ',
|
||||
'description' => 'Converts a CBR or CBZ file to CBZ format and returns the converted file for download',
|
||||
'requestBody' => [
|
||||
'content' => [
|
||||
'multipart/form-data' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'required' => ['file'],
|
||||
'properties' => [
|
||||
'file' => [
|
||||
'type' => 'string',
|
||||
'format' => 'binary',
|
||||
'description' => 'Comic book file to convert (CBR, CBZ, max 150MB)'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'File converted successfully',
|
||||
'content' => [
|
||||
'application/x-cbz' => [
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'format' => 'binary'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ConvertFileResource
|
||||
{
|
||||
public ?File $file = null;
|
||||
|
||||
public ?string $fileName = null;
|
||||
|
||||
// Propriétés pour la réponse
|
||||
public mixed $fileContent = null;
|
||||
public ?string $filename = null;
|
||||
public ?string $originalConvertedFilePath = null;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Conversion\Infrastructure\Service;
|
||||
|
||||
use App\Domain\Conversion\Domain\Contract\ConversionServiceInterface;
|
||||
use App\Domain\Conversion\Domain\Exception\ConversionException;
|
||||
use App\Domain\Conversion\Domain\Model\ConversionRequest;
|
||||
use App\Domain\Conversion\Domain\Model\ConversionResult;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
final class ConversionService implements ConversionServiceInterface
|
||||
{
|
||||
private readonly string $tempDir;
|
||||
private readonly Filesystem $filesystem;
|
||||
|
||||
public function __construct(string $projectDir)
|
||||
{
|
||||
$this->tempDir = $projectDir . '/public/tmp';
|
||||
$this->filesystem = new Filesystem();
|
||||
}
|
||||
|
||||
public function convert(ConversionRequest $request): ConversionResult
|
||||
{
|
||||
try {
|
||||
$convertedFilePath = $this->convertCbrToCbz($request->getFilePath());
|
||||
|
||||
$convertedFileSize = file_exists($convertedFilePath) ? filesize($convertedFilePath) : 0;
|
||||
|
||||
return new ConversionResult(
|
||||
convertedFilePath: $convertedFilePath,
|
||||
outputFilename: $request->getOutputFilename(),
|
||||
originalFileSize: $request->getFileSize(),
|
||||
convertedFileSize: $convertedFileSize
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
throw ConversionException::conversionFailed($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function convertCbrToCbz(string $cbrPath): string
|
||||
{
|
||||
$tempDir = $this->tempDir . '/' . uniqid('cbr_conversion_');
|
||||
$this->filesystem->mkdir($tempDir);
|
||||
|
||||
$extractDir = $tempDir . '/extract';
|
||||
$this->filesystem->mkdir($extractDir);
|
||||
|
||||
// Essayer d'extraire avec unrar-free
|
||||
$process = new Process(['unrar-free', 'x', $cbrPath, $extractDir]);
|
||||
$process->run();
|
||||
|
||||
// Si unrar échoue, essayer avec 7z
|
||||
if (!$process->isSuccessful()) {
|
||||
$process = new Process(['7z', 'x', $cbrPath, "-o$extractDir"]);
|
||||
$process->run();
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new \RuntimeException("Extraction failed: " . $process->getErrorOutput());
|
||||
}
|
||||
}
|
||||
|
||||
// Créer le CBZ
|
||||
$cbzFileName = pathinfo($cbrPath, PATHINFO_FILENAME) . '.cbz';
|
||||
$cbzPath = $this->tempDir . '/' . $cbzFileName;
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($cbzPath, \ZipArchive::CREATE) !== true) {
|
||||
throw new \RuntimeException("Cannot create ZIP file");
|
||||
}
|
||||
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($extractDir),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (!$file->isDir()) {
|
||||
$filePath = $file->getRealPath();
|
||||
$relativePath = substr($filePath, strlen($extractDir) + 1);
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
// Nettoyer le dossier temporaire d'extraction
|
||||
$this->filesystem->remove($tempDir);
|
||||
|
||||
return $cbzPath;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user