feature/upgrade-symfony-8 #35
@@ -1,7 +1,7 @@
|
||||
#syntax=docker/dockerfile:1.4
|
||||
|
||||
# Versions
|
||||
FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream
|
||||
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
|
||||
|
||||
# The different stages of this Dockerfile are meant to be built into separate images
|
||||
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
|
||||
|
||||
@@ -242,8 +242,17 @@ watch(() => props.source, (newSource) => {
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const buildPayload = (formData) => {
|
||||
const data = { ...formData };
|
||||
const raw = data.testChapterNumber;
|
||||
data.testChapterNumber = (raw === '' || raw === null || raw === undefined)
|
||||
? null
|
||||
: parseFloat(raw);
|
||||
return data;
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
emit('submit', { ...form.value });
|
||||
emit('submit', buildPayload(form.value));
|
||||
};
|
||||
|
||||
defineExpose({ submitForm: handleSubmit });
|
||||
@@ -252,7 +261,7 @@ const testConfiguration = async () => {
|
||||
testing.value = true;
|
||||
try {
|
||||
await emit('test', {
|
||||
configuration: { ...form.value },
|
||||
configuration: buildPayload(form.value),
|
||||
testData: {
|
||||
mangaSlug: form.value.testSlug,
|
||||
chapterNumber: form.value.testChapterNumber,
|
||||
|
||||
@@ -3,56 +3,55 @@
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"description": "A minimal Symfony project recommended to create bare bones applications",
|
||||
"minimum-stability": "stable",
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.3.1",
|
||||
"php": ">=8.4.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-zip": "*",
|
||||
"api-platform/core": "^3.2",
|
||||
"doctrine/dbal": "^3",
|
||||
"doctrine/doctrine-bundle": "^2.11",
|
||||
"api-platform/core": "^4.0",
|
||||
"doctrine/dbal": "^4",
|
||||
"doctrine/doctrine-bundle": "^3.0",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||
"doctrine/orm": "^2.17",
|
||||
"doctrine/orm": "^3.0",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"intervention/image": "^3.7",
|
||||
"nelmio/cors-bundle": "^2.4",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
"phpstan/phpdoc-parser": "^1.25",
|
||||
"ramsey/uuid": "^4.7",
|
||||
"runtime/frankenphp-symfony": "^0.2.0",
|
||||
"symfony/asset": "7.0.*",
|
||||
"symfony/console": "7.0.*",
|
||||
"symfony/css-selector": "7.0.*",
|
||||
"symfony/doctrine-messenger": "7.0.*",
|
||||
"symfony/dotenv": "7.0.*",
|
||||
"symfony/expression-language": "7.0.*",
|
||||
"symfony/asset": "8.0.*",
|
||||
"symfony/console": "8.0.*",
|
||||
"symfony/css-selector": "8.0.*",
|
||||
"symfony/doctrine-messenger": "8.0.*",
|
||||
"symfony/dotenv": "8.0.*",
|
||||
"symfony/expression-language": "8.0.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "7.0.*",
|
||||
"symfony/framework-bundle": "7.0.*",
|
||||
"symfony/http-client": "7.0.*",
|
||||
"symfony/mercure-bundle": "^0.3.9",
|
||||
"symfony/messenger": "7.0.*",
|
||||
"symfony/mime": "7.0.*",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/form": "8.0.*",
|
||||
"symfony/framework-bundle": "8.0.*",
|
||||
"symfony/http-client": "8.0.*",
|
||||
"symfony/mercure-bundle": "^0.4",
|
||||
"symfony/messenger": "8.0.*",
|
||||
"symfony/mime": "8.0.*",
|
||||
"symfony/monolog-bundle": "^4.0",
|
||||
"symfony/panther": "^2.1",
|
||||
"symfony/property-access": "7.0.*",
|
||||
"symfony/property-info": "7.0.*",
|
||||
"symfony/runtime": "7.0.*",
|
||||
"symfony/scheduler": "7.0.*",
|
||||
"symfony/security-bundle": "7.0.*",
|
||||
"symfony/serializer": "7.0.*",
|
||||
"symfony/property-access": "8.0.*",
|
||||
"symfony/property-info": "8.0.*",
|
||||
"symfony/runtime": "8.0.*",
|
||||
"symfony/scheduler": "8.0.*",
|
||||
"symfony/security-bundle": "8.0.*",
|
||||
"symfony/serializer": "8.0.*",
|
||||
"symfony/stimulus-bundle": "^2.17",
|
||||
"symfony/twig-bundle": "7.0.*",
|
||||
"symfony/twig-bundle": "8.0.*",
|
||||
"symfony/ux-live-component": "^2.17",
|
||||
"symfony/ux-react": "^2.23",
|
||||
"symfony/ux-turbo": "^2.18",
|
||||
"symfony/validator": "7.0.*",
|
||||
"symfony/validator": "8.0.*",
|
||||
"symfony/webpack-encore-bundle": "^2.1",
|
||||
"symfony/yaml": "7.0.*",
|
||||
"symfony/yaml": "8.0.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0",
|
||||
"vich/uploader-bundle": "^2.7"
|
||||
@@ -103,7 +102,7 @@
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.0.*",
|
||||
"require": "8.0.*",
|
||||
"docker": true
|
||||
}
|
||||
},
|
||||
@@ -111,18 +110,18 @@
|
||||
"dama/doctrine-test-bundle": "^8.2",
|
||||
"dbrekelmans/bdi": "^1.3",
|
||||
"deployer/deployer": "^7.5",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.5",
|
||||
"doctrine/doctrine-fixtures-bundle": "^4.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.48",
|
||||
"mtdowling/jmespath.php": "^2.7",
|
||||
"phparkitect/phparkitect": "^0.3.33",
|
||||
"phpmd/phpmd": "^2.15",
|
||||
"phparkitect/phparkitect": "^0.8",
|
||||
"phpmd/phpmd": "3.x-dev",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"symfony/browser-kit": "7.0.*",
|
||||
"symfony/browser-kit": "8.0.*",
|
||||
"symfony/maker-bundle": "^1.52",
|
||||
"symfony/phpunit-bridge": "^7.0",
|
||||
"symfony/stopwatch": "7.0.*",
|
||||
"symfony/web-profiler-bundle": "7.0.*",
|
||||
"symfony/phpunit-bridge": "^8.0",
|
||||
"symfony/stopwatch": "8.0.*",
|
||||
"symfony/web-profiler-bundle": "8.0.*",
|
||||
"zenstruck/browser": "^1.8",
|
||||
"zenstruck/foundry": "^1.36"
|
||||
"zenstruck/foundry": "^2.0"
|
||||
}
|
||||
}
|
||||
|
||||
3796
composer.lock
generated
3796
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -23,8 +23,6 @@ api_platform:
|
||||
extra_properties:
|
||||
standard_put: true
|
||||
rfc_7807_compliant_errors: true
|
||||
event_listeners_backward_compatibility_layer: false
|
||||
keep_legacy_inflector: false
|
||||
mapping:
|
||||
paths:
|
||||
- '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto'
|
||||
|
||||
11
config/packages/csrf.yaml
Normal file
11
config/packages/csrf.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Enable stateless CSRF protection for forms and logins/logouts
|
||||
framework:
|
||||
form:
|
||||
csrf_protection:
|
||||
token_id: submit
|
||||
|
||||
csrf_protection:
|
||||
stateless_token_ids:
|
||||
- submit
|
||||
- authenticate
|
||||
- logout
|
||||
@@ -3,7 +3,6 @@ doctrine:
|
||||
connections:
|
||||
default:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
use_savepoints: true
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
@@ -11,9 +10,6 @@ doctrine:
|
||||
#server_version: '16'
|
||||
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
@@ -40,7 +36,6 @@ when@test:
|
||||
dbal:
|
||||
connections:
|
||||
default:
|
||||
use_savepoints: true
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
|
||||
3
config/packages/property_info.yaml
Normal file
3
config/packages/property_info.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
2106
config/reference.php
Normal file
2106
config/reference.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /_error
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||
prefix: /_profiler
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Deployer;
|
||||
|
||||
require 'recipe/symfony.php';
|
||||
@@ -36,12 +37,13 @@ task('deploy:vendors', function () {
|
||||
$releaseDir = get('release_path');
|
||||
$previousDir = get('previous_release');
|
||||
|
||||
if ($previousDir !== null) {
|
||||
if (null !== $previousDir) {
|
||||
$lockUnchanged = test("diff -q $previousDir/composer.lock $releaseDir/composer.lock > /dev/null 2>&1");
|
||||
$vendorPopulated = test("[ -d $releaseDir/vendor/composer ]");
|
||||
|
||||
if ($lockUnchanged && $vendorPopulated) {
|
||||
writeln('<info>deploy:vendors skipped — composer.lock unchanged</info>');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -67,7 +69,7 @@ task('webpack_encore:build', function () {
|
||||
$previousDir = get('previous_release'); // null au 1er déploiement
|
||||
|
||||
// --- COUCHE 1 : skip total si aucun fichier front-end n'a changé ---
|
||||
if ($previousDir !== null) {
|
||||
if (null !== $previousDir) {
|
||||
$watchList = ['assets', 'templates', 'package.json', 'package-lock.json',
|
||||
'webpack.config.js', 'postcss.config.js', 'tailwind.config.js'];
|
||||
|
||||
@@ -81,13 +83,14 @@ task('webpack_encore:build', function () {
|
||||
if ($hasPreviousBuild && test("($diffChecks)")) {
|
||||
run("cp -al $previousDir/public/build $releaseDir/public/build");
|
||||
writeln('<info>webpack_encore:build skipped — no front-end files changed</info>');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// --- COUCHE 2 : skip npm install si package-lock.json inchangé ---
|
||||
$needsNpmInstall = true;
|
||||
if ($previousDir !== null) {
|
||||
if (null !== $previousDir) {
|
||||
$lockUnchanged = test("diff -q $previousDir/package-lock.json $releaseDir/package-lock.json > /dev/null 2>&1");
|
||||
$nmPopulated = test("[ -d $sharedNodeModules/.bin ]");
|
||||
if ($lockUnchanged && $nmPopulated) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
worker {
|
||||
file ./public/index.php
|
||||
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
|
||||
}
|
||||
|
||||
97
migrations/Version20260326165659.php
Normal file
97
migrations/Version20260326165659.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260326165659 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Migrate manga.genres column from PHP-serialized array to JSON';
|
||||
}
|
||||
|
||||
public function preUp(Schema $schema): void
|
||||
{
|
||||
// Convert existing PHP-serialized data to JSON before changing the column type
|
||||
$rows = $this->connection->fetchAllAssociative('SELECT id, genres FROM manga WHERE genres IS NOT NULL');
|
||||
foreach ($rows as $row) {
|
||||
$raw = $row['genres'];
|
||||
// Skip if already valid JSON
|
||||
json_decode($raw);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
continue;
|
||||
}
|
||||
// Unserialize PHP format and re-encode as JSON
|
||||
$value = @unserialize($raw);
|
||||
if ($value === false && $raw !== 'b:0;') {
|
||||
$value = [];
|
||||
}
|
||||
$this->connection->executeStatement(
|
||||
'UPDATE manga SET genres = :json WHERE id = :id',
|
||||
['json' => json_encode($value), 'id' => $row['id']]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('COMMENT ON COLUMN api_token.expires_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN content_source.health_last_tested_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN failed_job.failed_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN job.created_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN job.started_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN job.completed_at IS \'\'');
|
||||
$this->addSql('ALTER TABLE manga ALTER genres TYPE JSON USING genres::json');
|
||||
$this->addSql('COMMENT ON COLUMN manga.genres IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga.created_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga.last_monitoring_check IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga_preferred_sources.created_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga_preferred_sources.updated_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN source.created_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN source.updated_at IS \'\'');
|
||||
$this->addSql('DROP INDEX idx_75ea56e0e3bd61ce');
|
||||
$this->addSql('DROP INDEX idx_75ea56e0fb7336f0');
|
||||
$this->addSql('DROP INDEX idx_75ea56e016ba31db');
|
||||
$this->addSql('ALTER TABLE messenger_messages ALTER id DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE messenger_messages ALTER id ADD GENERATED BY DEFAULT AS IDENTITY');
|
||||
$this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'\'');
|
||||
$this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'\'');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('COMMENT ON COLUMN api_token.expires_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN content_source.health_last_tested_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN failed_job.failed_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN job.created_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN job.started_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN job.completed_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE manga ALTER genres TYPE TEXT');
|
||||
$this->addSql('COMMENT ON COLUMN manga.genres IS \'(DC2Type:array)\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga.created_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga.last_monitoring_check IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga_preferred_sources.created_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN manga_preferred_sources.updated_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('DROP INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750');
|
||||
$this->addSql('ALTER TABLE messenger_messages ALTER id SET DEFAULT nextval(\'messenger_messages_id_seq\'::regclass)');
|
||||
$this->addSql('ALTER TABLE messenger_messages ALTER id DROP IDENTITY');
|
||||
$this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('CREATE INDEX idx_75ea56e0e3bd61ce ON messenger_messages (available_at)');
|
||||
$this->addSql('CREATE INDEX idx_75ea56e0fb7336f0 ON messenger_messages (queue_name)');
|
||||
$this->addSql('CREATE INDEX idx_75ea56e016ba31db ON messenger_messages (delivered_at)');
|
||||
$this->addSql('COMMENT ON COLUMN source.created_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN source.updated_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ return static function (Config $config): void {
|
||||
// Dépendances externes autorisées
|
||||
$externalDependencies = [
|
||||
'Symfony\Component\Messenger',
|
||||
'Ramsey\Uuid'
|
||||
'Ramsey\Uuid',
|
||||
];
|
||||
|
||||
// Règle pour le namespace cohérent
|
||||
@@ -72,7 +72,7 @@ return static function (Config $config): void {
|
||||
// Interdiction explicite pour l'Application d'accéder à l'Infrastructure
|
||||
$rules[] = Rule::allClasses()
|
||||
->that(new ResideInOneOfTheseNamespaces("App\Domain\\$domain\Application"))
|
||||
->should(new NotDependsOnTheseNamespaces("App\Domain\\$domain\Infrastructure"))
|
||||
->should(new NotDependsOnTheseNamespaces(["App\Domain\\$domain\Infrastructure"]))
|
||||
->because("la couche Application de $domain ne doit jamais dépendre de l'Infrastructure, même au sein de son propre domaine");
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
class SendTestNotificationCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NotificationInterface $notification
|
||||
private readonly NotificationInterface $notification,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -38,6 +38,7 @@ class SendTestNotificationCommand extends Command
|
||||
$allowed = ['info', 'success', 'error', 'warning'];
|
||||
if (!in_array($type, $allowed, true)) {
|
||||
$output->writeln(sprintf('<error>Type invalide "%s". Valeurs acceptées : %s</error>', $type, implode(', ', $allowed)));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Controller;
|
||||
|
||||
use ApiPlatform\Api\IriConverterInterface;
|
||||
use App\Entity\User;
|
||||
use Exception;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@@ -13,11 +12,11 @@ use Symfony\Component\Security\Http\Attribute\CurrentUser;
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route('/login', name: 'app_login', methods: ['GET', 'POST'])]
|
||||
public function login(IriConverterInterface $iriConverter, #[CurrentUser] User $user = null): Response
|
||||
public function login(IriConverterInterface $iriConverter, #[CurrentUser] ?User $user = null): Response
|
||||
{
|
||||
if (!$user) {
|
||||
return $this->json([
|
||||
'error' => 'Invalid credentials'
|
||||
'error' => 'Invalid credentials',
|
||||
], 401);
|
||||
}
|
||||
|
||||
@@ -27,11 +26,11 @@ class SecurityController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[Route('/logout', name: 'app_logout', methods: ['GET'])]
|
||||
public function logout(): void
|
||||
{
|
||||
throw new Exception('This method can be blank.');
|
||||
throw new \Exception('This method can be blank.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ final readonly class ConvertFileCommand
|
||||
public function __construct(
|
||||
public string $filePath,
|
||||
public string $originalFilename,
|
||||
public int $fileSize
|
||||
public int $fileSize,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ final readonly class ConvertFileCommandHandler
|
||||
private const MAX_FILE_SIZE = 150 * 1024 * 1024; // 150MB
|
||||
|
||||
public function __construct(
|
||||
private ConversionServiceInterface $conversionService
|
||||
private ConversionServiceInterface $conversionService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ final readonly class ConversionResponse
|
||||
public string $convertedFilePath,
|
||||
public string $outputFilename,
|
||||
public int $originalFileSize,
|
||||
public int $convertedFileSize
|
||||
public int $convertedFileSize,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Conversion\Domain\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ConversionException extends RuntimeException
|
||||
class ConversionException extends \RuntimeException
|
||||
{
|
||||
public static function fileNotFound(string $filePath): self
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ final readonly class ConversionRequest
|
||||
public function __construct(
|
||||
private string $filePath,
|
||||
private string $originalFilename,
|
||||
private int $fileSize
|
||||
private int $fileSize,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ final readonly class ConversionRequest
|
||||
public function getOutputFilename(): string
|
||||
{
|
||||
$pathInfo = pathinfo($this->originalFilename, PATHINFO_FILENAME);
|
||||
|
||||
return $pathInfo.'.cbz';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ final readonly class ConversionResult
|
||||
private string $convertedFilePath,
|
||||
private string $outputFilename,
|
||||
private int $originalFileSize,
|
||||
private int $convertedFileSize
|
||||
private int $convertedFileSize,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,16 @@ 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
|
||||
private readonly ConvertFileCommandHandler $commandHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -25,7 +23,7 @@ final class ConvertFileController extends AbstractController
|
||||
$uploadedFile = $request->files->get('file');
|
||||
if (!$uploadedFile) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file']
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
@@ -58,7 +56,6 @@ final class ConvertFileController extends AbstractController
|
||||
'Content-Disposition' => sprintf('attachment; filename=%s', $response->outputFilename),
|
||||
]
|
||||
);
|
||||
|
||||
} catch (ConversionException $e) {
|
||||
return $this->json(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
@@ -72,8 +69,9 @@ final class ConvertFileController extends AbstractController
|
||||
if (!$uploadedFile->isValid()) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage()
|
||||
'message' => 'The uploaded file is not valid: '.$uploadedFile->getErrorMessage(),
|
||||
];
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
@@ -82,7 +80,7 @@ final class ConvertFileController extends AbstractController
|
||||
if ($uploadedFile->getSize() > $maxSize) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is too large. Allowed size is 150MB.'
|
||||
'message' => 'The uploaded file is too large. Allowed size is 150MB.',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -93,7 +91,7 @@ final class ConvertFileController extends AbstractController
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'Please upload a valid CBR or CBZ file'
|
||||
'message' => 'Please upload a valid CBR or CBZ file',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ namespace App\Domain\Conversion\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\RequestBody;
|
||||
use App\Domain\Conversion\Infrastructure\ApiPlatform\Controller\ConvertFileController;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'Conversion',
|
||||
@@ -16,11 +16,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
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' => [
|
||||
openapi: new Operation(
|
||||
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: new RequestBody(
|
||||
content: new \ArrayObject([
|
||||
'multipart/form-data' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
@@ -29,28 +29,28 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
'file' => [
|
||||
'type' => 'string',
|
||||
'format' => 'binary',
|
||||
'description' => 'Comic book file to convert (CBR, CBZ, max 150MB)'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'description' => 'Comic book file to convert (CBR, CBZ, max 150MB)',
|
||||
],
|
||||
'responses' => [
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
),
|
||||
responses: [
|
||||
'200' => [
|
||||
'description' => 'File converted successfully',
|
||||
'content' => [
|
||||
'application/x-cbz' => [
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'format' => 'binary'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
'format' => 'binary',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class ConvertFileResource
|
||||
|
||||
@@ -56,7 +56,7 @@ final class ConversionService implements ConversionServiceInterface
|
||||
$process->run();
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new \RuntimeException("Extraction failed: " . $process->getErrorOutput());
|
||||
throw new \RuntimeException('Extraction failed: '.$process->getErrorOutput());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ final class ConversionService implements ConversionServiceInterface
|
||||
$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");
|
||||
if (true !== $zip->open($cbzPath, \ZipArchive::CREATE)) {
|
||||
throw new \RuntimeException('Cannot create ZIP file');
|
||||
}
|
||||
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
|
||||
@@ -7,7 +7,7 @@ readonly class ChapterEditData
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public ?string $title = null,
|
||||
public ?int $volume = null
|
||||
public ?int $volume = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
namespace App\Domain\Manga\Application\Command;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
readonly class CheckMonitoredMangas
|
||||
{
|
||||
public function __construct(
|
||||
public ?DateTimeImmutable $since = null
|
||||
public ?\DateTimeImmutable $since = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ readonly class CreateManga
|
||||
public string $status,
|
||||
public ?string $externalId,
|
||||
public ?string $imageUrl,
|
||||
public ?float $rating
|
||||
public ?float $rating,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Command;
|
||||
readonly class CreateMangaFromMangadex
|
||||
{
|
||||
public function __construct(
|
||||
public string $externalId
|
||||
public string $externalId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteCbz implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $chapterId
|
||||
public string $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteChapter implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $chapterId
|
||||
public string $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteManga implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId
|
||||
public string $mangaId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class EditManga
|
||||
public ?array $genres = null,
|
||||
public ?string $status = null,
|
||||
public ?float $rating = null,
|
||||
public ?array $alternativeSlugs = null
|
||||
public ?array $alternativeSlugs = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class EditMultipleChapters
|
||||
* @param array<ChapterEditData> $chapters
|
||||
*/
|
||||
public function __construct(
|
||||
public array $chapters
|
||||
public array $chapters,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
readonly class FetchMangaChapters
|
||||
{
|
||||
public function __construct(
|
||||
public MangaId $mangaId
|
||||
public MangaId $mangaId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ readonly class ImportChapter
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public float $chapterNumber,
|
||||
public string $fileBinary
|
||||
public string $fileBinary,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ readonly class ImportVolume
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public int $volumeNumber,
|
||||
public string $fileBinary
|
||||
public string $fileBinary,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
readonly class RefreshMangaChapters
|
||||
{
|
||||
public function __construct(
|
||||
public MangaId $mangaId
|
||||
public MangaId $mangaId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class ToggleMangaMonitoring
|
||||
{
|
||||
public function __construct(
|
||||
public MangaId $mangaId,
|
||||
public bool $enabled
|
||||
public bool $enabled,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ use App\Domain\Manga\Application\Command\CheckMonitoredMangas;
|
||||
use App\Domain\Manga\Application\Command\RefreshMangaChapters;
|
||||
use App\Domain\Manga\Application\Query\MonitoringCriteria;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
readonly class CheckMonitoredMangasHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private MessageBusInterface $commandBus
|
||||
private MessageBusInterface $commandBus,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -21,7 +20,7 @@ readonly class CheckMonitoredMangasHandler
|
||||
{
|
||||
$criteria = new MonitoringCriteria(
|
||||
enabled: true,
|
||||
lastCheckBefore: $command->since ?? new DateTimeImmutable('-1 hour')
|
||||
lastCheckBefore: $command->since ?? new \DateTimeImmutable('-1 hour')
|
||||
);
|
||||
|
||||
$monitoredMangas = $this->mangaRepository->findByMonitoringCriteria($criteria);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\CreateMangaFromMangadex;
|
||||
use App\Domain\Manga\Application\Response\CreateMangaResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Provider\MangaProviderInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Service\ImageProcessorInterface;
|
||||
@@ -19,7 +18,7 @@ readonly class CreateMangaFromMangadexHandler
|
||||
private MangaProviderInterface $mangaProvider,
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageProcessorInterface $imageProcessor,
|
||||
private EventDispatcherInterface $eventDispatcher
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -27,7 +26,7 @@ readonly class CreateMangaFromMangadexHandler
|
||||
{
|
||||
$manga = $this->mangaProvider->findByExternalId(new ExternalId($command->externalId));
|
||||
|
||||
if ($manga === null) {
|
||||
if (null === $manga) {
|
||||
throw new MangaNotFoundException('Manga not found on Mangadex');
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ readonly class CreateMangaHandler
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageProcessorInterface $imageProcessor,
|
||||
private MessageBusInterface $messageBus
|
||||
private MessageBusInterface $messageBus,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace App\Domain\Manga\Application\CommandHandler;
|
||||
use App\Domain\Manga\Application\Command\DeleteCbz;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Shared\Domain\Contract\CommandHandlerInterface;
|
||||
use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
|
||||
@@ -14,7 +14,7 @@ readonly class DeleteCbzHandler implements CommandHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private FileServiceInterface $fileService
|
||||
private FileServiceInterface $fileService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteChapterHandler implements CommandHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteMangaHandler implements CommandHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
readonly class EditMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -23,35 +23,35 @@ readonly class EditMangaHandler
|
||||
}
|
||||
|
||||
// Update only provided fields (partial update)
|
||||
if ($command->title !== null) {
|
||||
if (null !== $command->title) {
|
||||
$manga->updateTitle(new MangaTitle($command->title));
|
||||
}
|
||||
|
||||
if ($command->description !== null) {
|
||||
if (null !== $command->description) {
|
||||
$manga->updateDescription($command->description);
|
||||
}
|
||||
|
||||
if ($command->author !== null) {
|
||||
if (null !== $command->author) {
|
||||
$manga->updateAuthor($command->author);
|
||||
}
|
||||
|
||||
if ($command->publicationYear !== null) {
|
||||
if (null !== $command->publicationYear) {
|
||||
$manga->updatePublicationYear($command->publicationYear);
|
||||
}
|
||||
|
||||
if ($command->genres !== null) {
|
||||
if (null !== $command->genres) {
|
||||
$manga->updateGenres($command->genres);
|
||||
}
|
||||
|
||||
if ($command->status !== null) {
|
||||
if (null !== $command->status) {
|
||||
$manga->updateStatus($command->status);
|
||||
}
|
||||
|
||||
if ($command->rating !== null) {
|
||||
if (null !== $command->rating) {
|
||||
$manga->setRating($command->rating);
|
||||
}
|
||||
|
||||
if ($command->alternativeSlugs !== null) {
|
||||
if (null !== $command->alternativeSlugs) {
|
||||
$manga->updateAlternativeSlugs($command->alternativeSlugs);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
readonly class EditMultipleChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ readonly class EditMultipleChaptersHandler
|
||||
|
||||
$manga = $this->mangaRepository->findById($chapter->getMangaId()->getValue());
|
||||
|
||||
if ($chapterData->title !== null) {
|
||||
if (null !== $chapterData->title) {
|
||||
$manga->updateChapterTitle($chapter, $chapterData->title);
|
||||
}
|
||||
|
||||
if ($chapterData->volume !== null) {
|
||||
if (null !== $chapterData->volume) {
|
||||
$manga->updateChapterVolume($chapter, $chapterData->volume);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ readonly class FetchMangaChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ChapterSynchronizationServiceInterface $chapterSynchronizationService
|
||||
private ChapterSynchronizationServiceInterface $chapterSynchronizationService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ readonly class FetchMangaChaptersHandler
|
||||
{
|
||||
$manga = $this->mangaRepository->findById($command->mangaId->getValue());
|
||||
|
||||
if ($manga === null) {
|
||||
if (null === $manga) {
|
||||
throw new MangaNotFoundException();
|
||||
}
|
||||
|
||||
if ($manga->getExternalId() === null) {
|
||||
throw new MangadexApiException("Manga has no external_id");
|
||||
if (null === $manga->getExternalId()) {
|
||||
throw new MangadexApiException('Manga has no external_id');
|
||||
}
|
||||
|
||||
// Synchronisation initiale (pas d'événements)
|
||||
|
||||
@@ -4,15 +4,15 @@ namespace App\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\ImportChapter;
|
||||
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\Exception\MangaNotFoundException;
|
||||
use App\Domain\Shared\Domain\Contract\ImageStorageInterface;
|
||||
|
||||
readonly class ImportChapterHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageStorageInterface $imageStorage
|
||||
private ImageStorageInterface $imageStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -55,6 +55,6 @@ readonly class ImportChapterHandler
|
||||
{
|
||||
$zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04
|
||||
|
||||
return strpos($fileBinary, $zipMagicNumber) === 0;
|
||||
return 0 === strpos($fileBinary, $zipMagicNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ readonly class ImportVolumeHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageStorageInterface $imageStorage
|
||||
private ImageStorageInterface $imageStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -35,9 +35,7 @@ readonly class ImportVolumeHandler
|
||||
);
|
||||
|
||||
if (empty($chapters)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"No chapters found for manga {$command->mangaId} in volume {$command->volumeNumber}"
|
||||
);
|
||||
throw new \InvalidArgumentException("No chapters found for manga {$command->mangaId} in volume {$command->volumeNumber}");
|
||||
}
|
||||
|
||||
// 4. Extract CBZ into individual images storage (shared directory for all volume chapters)
|
||||
@@ -56,6 +54,6 @@ readonly class ImportVolumeHandler
|
||||
{
|
||||
$zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04
|
||||
|
||||
return strpos($fileBinary, $zipMagicNumber) === 0;
|
||||
return 0 === strpos($fileBinary, $zipMagicNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Service\ChapterSynchronizationServiceInterface;
|
||||
use App\Domain\Manga\Domain\Event\ChapterReadyForScraping;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
readonly class RefreshMangaChaptersHandler
|
||||
@@ -15,7 +14,7 @@ readonly class RefreshMangaChaptersHandler
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ChapterSynchronizationServiceInterface $chapterSynchronizationService,
|
||||
private MessageBusInterface $eventBus
|
||||
private MessageBusInterface $eventBus,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -23,7 +22,7 @@ readonly class RefreshMangaChaptersHandler
|
||||
{
|
||||
$manga = $this->mangaRepository->findById($command->mangaId->getValue());
|
||||
|
||||
if ($manga === null) {
|
||||
if (null === $manga) {
|
||||
throw new \RuntimeException('Manga not found');
|
||||
}
|
||||
|
||||
@@ -31,7 +30,7 @@ readonly class RefreshMangaChaptersHandler
|
||||
$newChapterIds = $this->chapterSynchronizationService->synchronizeChapters($manga);
|
||||
|
||||
// Mise à jour de la date de monitoring
|
||||
$manga->updateLastMonitoringCheck(new DateTimeImmutable());
|
||||
$manga->updateLastMonitoringCheck(new \DateTimeImmutable());
|
||||
$this->mangaRepository->save($manga);
|
||||
|
||||
// Événement de scraping pour chaque nouveau chapitre
|
||||
|
||||
@@ -9,7 +9,7 @@ use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
readonly class ToggleMangaMonitoringHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
readonly class MangaCreatedEventListener
|
||||
{
|
||||
public function __construct(
|
||||
private FetchMangaChaptersHandler $fetchMangaChaptersHandler
|
||||
private FetchMangaChaptersHandler $fetchMangaChaptersHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ readonly class VolumeImportedEventListener
|
||||
}
|
||||
|
||||
$chapters = $this->mangaRepository->findChaptersByMangaIdAndVolume($manga->getId()->getValue(), (int) $event->volume);
|
||||
if ($chapters === []) {
|
||||
if ([] === $chapters) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\QueryInterface;
|
||||
readonly class DownloadCbz implements QueryInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $chapterId
|
||||
public string $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class DownloadVolume implements QueryInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public int $volume
|
||||
public int $volume,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class FindMangaMatchByFilename
|
||||
{
|
||||
public function __construct(
|
||||
public string $filename
|
||||
public string $filename,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class GetMangaById
|
||||
{
|
||||
public function __construct(
|
||||
public string $id
|
||||
public string $id,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class GetMangaBySlug
|
||||
{
|
||||
public function __construct(
|
||||
public string $slug
|
||||
public string $slug,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class GetMangaChapters
|
||||
public string $mangaId,
|
||||
public ?int $page = 1,
|
||||
public ?int $limit = 20,
|
||||
public ?string $sortOrder = 'desc'
|
||||
public ?string $sortOrder = 'desc',
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class GetMangaList
|
||||
public ?int $page = 1,
|
||||
public ?int $limit = 20,
|
||||
public ?string $sortBy = 'title',
|
||||
public ?string $sortOrder = 'asc'
|
||||
public ?string $sortOrder = 'asc',
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
namespace App\Domain\Manga\Application\Query;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
readonly class MonitoringCriteria
|
||||
{
|
||||
public function __construct(
|
||||
public bool $enabled,
|
||||
public ?DateTimeImmutable $lastCheckBefore = null
|
||||
public ?\DateTimeImmutable $lastCheckBefore = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ readonly class SearchLocalManga
|
||||
public function __construct(
|
||||
public string $query,
|
||||
public int $page = 1,
|
||||
public int $limit = 20
|
||||
public int $limit = 20,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class SearchManga
|
||||
{
|
||||
public function __construct(
|
||||
public string $title
|
||||
public string $title,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class DiscoverMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private MangaProviderInterface $mangaProvider
|
||||
private MangaProviderInterface $mangaProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ readonly class DiscoverMangaHandler
|
||||
|
||||
$recommendations = array_values(array_filter(
|
||||
$collection->getItems(),
|
||||
fn (Manga $m) => $m->getExternalId() === null
|
||||
fn (Manga $m) => null === $m->getExternalId()
|
||||
|| !in_array($m->getExternalId()->getValue(), $ownedExternalIds, true)
|
||||
));
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ use App\Domain\Manga\Application\Response\DownloadResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface;
|
||||
use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotAvailableException;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Shared\Domain\Contract\QueryHandlerInterface;
|
||||
use App\Domain\Shared\Domain\Contract\QueryInterface;
|
||||
use App\Domain\Shared\Domain\Contract\ResponseInterface;
|
||||
@@ -17,7 +17,7 @@ readonly class DownloadCbzHandler implements QueryHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private FileServiceInterface $fileService
|
||||
private FileServiceInterface $fileService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ readonly class DownloadVolumeHandler implements QueryHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private FileServiceInterface $fileService
|
||||
private FileServiceInterface $fileService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ readonly class FindMangaMatchByFilenameHandler
|
||||
{
|
||||
public function __construct(
|
||||
private FilenameAnalyzerInterface $filenameAnalyzer,
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ readonly class FindMangaMatchByFilenameHandler
|
||||
|
||||
/**
|
||||
* Calcule un score de correspondance entre le manga et le titre recherché
|
||||
* Score plus élevé = meilleure correspondance
|
||||
* Score plus élevé = meilleure correspondance.
|
||||
*/
|
||||
private function calculateMatchScore(Manga $manga, string $searchedTitle): int
|
||||
{
|
||||
@@ -97,12 +97,12 @@ readonly class FindMangaMatchByFilenameHandler
|
||||
}
|
||||
|
||||
// Le titre du manga contient le terme recherché
|
||||
if (stripos($mangaTitle, $searchedTitle) !== false) {
|
||||
if (false !== stripos($mangaTitle, $searchedTitle)) {
|
||||
$score += 50;
|
||||
}
|
||||
|
||||
// Le terme recherché contient le titre du manga
|
||||
if (stripos($searchedTitle, $mangaTitle) !== false) {
|
||||
if (false !== stripos($searchedTitle, $mangaTitle)) {
|
||||
$score += 40;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
readonly class GetMangaByIdHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
readonly class GetMangaBySlugHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use App\Domain\Manga\Domain\Model\Chapter;
|
||||
readonly class GetMangaChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ readonly class GetMangaChaptersHandler
|
||||
|
||||
$grouped = $this->groupChapters($allChapters);
|
||||
|
||||
if ($query->sortOrder === 'desc') {
|
||||
if ('desc' === $query->sortOrder) {
|
||||
usort($grouped, fn (ChapterResponse $a, ChapterResponse $b) => $b->number <=> $a->number);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ readonly class GetMangaChaptersHandler
|
||||
$pagesDir = $chapter->getPagesDirectory();
|
||||
$volume = $chapter->getVolume();
|
||||
|
||||
if ($pagesDir !== null && $volume !== null) {
|
||||
if (null !== $pagesDir && null !== $volume) {
|
||||
if ($pagesDir === $currentPagesDir && $volume === $currentVolume) {
|
||||
$currentGroup[] = $chapter;
|
||||
} else {
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
namespace App\Domain\Manga\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Query\GetMangaList;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Application\Response\MangaListResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
|
||||
readonly class GetMangaListHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\Manga;
|
||||
readonly class SearchLocalMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $repository
|
||||
private MangaRepositoryInterface $repository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\Manga;
|
||||
readonly class SearchMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaProviderInterface $mangaProvider
|
||||
private MangaProviderInterface $mangaProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ readonly class SearchMangaHandler
|
||||
{
|
||||
$mangaCollection = $this->mangaProvider->search($query->title);
|
||||
|
||||
|
||||
return new MangaSearchResponse(
|
||||
array_map(
|
||||
fn (Manga $manga, int $index) => new MangaSearchItem(
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class ChapterListResponse
|
||||
public array $chapters,
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit
|
||||
public int $limit,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
readonly class DownloadResponse implements ResponseInterface
|
||||
{
|
||||
public function __construct(
|
||||
public Response $httpResponse
|
||||
public Response $httpResponse,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ readonly class MangaListResponse
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit,
|
||||
public array $chapterCounts = []
|
||||
public array $chapterCounts = [],
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ readonly class MangaMatchItem
|
||||
public ?string $thumbnailUrl,
|
||||
public int $matchScore,
|
||||
public ?float $chapterNumber = null,
|
||||
public ?float $volumeNumber = null
|
||||
public ?float $volumeNumber = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class MangaMatchResponse
|
||||
public array $matches,
|
||||
public ?float $chapterNumber,
|
||||
public ?float $volumeNumber,
|
||||
public array $possibleTitles
|
||||
public array $possibleTitles,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ readonly class MangaResponse
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating,
|
||||
public bool $monitored
|
||||
public bool $monitored,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ readonly class MangaSearchItem
|
||||
public string $status,
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating
|
||||
public ?float $rating,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ readonly class SearchLocalMangaResponse
|
||||
public array $items,
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit
|
||||
public int $limit,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ use App\Domain\Manga\Domain\Exception\MangadexAuthenticationException;
|
||||
interface MangadexClientInterface
|
||||
{
|
||||
/**
|
||||
* @throws \App\Domain\Manga\Domain\Exception\MangadexAuthenticationException
|
||||
* @throws MangadexAuthenticationException
|
||||
*/
|
||||
public function authenticate(): void;
|
||||
|
||||
/**
|
||||
* @throws \App\Domain\Manga\Domain\Exception\MangadexAuthenticationException
|
||||
* @throws MangadexAuthenticationException
|
||||
*/
|
||||
public function refreshToken(): void;
|
||||
|
||||
@@ -39,6 +39,7 @@ interface MangadexClientInterface
|
||||
|
||||
/**
|
||||
* @param string[] $mangaIds
|
||||
*
|
||||
* @return array{
|
||||
* statistics: array<string, array{
|
||||
* rating: array{average: float}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace App\Domain\Manga\Domain\Contract\Repository;
|
||||
|
||||
use App\Domain\Manga\Application\Query\MonitoringCriteria;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
|
||||
@@ -13,13 +13,21 @@ interface MangaRepositoryInterface
|
||||
// --- Manga ---
|
||||
|
||||
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array;
|
||||
|
||||
public function count(): int;
|
||||
|
||||
public function findById(string $id): ?Manga;
|
||||
|
||||
public function findBySlug(MangaSlug $slug): ?Manga;
|
||||
|
||||
public function findByExternalId(ExternalId $externalId): ?Manga;
|
||||
|
||||
public function save(Manga $manga): void;
|
||||
|
||||
public function delete(Manga $manga): void;
|
||||
|
||||
public function search(string $query, int $page = 1, int $limit = 20): array;
|
||||
|
||||
public function countSearch(string $query): int;
|
||||
|
||||
/**
|
||||
@@ -35,14 +43,20 @@ interface MangaRepositoryInterface
|
||||
* @return Chapter[]
|
||||
*/
|
||||
public function findAllChapters(string $mangaId, string $sortOrder = 'desc'): array;
|
||||
|
||||
public function countChapters(string $mangaId): int;
|
||||
|
||||
public function countAvailableChapters(string $mangaId): int;
|
||||
|
||||
public function findChapterById(string $id): ?Chapter;
|
||||
|
||||
public function findVisibleChapterById(string $id): ?Chapter;
|
||||
|
||||
public function findChapterByMangaIdAndNumber(string $mangaId, float $chapterNumber): ?Chapter;
|
||||
|
||||
/**
|
||||
* @param float[] $chapterNumbers
|
||||
*
|
||||
* @return array<float, Chapter>
|
||||
*/
|
||||
public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array;
|
||||
@@ -61,5 +75,4 @@ interface MangaRepositoryInterface
|
||||
* @return Chapter[]
|
||||
*/
|
||||
public function findVisibleChaptersWithPagesByMangaIdAndVolume(string $mangaId, int $volume): array;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ use App\Domain\Manga\Domain\Model\Manga;
|
||||
interface ChapterSynchronizationServiceInterface
|
||||
{
|
||||
/**
|
||||
* Synchronise les chapitres d'un manga depuis la source externe
|
||||
* Synchronise les chapitres d'un manga depuis la source externe.
|
||||
*
|
||||
* @return string[] IDs des nouveaux chapitres ajoutés
|
||||
*/
|
||||
public function synchronizeChapters(Manga $manga): array;
|
||||
|
||||
@@ -7,23 +7,24 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
interface FileServiceInterface
|
||||
{
|
||||
/**
|
||||
* Télécharge un fichier CBZ
|
||||
* Télécharge un fichier CBZ.
|
||||
*/
|
||||
public function downloadCbz(string $filePath, string $filename): Response;
|
||||
|
||||
/**
|
||||
* Crée un fichier ZIP contenant plusieurs CBZ
|
||||
* Crée un fichier ZIP contenant plusieurs CBZ.
|
||||
*
|
||||
* @param array<string> $cbzPaths
|
||||
*/
|
||||
public function createVolumeCbz(array $cbzPaths, string $volumeName): Response;
|
||||
|
||||
/**
|
||||
* Supprime un fichier CBZ du système de fichiers
|
||||
* Supprime un fichier CBZ du système de fichiers.
|
||||
*/
|
||||
public function deleteCbzFile(string $filePath): bool;
|
||||
|
||||
/**
|
||||
* Vérifie si un fichier CBZ existe
|
||||
* Vérifie si un fichier CBZ existe.
|
||||
*/
|
||||
public function cbzExists(string $filePath): bool;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
readonly class ChapterReadyForScraping
|
||||
{
|
||||
public function __construct(
|
||||
public ChapterId $chapterId
|
||||
public ChapterId $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ readonly class MangaCreated
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public string $externalId
|
||||
public string $externalId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class CbzFileNotFoundException extends DomainException
|
||||
class CbzFileNotFoundException extends \DomainException
|
||||
{
|
||||
public function __construct(string $filePath)
|
||||
{
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class ChapterNotAvailableException extends DomainException
|
||||
class ChapterNotAvailableException extends \DomainException
|
||||
{
|
||||
public function __construct(int $chapterId)
|
||||
{
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class ChapterNotFoundException extends DomainException
|
||||
class ChapterNotFoundException extends \DomainException
|
||||
{
|
||||
public function __construct(string $chapterId)
|
||||
{
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class VolumeNotFoundException extends DomainException
|
||||
class VolumeNotFoundException extends \DomainException
|
||||
{
|
||||
public function __construct(string $mangaId, int $volume)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class AnalyzedFilename
|
||||
public function __construct(
|
||||
private MangaTitle $title,
|
||||
private ?ChapterNumber $chapterNumber = null,
|
||||
private ?VolumeNumber $volumeNumber = null
|
||||
private ?VolumeNumber $volumeNumber = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ readonly class AnalyzedFilename
|
||||
|
||||
public function hasChapterNumber(): bool
|
||||
{
|
||||
return $this->chapterNumber !== null;
|
||||
return null !== $this->chapterNumber;
|
||||
}
|
||||
|
||||
public function hasVolumeNumber(): bool
|
||||
{
|
||||
return $this->volumeNumber !== null;
|
||||
return null !== $this->volumeNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class Chapter
|
||||
private bool $isVisible,
|
||||
private ?string $pagesDirectory = null,
|
||||
private int $pageCount = 0,
|
||||
private \DateTimeImmutable $createdAt = new \DateTimeImmutable()
|
||||
private \DateTimeImmutable $createdAt = new \DateTimeImmutable(),
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class Chapter
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return $this->pagesDirectory !== null;
|
||||
return null !== $this->pagesDirectory;
|
||||
}
|
||||
|
||||
public function getPagesDirectory(): ?string
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MonitoringStatus;
|
||||
use App\Domain\Shared\Domain\Model\AggregateRoot;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final class Manga extends AggregateRoot
|
||||
{
|
||||
@@ -36,9 +35,9 @@ final class Manga extends AggregateRoot
|
||||
private ?float $rating = null,
|
||||
private ?ImageUrls $imageUrls = null,
|
||||
private array $alternativeSlugs = [],
|
||||
private ?DateTimeImmutable $createdAt = null,
|
||||
private ?\DateTimeImmutable $createdAt = null,
|
||||
private ?MonitoringStatus $monitoringStatus = null,
|
||||
private ?DateTimeImmutable $lastMonitoringCheck = null,
|
||||
private ?\DateTimeImmutable $lastMonitoringCheck = null,
|
||||
) {
|
||||
$this->monitoringStatus = $this->monitoringStatus ?? MonitoringStatus::disabled();
|
||||
}
|
||||
@@ -158,7 +157,7 @@ final class Manga extends AggregateRoot
|
||||
$this->alternativeSlugs = $alternativeSlugs;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTimeImmutable
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
@@ -181,7 +180,7 @@ final class Manga extends AggregateRoot
|
||||
public function enableMonitoring(): void
|
||||
{
|
||||
$this->monitoringStatus = MonitoringStatus::enabled();
|
||||
$this->lastMonitoringCheck = new DateTimeImmutable();
|
||||
$this->lastMonitoringCheck = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function disableMonitoring(): void
|
||||
@@ -190,12 +189,12 @@ final class Manga extends AggregateRoot
|
||||
$this->lastMonitoringCheck = null;
|
||||
}
|
||||
|
||||
public function getLastMonitoringCheck(): ?DateTimeImmutable
|
||||
public function getLastMonitoringCheck(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->lastMonitoringCheck;
|
||||
}
|
||||
|
||||
public function updateLastMonitoringCheck(DateTimeImmutable $lastMonitoringCheck): void
|
||||
public function updateLastMonitoringCheck(\DateTimeImmutable $lastMonitoringCheck): void
|
||||
{
|
||||
$this->lastMonitoringCheck = $lastMonitoringCheck;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||
readonly class ChapterId
|
||||
{
|
||||
public function __construct(
|
||||
private string $value
|
||||
private string $value,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
readonly class ChapterNumber
|
||||
{
|
||||
public function __construct(
|
||||
private float $value
|
||||
private float $value,
|
||||
) {
|
||||
if ($value < 0) {
|
||||
throw new InvalidArgumentException('Chapter number cannot be negative');
|
||||
throw new \InvalidArgumentException('Chapter number cannot be negative');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidExternalIdException;
|
||||
readonly class ExternalId
|
||||
{
|
||||
public function __construct(
|
||||
private string $value
|
||||
private string $value,
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidExternalIdException('External ID cannot be empty');
|
||||
|
||||
@@ -6,7 +6,7 @@ readonly class ImageUrls
|
||||
{
|
||||
public function __construct(
|
||||
private string $full,
|
||||
private string $thumbnail
|
||||
private string $thumbnail,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user