feat: ajout de la gestion des sources de contenu avec des commandes et des gestionnaires pour l'importation, la mise à jour et l'exportation, ainsi que la création des ressources API correspondantes.
This commit is contained in:
parent
ebcca466a9
commit
32b4e4fbb2
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Command;
|
||||
|
||||
readonly class ImportContentSourceCommand
|
||||
{
|
||||
public function __construct(
|
||||
public array $contentSources
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Command;
|
||||
|
||||
readonly class UpsertContentSourceCommand
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public string $baseUrl,
|
||||
public string $chapterUrlFormat,
|
||||
public string $scrapingType,
|
||||
public ?string $imageSelector = null,
|
||||
public ?string $nextPageSelector = null,
|
||||
public ?string $chapterSelector = null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Setting\Application\Command\ImportContentSourceCommand;
|
||||
use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface;
|
||||
use App\Domain\Setting\Domain\Model\ContentSource;
|
||||
use InvalidArgumentException;
|
||||
|
||||
readonly class ImportContentSourceCommandHandler
|
||||
{
|
||||
public function __construct(
|
||||
private ContentSourceRepositoryInterface $contentSourceRepository
|
||||
) {}
|
||||
|
||||
public function handle(ImportContentSourceCommand $command): void
|
||||
{
|
||||
// Validation des données d'import
|
||||
if (!is_array($command->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Setting\Application\Command\UpsertContentSourceCommand;
|
||||
use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface;
|
||||
use App\Domain\Setting\Domain\Model\ContentSource;
|
||||
|
||||
readonly class UpsertContentSourceCommandHandler
|
||||
{
|
||||
public function __construct(
|
||||
private ContentSourceRepositoryInterface $contentSourceRepository
|
||||
) {}
|
||||
|
||||
public function handle(UpsertContentSourceCommand $command): void
|
||||
{
|
||||
if ($command->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Query;
|
||||
|
||||
readonly class ExportContentSourceQuery
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Pas de paramètres pour cette query simple
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Query;
|
||||
|
||||
readonly class GetContentSourceQuery
|
||||
{
|
||||
public function __construct(
|
||||
public int $id
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Query;
|
||||
|
||||
readonly class ListContentSourceQuery
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Pas de paramètres pour cette query simple
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Setting\Application\Query\ExportContentSourceQuery;
|
||||
use App\Domain\Setting\Application\Response\ExportContentSourceResponse;
|
||||
use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface;
|
||||
use DateTimeImmutable;
|
||||
|
||||
readonly class ExportContentSourceQueryHandler
|
||||
{
|
||||
public function __construct(
|
||||
private ContentSourceRepositoryInterface $contentSourceRepository
|
||||
) {}
|
||||
|
||||
public function handle(ExportContentSourceQuery $query): ExportContentSourceResponse
|
||||
{
|
||||
$contentSources = $this->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')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Setting\Application\Query\GetContentSourceQuery;
|
||||
use App\Domain\Setting\Application\Response\ContentSourceResponse;
|
||||
use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface;
|
||||
use App\Domain\Setting\Domain\Exception\ContentSourceNotFoundException;
|
||||
|
||||
readonly class GetContentSourceQueryHandler
|
||||
{
|
||||
public function __construct(
|
||||
private ContentSourceRepositoryInterface $contentSourceRepository
|
||||
) {}
|
||||
|
||||
public function handle(GetContentSourceQuery $query): ContentSourceResponse
|
||||
{
|
||||
$contentSource = $this->contentSourceRepository->findById($query->id);
|
||||
|
||||
if (!$contentSource) {
|
||||
throw new ContentSourceNotFoundException($query->id);
|
||||
}
|
||||
|
||||
return ContentSourceResponse::fromDomain($contentSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Setting\Application\Query\ListContentSourceQuery;
|
||||
use App\Domain\Setting\Application\Response\ContentSourceListResponse;
|
||||
use App\Domain\Setting\Application\Response\ContentSourceResponse;
|
||||
use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface;
|
||||
|
||||
readonly class ListContentSourceQueryHandler
|
||||
{
|
||||
public function __construct(
|
||||
private ContentSourceRepositoryInterface $contentSourceRepository
|
||||
) {}
|
||||
|
||||
public function handle(ListContentSourceQuery $query): ContentSourceListResponse
|
||||
{
|
||||
$contentSources = $this->contentSourceRepository->findAll();
|
||||
|
||||
$responses = array_map(
|
||||
fn($contentSource) => ContentSourceResponse::fromDomain($contentSource),
|
||||
$contentSources
|
||||
);
|
||||
|
||||
return new ContentSourceListResponse(
|
||||
contentSources: $responses,
|
||||
total: count($responses)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Response;
|
||||
|
||||
readonly class ContentSourceListResponse
|
||||
{
|
||||
/**
|
||||
* @param ContentSourceResponse[] $contentSources
|
||||
*/
|
||||
public function __construct(
|
||||
public array $contentSources,
|
||||
public int $total,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Response;
|
||||
|
||||
use App\Domain\Setting\Domain\Model\ContentSource;
|
||||
|
||||
readonly class ContentSourceResponse
|
||||
{
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $baseUrl,
|
||||
public string $chapterUrlFormat,
|
||||
public string $scrapingType,
|
||||
public ?string $imageSelector,
|
||||
public ?string $nextPageSelector,
|
||||
public ?string $chapterSelector,
|
||||
public string $cleanBaseUrl,
|
||||
) {}
|
||||
|
||||
public static function fromDomain(ContentSource $contentSource): self
|
||||
{
|
||||
return new self(
|
||||
id: $contentSource->getId(),
|
||||
baseUrl: $contentSource->getBaseUrl(),
|
||||
chapterUrlFormat: $contentSource->getChapterUrlFormat(),
|
||||
scrapingType: $contentSource->getScrapingType(),
|
||||
imageSelector: $contentSource->getImageSelector(),
|
||||
nextPageSelector: $contentSource->getNextPageSelector(),
|
||||
chapterSelector: $contentSource->getChapterSelector(),
|
||||
cleanBaseUrl: $contentSource->getCleanBaseUrl(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Application\Response;
|
||||
|
||||
readonly class ExportContentSourceResponse
|
||||
{
|
||||
public function __construct(
|
||||
public array $contentSources,
|
||||
public string $exportDate,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Domain\Contract\Repository;
|
||||
|
||||
use App\Domain\Setting\Domain\Model\ContentSource;
|
||||
|
||||
interface ContentSourceRepositoryInterface
|
||||
{
|
||||
public function findAll(): array;
|
||||
public function findById(int $id): ?ContentSource;
|
||||
public function save(ContentSource $contentSource): void;
|
||||
public function delete(ContentSource $contentSource): void;
|
||||
public function deleteAll(): void;
|
||||
/**
|
||||
* @param ContentSource[] $contentSources
|
||||
*/
|
||||
public function saveMultiple(array $contentSources): void;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Domain\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ContentSourceNotFoundException extends RuntimeException
|
||||
{
|
||||
public function __construct(int $id)
|
||||
{
|
||||
parent::__construct(sprintf('ContentSource with id %d not found', $id));
|
||||
}
|
||||
}
|
||||
100
src/Domain/Setting/Domain/Model/ContentSource.php
Normal file
100
src/Domain/Setting/Domain/Model/ContentSource.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Domain\Model;
|
||||
|
||||
final class ContentSource
|
||||
{
|
||||
public function __construct(
|
||||
private ?int $id,
|
||||
private string $baseUrl,
|
||||
private string $chapterUrlFormat,
|
||||
private string $scrapingType,
|
||||
private ?string $imageSelector = null,
|
||||
private ?string $nextPageSelector = null,
|
||||
private ?string $chapterSelector = null,
|
||||
) {}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider\ExportContentSourceStateProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ContentSourceExport',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/content-sources/export',
|
||||
provider: ExportContentSourceStateProvider::class,
|
||||
description: 'Exporte toutes les sources de contenu au format JSON'
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ExportContentSourceResource
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider\GetContentSourceStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ContentSource',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/content-sources/{id}',
|
||||
provider: GetContentSourceStateProvider::class,
|
||||
description: 'Récupère une source de contenu par son identifiant'
|
||||
)
|
||||
]
|
||||
)]
|
||||
class GetContentSourceResource
|
||||
{
|
||||
public function __construct(
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Type('integer')]
|
||||
public readonly int $id,
|
||||
public readonly string $baseUrl,
|
||||
public readonly string $chapterUrlFormat,
|
||||
public readonly string $scrapingType,
|
||||
public readonly ?string $imageSelector,
|
||||
public readonly ?string $nextPageSelector,
|
||||
public readonly ?string $chapterSelector,
|
||||
public readonly string $cleanBaseUrl,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\State\Processor\ImportContentSourceStateProcessor;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ContentSourceImport',
|
||||
operations: [
|
||||
new Post(
|
||||
uriTemplate: '/content-sources/import',
|
||||
processor: ImportContentSourceStateProcessor::class,
|
||||
input: ImportContentSourceResource::class,
|
||||
status: 201,
|
||||
description: 'Importe des sources de contenu depuis un tableau JSON'
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ImportContentSourceResource
|
||||
{
|
||||
public function __construct(
|
||||
#[Assert\NotBlank(message: 'Les sources de contenu sont obligatoires')]
|
||||
#[Assert\Type('array', message: 'Les sources de contenu doivent être un tableau')]
|
||||
#[Assert\Count(min: 1, minMessage: 'Au moins une source de contenu doit être fournie')]
|
||||
public readonly array $contentSources = []
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider\ListContentSourceStateProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ContentSource',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/content-sources',
|
||||
provider: ListContentSourceStateProvider::class,
|
||||
description: 'Récupère la liste des sources de contenu'
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ListContentSourceResource
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\State\Processor\UpsertContentSourceStateProcessor;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'ContentSource',
|
||||
operations: [
|
||||
new Post(
|
||||
uriTemplate: '/content-sources',
|
||||
processor: UpsertContentSourceStateProcessor::class,
|
||||
input: UpsertContentSourceResource::class,
|
||||
status: 201,
|
||||
description: 'Crée une nouvelle source de contenu'
|
||||
),
|
||||
new Put(
|
||||
uriTemplate: '/content-sources/{id}',
|
||||
processor: UpsertContentSourceStateProcessor::class,
|
||||
input: UpsertContentSourceResource::class,
|
||||
description: 'Met à jour une source de contenu existante'
|
||||
)
|
||||
]
|
||||
)]
|
||||
class UpsertContentSourceResource
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?int $id = null,
|
||||
|
||||
#[Assert\NotBlank(message: 'L\'URL de base est obligatoire')]
|
||||
#[Assert\Url(message: 'L\'URL de base doit être une URL valide')]
|
||||
public readonly string $baseUrl = '',
|
||||
|
||||
#[Assert\NotBlank(message: 'Le format d\'URL de chapitre est obligatoire')]
|
||||
public readonly string $chapterUrlFormat = '',
|
||||
|
||||
#[Assert\NotBlank(message: 'Le type de scraping est obligatoire')]
|
||||
#[Assert\Choice(choices: ['html', 'javascript'], message: 'Le type de scraping doit être html ou javascript')]
|
||||
public readonly string $scrapingType = '',
|
||||
|
||||
public readonly ?string $imageSelector = null,
|
||||
public readonly ?string $nextPageSelector = null,
|
||||
public readonly ?string $chapterSelector = null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\State\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Domain\Setting\Application\Command\ImportContentSourceCommand;
|
||||
use App\Domain\Setting\Application\CommandHandler\ImportContentSourceCommandHandler;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\Resource\ImportContentSourceResource;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
readonly class ImportContentSourceStateProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ImportContentSourceCommandHandler $handler
|
||||
) {}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): int
|
||||
{
|
||||
assert($data instanceof ImportContentSourceResource);
|
||||
|
||||
try {
|
||||
$command = new ImportContentSourceCommand(
|
||||
contentSources: $data->contentSources
|
||||
);
|
||||
|
||||
$this->handler->handle($command);
|
||||
|
||||
return Response::HTTP_CREATED;
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new BadRequestHttpException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\State\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Domain\Setting\Application\Command\UpsertContentSourceCommand;
|
||||
use App\Domain\Setting\Application\CommandHandler\UpsertContentSourceCommandHandler;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\Resource\UpsertContentSourceResource;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
readonly class UpsertContentSourceStateProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private UpsertContentSourceCommandHandler $handler
|
||||
) {}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): int
|
||||
{
|
||||
assert($data instanceof UpsertContentSourceResource);
|
||||
|
||||
$id = $uriVariables['id'] ?? $data->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\Setting\Application\Query\ExportContentSourceQuery;
|
||||
use App\Domain\Setting\Application\QueryHandler\ExportContentSourceQueryHandler;
|
||||
|
||||
readonly class ExportContentSourceStateProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ExportContentSourceQueryHandler $handler
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
{
|
||||
$query = new ExportContentSourceQuery();
|
||||
$response = $this->handler->handle($query);
|
||||
|
||||
return [
|
||||
'contentSources' => $response->contentSources,
|
||||
'exportDate' => $response->exportDate,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\Setting\Application\Query\GetContentSourceQuery;
|
||||
use App\Domain\Setting\Application\QueryHandler\GetContentSourceQueryHandler;
|
||||
use App\Domain\Setting\Domain\Exception\ContentSourceNotFoundException;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\Resource\GetContentSourceResource;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
readonly class GetContentSourceStateProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private GetContentSourceQueryHandler $handler
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?GetContentSourceResource
|
||||
{
|
||||
try {
|
||||
$query = new GetContentSourceQuery($uriVariables['id']);
|
||||
$response = $this->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\Setting\Application\Query\ListContentSourceQuery;
|
||||
use App\Domain\Setting\Application\QueryHandler\ListContentSourceQueryHandler;
|
||||
use App\Domain\Setting\Infrastructure\ApiPlatform\Resource\GetContentSourceResource;
|
||||
|
||||
readonly class ListContentSourceStateProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ListContentSourceQueryHandler $handler
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
{
|
||||
$query = new ListContentSourceQuery();
|
||||
$response = $this->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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\Persistence\Mapper;
|
||||
|
||||
use App\Domain\Setting\Domain\Model\ContentSource;
|
||||
use App\Entity\ContentSource as ContentSourceEntity;
|
||||
|
||||
readonly class ContentSourceMapper
|
||||
{
|
||||
public function toDomain(ContentSourceEntity $entity): ContentSource
|
||||
{
|
||||
return new ContentSource(
|
||||
id: $entity->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\Persistence\Repository;
|
||||
|
||||
use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface;
|
||||
use App\Domain\Setting\Domain\Model\ContentSource;
|
||||
use App\Domain\Setting\Infrastructure\Persistence\Mapper\ContentSourceMapper;
|
||||
use App\Entity\ContentSource as ContentSourceEntity;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
readonly class DoctrineContentSourceRepository implements ContentSourceRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private ContentSourceMapper $mapper
|
||||
) {}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
$entities = $this->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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user