diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index f343459..04a7cef 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -28,6 +28,7 @@ api_platform: - '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto' - '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource' - '%kernel.project_dir%/src/Domain/Manga/Infrastructure/ApiPlatform/Resource' + - '%kernel.project_dir%/src/Domain/Setting/Infrastructure/ApiPlatform/Resource' - '%kernel.project_dir%/src/Domain/Reader/Infrastructure/ApiPlatform/Resource' - '%kernel.project_dir%/src/Domain/Shared/Infrastructure/ApiPlatform/Resource' patch_formats: diff --git a/public/api-docs.json b/public/api-docs.json index eef7d87..99fb76d 100644 --- a/public/api-docs.json +++ b/public/api-docs.json @@ -12,6 +12,558 @@ } ], "paths": { + "/api/content-sources": { + "get": { + "operationId": "api_content-sources_get_collection", + "tags": [ + "ContentSource" + ], + "responses": { + "200": { + "description": "ContentSource collection", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContentSource" + } + } + }, + "application/ld+json": { + "schema": { + "type": "object", + "properties": { + "hydra:member": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContentSource.jsonld" + } + }, + "hydra:totalItems": { + "type": "integer", + "minimum": 0 + }, + "hydra:view": { + "type": "object", + "properties": { + "@id": { + "type": "string", + "format": "iri-reference" + }, + "@type": { + "type": "string" + }, + "hydra:first": { + "type": "string", + "format": "iri-reference" + }, + "hydra:last": { + "type": "string", + "format": "iri-reference" + }, + "hydra:previous": { + "type": "string", + "format": "iri-reference" + }, + "hydra:next": { + "type": "string", + "format": "iri-reference" + } + }, + "example": { + "@id": "string", + "type": "string", + "hydra:first": "string", + "hydra:last": "string", + "hydra:previous": "string", + "hydra:next": "string" + } + }, + "hydra:search": { + "type": "object", + "properties": { + "@type": { + "type": "string" + }, + "hydra:template": { + "type": "string" + }, + "hydra:variableRepresentation": { + "type": "string" + }, + "hydra:mapping": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string" + }, + "variable": { + "type": "string" + }, + "property": { + "type": [ + "string", + "null" + ] + }, + "required": { + "type": "boolean" + } + } + } + } + } + } + }, + "required": [ + "hydra:member" + ] + } + }, + "text/html": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContentSource" + } + } + }, + "application/hal+json": { + "schema": { + "type": "object", + "properties": { + "_embedded": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContentSource.jsonhal" + } + }, + "totalItems": { + "type": "integer", + "minimum": 0 + }, + "itemsPerPage": { + "type": "integer", + "minimum": 0 + }, + "_links": { + "type": "object", + "properties": { + "self": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + }, + "first": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + }, + "last": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + }, + "next": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + }, + "previous": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + } + } + } + }, + "required": [ + "_links", + "_embedded" + ] + } + } + } + } + }, + "summary": "Retrieves the collection of ContentSource resources.", + "description": "Retrieves the collection of ContentSource resources.", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "The collection page number", + "required": false, + "deprecated": false, + "allowEmptyValue": true, + "schema": { + "type": "integer", + "default": 1 + }, + "style": "form", + "explode": false, + "allowReserved": false + } + ], + "deprecated": false + }, + "post": { + "operationId": "api_content-sources_post", + "tags": [ + "ContentSource" + ], + "responses": { + "201": { + "description": "ContentSource resource created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonhal" + } + } + }, + "links": {} + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Unprocessable entity" + } + }, + "summary": "Creates a ContentSource resource.", + "description": "Creates a ContentSource resource.", + "parameters": [], + "requestBody": { + "description": "The new ContentSource resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonhal" + } + } + }, + "required": true + }, + "deprecated": false + }, + "parameters": [] + }, + "/api/content-sources/export": { + "get": { + "operationId": "api_content-sourcesexport_get", + "tags": [ + "ContentSourceExport" + ], + "responses": { + "200": { + "description": "ContentSourceExport resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceExport" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceExport.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSourceExport" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceExport.jsonhal" + } + } + } + }, + "404": { + "description": "Resource not found" + } + }, + "summary": "Retrieves a ContentSourceExport resource.", + "description": "Retrieves a ContentSourceExport resource.", + "parameters": [], + "deprecated": false + }, + "parameters": [] + }, + "/api/content-sources/import": { + "post": { + "operationId": "api_content-sourcesimport_post", + "tags": [ + "ContentSourceImport" + ], + "responses": { + "201": { + "description": "ContentSourceImport resource created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport.jsonhal" + } + } + }, + "links": {} + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Unprocessable entity" + } + }, + "summary": "Creates a ContentSourceImport resource.", + "description": "Creates a ContentSourceImport resource.", + "parameters": [], + "requestBody": { + "description": "The new ContentSourceImport resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSourceImport.jsonhal" + } + } + }, + "required": true + }, + "deprecated": false + }, + "parameters": [] + }, + "/api/content-sources/{id}": { + "get": { + "operationId": "api_content-sources_id_get", + "tags": [ + "ContentSource" + ], + "responses": { + "200": { + "description": "ContentSource resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonhal" + } + } + } + }, + "404": { + "description": "Resource not found" + } + }, + "summary": "Retrieves a ContentSource resource.", + "description": "Retrieves a ContentSource resource.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "GetContentSourceResource identifier", + "required": true, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string" + }, + "style": "simple", + "explode": false, + "allowReserved": false + } + ], + "deprecated": false + }, + "put": { + "operationId": "api_content-sources_id_put", + "tags": [ + "ContentSource" + ], + "responses": { + "200": { + "description": "ContentSource resource updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonhal" + } + } + }, + "links": {} + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Unprocessable entity" + }, + "404": { + "description": "Resource not found" + } + }, + "summary": "Replaces the ContentSource resource.", + "description": "Replaces the ContentSource resource.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "UpsertContentSourceResource identifier", + "required": true, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string" + }, + "style": "simple", + "explode": false, + "allowReserved": false + } + ], + "requestBody": { + "description": "The updated ContentSource resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ContentSource" + } + }, + "application/hal+json": { + "schema": { + "$ref": "#/components/schemas/ContentSource.jsonhal" + } + } + }, + "required": true + }, + "deprecated": false + }, + "parameters": [] + }, "/api/jobs": { "get": { "operationId": "api_jobs_get_collection", @@ -1846,6 +2398,352 @@ } } }, + "ContentSource": { + "type": "object", + "description": "R\u00e9cup\u00e8re une source de contenu par son identifiant", + "deprecated": false, + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + }, + "baseUrl": { + "type": "string" + }, + "chapterUrlFormat": { + "type": "string" + }, + "scrapingType": { + "type": "string" + }, + "imageSelector": { + "type": [ + "string", + "null" + ] + }, + "nextPageSelector": { + "type": [ + "string", + "null" + ] + }, + "chapterSelector": { + "type": [ + "string", + "null" + ] + }, + "cleanBaseUrl": { + "type": "string" + } + } + }, + "ContentSource.jsonhal": { + "type": "object", + "description": "R\u00e9cup\u00e8re une source de contenu par son identifiant", + "deprecated": false, + "required": [ + "id" + ], + "properties": { + "_links": { + "type": "object", + "properties": { + "self": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + } + } + }, + "id": { + "type": "integer" + }, + "baseUrl": { + "type": "string" + }, + "chapterUrlFormat": { + "type": "string" + }, + "scrapingType": { + "type": "string" + }, + "imageSelector": { + "type": [ + "string", + "null" + ] + }, + "nextPageSelector": { + "type": [ + "string", + "null" + ] + }, + "chapterSelector": { + "type": [ + "string", + "null" + ] + }, + "cleanBaseUrl": { + "type": "string" + } + } + }, + "ContentSource.jsonld": { + "type": "object", + "description": "R\u00e9cup\u00e8re une source de contenu par son identifiant", + "deprecated": false, + "required": [ + "id" + ], + "properties": { + "@context": { + "readOnly": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "@vocab": { + "type": "string" + }, + "hydra": { + "type": "string", + "enum": [ + "http://www.w3.org/ns/hydra/core#" + ] + } + }, + "required": [ + "@vocab", + "hydra" + ], + "additionalProperties": true + } + ] + }, + "@id": { + "readOnly": true, + "type": "string" + }, + "@type": { + "readOnly": true, + "type": "string" + }, + "id": { + "type": "integer" + }, + "baseUrl": { + "type": "string" + }, + "chapterUrlFormat": { + "type": "string" + }, + "scrapingType": { + "type": "string" + }, + "imageSelector": { + "type": [ + "string", + "null" + ] + }, + "nextPageSelector": { + "type": [ + "string", + "null" + ] + }, + "chapterSelector": { + "type": [ + "string", + "null" + ] + }, + "cleanBaseUrl": { + "type": "string" + } + } + }, + "ContentSourceExport": { + "type": "object", + "description": "Exporte toutes les sources de contenu au format JSON", + "deprecated": false + }, + "ContentSourceExport.jsonhal": { + "type": "object", + "description": "Exporte toutes les sources de contenu au format JSON", + "deprecated": false, + "properties": { + "_links": { + "type": "object", + "properties": { + "self": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + } + } + } + } + }, + "ContentSourceExport.jsonld": { + "type": "object", + "description": "Exporte toutes les sources de contenu au format JSON", + "deprecated": false, + "properties": { + "@context": { + "readOnly": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "@vocab": { + "type": "string" + }, + "hydra": { + "type": "string", + "enum": [ + "http://www.w3.org/ns/hydra/core#" + ] + } + }, + "required": [ + "@vocab", + "hydra" + ], + "additionalProperties": true + } + ] + }, + "@id": { + "readOnly": true, + "type": "string" + }, + "@type": { + "readOnly": true, + "type": "string" + } + } + }, + "ContentSourceImport": { + "type": "object", + "description": "Importe des sources de contenu depuis un tableau JSON", + "deprecated": false, + "required": [ + "contentSources" + ], + "properties": { + "contentSources": { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ContentSourceImport.jsonhal": { + "type": "object", + "description": "Importe des sources de contenu depuis un tableau JSON", + "deprecated": false, + "required": [ + "contentSources" + ], + "properties": { + "_links": { + "type": "object", + "properties": { + "self": { + "type": "object", + "properties": { + "href": { + "type": "string", + "format": "iri-reference" + } + } + } + } + }, + "contentSources": { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ContentSourceImport.jsonld": { + "type": "object", + "description": "Importe des sources de contenu depuis un tableau JSON", + "deprecated": false, + "required": [ + "contentSources" + ], + "properties": { + "@context": { + "readOnly": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "@vocab": { + "type": "string" + }, + "hydra": { + "type": "string", + "enum": [ + "http://www.w3.org/ns/hydra/core#" + ] + } + }, + "required": [ + "@vocab", + "hydra" + ], + "additionalProperties": true + } + ] + }, + "@id": { + "readOnly": true, + "type": "string" + }, + "@type": { + "readOnly": true, + "type": "string" + }, + "contentSources": { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + } + }, "Job": { "type": "object", "description": "Liste des jobs", diff --git a/src/Domain/Setting/Application/Command/ImportContentSourceCommand.php b/src/Domain/Setting/Application/Command/ImportContentSourceCommand.php new file mode 100644 index 0000000..1f3fc9a --- /dev/null +++ b/src/Domain/Setting/Application/Command/ImportContentSourceCommand.php @@ -0,0 +1,10 @@ +contentSources)) { + throw new InvalidArgumentException('Content sources must be an array'); + } + + $contentSourcesToImport = []; + + foreach ($command->contentSources as $data) { + if (!$this->isValidContentSourceData($data)) { + throw new InvalidArgumentException('Invalid content source data provided'); + } + + $contentSource = ContentSource::create( + baseUrl: $data['baseUrl'], + chapterUrlFormat: $data['chapterUrlFormat'], + scrapingType: $data['scrapingType'], + imageSelector: $data['imageSelector'] ?? null, + nextPageSelector: $data['nextPageSelector'] ?? null, + chapterSelector: $data['chapterSelector'] ?? null, + ); + + $contentSourcesToImport[] = $contentSource; + } + + // Supprime tous les content sources existants puis importe les nouveaux + $this->contentSourceRepository->deleteAll(); + $this->contentSourceRepository->saveMultiple($contentSourcesToImport); + } + + private function isValidContentSourceData(mixed $data): bool + { + if (!is_array($data)) { + return false; + } + + $requiredFields = ['baseUrl', 'chapterUrlFormat', 'scrapingType']; + foreach ($requiredFields as $field) { + if (!isset($data[$field]) || !is_string($data[$field]) || empty($data[$field])) { + return false; + } + } + + return true; + } +} diff --git a/src/Domain/Setting/Application/CommandHandler/UpsertContentSourceCommandHandler.php b/src/Domain/Setting/Application/CommandHandler/UpsertContentSourceCommandHandler.php new file mode 100644 index 0000000..0df66c1 --- /dev/null +++ b/src/Domain/Setting/Application/CommandHandler/UpsertContentSourceCommandHandler.php @@ -0,0 +1,44 @@ +id) { + // Update existing + $contentSource = $this->contentSourceRepository->findById($command->id); + if ($contentSource) { + $contentSource->update( + baseUrl: $command->baseUrl, + chapterUrlFormat: $command->chapterUrlFormat, + scrapingType: $command->scrapingType, + imageSelector: $command->imageSelector, + nextPageSelector: $command->nextPageSelector, + chapterSelector: $command->chapterSelector, + ); + $this->contentSourceRepository->save($contentSource); + } + } else { + // Create new + $contentSource = ContentSource::create( + baseUrl: $command->baseUrl, + chapterUrlFormat: $command->chapterUrlFormat, + scrapingType: $command->scrapingType, + imageSelector: $command->imageSelector, + nextPageSelector: $command->nextPageSelector, + chapterSelector: $command->chapterSelector, + ); + $this->contentSourceRepository->save($contentSource); + } + } +} diff --git a/src/Domain/Setting/Application/Query/ExportContentSourceQuery.php b/src/Domain/Setting/Application/Query/ExportContentSourceQuery.php new file mode 100644 index 0000000..c4c8d6b --- /dev/null +++ b/src/Domain/Setting/Application/Query/ExportContentSourceQuery.php @@ -0,0 +1,11 @@ +contentSourceRepository->findAll(); + + $exportData = array_map(function ($contentSource) { + return [ + 'baseUrl' => $contentSource->getBaseUrl(), + 'chapterUrlFormat' => $contentSource->getChapterUrlFormat(), + 'scrapingType' => $contentSource->getScrapingType(), + 'imageSelector' => $contentSource->getImageSelector(), + 'nextPageSelector' => $contentSource->getNextPageSelector(), + 'chapterSelector' => $contentSource->getChapterSelector(), + ]; + }, $contentSources); + + return new ExportContentSourceResponse( + contentSources: $exportData, + exportDate: (new DateTimeImmutable())->format('Y-m-d H:i:s') + ); + } +} diff --git a/src/Domain/Setting/Application/QueryHandler/GetContentSourceQueryHandler.php b/src/Domain/Setting/Application/QueryHandler/GetContentSourceQueryHandler.php new file mode 100644 index 0000000..db3b74c --- /dev/null +++ b/src/Domain/Setting/Application/QueryHandler/GetContentSourceQueryHandler.php @@ -0,0 +1,26 @@ +contentSourceRepository->findById($query->id); + + if (!$contentSource) { + throw new ContentSourceNotFoundException($query->id); + } + + return ContentSourceResponse::fromDomain($contentSource); + } +} diff --git a/src/Domain/Setting/Application/QueryHandler/ListContentSourceQueryHandler.php b/src/Domain/Setting/Application/QueryHandler/ListContentSourceQueryHandler.php new file mode 100644 index 0000000..2b2535f --- /dev/null +++ b/src/Domain/Setting/Application/QueryHandler/ListContentSourceQueryHandler.php @@ -0,0 +1,30 @@ +contentSourceRepository->findAll(); + + $responses = array_map( + fn($contentSource) => ContentSourceResponse::fromDomain($contentSource), + $contentSources + ); + + return new ContentSourceListResponse( + contentSources: $responses, + total: count($responses) + ); + } +} diff --git a/src/Domain/Setting/Application/Response/ContentSourceListResponse.php b/src/Domain/Setting/Application/Response/ContentSourceListResponse.php new file mode 100644 index 0000000..f390d6b --- /dev/null +++ b/src/Domain/Setting/Application/Response/ContentSourceListResponse.php @@ -0,0 +1,14 @@ +getId(), + baseUrl: $contentSource->getBaseUrl(), + chapterUrlFormat: $contentSource->getChapterUrlFormat(), + scrapingType: $contentSource->getScrapingType(), + imageSelector: $contentSource->getImageSelector(), + nextPageSelector: $contentSource->getNextPageSelector(), + chapterSelector: $contentSource->getChapterSelector(), + cleanBaseUrl: $contentSource->getCleanBaseUrl(), + ); + } +} diff --git a/src/Domain/Setting/Application/Response/ExportContentSourceResponse.php b/src/Domain/Setting/Application/Response/ExportContentSourceResponse.php new file mode 100644 index 0000000..fc25072 --- /dev/null +++ b/src/Domain/Setting/Application/Response/ExportContentSourceResponse.php @@ -0,0 +1,11 @@ +id; + } + + public function getBaseUrl(): string + { + return $this->baseUrl; + } + + public function getChapterUrlFormat(): string + { + return $this->chapterUrlFormat; + } + + public function getScrapingType(): string + { + return $this->scrapingType; + } + + public function getImageSelector(): ?string + { + return $this->imageSelector; + } + + public function getNextPageSelector(): ?string + { + return $this->nextPageSelector; + } + + public function getChapterSelector(): ?string + { + return $this->chapterSelector; + } + + public function updateId(int $id): void + { + $this->id = $id; + } + + public function getCleanBaseUrl(): string + { + return preg_replace( + '/^(https?:\/\/)?(www\.)?|\/+$/', + '', + $this->baseUrl + ); + } + + public static function create( + string $baseUrl, + string $chapterUrlFormat, + string $scrapingType, + ?string $imageSelector = null, + ?string $nextPageSelector = null, + ?string $chapterSelector = null, + ): self { + return new self( + id: null, + baseUrl: $baseUrl, + chapterUrlFormat: $chapterUrlFormat, + scrapingType: $scrapingType, + imageSelector: $imageSelector, + nextPageSelector: $nextPageSelector, + chapterSelector: $chapterSelector, + ); + } + + public function update( + string $baseUrl, + string $chapterUrlFormat, + string $scrapingType, + ?string $imageSelector = null, + ?string $nextPageSelector = null, + ?string $chapterSelector = null, + ): void { + $this->baseUrl = $baseUrl; + $this->chapterUrlFormat = $chapterUrlFormat; + $this->scrapingType = $scrapingType; + $this->imageSelector = $imageSelector; + $this->nextPageSelector = $nextPageSelector; + $this->chapterSelector = $chapterSelector; + } +} diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ExportContentSourceResource.php b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ExportContentSourceResource.php new file mode 100644 index 0000000..bcea491 --- /dev/null +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ExportContentSourceResource.php @@ -0,0 +1,21 @@ +contentSources + ); + + $this->handler->handle($command); + + return Response::HTTP_CREATED; + } catch (InvalidArgumentException $e) { + throw new BadRequestHttpException($e->getMessage()); + } + } +} diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/UpsertContentSourceStateProcessor.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/UpsertContentSourceStateProcessor.php new file mode 100644 index 0000000..9958510 --- /dev/null +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/UpsertContentSourceStateProcessor.php @@ -0,0 +1,38 @@ +id ?? null; + + $command = new UpsertContentSourceCommand( + id: $id, + baseUrl: $data->baseUrl, + chapterUrlFormat: $data->chapterUrlFormat, + scrapingType: $data->scrapingType, + imageSelector: $data->imageSelector, + nextPageSelector: $data->nextPageSelector, + chapterSelector: $data->chapterSelector, + ); + + $this->handler->handle($command); + + return $id ? Response::HTTP_OK : Response::HTTP_CREATED; + } +} diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ExportContentSourceStateProvider.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ExportContentSourceStateProvider.php new file mode 100644 index 0000000..d09ed7d --- /dev/null +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ExportContentSourceStateProvider.php @@ -0,0 +1,26 @@ +handler->handle($query); + + return [ + 'contentSources' => $response->contentSources, + 'exportDate' => $response->exportDate, + ]; + } +} diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/GetContentSourceStateProvider.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/GetContentSourceStateProvider.php new file mode 100644 index 0000000..f5d5c5c --- /dev/null +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/GetContentSourceStateProvider.php @@ -0,0 +1,39 @@ +handler->handle($query); + + return new GetContentSourceResource( + id: $response->id, + baseUrl: $response->baseUrl, + chapterUrlFormat: $response->chapterUrlFormat, + scrapingType: $response->scrapingType, + imageSelector: $response->imageSelector, + nextPageSelector: $response->nextPageSelector, + chapterSelector: $response->chapterSelector, + cleanBaseUrl: $response->cleanBaseUrl, + ); + } catch (ContentSourceNotFoundException $e) { + throw new NotFoundHttpException($e->getMessage()); + } + } +} diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ListContentSourceStateProvider.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ListContentSourceStateProvider.php new file mode 100644 index 0000000..48325ea --- /dev/null +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ListContentSourceStateProvider.php @@ -0,0 +1,36 @@ +handler->handle($query); + + return array_map( + fn($contentSourceResponse) => new GetContentSourceResource( + id: $contentSourceResponse->id, + baseUrl: $contentSourceResponse->baseUrl, + chapterUrlFormat: $contentSourceResponse->chapterUrlFormat, + scrapingType: $contentSourceResponse->scrapingType, + imageSelector: $contentSourceResponse->imageSelector, + nextPageSelector: $contentSourceResponse->nextPageSelector, + chapterSelector: $contentSourceResponse->chapterSelector, + cleanBaseUrl: $contentSourceResponse->cleanBaseUrl, + ), + $response->contentSources + ); + } +} diff --git a/src/Domain/Setting/Infrastructure/Persistence/Mapper/ContentSourceMapper.php b/src/Domain/Setting/Infrastructure/Persistence/Mapper/ContentSourceMapper.php new file mode 100644 index 0000000..dc83996 --- /dev/null +++ b/src/Domain/Setting/Infrastructure/Persistence/Mapper/ContentSourceMapper.php @@ -0,0 +1,36 @@ +getId(), + baseUrl: $entity->getBaseUrl(), + chapterUrlFormat: $entity->getChapterUrlFormat(), + scrapingType: $entity->getScrapingType(), + imageSelector: $entity->getImageSelector(), + nextPageSelector: $entity->getNextPageSelector(), + chapterSelector: $entity->getChapterSelector(), + ); + } + + public function toEntity(ContentSource $contentSource): ContentSourceEntity + { + $entity = new ContentSourceEntity(); + + $entity->setBaseUrl($contentSource->getBaseUrl()) + ->setChapterUrlFormat($contentSource->getChapterUrlFormat()) + ->setScrapingType($contentSource->getScrapingType()) + ->setImageSelector($contentSource->getImageSelector()) + ->setNextPageSelector($contentSource->getNextPageSelector()) + ->setChapterSelector($contentSource->getChapterSelector()); + + return $entity; + } +} diff --git a/src/Domain/Setting/Infrastructure/Persistence/Repository/DoctrineContentSourceRepository.php b/src/Domain/Setting/Infrastructure/Persistence/Repository/DoctrineContentSourceRepository.php new file mode 100644 index 0000000..686efc1 --- /dev/null +++ b/src/Domain/Setting/Infrastructure/Persistence/Repository/DoctrineContentSourceRepository.php @@ -0,0 +1,73 @@ +entityManager->getRepository(ContentSourceEntity::class)->findAll(); + + return array_map( + fn(ContentSourceEntity $entity) => $this->mapper->toDomain($entity), + $entities + ); + } + + public function findById(int $id): ?ContentSource + { + $entity = $this->entityManager->find(ContentSourceEntity::class, $id); + + return $entity ? $this->mapper->toDomain($entity) : null; + } + + public function save(ContentSource $contentSource): void + { + $entity = $this->mapper->toEntity($contentSource); + + $this->entityManager->persist($entity); + $this->entityManager->flush(); + + // Met à jour l'ID du modèle du domaine si nécessaire + if ($entity->getId() && $contentSource->getId() === null) { + $contentSource->updateId($entity->getId()); + } + } + + public function delete(ContentSource $contentSource): void + { + if ($contentSource->getId()) { + $entity = $this->entityManager->find(ContentSourceEntity::class, $contentSource->getId()); + if ($entity) { + $this->entityManager->remove($entity); + $this->entityManager->flush(); + } + } + } + + public function deleteAll(): void + { + $this->entityManager->createQuery('DELETE FROM ' . ContentSourceEntity::class)->execute(); + } + + public function saveMultiple(array $contentSources): void + { + foreach ($contentSources as $contentSource) { + $entity = $this->mapper->toEntity($contentSource); + $this->entityManager->persist($entity); + } + + $this->entityManager->flush(); + } +}