From 2af5a9b05a3c8f5f71409062bca3c6098cb25815 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 28 Oct 2024 10:38:57 +0100 Subject: [PATCH 01/52] Refactored feedSourceProcessor to reflect existing state processors --- src/State/FeedSourceProcessor.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 37e6222d..e0a7edce 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -10,11 +10,15 @@ use App\Entity\Tenant\FeedSource; use Doctrine\ORM\EntityManagerInterface; -class FeedSourceProcessor implements ProcessorInterface +class FeedSourceProcessor extends AbstractProcessor { public function __construct( private readonly EntityManagerInterface $entityManager, - ) {} + ProcessorInterface $persistProcessor, + ProcessorInterface $removeProcessor, + ) { + parent::__construct($entityManager, $persistProcessor, $removeProcessor); + } /** * {@inheritdoc} @@ -26,12 +30,12 @@ public function process(mixed $object, Operation $operation, array $uriVariables $this->entityManager->flush(); return $entity; - } + } /** * @return T */ - protected function fromInput(FeedSourceInput $object, Operation $operation, array $uriVariables, array $context): FeedSource + protected function fromInput(mixed $object, Operation $operation, array $uriVariables, array $context): FeedSource { // FIXME Do we really have to do (something like) this to load an existing object into the entity manager? $feedSource = $this->loadPrevious(new FeedSource(), $context); From c62756b6862ad6aec1164105b3bd9eb25de1a2a7 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 28 Oct 2024 10:39:17 +0100 Subject: [PATCH 02/52] Added post, put, delete config to feed source config --- config/api_platform/feed_source.yaml | 41 +++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/config/api_platform/feed_source.yaml b/config/api_platform/feed_source.yaml index 2a51c034..cadfb317 100644 --- a/config/api_platform/feed_source.yaml +++ b/config/api_platform/feed_source.yaml @@ -110,7 +110,46 @@ resources: application/ld+json: examples: headers: {} - + ApiPlatform\Metadata\Put: + security: 'is_granted("ROLE_ADMIN")' + openapiContext: + description: Update a Feed Source resource. + summary: Update a Feed Source resource. + operationId: put-v2-feed-source-id + tags: + - FeedSources + parameters: + - schema: + type: string + format: ulid + pattern: "^[A-Za-z0-9]{26}$" + name: id + in: path + required: true + ApiPlatform\Metadata\Delete: + security: 'is_granted("ROLE_ADMIN")' + openapiContext: + description: Delete a Feed Source resource. + summary: Delete a Feed Source resource. + operationId: delete-v2-feed-source-id + tags: + - FeedSources + parameters: + - schema: + type: string + format: ulid + pattern: "^[A-Za-z0-9]{26}$" + name: id + in: path + required: true + ApiPlatform\Metadata\Post: + security: 'is_granted("ROLE_ADMIN")' + openapiContext: + operationId: create-v2-feed-source + description: Creates a Feed Source resource. + summary: Creates a Feed Source resource. + tags: + - FeedSources # Our DTO must be a resource to get a proper URL # @see https://stackoverflow.com/a/75705084 # @see https://github.com/api-platform/core/issues/5451 From 0b41ea3f59b1d3de565f80e3ec6cc59844541c43 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 28 Oct 2024 13:34:19 +0100 Subject: [PATCH 03/52] Added tests for FeedSource in FeedSourceTest --- tests/Api/FeedSourceTest.php | 146 +++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index f0e1a000..57a29be2 100644 --- a/tests/Api/FeedSourceTest.php +++ b/tests/Api/FeedSourceTest.php @@ -4,6 +4,7 @@ namespace App\Tests\Api; +use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; use App\Tests\AbstractBaseApiTestCase; @@ -48,4 +49,149 @@ public function testGetItem(): void '@id' => $iri, ]); } + + public function testCreateFeedSource(): void + { + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + + $response = $client->request('POST', '/v2/feed-sources', [ + 'json' => [ + 'title' => 'Test feed source', + 'description' => 'This is a test feed source', + 'outputType' => 'This is a test output type', + 'feedType' => 'This is a test feed type', + 'secrets' => [ + 'test secret', + ], + 'feeds' => [ + 'test feed', + ], + 'supportedFeedOutputType' => 'Supported feed output type', + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + + $this->assertResponseStatusCodeSame(201); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + $this->assertJsonContains([ + '@context' => '/contexts/FeedSource', + '@type' => 'FeedSource', + 'feedType' => 'This is a test feed type', + 'secrets' => [ + 'test secret', + ], + 'feeds' => [], + 'supportedFeedOutputType' => 'Supported feed output type', + 'title' => 'Test feed source', + 'description' => 'This is a test feed source', + 'createdBy' => 'test@example.com', + 'modifiedBy' => 'test@example.com', + ]); + $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); + } + + public function testCreateInvalidFeedSource(): void + { + $this->getAuthenticatedClient('ROLE_ADMIN')->request('POST', '/v2/feed-sources', [ + 'json' => [ + 'title' => 123_456_789, + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + + $this->assertResponseStatusCodeSame(400); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + + $this->assertJsonContains([ + '@context' => '/contexts/Error', + '@type' => 'hydra:Error', + 'hydra:title' => 'An error occurred', + 'hydra:description' => 'The input data is misformatted.', + ]); + } + + public function testUpdateFeedSource(): void + { + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + $iri = $this->findIriBy(FeedSource::class, ['tenant' => $this->tenant, 'title' => 'feed_source_abc_1']); + + $client->request('PUT', $iri, [ + 'json' => [ + 'title' => 'Updated title', + 'feedType' => 'Updated feed type', + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonContains([ + '@type' => 'FeedSource', + '@id' => $iri, + 'title' => 'Updated title', + 'feedType' => 'Updated feed type', + ]); + } + + public function testDeleteFeedSource(): void + { + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + + $response = $client->request('POST', '/v2/feed-sources', [ + 'json' => [ + 'title' => 'Test feed source', + 'description' => 'This is a test feed source', + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + $this->assertResponseIsSuccessful(); + + $iri = $response->toArray()['@id']; + $client->request('DELETE', $iri); + + $this->assertResponseStatusCodeSame(204); + + $ulid = $this->iriHelperUtils->getUlidFromIRI($iri); + $this->assertNull( + static::getContainer()->get('doctrine')->getRepository(FeedSource::class)->findOneBy(['id' => $ulid]) + ); + } + public function testDeleteFeedSourceInUse(): void + { + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + + $qb = static::getContainer()->get('doctrine')->getRepository(Feed::class)->createQueryBuilder('s'); + + /** @var Feed $feed */ + $feed = $qb + ->leftJoin('s.tenant', 'tenant')->addSelect('tenant') + ->where('s.feedSource IS NOT NULL') + ->andWhere('tenant.tenantKey = :tenantKey') + ->setParameter('tenantKey', 'ABC') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + + + $feedSourceId = $feed->getFeedSource()->getId(); + + $feedSourceIri = $this->findIriBy(FeedSource::class, ['id' => $feedSourceId]); + + $client->request('DELETE', $feedSourceIri); + + $this->assertResponseStatusCodeSame(409); + + $ulid = $this->iriHelperUtils->getUlidFromIRI($feedSourceIri); + + $this->assertNotNull( + static::getContainer()->get('doctrine')->getRepository(FeedSource::class)->findOneBy(['id' => $ulid]) + ); + } } From 2dbff5d3360fe728e9e110e596e4ec2198880a06 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 28 Oct 2024 13:47:41 +0100 Subject: [PATCH 04/52] Updated variable names and assertions in FeedSourceTest --- tests/Api/FeedSourceTest.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index 57a29be2..22a05268 100644 --- a/tests/Api/FeedSourceTest.php +++ b/tests/Api/FeedSourceTest.php @@ -167,26 +167,28 @@ public function testDeleteFeedSourceInUse(): void { $client = $this->getAuthenticatedClient('ROLE_ADMIN'); - $qb = static::getContainer()->get('doctrine')->getRepository(Feed::class)->createQueryBuilder('s'); + $qb = static::getContainer()->get('doctrine')->getRepository(Feed::class)->createQueryBuilder('f'); /** @var Feed $feed */ $feed = $qb - ->leftJoin('s.tenant', 'tenant')->addSelect('tenant') - ->where('s.feedSource IS NOT NULL') + ->leftJoin('f.feedSource', 'feedSource')->addSelect('feedSource') + ->leftJoin('f.tenant', 'tenant')->addSelect('tenant') + ->where('f.feedSource IS NOT NULL') ->andWhere('tenant.tenantKey = :tenantKey') ->setParameter('tenantKey', 'ABC') ->setMaxResults(1) ->getQuery() ->getOneOrNullResult(); + $feedSource = $feed->getFeedSource(); - $feedSourceId = $feed->getFeedSource()->getId(); + $feedSourceId = $feedSource->getId(); $feedSourceIri = $this->findIriBy(FeedSource::class, ['id' => $feedSourceId]); $client->request('DELETE', $feedSourceIri); - $this->assertResponseStatusCodeSame(409); + $this->assertResponseStatusCodeSame(500); $ulid = $this->iriHelperUtils->getUlidFromIRI($feedSourceIri); From 980ee637a3bd70a725484d544f5fd4fef97590ab Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 28 Oct 2024 15:50:07 +0100 Subject: [PATCH 05/52] Linted yaml --- config/api_platform/feed_source.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/config/api_platform/feed_source.yaml b/config/api_platform/feed_source.yaml index cadfb317..abf90bbc 100644 --- a/config/api_platform/feed_source.yaml +++ b/config/api_platform/feed_source.yaml @@ -119,13 +119,13 @@ resources: tags: - FeedSources parameters: - - schema: - type: string - format: ulid - pattern: "^[A-Za-z0-9]{26}$" - name: id - in: path - required: true + - schema: + type: string + format: ulid + pattern: "^[A-Za-z0-9]{26}$" + name: id + in: path + required: true ApiPlatform\Metadata\Delete: security: 'is_granted("ROLE_ADMIN")' openapiContext: @@ -135,13 +135,13 @@ resources: tags: - FeedSources parameters: - - schema: - type: string - format: ulid - pattern: "^[A-Za-z0-9]{26}$" - name: id - in: path - required: true + - schema: + type: string + format: ulid + pattern: "^[A-Za-z0-9]{26}$" + name: id + in: path + required: true ApiPlatform\Metadata\Post: security: 'is_granted("ROLE_ADMIN")' openapiContext: From 10202f3a1300f9f27da85a8ffc1a0e3dc978807e Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 29 Oct 2024 14:44:36 +0100 Subject: [PATCH 06/52] Corrected yaml formatting --- config/api_platform/feed_source.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/config/api_platform/feed_source.yaml b/config/api_platform/feed_source.yaml index abf90bbc..cadfb317 100644 --- a/config/api_platform/feed_source.yaml +++ b/config/api_platform/feed_source.yaml @@ -119,13 +119,13 @@ resources: tags: - FeedSources parameters: - - schema: - type: string - format: ulid - pattern: "^[A-Za-z0-9]{26}$" - name: id - in: path - required: true + - schema: + type: string + format: ulid + pattern: "^[A-Za-z0-9]{26}$" + name: id + in: path + required: true ApiPlatform\Metadata\Delete: security: 'is_granted("ROLE_ADMIN")' openapiContext: @@ -135,13 +135,13 @@ resources: tags: - FeedSources parameters: - - schema: - type: string - format: ulid - pattern: "^[A-Za-z0-9]{26}$" - name: id - in: path - required: true + - schema: + type: string + format: ulid + pattern: "^[A-Za-z0-9]{26}$" + name: id + in: path + required: true ApiPlatform\Metadata\Post: security: 'is_granted("ROLE_ADMIN")' openapiContext: From cdffb9660977de1498017755ba47ebf2d897106c Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 29 Oct 2024 14:45:18 +0100 Subject: [PATCH 07/52] Improved feed source unit test --- tests/Api/FeedSourceTest.php | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index 22a05268..6d7b8007 100644 --- a/tests/Api/FeedSourceTest.php +++ b/tests/Api/FeedSourceTest.php @@ -6,6 +6,7 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Entity\Tenant\Slide; use App\Tests\AbstractBaseApiTestCase; class FeedSourceTest extends AbstractBaseApiTestCase @@ -163,37 +164,32 @@ public function testDeleteFeedSource(): void static::getContainer()->get('doctrine')->getRepository(FeedSource::class)->findOneBy(['id' => $ulid]) ); } + public function testDeleteFeedSourceInUse(): void { $client = $this->getAuthenticatedClient('ROLE_ADMIN'); - $qb = static::getContainer()->get('doctrine')->getRepository(Feed::class)->createQueryBuilder('f'); - - /** @var Feed $feed */ - $feed = $qb - ->leftJoin('f.feedSource', 'feedSource')->addSelect('feedSource') - ->leftJoin('f.tenant', 'tenant')->addSelect('tenant') - ->where('f.feedSource IS NOT NULL') - ->andWhere('tenant.tenantKey = :tenantKey') - ->setParameter('tenantKey', 'ABC') - ->setMaxResults(1) - ->getQuery() - ->getOneOrNullResult(); + $manager = static::getContainer()->get('doctrine')->getManager(); - $feedSource = $feed->getFeedSource(); + $slide = $manager->getRepository(Slide::class)->findOneBy(['title' => 'slide_abc_1']); + $this->assertInstanceOf(Slide::class, $slide, 'This test requires the slide titled slide_abc_1 with connected feed and feed source'); - $feedSourceId = $feedSource->getId(); + $feed = $slide->getFeed(); + $this->assertInstanceOf(Feed::class, $feed, 'This test requires a slide with a feed connected'); - $feedSourceIri = $this->findIriBy(FeedSource::class, ['id' => $feedSourceId]); + $feedSource = $feed->getFeedSource(); + $this->assertInstanceOf(FeedSource::class, $feedSource, 'This test requires a feed with a feed source connected'); + $feedSourceIri = $this->findIriBy(FeedSource::class, ['id' => $feedSource->getId()]); $client->request('DELETE', $feedSourceIri); + // Assert that delete request throws an integrity constraint violation error $this->assertResponseStatusCodeSame(500); $ulid = $this->iriHelperUtils->getUlidFromIRI($feedSourceIri); $this->assertNotNull( - static::getContainer()->get('doctrine')->getRepository(FeedSource::class)->findOneBy(['id' => $ulid]) + $manager->getRepository(FeedSource::class)->findOneBy(['id' => $ulid]) ); } } From 6ea48d9e3d1cf58b5bfbe62b2ab82e3bb7239c1c Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 4 Nov 2024 14:58:34 +0100 Subject: [PATCH 08/52] Added feed source object validation logic --- src/State/FeedSourceProcessor.php | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index e0a7edce..2b9b0d3c 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -4,6 +4,7 @@ namespace App\State; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use App\Dto\FeedSourceInput; @@ -49,6 +50,64 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari empty($object->feedType) ?: $feedSource->setFeedType($object->feedType); empty($object->supportedFeedOutputType) ?: $feedSource->setSupportedFeedOutputType($object->supportedFeedOutputType); + $this->validateFeedSource($object); + return $feedSource; } + + private function validateFeedSource(object $object): void + { + $title = $object->title; + + // Check title isset + if (empty($title) || !is_string($title)) { + throw new InvalidArgumentException('A feed source must have a title'); + } + + $description = $object->description; + + // Check description isset + if (empty($description) || !is_string($description)) { + throw new InvalidArgumentException('A feed source must have a description'); + } + + $feedType = $object->feedType; + + // Check feedType isset + if (empty($feedType) || !is_string($feedType)) { + throw new InvalidArgumentException('A feed source must have a type'); + } + + switch ($object->feedType) { + case 'App\\Feed\\EventDatabaseApiFeedType': + $host = $object->secrets[0]['host']; + $patternWithoutProtocol = '^((?!-)[A-Za-z0-9-]{1,63}(?secrets[0]['token']; + + // Check token isset + if (!isset($token) || !is_string($token)) { + throw new InvalidArgumentException('This feed source type must have a token defined'); + } + break; + case '': + break; + } + } } From 92c09368e28b3015fc94767b31934f954eef4b10 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 4 Nov 2024 15:19:28 +0100 Subject: [PATCH 09/52] Refactored FeedSourceProcessor: moved URL validation patterns to class constants, used constants in checks and added SparkleIOFeedType handling --- src/State/FeedSourceProcessor.php | 34 ++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 2b9b0d3c..ba859ef0 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -13,6 +13,9 @@ class FeedSourceProcessor extends AbstractProcessor { + private const PATTERN_WITHOUT_PROTOCOL = '^((?!-)[A-Za-z0-9-]{1,63}(?feedType) { case 'App\\Feed\\EventDatabaseApiFeedType': $host = $object->secrets[0]['host']; - $patternWithoutProtocol = '^((?!-)[A-Za-z0-9-]{1,63}(?secrets[0]['BaseUrl']; + + // Check baseUrl valid url + if (!preg_match("`" . self::PATTERN_WITH_PROTOCOL . "`", $BaseUrl)) { + if (!preg_match("`" . self::PATTERN_WITHOUT_PROTOCOL . "`", $BaseUrl)) { + throw new InvalidArgumentException('The host must be a valid URL'); + } else { + throw new InvalidArgumentException('The host must be a valid URL including http or https'); + } + } + $clientId = $object->secrets[0]['clientId']; + + // Check clientId isset + if (empty($clientId) || !is_string($clientId)) { + throw new InvalidArgumentException('This feed source type must have a host defined'); + } + + $clientSecret = $object->secrets[0]['clientSecret']; + + // Check clientSecret isset + if (empty($clientSecret) || !is_string($clientSecret)) { + throw new InvalidArgumentException('This feed source type must have a host defined'); + } break; } } From e0cb0a5fc05a40823cd032d3ac72a820e494afee Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 4 Nov 2024 15:22:38 +0100 Subject: [PATCH 10/52] Added types to constants PATTERN_WITHOUT_PROTOCOL and PATTERN_WITH_PROTOCOL in FeedSourceProcessor --- src/State/FeedSourceProcessor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index ba859ef0..32e37824 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -13,8 +13,8 @@ class FeedSourceProcessor extends AbstractProcessor { - private const PATTERN_WITHOUT_PROTOCOL = '^((?!-)[A-Za-z0-9-]{1,63}(? Date: Tue, 5 Nov 2024 07:51:15 +0100 Subject: [PATCH 11/52] format yaml --- config/api_platform/feed_source.yaml | 74 +++++++++++++--------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/config/api_platform/feed_source.yaml b/config/api_platform/feed_source.yaml index cadfb317..8d4363f6 100644 --- a/config/api_platform/feed_source.yaml +++ b/config/api_platform/feed_source.yaml @@ -5,9 +5,8 @@ resources: output: App\Dto\FeedSource provider: App\State\FeedSourceProvider processor: App\State\FeedSourceProcessor - operations: - ApiPlatform\Metadata\Get: &get + ApiPlatform\Metadata\Get: &ref_0 normalizationContext: jsonld_embed_context: true openapiContext: @@ -20,7 +19,7 @@ resources: - schema: type: string format: ulid - pattern: "^[A-Za-z0-9]{26}$" + pattern: '^[A-Za-z0-9]{26}$' name: id in: path required: true @@ -29,10 +28,8 @@ resources: description: OK content: application/ld+json: - examples: + examples: null headers: {} - - # https://api-platform.com/docs/core/controllers/ _api_Feed_get_source_config: class: ApiPlatform\Metadata\Get method: GET @@ -49,13 +46,13 @@ resources: - schema: type: string format: ulid - pattern: "^[A-Za-z0-9]{26}$" + pattern: '^[A-Za-z0-9]{26}$' name: id in: path required: true - schema: type: string - pattern: "^[A-Za-z0-9]*$" + pattern: '^[A-Za-z0-9]*$' name: name in: path required: true @@ -66,17 +63,18 @@ resources: examples: example1: value: - - {key: 'key1', id: 'id1', value: 'value1'} + - key: key1 + id: id1 + value: value1 headers: {} - ApiPlatform\Metadata\GetCollection: filters: - - 'entity.search_filter' - - 'entity.blameable_filter' - - 'entity.order_filter' - - 'created.at.order_filter' - - 'modified.at.order_filter' - - 'feed_source.search_filter' + - entity.search_filter + - entity.blameable_filter + - entity.order_filter + - created.at.order_filter + - modified.at.order_filter + - feed_source.search_filter openapiContext: operationId: get-v2-feed-sources description: Retrieves a collection of FeedSource resources. @@ -99,7 +97,7 @@ resources: description: The number of items per page - schema: type: string - pattern: "^[A-Za-z0-9]*$" + pattern: '^[A-Za-z0-9]*$' name: supportedFeedOutputType in: query required: true @@ -108,10 +106,10 @@ resources: description: OK content: application/ld+json: - examples: + examples: null headers: {} ApiPlatform\Metadata\Put: - security: 'is_granted("ROLE_ADMIN")' + security: is_granted("ROLE_ADMIN") openapiContext: description: Update a Feed Source resource. summary: Update a Feed Source resource. @@ -119,15 +117,15 @@ resources: tags: - FeedSources parameters: - - schema: - type: string - format: ulid - pattern: "^[A-Za-z0-9]{26}$" - name: id - in: path - required: true + - schema: + type: string + format: ulid + pattern: '^[A-Za-z0-9]{26}$' + name: id + in: path + required: true ApiPlatform\Metadata\Delete: - security: 'is_granted("ROLE_ADMIN")' + security: is_granted("ROLE_ADMIN") openapiContext: description: Delete a Feed Source resource. summary: Delete a Feed Source resource. @@ -135,26 +133,22 @@ resources: tags: - FeedSources parameters: - - schema: - type: string - format: ulid - pattern: "^[A-Za-z0-9]{26}$" - name: id - in: path - required: true + - schema: + type: string + format: ulid + pattern: '^[A-Za-z0-9]{26}$' + name: id + in: path + required: true ApiPlatform\Metadata\Post: - security: 'is_granted("ROLE_ADMIN")' + security: is_granted("ROLE_ADMIN") openapiContext: operationId: create-v2-feed-source description: Creates a Feed Source resource. summary: Creates a Feed Source resource. tags: - FeedSources - # Our DTO must be a resource to get a proper URL - # @see https://stackoverflow.com/a/75705084 - # @see https://github.com/api-platform/core/issues/5451 App\Dto\FeedSource: provider: App\State\FeedSourceProvider - operations: - ApiPlatform\Metadata\Get: *get + ApiPlatform\Metadata\Get: *ref_0 From d8045d0e3034a0d7417147b95fe2f10b778932ae Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 5 Nov 2024 07:54:01 +0100 Subject: [PATCH 12/52] Updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04f8670f..efe014fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ All notable changes to this project will be documented in this file. - [#219](https://github.com/os2display/display-api-service/pull/219) - Fixed psalm, test, coding standards and updated api spec. +- [#222](https://github.com/os2display/display-api-service/pull/222) + - Adds create, update, delete operations to feed-source endpoint. + - Adds data validation for feed source. + ## [2.1.3] - 2024-10-25 - [#220](https://github.com/os2display/display-api-service/pull/220) From 4f530e0bbe49c448d12a14a06ed271feef514c1b Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 5 Nov 2024 07:56:10 +0100 Subject: [PATCH 13/52] Correctly formatted markdown --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efe014fd..93917ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ All notable changes to this project will be documented in this file. - Fixed psalm, test, coding standards and updated api spec. - [#222](https://github.com/os2display/display-api-service/pull/222) - - Adds create, update, delete operations to feed-source endpoint. - - Adds data validation for feed source. + - Adds create, update, delete operations to feed-source endpoint. + - Adds data validation for feed source. ## [2.1.3] - 2024-10-25 From 119e12f2ccf3014f3419bb7a83672e67ee59de17 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 12:46:11 +0100 Subject: [PATCH 14/52] Added feed source slide provider --- src/State/FeedSourceSlideProvider.php | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/State/FeedSourceSlideProvider.php diff --git a/src/State/FeedSourceSlideProvider.php b/src/State/FeedSourceSlideProvider.php new file mode 100644 index 00000000..a3c14fb9 --- /dev/null +++ b/src/State/FeedSourceSlideProvider.php @@ -0,0 +1,75 @@ +validationUtils->validateUlid($id); + + $queryBuilder = $this->feedSourceRepository->getFeedSourceSlideRelationsFromFeedSourceId($feedSourceUlid); + + foreach ($this->collectionExtensions as $extension) { + if ($extension instanceof QueryCollectionExtensionInterface) { + $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operation, + $context); + } + } + + $request = $this->requestStack->getCurrentRequest(); + $itemsPerPage = $request->query?->get('itemsPerPage') ?? 10; + $page = $request->query?->get('page') ?? 1; + $firstResult = ((int) $page - 1) * (int) $itemsPerPage; + $query = $queryBuilder->getQuery() + ->setFirstResult($firstResult) + ->setMaxResults((int) $itemsPerPage); + + $doctrinePaginator = new DoctrinePaginator($query); + + return new Paginator($doctrinePaginator); + } + + public function toOutput(object $object): SlideDTO + { + assert($object instanceof Slide); + $output = new SlideDTO(); + + $output->id = $object->getId(); + $output->title = $object->getTitle(); + + return $output; + } +} From bde434d787c79eadf9eab26c99c5bf5d47ab978f Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 12:48:06 +0100 Subject: [PATCH 15/52] Update feed_source.yaml with new GET methods and resource descriptions --- config/api_platform/feed_source.yaml | 51 +++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/config/api_platform/feed_source.yaml b/config/api_platform/feed_source.yaml index 8d4363f6..43896356 100644 --- a/config/api_platform/feed_source.yaml +++ b/config/api_platform/feed_source.yaml @@ -100,7 +100,6 @@ resources: pattern: '^[A-Za-z0-9]*$' name: supportedFeedOutputType in: query - required: true responses: '200': description: OK @@ -148,7 +147,57 @@ resources: summary: Creates a Feed Source resource. tags: - FeedSources + _api_/feed_sources/{id}/slides_get: &get_slides + normalizationContext: + groups: [ 'playlist-slide:read' ] + class: ApiPlatform\Metadata\GetCollection + method: GET + provider: App\State\FeedSourceSlideProvider + filters: + - 'entity.search_filter' + - 'entity.blameable_filter' + - 'App\Filter\PublishedFilter' + - 'entity.order_filter' + - 'created.at.order_filter' + - 'modified.at.order_filter' + uriTemplate: '/feed-sources/{id}/slides' + openapiContext: + description: Retrieves collection of weighted slide resources (feedsource). + summary: Retrieves collection of weighted slide resources (feedsource). + operationId: get-v2-feed-source-slide-id + tags: + - FeedSources + parameters: + - schema: + type: string + format: ulid + pattern: "^[A-Za-z0-9]{26}$" + name: id + in: path + required: true + - schema: + type: integer + minimum: 0 + format: int32 + default: 1 + in: query + name: page + required: true + - schema: + type: string + default: '10' + in: query + name: itemsPerPage + description: The number of items per page + responses: + '200': + description: OK + content: + application/ld+json: + examples: + headers: { } App\Dto\FeedSource: provider: App\State\FeedSourceProvider operations: ApiPlatform\Metadata\Get: *ref_0 + get_slides: *get_slides From a1ce0006d90715cb0858fa9f4e74331dd810924e Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 12:48:06 +0100 Subject: [PATCH 16/52] Register FeedSourceSlideProvider in services --- config/services.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/services.yaml b/config/services.yaml index 2b381d94..72293eb9 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -204,6 +204,11 @@ services: arguments: $collectionExtensions: !tagged_iterator api_platform.doctrine.orm.query_extension.collection + App\State\FeedSourceSlideProvider: + tags: [ { name: 'api_platform.state_provider', priority: 2 } ] + arguments: + $collectionExtensions: !tagged_iterator api_platform.doctrine.orm.query_extension.collection + App\State\FeedProvider: tags: [ { name: 'api_platform.state_provider', priority: 2 } ] arguments: From c0ea466a338dc03620a03fcdee5b703e23216cbb Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 12:48:06 +0100 Subject: [PATCH 17/52] Add method to get feed source slide relations by feed source id in FeedSourceRepository --- src/Repository/FeedSourceRepository.php | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Repository/FeedSourceRepository.php b/src/Repository/FeedSourceRepository.php index 46972d1c..85c2fc8f 100644 --- a/src/Repository/FeedSourceRepository.php +++ b/src/Repository/FeedSourceRepository.php @@ -5,8 +5,12 @@ namespace App\Repository; use App\Entity\Tenant\FeedSource; +use App\Entity\Tenant\Slide; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Uid\Ulid; /** * @method FeedSource|null find($id, $lockMode = null, $lockVersion = null) @@ -16,8 +20,25 @@ */ class FeedSourceRepository extends ServiceEntityRepository { - public function __construct(ManagerRegistry $registry) - { + public function __construct( + ManagerRegistry $registry, + private readonly EntityManagerInterface $entityManager, + ) { parent::__construct($registry, FeedSource::class); } + + public function getFeedSourceSlideRelationsFromFeedSourceId(Ulid $feedSourceUlid): QueryBuilder + { + $queryBuilder = $this->entityManager->createQueryBuilder(); + + $queryBuilder + ->select('s', 'f', 'fs') + ->from(Slide::class, 's') + ->leftJoin('s.feed', 'f') + ->leftJoin('f.feedSource', 'fs') + ->where('fs.id = :feedSourceId') + ->setParameter('feedSourceId', $feedSourceUlid, 'ulid'); + + return $queryBuilder; + } } From ad12ecc3927be7476fc0d43480cd70aac72e6c10 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 12:48:06 +0100 Subject: [PATCH 18/52] Validate 'supportedFeedOutputType' in the 'FeedSourceProcessor' and modify regex patterns --- src/State/FeedSourceProcessor.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 32e37824..c1320479 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -74,6 +74,13 @@ private function validateFeedSource(object $object): void throw new InvalidArgumentException('A feed source must have a description'); } + $supportedFeedOutputType = $object->supportedFeedOutputType; + + // Check description isset + if (empty($supportedFeedOutputType) || !is_string($supportedFeedOutputType)) { + throw new InvalidArgumentException('A feed source must have a supported feed output type'); + } + $feedType = $object->feedType; // Check feedType isset @@ -91,8 +98,8 @@ private function validateFeedSource(object $object): void } // Check host valid url - if (!preg_match("`" . self::PATTERN_WITH_PROTOCOL . "`", $host)) { - if (!preg_match("`" . self::PATTERN_WITHOUT_PROTOCOL . "`", $host)) { + if (!preg_match('`'.self::PATTERN_WITH_PROTOCOL.'`', $host)) { + if (!preg_match('`'.self::PATTERN_WITHOUT_PROTOCOL.'`', $host)) { throw new InvalidArgumentException('The host must be a valid URL'); } else { throw new InvalidArgumentException('The host must be a valid URL including http or https'); @@ -111,8 +118,8 @@ private function validateFeedSource(object $object): void $BaseUrl = $object->secrets[0]['BaseUrl']; // Check baseUrl valid url - if (!preg_match("`" . self::PATTERN_WITH_PROTOCOL . "`", $BaseUrl)) { - if (!preg_match("`" . self::PATTERN_WITHOUT_PROTOCOL . "`", $BaseUrl)) { + if (!preg_match('`'.self::PATTERN_WITH_PROTOCOL.'`', $BaseUrl)) { + if (!preg_match('`'.self::PATTERN_WITHOUT_PROTOCOL.'`', $BaseUrl)) { throw new InvalidArgumentException('The host must be a valid URL'); } else { throw new InvalidArgumentException('The host must be a valid URL including http or https'); From 245bcc82ebbb5cb73f8cedbb57c07bb50e8ddfdb Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 12:48:06 +0100 Subject: [PATCH 19/52] Update test cases to reflect changes in 'Feed Source' --- tests/Api/FeedSourceTest.php | 134 +++++++++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 14 deletions(-) diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index 6d7b8007..4f8f7f39 100644 --- a/tests/Api/FeedSourceTest.php +++ b/tests/Api/FeedSourceTest.php @@ -8,6 +8,7 @@ use App\Entity\Tenant\FeedSource; use App\Entity\Tenant\Slide; use App\Tests\AbstractBaseApiTestCase; +use Symfony\Component\HttpClient\Exception\ClientException; class FeedSourceTest extends AbstractBaseApiTestCase { @@ -93,37 +94,133 @@ public function testCreateFeedSource(): void $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); } - public function testCreateInvalidFeedSource(): void + public function testCreateFeedSourceWithoutTitle(): void { - $this->getAuthenticatedClient('ROLE_ADMIN')->request('POST', '/v2/feed-sources', [ + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + + $this->expectException(ClientException::class); + + $response = $client->request('POST', '/v2/feed-sources', [ 'json' => [ - 'title' => 123_456_789, + 'description' => 'This is a test feed source', + 'outputType' => 'This is a test output type', + 'feedType' => 'This is a test feed type', + 'secrets' => [ + 'test secret', + ], + 'feeds' => [ + 'test feed', + ], + 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ 'Content-Type' => 'application/ld+json', ], ]); + $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); - $this->assertResponseStatusCodeSame(400); - $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + } - $this->assertJsonContains([ - '@context' => '/contexts/Error', - '@type' => 'hydra:Error', - 'hydra:title' => 'An error occurred', - 'hydra:description' => 'The input data is misformatted.', + public function testCreateFeedSourceWithoutDescription(): void + { + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + + $this->expectException(ClientException::class); + + $response = $client->request('POST', '/v2/feed-sources', [ + 'json' => [ + 'title' => 'Test feed source', + 'outputType' => 'This is a test output type', + 'feedType' => 'This is a test feed type', + 'secrets' => [ + 'test secret', + ], + 'feeds' => [ + 'test feed', + ], + 'supportedFeedOutputType' => 'Supported feed output type', + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + + $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); + } + + public function testCreateFeedSourceWithEventDatabaseFeedTypeWithoutRequiredSecret(): void + { + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + + $this->expectException(ClientException::class); + + $response = $client->request('POST', '/v2/feed-sources', [ + 'json' => [ + 'title' => 'Test feed source', + 'outputType' => 'This is a test output type', + 'feedType' => 'App\\Feed\\EventDatabaseApiFeedType', + 'secrets' => [ + 'test secret', + ], + 'feeds' => [ + 'test feed', + ], + 'supportedFeedOutputType' => 'Supported feed output type', + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], ]); + + $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); + } + + public function testCreateFeedSourceWithEventDatabaseFeedTypeWithRequiredSecret(): void + { + $client = $this->getAuthenticatedClient('ROLE_ADMIN'); + + $this->expectException(ClientException::class); + $response = $client->request('POST', '/v2/feed-sources', [ + 'json' => [ + 'title' => 'Test feed source', + 'description' => 'This is a test feed source', + 'outputType' => 'This is a test output type', + 'feedType' => 'App\\Feed\\EventDatabaseApiFeedType', + 'secrets' => [ + 'host' => 'https://www.test.dk', + ], + 'feeds' => [ + 'test feed', + ], + 'supportedFeedOutputType' => 'Supported feed output type', + ], + 'headers' => [ + 'Content-Type' => 'application/ld+json', + ], + ]); + + //$this->assertResponseIsSuccessful(); + $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); } public function testUpdateFeedSource(): void { $client = $this->getAuthenticatedClient('ROLE_ADMIN'); - $iri = $this->findIriBy(FeedSource::class, ['tenant' => $this->tenant, 'title' => 'feed_source_abc_1']); + $iri = $this->findIriBy(FeedSource::class, ['tenant' => $this->tenant, 'title' => 'Test feed source']); $client->request('PUT', $iri, [ 'json' => [ 'title' => 'Updated title', - 'feedType' => 'Updated feed type', + 'description' => 'Updated description', + 'outputType' => 'This is a test output type', + 'feedType' => 'This is a test feed type', + 'secrets' => [ + 'test secret', + ], + 'feeds' => [ + 'test feed', + ], + 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ 'Content-Type' => 'application/ld+json', @@ -135,7 +232,7 @@ public function testUpdateFeedSource(): void '@type' => 'FeedSource', '@id' => $iri, 'title' => 'Updated title', - 'feedType' => 'Updated feed type', + 'description' => 'Updated description', ]); } @@ -145,8 +242,17 @@ public function testDeleteFeedSource(): void $response = $client->request('POST', '/v2/feed-sources', [ 'json' => [ - 'title' => 'Test feed source', + 'title' => 'Test feed source to delete', 'description' => 'This is a test feed source', + 'outputType' => 'This is a test output type', + 'feedType' => 'This is a test feed type', + 'secrets' => [ + 'test secret', + ], + 'feeds' => [ + 'test feed', + ], + 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ 'Content-Type' => 'application/ld+json', From 34ae449ba60afca880ac052eaa9faa90d8d69c86 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 14:01:34 +0100 Subject: [PATCH 20/52] Added a check to serve a more user friendly error if a feed source cannot be delete because of slides in use --- src/State/FeedSourceProcessor.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index c1320479..d11aac69 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -19,26 +19,26 @@ class FeedSourceProcessor extends AbstractProcessor public function __construct( private readonly EntityManagerInterface $entityManager, ProcessorInterface $persistProcessor, - ProcessorInterface $removeProcessor, + private readonly ProcessorInterface $removeProcessor, + private readonly FeedSourceRepository $feedSourceRepository, ) { parent::__construct($entityManager, $persistProcessor, $removeProcessor); } - /** - * {@inheritdoc} - */ - public function process(mixed $object, Operation $operation, array $uriVariables = [], array $context = []) - { - $entity = $this->fromInput($object, $operation, $uriVariables, $context); - $this->entityManager->persist($entity); - $this->entityManager->flush(); - return $entity; + public function process($data, Operation $operation, array $uriVariables = [], array $context = []): void + { + if ($operation instanceof DeleteOperationInterface) { + $queryBuilder = $this->feedSourceRepository->getFeedSourceSlideRelationsFromFeedSourceId($uriVariables['id']); + $hasSlides = $queryBuilder->getQuery()->getResult(); + if ($hasSlides) { + throw new ConflictHttpException("This feed source is used by one or more slides and cannot be deleted."); + } } + $this->removeProcessor->process($data, $operation, $uriVariables, $context); + + } - /** - * @return T - */ protected function fromInput(mixed $object, Operation $operation, array $uriVariables, array $context): FeedSource { // FIXME Do we really have to do (something like) this to load an existing object into the entity manager? From 6f069c53febbec7b7d52b8227e1c05f275388b71 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 7 Nov 2024 14:08:53 +0100 Subject: [PATCH 21/52] Adjusted the process method to call the parent process method outside of conditional check --- src/State/FeedSourceProcessor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index d11aac69..f5ce4155 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -34,9 +34,9 @@ public function process($data, Operation $operation, array $uriVariables = [], a if ($hasSlides) { throw new ConflictHttpException("This feed source is used by one or more slides and cannot be deleted."); } + $this->removeProcessor->process($data, $operation, $uriVariables, $context); } - $this->removeProcessor->process($data, $operation, $uriVariables, $context); - + parent::process($data, $operation, $uriVariables, $context); } protected function fromInput(mixed $object, Operation $operation, array $uriVariables, array $context): FeedSource From 22e341f95afb38c9be72f5e9509ca3e639531839 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 11 Nov 2024 10:27:14 +0100 Subject: [PATCH 22/52] Removed discontinued feed type --- src/State/FeedSourceProcessor.php | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index f5ce4155..5a286aa7 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -9,7 +9,9 @@ use ApiPlatform\State\ProcessorInterface; use App\Dto\FeedSourceInput; use App\Entity\Tenant\FeedSource; +use App\Repository\FeedSourceRepository; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; class FeedSourceProcessor extends AbstractProcessor { @@ -32,7 +34,7 @@ public function process($data, Operation $operation, array $uriVariables = [], a $queryBuilder = $this->feedSourceRepository->getFeedSourceSlideRelationsFromFeedSourceId($uriVariables['id']); $hasSlides = $queryBuilder->getQuery()->getResult(); if ($hasSlides) { - throw new ConflictHttpException("This feed source is used by one or more slides and cannot be deleted."); + throw new ConflictHttpException('This feed source is used by one or more slides and cannot be deleted.'); } $this->removeProcessor->process($data, $operation, $uriVariables, $context); } @@ -114,31 +116,6 @@ private function validateFeedSource(object $object): void throw new InvalidArgumentException('This feed source type must have a token defined'); } break; - case "App\Feed\SparkleIOFeedType": - $BaseUrl = $object->secrets[0]['BaseUrl']; - - // Check baseUrl valid url - if (!preg_match('`'.self::PATTERN_WITH_PROTOCOL.'`', $BaseUrl)) { - if (!preg_match('`'.self::PATTERN_WITHOUT_PROTOCOL.'`', $BaseUrl)) { - throw new InvalidArgumentException('The host must be a valid URL'); - } else { - throw new InvalidArgumentException('The host must be a valid URL including http or https'); - } - } - $clientId = $object->secrets[0]['clientId']; - - // Check clientId isset - if (empty($clientId) || !is_string($clientId)) { - throw new InvalidArgumentException('This feed source type must have a host defined'); - } - - $clientSecret = $object->secrets[0]['clientSecret']; - - // Check clientSecret isset - if (empty($clientSecret) || !is_string($clientSecret)) { - throw new InvalidArgumentException('This feed source type must have a host defined'); - } - break; } } } From 776595d7e795dc96e1d6db7d185294de661bcb7e Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 11 Nov 2024 16:15:17 +0100 Subject: [PATCH 23/52] Added getSchema method for FeedSource entity --- src/Entity/Tenant/FeedSource.php | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/Entity/Tenant/FeedSource.php b/src/Entity/Tenant/FeedSource.php index f4dd4372..a673509a 100644 --- a/src/Entity/Tenant/FeedSource.php +++ b/src/Entity/Tenant/FeedSource.php @@ -105,4 +105,46 @@ public function setSupportedFeedOutputType(string $supportedFeedOutputType): sel return $this; } + + /** + * @throws \JsonException + */ + public function getSchema(): mixed + { + $jsonSchema = <<<'JSON' + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://os2display.dk/config-schema.json", + "title": "Config file schema", + "description": "Schema for defining config files for templates", + "type": "object", + "properties": { + "title": { + "description": "The title of the feed source", + "type": "string", + "minLength": 1 + }, + "description": { + "description": "A description of the feed source", + "type": "string", + "minLength": 1 + }, + "feedType": { + "description": "The type of the feed source", + "type": "string", + "minLength": 1 + }, + "secrets": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["title", "description", "feedType", "secrets"] + } + JSON; + + return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + } } From 07b22bcc1c1450f0989a3c76974a2e99a3fdf0a8 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 11 Nov 2024 16:15:17 +0100 Subject: [PATCH 24/52] Adjusted constructor, refactored validateFeedSource method and added getSchema method in EventDatabaseApiFeedType --- src/Feed/EventDatabaseApiFeedType.php | 47 ++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index 77545187..e0982420 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -23,11 +23,13 @@ class EventDatabaseApiFeedType implements FeedTypeInterface final public const int REQUEST_TIMEOUT = 10; public function __construct( - private readonly FeedService $feedService, - private readonly HttpClientInterface $client, - private readonly LoggerInterface $logger, + private readonly FeedService $feedService, + private readonly HttpClientInterface $client, + private readonly LoggerInterface $logger, private readonly EntityManagerInterface $entityManager, - ) {} + ) + { + } /** * @param Feed $feed @@ -57,9 +59,9 @@ public function getData(Feed $feed): array $queryParams = array_filter([ 'items_per_page' => $numberOfItems, - 'occurrences.place.id' => array_map(static fn ($place) => str_replace('/api/places/', '', (string) $place['value']), $places), - 'organizer.id' => array_map(static fn ($organizer) => str_replace('/api/organizers/', '', (string) $organizer['value']), $organizers), - 'tags' => array_map(static fn ($tag) => str_replace('/api/tags/', '', (string) $tag['value']), $tags), + 'occurrences.place.id' => array_map(static fn($place) => str_replace('/api/places/', '', (string)$place['value']), $places), + 'organizer.id' => array_map(static fn($organizer) => str_replace('/api/organizers/', '', (string)$organizer['value']), $organizers), + 'tags' => array_map(static fn($tag) => str_replace('/api/tags/', '', (string)$tag['value']), $tags), ]); $response = $this->client->request( @@ -90,9 +92,9 @@ public function getData(Feed $feed): array $content = $response->getContent(); $decoded = json_decode($content, null, 512, JSON_THROW_ON_ERROR); - $baseUrl = parse_url((string) $decoded->event->{'url'}, PHP_URL_HOST); + $baseUrl = parse_url((string)$decoded->event->{'url'}, PHP_URL_HOST); - $eventOccurrence = (object) [ + $eventOccurrence = (object)[ 'eventId' => $decoded->event->{'@id'}, 'occurrenceId' => $decoded->{'@id'}, 'ticketPurchaseUrl' => $decoded->event->{'ticketPurchaseUrl'}, @@ -108,7 +110,7 @@ public function getData(Feed $feed): array ]; if (isset($decoded->place)) { - $eventOccurrence->place = (object) [ + $eventOccurrence->place = (object)[ 'name' => $decoded->place->name, 'streetAddress' => $decoded->place->streetAddress, 'addressLocality' => $decoded->place->addressLocality, @@ -255,7 +257,7 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin foreach ($members as $member) { // Special handling of searching in tags, since EventDatabaseApi does not support this. if ('tags' == $type) { - if (!isset($queryParams['name']) || str_contains(strtolower((string) $member->name), strtolower((string) $queryParams['name']))) { + if (!isset($queryParams['name']) || str_contains(strtolower((string)$member->name), strtolower((string)$queryParams['name']))) { $result[] = $displayAsOptions ? [ 'label' => $member->name, 'value' => $member->{'@id'}, @@ -304,4 +306,27 @@ public function getSupportedFeedOutputType(): string { return self::SUPPORTED_FEED_TYPE; } + + /** + * @throws \JsonException + */ + public static function getSchema(): mixed + { + $jsonSchema = <<<'JSON' + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "host": { + "type": "string", + "format": "url", + "pattern": "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-zA-Z0-9()]{2,6}\\b([-a-zA-Z0-9()@:%_\\+~#?&//=]*)" + } + }, + "required": ["host"] + } + JSON; + + return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + } } From fac8be160ce1baff50d33a01f0c46f27a2d15cb6 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 11 Nov 2024 16:15:17 +0100 Subject: [PATCH 25/52] Added getSchema method in NotifiedFeedType class --- src/Feed/NotifiedFeedType.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/NotifiedFeedType.php index 5fd3996a..a9370abe 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/NotifiedFeedType.php @@ -247,4 +247,22 @@ private function wrapTags(string $input): string '', ]); } + + public static function getSchema(): mixed + { + $jsonSchema = <<<'JSON' + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } + }, + "required": ["token"] + } + JSON; + + return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + } } From 48e7ed1e836b70963f671997c2c34301d9f0d367 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 11 Nov 2024 16:15:17 +0100 Subject: [PATCH 26/52] Added getSchema method in RssFeedType class --- src/Feed/RssFeedType.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Feed/RssFeedType.php b/src/Feed/RssFeedType.php index ed3bf8e7..2b403da3 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -136,4 +136,16 @@ public function getSupportedFeedOutputType(): string { return self::SUPPORTED_FEED_TYPE; } + + public static function getSchema(): mixed + { + $jsonSchema = <<<'JSON' + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object" + } + JSON; + + return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + } } From 43611d64379fff061e35b1613a24fea464c75687 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Mon, 11 Nov 2024 16:15:17 +0100 Subject: [PATCH 27/52] Refactored validateFeedSource method in FeedSourceProcessor class --- src/State/FeedSourceProcessor.php | 78 +++++++++++-------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 5a286aa7..ffd9d507 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -6,11 +6,16 @@ use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Put; use ApiPlatform\State\ProcessorInterface; use App\Dto\FeedSourceInput; use App\Entity\Tenant\FeedSource; use App\Repository\FeedSourceRepository; use Doctrine\ORM\EntityManagerInterface; +use JsonSchema\Constraints\Factory; +use JsonSchema\SchemaStorage; +use JsonSchema\Validator; +use PHPStan\BetterReflection\Reflection\Exception\ClassDoesNotExist; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; class FeedSourceProcessor extends AbstractProcessor @@ -53,69 +58,42 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari empty($object->modifiedBy) ?: $feedSource->setModifiedBy($object->modifiedBy); empty($object->secrets) ?: $feedSource->setSecrets($object->secrets); empty($object->feedType) ?: $feedSource->setFeedType($object->feedType); - empty($object->supportedFeedOutputType) ?: $feedSource->setSupportedFeedOutputType($object->supportedFeedOutputType); - $this->validateFeedSource($object); + $this->validateFeedSource($object, $operation); return $feedSource; } - private function validateFeedSource(object $object): void + /** + * @throws \JsonException + */ + private function validateFeedSource(object $object, Operation $operation): void { - $title = $object->title; - - // Check title isset - if (empty($title) || !is_string($title)) { - throw new InvalidArgumentException('A feed source must have a title'); + $schemaStorage = new SchemaStorage(); + $feedSourceValidationSchema = (new FeedSource())->getSchema(); + $schemaStorage->addSchema('file://contentSchema', $feedSourceValidationSchema); + $validator = new Validator(new Factory($schemaStorage)); + $validator->validate($object, $feedSourceValidationSchema); + + if (!$validator->isValid()) { + throw new InvalidArgumentException($validator->getErrors()[0]['property'].' '.$validator->getErrors()[0]['message']); } - $description = $object->description; + $feedTypeClassName = $object->feedType; - // Check description isset - if (empty($description) || !is_string($description)) { - throw new InvalidArgumentException('A feed source must have a description'); + if (!class_exists($feedTypeClassName)) { + throw new ClassDoesNotExist('Provided feed type class does not exist'); } + $feedTypeValidationSchema = $feedTypeClassName::getSchema(); - $supportedFeedOutputType = $object->supportedFeedOutputType; - - // Check description isset - if (empty($supportedFeedOutputType) || !is_string($supportedFeedOutputType)) { - throw new InvalidArgumentException('A feed source must have a supported feed output type'); - } - - $feedType = $object->feedType; - - // Check feedType isset - if (empty($feedType) || !is_string($feedType)) { - throw new InvalidArgumentException('A feed source must have a type'); + if($operation instanceof Put && empty($object->secrets)) { + return; } + $secrets = (object) $object->secrets; + $validator->validate($secrets, $feedTypeValidationSchema); - switch ($object->feedType) { - case 'App\\Feed\\EventDatabaseApiFeedType': - $host = $object->secrets[0]['host']; - - // Check host isset - if (empty($host) || !is_string($host)) { - throw new InvalidArgumentException('This feed source type must have a host defined'); - } - - // Check host valid url - if (!preg_match('`'.self::PATTERN_WITH_PROTOCOL.'`', $host)) { - if (!preg_match('`'.self::PATTERN_WITHOUT_PROTOCOL.'`', $host)) { - throw new InvalidArgumentException('The host must be a valid URL'); - } else { - throw new InvalidArgumentException('The host must be a valid URL including http or https'); - } - } - break; - case "App\Feed\NotifiedFeedType": - $token = $object->secrets[0]['token']; - - // Check token isset - if (!isset($token) || !is_string($token)) { - throw new InvalidArgumentException('This feed source type must have a token defined'); - } - break; + if (!$validator->isValid()) { + throw new InvalidArgumentException($validator->getErrors()[0]['property'].' '.$validator->getErrors()[0]['message']); } } } From 86a7485923d3f2509417a898a4938423ff6092e1 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 09:53:19 +0100 Subject: [PATCH 28/52] Changed getSchema function from static to non-static in EventDatabaseApiFeedType. --- src/Feed/EventDatabaseApiFeedType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index e0982420..917e98c8 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -310,7 +310,7 @@ public function getSupportedFeedOutputType(): string /** * @throws \JsonException */ - public static function getSchema(): mixed + public function getSchema(): mixed { $jsonSchema = <<<'JSON' { From 9a5e842ca86aea5eb96a6853656a02e6a95ddae6 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 09:53:20 +0100 Subject: [PATCH 29/52] Added getSchema method declaration in FeedTypeInterface. --- src/Feed/FeedTypeInterface.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Feed/FeedTypeInterface.php b/src/Feed/FeedTypeInterface.php index 2eda1837..93188eab 100644 --- a/src/Feed/FeedTypeInterface.php +++ b/src/Feed/FeedTypeInterface.php @@ -60,4 +60,11 @@ public function getRequiredConfiguration(): array; * @return string */ public function getSupportedFeedOutputType(): string; + + /** + * Get validation scheme for feed type. + * + * @return mixed + */ + public function getSchema(): mixed; } From 9990b3b1fb29ab4ff6440fb0c0779a045b9774ae Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 09:53:20 +0100 Subject: [PATCH 30/52] Added dynamic getSchema method in KobaFeedType class. --- src/Feed/KobaFeedType.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Feed/KobaFeedType.php b/src/Feed/KobaFeedType.php index d18b26b2..e0d48b58 100644 --- a/src/Feed/KobaFeedType.php +++ b/src/Feed/KobaFeedType.php @@ -257,4 +257,16 @@ private function getBookingsFromResource(string $host, string $apikey, string $r return $response->toArray(); } + + public function getSchema(): mixed + { + $jsonSchema = <<<'JSON' + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object" + } + JSON; + + return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + } } From e5a259e251cbdfbe56e14594711f7c0a9660d83c Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 09:53:20 +0100 Subject: [PATCH 31/52] Changed getSchema function from static to non-static in NotifiedFeedType. --- src/Feed/NotifiedFeedType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/NotifiedFeedType.php index a9370abe..afffd0e1 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/NotifiedFeedType.php @@ -248,7 +248,7 @@ private function wrapTags(string $input): string ]); } - public static function getSchema(): mixed + public function getSchema(): mixed { $jsonSchema = <<<'JSON' { From d8421f52728b5920313447d4acfaf33845822b99 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 09:53:20 +0100 Subject: [PATCH 32/52] Changed getSchema function from static to non-static in RssFeedType. --- src/Feed/RssFeedType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Feed/RssFeedType.php b/src/Feed/RssFeedType.php index 2b403da3..26d07757 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -137,7 +137,7 @@ public function getSupportedFeedOutputType(): string return self::SUPPORTED_FEED_TYPE; } - public static function getSchema(): mixed + public function getSchema(): mixed { $jsonSchema = <<<'JSON' { From cc27bdeb9b393643adb6dd7ea873f1cbb042f24b Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 09:53:20 +0100 Subject: [PATCH 33/52] Added dynamic getSchema method in SparkleIOFeedType class. --- src/Feed/SparkleIOFeedType.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Feed/SparkleIOFeedType.php b/src/Feed/SparkleIOFeedType.php index 5e13bc4f..ccaa02f1 100644 --- a/src/Feed/SparkleIOFeedType.php +++ b/src/Feed/SparkleIOFeedType.php @@ -284,4 +284,16 @@ private function wrapTags(string $input): string return $text; } + + public function getSchema(): mixed + { + $jsonSchema = <<<'JSON' + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object" + } + JSON; + + return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + } } From cb1af88945458415831311bcaddeca40e9b0e737 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 09:53:20 +0100 Subject: [PATCH 34/52] Refactored FeedSourceProcessor with updated validations and schema preparations. --- src/State/FeedSourceProcessor.php | 94 ++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index ffd9d507..f282c642 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -10,24 +10,25 @@ use ApiPlatform\State\ProcessorInterface; use App\Dto\FeedSourceInput; use App\Entity\Tenant\FeedSource; +use App\Exceptions\UnknownFeedTypeException; use App\Repository\FeedSourceRepository; +use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use JsonSchema\Constraints\Factory; use JsonSchema\SchemaStorage; use JsonSchema\Validator; -use PHPStan\BetterReflection\Reflection\Exception\ClassDoesNotExist; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; class FeedSourceProcessor extends AbstractProcessor { - private const string PATTERN_WITHOUT_PROTOCOL = '^((?!-)[A-Za-z0-9-]{1,63}(?loadPrevious(new FeedSource(), $context); + $this->updateFeedSourceProperties($feedSource, $object); - /* @var FeedSourceInput $object */ - empty($object->title) ?: $feedSource->setTitle($object->title); - empty($object->description) ?: $feedSource->setDescription($object->description); - empty($object->createdBy) ?: $feedSource->setCreatedBy($object->createdBy); - empty($object->modifiedBy) ?: $feedSource->setModifiedBy($object->modifiedBy); - empty($object->secrets) ?: $feedSource->setSecrets($object->secrets); - empty($object->feedType) ?: $feedSource->setFeedType($object->feedType); + // Set tenant + $user = $this->security->getUser(); + $feedSource->setTenant($user->getActiveTenant()); + // Validate feed source $this->validateFeedSource($object, $operation); return $feedSource; } + protected function updateFeedSourceProperties(FeedSource $feedSource, FeedSourceInput $object): void + { + if (!empty($object->title)) { + $feedSource->setTitle($object->title); + } + if (!empty($object->description)) { + $feedSource->setDescription($object->description); + } + if (!empty($object->createdBy)) { + $feedSource->setCreatedBy($object->createdBy); + } + if (!empty($object->modifiedBy)) { + $feedSource->setModifiedBy($object->modifiedBy); + } + if (!empty($object->secrets)) { + $feedSource->setSecrets($object->secrets); + } + if (!empty($object->feedType)) { + $feedSource->setFeedType($object->feedType); + } + $feedSource->setSupportedFeedOutputType($feedSource->getSupportedFeedOutputType()); + } + /** * @throws \JsonException + * @throws UnknownFeedTypeException */ private function validateFeedSource(object $object, Operation $operation): void { - $schemaStorage = new SchemaStorage(); - $feedSourceValidationSchema = (new FeedSource())->getSchema(); - $schemaStorage->addSchema('file://contentSchema', $feedSourceValidationSchema); - $validator = new Validator(new Factory($schemaStorage)); - $validator->validate($object, $feedSourceValidationSchema); + $validator = $this->prepareValidator(); - if (!$validator->isValid()) { - throw new InvalidArgumentException($validator->getErrors()[0]['property'].' '.$validator->getErrors()[0]['message']); - } + // Validate base feed source + $this->executeValidation($object, $validator); + // Validate dynamic feed type class $feedTypeClassName = $object->feedType; + $feedType = $this->feedService->getFeedType($feedTypeClassName); - if (!class_exists($feedTypeClassName)) { - throw new ClassDoesNotExist('Provided feed type class does not exist'); - } - $feedTypeValidationSchema = $feedTypeClassName::getSchema(); + $feedTypeValidationSchema = $feedType->getSchema(); - if($operation instanceof Put && empty($object->secrets)) { + // If updating and secrets are not set, don't validate. + if ($operation instanceof Put && empty($object->secrets)) { return; } + + // Validate secrets based on specific feed type. $secrets = (object) $object->secrets; - $validator->validate($secrets, $feedTypeValidationSchema); + $this->executeValidation($secrets, $validator, $feedTypeValidationSchema); + } + + private function prepareValidator(): validator + { + $schemaStorage = new SchemaStorage(); + $feedSourceValidationSchema = (new FeedSource())->getSchema(); + $schemaStorage->addSchema('file://contentSchema', $feedSourceValidationSchema); + + return new Validator(new Factory($schemaStorage)); + } - if (!$validator->isValid()) { - throw new InvalidArgumentException($validator->getErrors()[0]['property'].' '.$validator->getErrors()[0]['message']); + private function executeValidation($object, $validator, $schema = null): void + { + $validator->validate($object, $schema ?? (new FeedSource())->getSchema()); + if (! $validator->isValid()) { + throw new InvalidArgumentException($this->getErrorMessage($validator)); } } + + private function getErrorMessage($validator): string + { + return $validator->getErrors()[0]['property'].' '.$validator->getErrors()[0]['message']; + } } From a77e2aa51bc1d00d58c6c18761cd75d1dddd1748 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 10:05:33 +0100 Subject: [PATCH 35/52] Updated api spec --- public/api-spec-v2.json | 665 ++++++++++++++++++++++++++++++++++++---- public/api-spec-v2.yaml | 421 ++++++++++++++++++++++++- 2 files changed, 1014 insertions(+), 72 deletions(-) diff --git a/public/api-spec-v2.json b/public/api-spec-v2.json index f35f1980..8f67e25d 100644 --- a/public/api-spec-v2.json +++ b/public/api-spec-v2.json @@ -211,11 +211,481 @@ "name": "supportedFeedOutputType", "in": "query", "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string" + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "title", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string" + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "description", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string" + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "createdBy", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string" + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "createdBy[]", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "style": "form", + "explode": true, + "allowReserved": false + }, + { + "name": "modifiedBy", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string" + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "modifiedBy[]", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "style": "form", + "explode": true, + "allowReserved": false + }, + { + "name": "order[title]", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "default": "asc", + "enum": [ + "asc", + "desc" + ] + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "order[description]", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "default": "asc", + "enum": [ + "asc", + "desc" + ] + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "order[createdAt]", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "default": "asc", + "enum": [ + "asc", + "desc" + ] + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "order[modifiedAt]", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "default": "asc", + "enum": [ + "asc", + "desc" + ] + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "supportedFeedOutputType[]", + "in": "query", + "description": "", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "style": "form", + "explode": true, + "allowReserved": false + } + ], + "deprecated": false + }, + "post": { + "operationId": "create-v2-feed-source", + "tags": [ + "FeedSources" + ], + "responses": { + "201": { + "description": "FeedSource resource created", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSource.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSource" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSource" + } + } + }, + "links": {} + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Unprocessable entity" + } + }, + "summary": "Creates a Feed Source resource.", + "description": "Creates a Feed Source resource.", + "parameters": [], + "requestBody": { + "description": "The new FeedSource resource", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSourceInput.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSourceInput" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSourceInput" + } + } + }, + "required": true + }, + "deprecated": false + } + }, + "/v2/feed-sources/{id}": { + "get": { + "operationId": "get-feed-source-id", + "tags": [ + "FeedSources" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/ld+json": { + "examples": null + } + }, + "headers": [] + } + }, + "summary": "Retrieve a Feed Source resource.", + "description": "Retrieves a Feed Source resource.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "required": true, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "ulid", + "pattern": "^[A-Za-z0-9]{26}$" + }, + "style": "simple", + "explode": false, + "allowReserved": false + } + ], + "deprecated": false + }, + "put": { + "operationId": "put-v2-feed-source-id", + "tags": [ + "FeedSources" + ], + "responses": { + "200": { + "description": "FeedSource resource updated", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSource.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSource" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSource" + } + } + }, + "links": {} + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Unprocessable entity" + }, + "404": { + "description": "Resource not found" + } + }, + "summary": "Update a Feed Source resource.", + "description": "Update a Feed Source resource.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "required": true, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "ulid", + "pattern": "^[A-Za-z0-9]{26}$" + }, + "style": "simple", + "explode": false, + "allowReserved": false + } + ], + "requestBody": { + "description": "The updated FeedSource resource", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSourceInput.jsonld" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSourceInput" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FeedSource.FeedSourceInput" + } + } + }, + "required": true + }, + "deprecated": false + }, + "delete": { + "operationId": "delete-v2-feed-source-id", + "tags": [ + "FeedSources" + ], + "responses": { + "204": { + "description": "FeedSource resource deleted" + }, + "404": { + "description": "Resource not found" + } + }, + "summary": "Delete a Feed Source resource.", + "description": "Delete a Feed Source resource.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "required": true, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "ulid", + "pattern": "^[A-Za-z0-9]{26}$" + }, + "style": "simple", + "explode": false, + "allowReserved": false + } + ], + "deprecated": false + } + }, + "/v2/feed-sources/{id}/slides": { + "get": { + "operationId": "get-v2-feed-source-slide-id", + "tags": [ + "FeedSources" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/ld+json": { + "examples": null + } + }, + "headers": [] + } + }, + "summary": "Retrieves collection of weighted slide resources (feedsource).", + "description": "Retrieves collection of weighted slide resources (feedsource).", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", "required": true, "deprecated": false, "allowEmptyValue": false, "schema": { - "type": "string" + "type": "string", + "format": "ulid", + "pattern": "^[A-Za-z0-9]{26}$" + }, + "style": "simple", + "explode": false, + "allowReserved": false + }, + { + "name": "page", + "in": "query", + "description": "", + "required": true, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "integer", + "minimum": 0, + "format": "int32", + "default": 1 + }, + "style": "form", + "explode": false, + "allowReserved": false + }, + { + "name": "itemsPerPage", + "in": "query", + "description": "The number of items per page", + "required": false, + "deprecated": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "default": "10" }, "style": "form", "explode": false, @@ -312,26 +782,21 @@ "allowReserved": false }, { - "name": "order[title]", + "name": "published", "in": "query", - "description": "", + "description": "If true only published content will be shown", "required": false, "deprecated": false, "allowEmptyValue": false, "schema": { - "type": "string", - "default": "asc", - "enum": [ - "asc", - "desc" - ] + "type": "boolean" }, "style": "form", "explode": false, "allowReserved": false }, { - "name": "order[description]", + "name": "order[title]", "in": "query", "description": "", "required": false, @@ -350,7 +815,7 @@ "allowReserved": false }, { - "name": "order[createdAt]", + "name": "order[description]", "in": "query", "description": "", "required": false, @@ -369,7 +834,7 @@ "allowReserved": false }, { - "name": "order[modifiedAt]", + "name": "order[createdAt]", "in": "query", "description": "", "required": false, @@ -388,59 +853,21 @@ "allowReserved": false }, { - "name": "supportedFeedOutputType[]", + "name": "order[modifiedAt]", "in": "query", "description": "", "required": false, "deprecated": false, "allowEmptyValue": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } - }, - "style": "form", - "explode": true, - "allowReserved": false - } - ], - "deprecated": false - } - }, - "/v2/feed-sources/{id}": { - "get": { - "operationId": "get-feed-source-id", - "tags": [ - "FeedSources" - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/ld+json": { - "examples": null - } - }, - "headers": [] - } - }, - "summary": "Retrieve a Feed Source resource.", - "description": "Retrieves a Feed Source resource.", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "", - "required": true, - "deprecated": false, - "allowEmptyValue": false, "schema": { "type": "string", - "format": "ulid", - "pattern": "^[A-Za-z0-9]{26}$" + "default": "asc", + "enum": [ + "asc", + "desc" + ] }, - "style": "simple", + "style": "form", "explode": false, "allowReserved": false } @@ -7758,6 +8185,11 @@ } } }, + "FeedSource-playlist-slide.read": { + "type": "object", + "description": "", + "deprecated": false + }, "FeedSource.FeedSource": { "type": "object", "description": "", @@ -7816,19 +8248,16 @@ } } }, + "FeedSource.FeedSource-playlist-slide.read": { + "type": "object", + "description": "", + "deprecated": false + }, "FeedSource.FeedSource.jsonld": { "type": "object", "description": "", "deprecated": false, "properties": { - "@id": { - "readOnly": true, - "type": "string" - }, - "@type": { - "readOnly": true, - "type": "string" - }, "@context": { "readOnly": true, "oneOf": [ @@ -7856,6 +8285,14 @@ } ] }, + "@id": { + "readOnly": true, + "type": "string" + }, + "@type": { + "readOnly": true, + "type": "string" + }, "title": { "type": "string" }, @@ -7909,6 +8346,89 @@ } } }, + "FeedSource.FeedSource.jsonld-playlist-slide.read": { + "type": "object", + "description": "", + "deprecated": false, + "properties": { + "@id": { + "readOnly": true, + "type": "string" + }, + "@type": { + "readOnly": true, + "type": "string" + } + } + }, + "FeedSource.FeedSourceInput": { + "type": "object", + "description": "", + "deprecated": false, + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "outputType": { + "type": "string" + }, + "feedType": { + "type": "string" + }, + "secrets": { + "type": "array", + "items": { + "type": "string" + } + }, + "feeds": { + "type": "array", + "items": { + "type": "string" + } + }, + "supportedFeedOutputType": { + "type": "string" + } + } + }, + "FeedSource.FeedSourceInput.jsonld": { + "type": "object", + "description": "", + "deprecated": false, + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "outputType": { + "type": "string" + }, + "feedType": { + "type": "string" + }, + "secrets": { + "type": "array", + "items": { + "type": "string" + } + }, + "feeds": { + "type": "array", + "items": { + "type": "string" + } + }, + "supportedFeedOutputType": { + "type": "string" + } + } + }, "FeedSource.jsonld": { "type": "object", "description": "", @@ -8002,6 +8522,21 @@ } } }, + "FeedSource.jsonld-playlist-slide.read": { + "type": "object", + "description": "", + "deprecated": false, + "properties": { + "@id": { + "readOnly": true, + "type": "string" + }, + "@type": { + "readOnly": true, + "type": "string" + } + } + }, "Media": { "type": "object", "description": "", diff --git a/public/api-spec-v2.yaml b/public/api-spec-v2.yaml index 88df1d6a..5eeaf4a5 100644 --- a/public/api-spec-v2.yaml +++ b/public/api-spec-v2.yaml @@ -149,7 +149,7 @@ paths: name: supportedFeedOutputType in: query description: '' - required: true + required: false deprecated: false allowEmptyValue: false schema: @@ -312,6 +312,45 @@ paths: explode: true allowReserved: false deprecated: false + post: + operationId: create-v2-feed-source + tags: + - FeedSources + responses: + '201': + description: 'FeedSource resource created' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/FeedSource.FeedSource.jsonld' + text/html: + schema: + $ref: '#/components/schemas/FeedSource.FeedSource' + multipart/form-data: + schema: + $ref: '#/components/schemas/FeedSource.FeedSource' + links: { } + '400': + description: 'Invalid input' + '422': + description: 'Unprocessable entity' + summary: 'Creates a Feed Source resource.' + description: 'Creates a Feed Source resource.' + parameters: [] + requestBody: + description: 'The new FeedSource resource' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/FeedSource.FeedSourceInput.jsonld' + text/html: + schema: + $ref: '#/components/schemas/FeedSource.FeedSourceInput' + multipart/form-data: + schema: + $ref: '#/components/schemas/FeedSource.FeedSourceInput' + required: true + deprecated: false '/v2/feed-sources/{id}': get: operationId: get-feed-source-id @@ -342,6 +381,298 @@ paths: explode: false allowReserved: false deprecated: false + put: + operationId: put-v2-feed-source-id + tags: + - FeedSources + responses: + '200': + description: 'FeedSource resource updated' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/FeedSource.FeedSource.jsonld' + text/html: + schema: + $ref: '#/components/schemas/FeedSource.FeedSource' + multipart/form-data: + schema: + $ref: '#/components/schemas/FeedSource.FeedSource' + links: { } + '400': + description: 'Invalid input' + '422': + description: 'Unprocessable entity' + '404': + description: 'Resource not found' + summary: 'Update a Feed Source resource.' + description: 'Update a Feed Source resource.' + parameters: + - + name: id + in: path + description: '' + required: true + deprecated: false + allowEmptyValue: false + schema: + type: string + format: ulid + pattern: '^[A-Za-z0-9]{26}$' + style: simple + explode: false + allowReserved: false + requestBody: + description: 'The updated FeedSource resource' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/FeedSource.FeedSourceInput.jsonld' + text/html: + schema: + $ref: '#/components/schemas/FeedSource.FeedSourceInput' + multipart/form-data: + schema: + $ref: '#/components/schemas/FeedSource.FeedSourceInput' + required: true + deprecated: false + delete: + operationId: delete-v2-feed-source-id + tags: + - FeedSources + responses: + '204': + description: 'FeedSource resource deleted' + '404': + description: 'Resource not found' + summary: 'Delete a Feed Source resource.' + description: 'Delete a Feed Source resource.' + parameters: + - + name: id + in: path + description: '' + required: true + deprecated: false + allowEmptyValue: false + schema: + type: string + format: ulid + pattern: '^[A-Za-z0-9]{26}$' + style: simple + explode: false + allowReserved: false + deprecated: false + '/v2/feed-sources/{id}/slides': + get: + operationId: get-v2-feed-source-slide-id + tags: + - FeedSources + responses: + '200': + description: OK + content: + application/ld+json: + examples: null + headers: [] + summary: 'Retrieves collection of weighted slide resources (feedsource).' + description: 'Retrieves collection of weighted slide resources (feedsource).' + parameters: + - + name: id + in: path + description: '' + required: true + deprecated: false + allowEmptyValue: false + schema: + type: string + format: ulid + pattern: '^[A-Za-z0-9]{26}$' + style: simple + explode: false + allowReserved: false + - + name: page + in: query + description: '' + required: true + deprecated: false + allowEmptyValue: false + schema: + type: integer + minimum: 0 + format: int32 + default: 1 + style: form + explode: false + allowReserved: false + - + name: itemsPerPage + in: query + description: 'The number of items per page' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + default: '10' + style: form + explode: false + allowReserved: false + - + name: title + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + style: form + explode: false + allowReserved: false + - + name: description + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + style: form + explode: false + allowReserved: false + - + name: createdBy + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + style: form + explode: false + allowReserved: false + - + name: 'createdBy[]' + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: array + items: + type: string + style: form + explode: true + allowReserved: false + - + name: modifiedBy + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + style: form + explode: false + allowReserved: false + - + name: 'modifiedBy[]' + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: array + items: + type: string + style: form + explode: true + allowReserved: false + - + name: published + in: query + description: 'If true only published content will be shown' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: boolean + style: form + explode: false + allowReserved: false + - + name: 'order[title]' + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + default: asc + enum: + - asc + - desc + style: form + explode: false + allowReserved: false + - + name: 'order[description]' + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + default: asc + enum: + - asc + - desc + style: form + explode: false + allowReserved: false + - + name: 'order[createdAt]' + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + default: asc + enum: + - asc + - desc + style: form + explode: false + allowReserved: false + - + name: 'order[modifiedAt]' + in: query + description: '' + required: false + deprecated: false + allowEmptyValue: false + schema: + type: string + default: asc + enum: + - asc + - desc + style: form + explode: false + allowReserved: false + deprecated: false '/v2/feed_sources/{id}/config/{name}': get: operationId: get-v2-feed-source-id-config-name @@ -5462,6 +5793,10 @@ components: modified: type: string format: date-time + FeedSource-playlist-slide.read: + type: object + description: '' + deprecated: false FeedSource.FeedSource: type: object description: '' @@ -5502,17 +5837,15 @@ components: modified: type: string format: date-time + FeedSource.FeedSource-playlist-slide.read: + type: object + description: '' + deprecated: false FeedSource.FeedSource.jsonld: type: object description: '' deprecated: false properties: - '@id': - readOnly: true - type: string - '@type': - readOnly: true - type: string '@context': readOnly: true oneOf: @@ -5530,6 +5863,12 @@ components: - '@vocab' - hydra additionalProperties: true + '@id': + readOnly: true + type: string + '@type': + readOnly: true + type: string title: type: string description: @@ -5565,6 +5904,63 @@ components: modified: type: string format: date-time + FeedSource.FeedSource.jsonld-playlist-slide.read: + type: object + description: '' + deprecated: false + properties: + '@id': + readOnly: true + type: string + '@type': + readOnly: true + type: string + FeedSource.FeedSourceInput: + type: object + description: '' + deprecated: false + properties: + title: + type: string + description: + type: string + outputType: + type: string + feedType: + type: string + secrets: + type: array + items: + type: string + feeds: + type: array + items: + type: string + supportedFeedOutputType: + type: string + FeedSource.FeedSourceInput.jsonld: + type: object + description: '' + deprecated: false + properties: + title: + type: string + description: + type: string + outputType: + type: string + feedType: + type: string + secrets: + type: array + items: + type: string + feeds: + type: array + items: + type: string + supportedFeedOutputType: + type: string FeedSource.jsonld: type: object description: '' @@ -5628,6 +6024,17 @@ components: modified: type: string format: date-time + FeedSource.jsonld-playlist-slide.read: + type: object + description: '' + deprecated: false + properties: + '@id': + readOnly: true + type: string + '@type': + readOnly: true + type: string Media: type: object description: '' From cab2f2038fda86b44791137c2b21455892d44c66 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 10:05:38 +0100 Subject: [PATCH 36/52] Coding standards --- src/Feed/EventDatabaseApiFeedType.php | 24 +++++++++++------------- src/State/FeedSourceProcessor.php | 5 ++--- tests/Api/FeedSourceTest.php | 3 +-- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index 917e98c8..ef057025 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -23,13 +23,11 @@ class EventDatabaseApiFeedType implements FeedTypeInterface final public const int REQUEST_TIMEOUT = 10; public function __construct( - private readonly FeedService $feedService, - private readonly HttpClientInterface $client, - private readonly LoggerInterface $logger, + private readonly FeedService $feedService, + private readonly HttpClientInterface $client, + private readonly LoggerInterface $logger, private readonly EntityManagerInterface $entityManager, - ) - { - } + ) {} /** * @param Feed $feed @@ -59,9 +57,9 @@ public function getData(Feed $feed): array $queryParams = array_filter([ 'items_per_page' => $numberOfItems, - 'occurrences.place.id' => array_map(static fn($place) => str_replace('/api/places/', '', (string)$place['value']), $places), - 'organizer.id' => array_map(static fn($organizer) => str_replace('/api/organizers/', '', (string)$organizer['value']), $organizers), - 'tags' => array_map(static fn($tag) => str_replace('/api/tags/', '', (string)$tag['value']), $tags), + 'occurrences.place.id' => array_map(static fn ($place) => str_replace('/api/places/', '', (string) $place['value']), $places), + 'organizer.id' => array_map(static fn ($organizer) => str_replace('/api/organizers/', '', (string) $organizer['value']), $organizers), + 'tags' => array_map(static fn ($tag) => str_replace('/api/tags/', '', (string) $tag['value']), $tags), ]); $response = $this->client->request( @@ -92,9 +90,9 @@ public function getData(Feed $feed): array $content = $response->getContent(); $decoded = json_decode($content, null, 512, JSON_THROW_ON_ERROR); - $baseUrl = parse_url((string)$decoded->event->{'url'}, PHP_URL_HOST); + $baseUrl = parse_url((string) $decoded->event->{'url'}, PHP_URL_HOST); - $eventOccurrence = (object)[ + $eventOccurrence = (object) [ 'eventId' => $decoded->event->{'@id'}, 'occurrenceId' => $decoded->{'@id'}, 'ticketPurchaseUrl' => $decoded->event->{'ticketPurchaseUrl'}, @@ -110,7 +108,7 @@ public function getData(Feed $feed): array ]; if (isset($decoded->place)) { - $eventOccurrence->place = (object)[ + $eventOccurrence->place = (object) [ 'name' => $decoded->place->name, 'streetAddress' => $decoded->place->streetAddress, 'addressLocality' => $decoded->place->addressLocality, @@ -257,7 +255,7 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin foreach ($members as $member) { // Special handling of searching in tags, since EventDatabaseApi does not support this. if ('tags' == $type) { - if (!isset($queryParams['name']) || str_contains(strtolower((string)$member->name), strtolower((string)$queryParams['name']))) { + if (!isset($queryParams['name']) || str_contains(strtolower((string) $member->name), strtolower((string) $queryParams['name']))) { $result[] = $displayAsOptions ? [ 'label' => $member->name, 'value' => $member->{'@id'}, diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index f282c642..8496d995 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -33,7 +33,6 @@ public function __construct( parent::__construct($entityManager, $persistProcessor, $removeProcessor); } - public function process($data, Operation $operation, array $uriVariables = [], array $context = []): void { if ($operation instanceof DeleteOperationInterface) { @@ -113,7 +112,7 @@ private function validateFeedSource(object $object, Operation $operation): void $this->executeValidation($secrets, $validator, $feedTypeValidationSchema); } - private function prepareValidator(): validator + private function prepareValidator(): Validator { $schemaStorage = new SchemaStorage(); $feedSourceValidationSchema = (new FeedSource())->getSchema(); @@ -125,7 +124,7 @@ private function prepareValidator(): validator private function executeValidation($object, $validator, $schema = null): void { $validator->validate($object, $schema ?? (new FeedSource())->getSchema()); - if (! $validator->isValid()) { + if (!$validator->isValid()) { throw new InvalidArgumentException($this->getErrorMessage($validator)); } } diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index 4f8f7f39..19bb47b2 100644 --- a/tests/Api/FeedSourceTest.php +++ b/tests/Api/FeedSourceTest.php @@ -118,7 +118,6 @@ public function testCreateFeedSourceWithoutTitle(): void ], ]); $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); - } public function testCreateFeedSourceWithoutDescription(): void @@ -199,7 +198,7 @@ public function testCreateFeedSourceWithEventDatabaseFeedTypeWithRequiredSecret( ], ]); - //$this->assertResponseIsSuccessful(); + // $this->assertResponseIsSuccessful(); $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); } From d1692946c21a33d977df4227a26ab5caa54496ce Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 10:07:25 +0100 Subject: [PATCH 37/52] Yaml lint --- config/api_platform/feed_source.yaml | 65 ++++++++++++++-------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/config/api_platform/feed_source.yaml b/config/api_platform/feed_source.yaml index 43896356..6375b101 100644 --- a/config/api_platform/feed_source.yaml +++ b/config/api_platform/feed_source.yaml @@ -147,19 +147,20 @@ resources: summary: Creates a Feed Source resource. tags: - FeedSources - _api_/feed_sources/{id}/slides_get: &get_slides + '_api_/feed_sources/{id}/slides_get': &ref_1 normalizationContext: - groups: [ 'playlist-slide:read' ] + groups: + - 'playlist-slide:read' class: ApiPlatform\Metadata\GetCollection method: GET provider: App\State\FeedSourceSlideProvider filters: - - 'entity.search_filter' - - 'entity.blameable_filter' - - 'App\Filter\PublishedFilter' - - 'entity.order_filter' - - 'created.at.order_filter' - - 'modified.at.order_filter' + - entity.search_filter + - entity.blameable_filter + - App\Filter\PublishedFilter + - entity.order_filter + - created.at.order_filter + - modified.at.order_filter uriTemplate: '/feed-sources/{id}/slides' openapiContext: description: Retrieves collection of weighted slide resources (feedsource). @@ -168,36 +169,36 @@ resources: tags: - FeedSources parameters: - - schema: - type: string - format: ulid - pattern: "^[A-Za-z0-9]{26}$" - name: id - in: path - required: true - - schema: - type: integer - minimum: 0 - format: int32 - default: 1 - in: query - name: page - required: true - - schema: - type: string - default: '10' - in: query - name: itemsPerPage - description: The number of items per page + - schema: + type: string + format: ulid + pattern: '^[A-Za-z0-9]{26}$' + name: id + in: path + required: true + - schema: + type: integer + minimum: 0 + format: int32 + default: 1 + in: query + name: page + required: true + - schema: + type: string + default: '10' + in: query + name: itemsPerPage + description: The number of items per page responses: '200': description: OK content: application/ld+json: - examples: - headers: { } + examples: null + headers: {} App\Dto\FeedSource: provider: App\State\FeedSourceProvider operations: ApiPlatform\Metadata\Get: *ref_0 - get_slides: *get_slides + get_slides: *ref_1 From 1f6d291aa3cadd90914a22f3eb9a283d9610c193 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 10:44:41 +0100 Subject: [PATCH 38/52] Coding standards --- psalm-baseline.xml | 11 ----------- src/State/FeedSourceProcessor.php | 28 +++++++++++++++++++++------ src/State/FeedSourceSlideProvider.php | 8 +++++++- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 3a079edf..ef8d120f 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -386,17 +386,6 @@ - - - - - - - - - - - getId()]]> diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 8496d995..456858cf 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -4,11 +4,13 @@ namespace App\State; +use ApiPlatform\Metadata\DeleteOperationInterface; use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Put; use ApiPlatform\State\ProcessorInterface; use App\Dto\FeedSourceInput; +use App\Entity\Interfaces\TenantScopedUserInterface; use App\Entity\Tenant\FeedSource; use App\Exceptions\UnknownFeedTypeException; use App\Repository\FeedSourceRepository; @@ -23,9 +25,9 @@ class FeedSourceProcessor extends AbstractProcessor { public function __construct( - private readonly EntityManagerInterface $entityManager, + EntityManagerInterface $entityManager, ProcessorInterface $persistProcessor, - private readonly ProcessorInterface $removeProcessor, + ProcessorInterface $removeProcessor, private readonly FeedSourceRepository $feedSourceRepository, private readonly FeedService $feedService, private readonly Security $security, @@ -41,19 +43,30 @@ public function process($data, Operation $operation, array $uriVariables = [], a if ($hasSlides) { throw new ConflictHttpException('This feed source is used by one or more slides and cannot be deleted.'); } - $this->removeProcessor->process($data, $operation, $uriVariables, $context); } parent::process($data, $operation, $uriVariables, $context); } + /** + * @throws UnknownFeedTypeException + * @throws \JsonException + */ protected function fromInput(mixed $object, Operation $operation, array $uriVariables, array $context): FeedSource { // Set feed source properties $feedSource = $this->loadPrevious(new FeedSource(), $context); + + if (!$feedSource instanceof FeedSource) { + throw new InvalidArgumentException('loadPrevious did not return a FeedSource object.'); + } + $this->updateFeedSourceProperties($feedSource, $object); // Set tenant $user = $this->security->getUser(); + if (!$user instanceof TenantScopedUserInterface) { + throw new InvalidArgumentException('The user is not a tenant owner.'); + } $feedSource->setTenant($user->getActiveTenant()); // Validate feed source @@ -82,7 +95,10 @@ protected function updateFeedSourceProperties(FeedSource $feedSource, FeedSource if (!empty($object->feedType)) { $feedSource->setFeedType($object->feedType); } - $feedSource->setSupportedFeedOutputType($feedSource->getSupportedFeedOutputType()); + $supportedFeedOutputType = $feedSource->getSupportedFeedOutputType(); + if ($supportedFeedOutputType !== null) { + $feedSource->setSupportedFeedOutputType($supportedFeedOutputType); + } } /** @@ -121,7 +137,7 @@ private function prepareValidator(): Validator return new Validator(new Factory($schemaStorage)); } - private function executeValidation($object, $validator, $schema = null): void + private function executeValidation(mixed $object, Validator $validator, ?array $schema = null): void { $validator->validate($object, $schema ?? (new FeedSource())->getSchema()); if (!$validator->isValid()) { @@ -129,7 +145,7 @@ private function executeValidation($object, $validator, $schema = null): void } } - private function getErrorMessage($validator): string + private function getErrorMessage(Validator $validator): string { return $validator->getErrors()[0]['property'].' '.$validator->getErrors()[0]['message']; } diff --git a/src/State/FeedSourceSlideProvider.php b/src/State/FeedSourceSlideProvider.php index a3c14fb9..631a9cdc 100644 --- a/src/State/FeedSourceSlideProvider.php +++ b/src/State/FeedSourceSlideProvider.php @@ -16,6 +16,7 @@ use App\Utils\ValidationUtils; use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Uid\Ulid; /** * A Playlist slide state provider. @@ -67,7 +68,12 @@ public function toOutput(object $object): SlideDTO assert($object instanceof Slide); $output = new SlideDTO(); - $output->id = $object->getId(); + $id = $object->getId(); + if (!$id instanceof Ulid) { + throw new \RuntimeException('Can\'t assign id as Slide->getId() did not return a Ulid object.'); + } + + $output->id = $id; $output->title = $object->getTitle(); return $output; From aef0260d140ae879ba9334e43dc9083eb4c6e3a9 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 13:14:36 +0100 Subject: [PATCH 39/52] Change json_decode from object to associative array --- src/Entity/Tenant/FeedSource.php | 2 +- src/Feed/EventDatabaseApiFeedType.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Entity/Tenant/FeedSource.php b/src/Entity/Tenant/FeedSource.php index a673509a..51506aff 100644 --- a/src/Entity/Tenant/FeedSource.php +++ b/src/Entity/Tenant/FeedSource.php @@ -145,6 +145,6 @@ public function getSchema(): mixed } JSON; - return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); } } diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index ef057025..7c4e122f 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -325,6 +325,6 @@ public function getSchema(): mixed } JSON; - return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); } } From 6ebb2efc0fb4b3e73e2c34c58959a127080b5382 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 13:14:36 +0100 Subject: [PATCH 40/52] Change return type of process() and refactor some conditions and methods --- src/State/FeedSourceProcessor.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 456858cf..2842c9fc 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -27,7 +27,7 @@ class FeedSourceProcessor extends AbstractProcessor public function __construct( EntityManagerInterface $entityManager, ProcessorInterface $persistProcessor, - ProcessorInterface $removeProcessor, + private readonly ProcessorInterface $removeProcessor, private readonly FeedSourceRepository $feedSourceRepository, private readonly FeedService $feedService, private readonly Security $security, @@ -35,7 +35,7 @@ public function __construct( parent::__construct($entityManager, $persistProcessor, $removeProcessor); } - public function process($data, Operation $operation, array $uriVariables = [], array $context = []): void + public function process($data, Operation $operation, array $uriVariables = [], array $context = []): ?object { if ($operation instanceof DeleteOperationInterface) { $queryBuilder = $this->feedSourceRepository->getFeedSourceSlideRelationsFromFeedSourceId($uriVariables['id']); @@ -44,7 +44,7 @@ public function process($data, Operation $operation, array $uriVariables = [], a throw new ConflictHttpException('This feed source is used by one or more slides and cannot be deleted.'); } } - parent::process($data, $operation, $uriVariables, $context); + return parent::process($data, $operation, $uriVariables, $context); } /** @@ -57,7 +57,7 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari $feedSource = $this->loadPrevious(new FeedSource(), $context); if (!$feedSource instanceof FeedSource) { - throw new InvalidArgumentException('loadPrevious did not return a FeedSource object.'); + throw new InvalidArgumentException('object must by of type FeedSource'); } $this->updateFeedSourceProperties($feedSource, $object); @@ -96,7 +96,7 @@ protected function updateFeedSourceProperties(FeedSource $feedSource, FeedSource $feedSource->setFeedType($object->feedType); } $supportedFeedOutputType = $feedSource->getSupportedFeedOutputType(); - if ($supportedFeedOutputType !== null) { + if (null !== $supportedFeedOutputType) { $feedSource->setSupportedFeedOutputType($supportedFeedOutputType); } } @@ -109,8 +109,11 @@ private function validateFeedSource(object $object, Operation $operation): void { $validator = $this->prepareValidator(); + // Prepare base feed source validation schema + $feedSourceValidationSchema = (new FeedSource())->getSchema(); + // Validate base feed source - $this->executeValidation($object, $validator); + $this->executeValidation($object, $validator, $feedSourceValidationSchema); // Validate dynamic feed type class $feedTypeClassName = $object->feedType; @@ -137,6 +140,9 @@ private function prepareValidator(): Validator return new Validator(new Factory($schemaStorage)); } + /** + * @throws \JsonException + */ private function executeValidation(mixed $object, Validator $validator, ?array $schema = null): void { $validator->validate($object, $schema ?? (new FeedSource())->getSchema()); From 44612a63e906b80dc7759f004fe798af404d322a Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 13:14:36 +0100 Subject: [PATCH 41/52] Update test scenarios due to recent code changes --- tests/Api/FeedSourceTest.php | 76 ++++++------------------------------ 1 file changed, 13 insertions(+), 63 deletions(-) diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index 19bb47b2..f427db7c 100644 --- a/tests/Api/FeedSourceTest.php +++ b/tests/Api/FeedSourceTest.php @@ -60,15 +60,10 @@ public function testCreateFeedSource(): void 'json' => [ 'title' => 'Test feed source', 'description' => 'This is a test feed source', - 'outputType' => 'This is a test output type', - 'feedType' => 'This is a test feed type', + 'feedType' => 'App\Feed\EventDatabaseApiFeedType', 'secrets' => [ - 'test secret', - ], - 'feeds' => [ - 'test feed', + 'host' => 'https://www.test.dk', ], - 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ 'Content-Type' => 'application/ld+json', @@ -80,14 +75,12 @@ public function testCreateFeedSource(): void $this->assertJsonContains([ '@context' => '/contexts/FeedSource', '@type' => 'FeedSource', - 'feedType' => 'This is a test feed type', - 'secrets' => [ - 'test secret', - ], - 'feeds' => [], - 'supportedFeedOutputType' => 'Supported feed output type', 'title' => 'Test feed source', 'description' => 'This is a test feed source', + 'feedType' => 'App\Feed\EventDatabaseApiFeedType', + 'secrets' => [ + 'host' => 'https://www.test.dk', + ], 'createdBy' => 'test@example.com', 'modifiedBy' => 'test@example.com', ]); @@ -108,9 +101,7 @@ public function testCreateFeedSourceWithoutTitle(): void 'secrets' => [ 'test secret', ], - 'feeds' => [ - 'test feed', - ], + 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ @@ -134,9 +125,7 @@ public function testCreateFeedSourceWithoutDescription(): void 'secrets' => [ 'test secret', ], - 'feeds' => [ - 'test feed', - ], + 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ @@ -161,36 +150,7 @@ public function testCreateFeedSourceWithEventDatabaseFeedTypeWithoutRequiredSecr 'secrets' => [ 'test secret', ], - 'feeds' => [ - 'test feed', - ], - 'supportedFeedOutputType' => 'Supported feed output type', - ], - 'headers' => [ - 'Content-Type' => 'application/ld+json', - ], - ]); - - $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); - } - public function testCreateFeedSourceWithEventDatabaseFeedTypeWithRequiredSecret(): void - { - $client = $this->getAuthenticatedClient('ROLE_ADMIN'); - - $this->expectException(ClientException::class); - $response = $client->request('POST', '/v2/feed-sources', [ - 'json' => [ - 'title' => 'Test feed source', - 'description' => 'This is a test feed source', - 'outputType' => 'This is a test output type', - 'feedType' => 'App\\Feed\\EventDatabaseApiFeedType', - 'secrets' => [ - 'host' => 'https://www.test.dk', - ], - 'feeds' => [ - 'test feed', - ], 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ @@ -198,7 +158,6 @@ public function testCreateFeedSourceWithEventDatabaseFeedTypeWithRequiredSecret( ], ]); - // $this->assertResponseIsSuccessful(); $this->assertMatchesRegularExpression('@^/v\d/[\w-]+/([A-Za-z0-9]{26})$@', $response->toArray()['@id']); } @@ -212,14 +171,9 @@ public function testUpdateFeedSource(): void 'title' => 'Updated title', 'description' => 'Updated description', 'outputType' => 'This is a test output type', - 'feedType' => 'This is a test feed type', + 'feedType' => 'App\Feed\EventDatabaseApiFeedType', 'secrets' => [ - 'test secret', - ], - 'feeds' => [ - 'test feed', ], - 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ 'Content-Type' => 'application/ld+json', @@ -241,17 +195,13 @@ public function testDeleteFeedSource(): void $response = $client->request('POST', '/v2/feed-sources', [ 'json' => [ - 'title' => 'Test feed source to delete', + 'title' => 'Test feed source', 'description' => 'This is a test feed source', 'outputType' => 'This is a test output type', - 'feedType' => 'This is a test feed type', + 'feedType' => 'App\Feed\EventDatabaseApiFeedType', 'secrets' => [ - 'test secret', - ], - 'feeds' => [ - 'test feed', + 'host' => 'https://www.test.dk', ], - 'supportedFeedOutputType' => 'Supported feed output type', ], 'headers' => [ 'Content-Type' => 'application/ld+json', @@ -289,7 +239,7 @@ public function testDeleteFeedSourceInUse(): void $client->request('DELETE', $feedSourceIri); // Assert that delete request throws an integrity constraint violation error - $this->assertResponseStatusCodeSame(500); + $this->assertResponseStatusCodeSame(409); $ulid = $this->iriHelperUtils->getUlidFromIRI($feedSourceIri); From 23e15af8373520e82a3157df3490a5c26e22ea03 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 13:15:27 +0100 Subject: [PATCH 42/52] Changed json_decode from object to associative array on the rest of the feed source types --- src/Feed/KobaFeedType.php | 2 +- src/Feed/NotifiedFeedType.php | 2 +- src/Feed/RssFeedType.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Feed/KobaFeedType.php b/src/Feed/KobaFeedType.php index e0d48b58..9e1259df 100644 --- a/src/Feed/KobaFeedType.php +++ b/src/Feed/KobaFeedType.php @@ -267,6 +267,6 @@ public function getSchema(): mixed } JSON; - return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); } } diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/NotifiedFeedType.php index afffd0e1..8ab642fb 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/NotifiedFeedType.php @@ -263,6 +263,6 @@ public function getSchema(): mixed } JSON; - return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); } } diff --git a/src/Feed/RssFeedType.php b/src/Feed/RssFeedType.php index 26d07757..3ffce214 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -146,6 +146,6 @@ public function getSchema(): mixed } JSON; - return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); } } From 19fc0be3149a0cc97a893c3f66fbc4bbc62b5aca Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Tue, 12 Nov 2024 13:29:54 +0100 Subject: [PATCH 43/52] Coding standards --- src/State/FeedSourceProcessor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 2842c9fc..c76d370d 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -44,6 +44,7 @@ public function process($data, Operation $operation, array $uriVariables = [], a throw new ConflictHttpException('This feed source is used by one or more slides and cannot be deleted.'); } } + return parent::process($data, $operation, $uriVariables, $context); } From 44ee0019300edde18d1890310ebef9c483ac4feb Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 14 Nov 2024 10:54:18 +0100 Subject: [PATCH 44/52] Modified getSchema implementation to return PHP array directly --- src/Entity/Tenant/FeedSource.php | 70 +++++++++++++-------------- src/Feed/EventDatabaseApiFeedType.php | 30 +++++------- src/Feed/KobaFeedType.php | 14 ++---- src/Feed/NotifiedFeedType.php | 26 +++++----- src/Feed/RssFeedType.php | 14 ++---- src/Feed/SparkleIOFeedType.php | 14 ++---- 6 files changed, 72 insertions(+), 96 deletions(-) diff --git a/src/Entity/Tenant/FeedSource.php b/src/Entity/Tenant/FeedSource.php index 51506aff..910ee4df 100644 --- a/src/Entity/Tenant/FeedSource.php +++ b/src/Entity/Tenant/FeedSource.php @@ -106,45 +106,41 @@ public function setSupportedFeedOutputType(string $supportedFeedOutputType): sel return $this; } + /** - * @throws \JsonException + * Retrieves the JSON schema for validation. + * + * @return array The JSON schema definition */ - public function getSchema(): mixed + public function getSchema(): array { - $jsonSchema = <<<'JSON' - { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://os2display.dk/config-schema.json", - "title": "Config file schema", - "description": "Schema for defining config files for templates", - "type": "object", - "properties": { - "title": { - "description": "The title of the feed source", - "type": "string", - "minLength": 1 - }, - "description": { - "description": "A description of the feed source", - "type": "string", - "minLength": 1 - }, - "feedType": { - "description": "The type of the feed source", - "type": "string", - "minLength": 1 - }, - "secrets": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["title", "description", "feedType", "secrets"] - } - JSON; - - return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); + return [ + '$schema' => 'https://json-schema.org/draft/2020-12/schema', + '$id' => 'https://os2display.dk/config-schema.json', + 'title' => 'Config file schema', + 'description' => 'Schema for defining config files for templates', + 'type' => 'object', + 'properties' => [ + 'title' => [ + 'description' => 'The title of the feed source', + 'type' => 'string', + 'minLength' => 1, + ], + 'description' => [ + 'description' => 'A description of the feed source', + 'type' => 'string', + 'minLength' => 1, + ], + 'feedType' => [ + 'description' => 'The type of the feed source', + 'type' => 'string', + 'minLength' => 1, + ], + 'secrets' => [ + 'type' => 'array', + ], + ], + 'required' => ['title', 'description', 'feedType', 'secrets'], + ]; } } diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index 7c4e122f..9d094ff2 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -308,23 +308,19 @@ public function getSupportedFeedOutputType(): string /** * @throws \JsonException */ - public function getSchema(): mixed + public function getSchema(): array { - $jsonSchema = <<<'JSON' - { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "host": { - "type": "string", - "format": "url", - "pattern": "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-zA-Z0-9()]{2,6}\\b([-a-zA-Z0-9()@:%_\\+~#?&//=]*)" - } - }, - "required": ["host"] - } - JSON; - - return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); + return [ + "\$schema" => "http://json-schema.org/draft-04/schema#", + "type" => "object", + "properties" => [ + "host" => [ + "type" => "string", + "format" => "url", + "pattern" => "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-zA-Z0-9()]{2,6}\\b([-a-zA-Z0-9()@:%_\\+~#?&//=]*)" + ] + ], + "required" => ["host"] + ]; } } diff --git a/src/Feed/KobaFeedType.php b/src/Feed/KobaFeedType.php index 9e1259df..f0391554 100644 --- a/src/Feed/KobaFeedType.php +++ b/src/Feed/KobaFeedType.php @@ -258,15 +258,11 @@ private function getBookingsFromResource(string $host, string $apikey, string $r return $response->toArray(); } - public function getSchema(): mixed + public function getSchema(): array { - $jsonSchema = <<<'JSON' - { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object" - } - JSON; - - return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); + return [ + "\$schema" => "http://json-schema.org/draft-04/schema#", + "type" => "object" + ]; } } diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/NotifiedFeedType.php index 8ab642fb..1c50dc95 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/NotifiedFeedType.php @@ -248,21 +248,17 @@ private function wrapTags(string $input): string ]); } - public function getSchema(): mixed + public function getSchema(): array { - $jsonSchema = <<<'JSON' - { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "token": { - "type": "string" - } - }, - "required": ["token"] - } - JSON; - - return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); + return [ + "\$schema" => "http://json-schema.org/draft-04/schema#", + "type" => "object", + "properties" => [ + "token" => [ + "type" => "string" + ] + ], + "required" => ["token"] + ]; } } diff --git a/src/Feed/RssFeedType.php b/src/Feed/RssFeedType.php index 3ffce214..9e602627 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -137,15 +137,11 @@ public function getSupportedFeedOutputType(): string return self::SUPPORTED_FEED_TYPE; } - public function getSchema(): mixed + public function getSchema(): array { - $jsonSchema = <<<'JSON' - { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object" - } - JSON; - - return json_decode($jsonSchema, true, 512, JSON_THROW_ON_ERROR); + return [ + "\$schema" => "http://json-schema.org/draft-04/schema#", + "type" => "object" + ]; } } diff --git a/src/Feed/SparkleIOFeedType.php b/src/Feed/SparkleIOFeedType.php index ccaa02f1..430d1bf5 100644 --- a/src/Feed/SparkleIOFeedType.php +++ b/src/Feed/SparkleIOFeedType.php @@ -285,15 +285,11 @@ private function wrapTags(string $input): string return $text; } - public function getSchema(): mixed + public function getSchema(): array { - $jsonSchema = <<<'JSON' - { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object" - } - JSON; - - return json_decode($jsonSchema, false, 512, JSON_THROW_ON_ERROR); + return [ + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object', + ]; } } From 920a2677c9527a78c8b5b78dfcf63554ddb5c238 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 14 Nov 2024 10:55:01 +0100 Subject: [PATCH 45/52] modified constructor properties --- src/State/FeedSourceProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index c76d370d..02253842 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -27,7 +27,7 @@ class FeedSourceProcessor extends AbstractProcessor public function __construct( EntityManagerInterface $entityManager, ProcessorInterface $persistProcessor, - private readonly ProcessorInterface $removeProcessor, + ProcessorInterface $removeProcessor, private readonly FeedSourceRepository $feedSourceRepository, private readonly FeedService $feedService, private readonly Security $security, From 90f9757fe5d9b822d4cc4fb562d9ddc321446a6a Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 14 Nov 2024 14:12:46 +0100 Subject: [PATCH 46/52] Coding standards --- src/Entity/Tenant/FeedSource.php | 1 - src/Feed/EventDatabaseApiFeedType.php | 18 +++++++++--------- src/Feed/KobaFeedType.php | 4 ++-- src/Feed/NotifiedFeedType.php | 14 +++++++------- src/Feed/RssFeedType.php | 4 ++-- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Entity/Tenant/FeedSource.php b/src/Entity/Tenant/FeedSource.php index 910ee4df..2c456595 100644 --- a/src/Entity/Tenant/FeedSource.php +++ b/src/Entity/Tenant/FeedSource.php @@ -106,7 +106,6 @@ public function setSupportedFeedOutputType(string $supportedFeedOutputType): sel return $this; } - /** * Retrieves the JSON schema for validation. * diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index 9d094ff2..e2d5416c 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -311,16 +311,16 @@ public function getSupportedFeedOutputType(): string public function getSchema(): array { return [ - "\$schema" => "http://json-schema.org/draft-04/schema#", - "type" => "object", - "properties" => [ - "host" => [ - "type" => "string", - "format" => "url", - "pattern" => "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-zA-Z0-9()]{2,6}\\b([-a-zA-Z0-9()@:%_\\+~#?&//=]*)" - ] + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object', + 'properties' => [ + 'host' => [ + 'type' => 'string', + 'format' => 'url', + 'pattern' => 'https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-zA-Z0-9()]{2,6}\\b([-a-zA-Z0-9()@:%_\\+~#?&//=]*)', + ], ], - "required" => ["host"] + 'required' => ['host'], ]; } } diff --git a/src/Feed/KobaFeedType.php b/src/Feed/KobaFeedType.php index f0391554..1d37cf42 100644 --- a/src/Feed/KobaFeedType.php +++ b/src/Feed/KobaFeedType.php @@ -261,8 +261,8 @@ private function getBookingsFromResource(string $host, string $apikey, string $r public function getSchema(): array { return [ - "\$schema" => "http://json-schema.org/draft-04/schema#", - "type" => "object" + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object', ]; } } diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/NotifiedFeedType.php index 1c50dc95..5bf387df 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/NotifiedFeedType.php @@ -251,14 +251,14 @@ private function wrapTags(string $input): string public function getSchema(): array { return [ - "\$schema" => "http://json-schema.org/draft-04/schema#", - "type" => "object", - "properties" => [ - "token" => [ - "type" => "string" - ] + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object', + 'properties' => [ + 'token' => [ + 'type' => 'string', + ], ], - "required" => ["token"] + 'required' => ['token'], ]; } } diff --git a/src/Feed/RssFeedType.php b/src/Feed/RssFeedType.php index 9e602627..7d3de686 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -140,8 +140,8 @@ public function getSupportedFeedOutputType(): string public function getSchema(): array { return [ - "\$schema" => "http://json-schema.org/draft-04/schema#", - "type" => "object" + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object', ]; } } From 936bb021ddfd8b468847dab5bcb7feeab00d0518 Mon Sep 17 00:00:00 2001 From: Jeppe Krogh Date: Thu, 14 Nov 2024 14:22:26 +0100 Subject: [PATCH 47/52] Cast feed source schema as object for addSchema method --- src/State/FeedSourceProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 02253842..051a3be0 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -135,7 +135,7 @@ private function validateFeedSource(object $object, Operation $operation): void private function prepareValidator(): Validator { $schemaStorage = new SchemaStorage(); - $feedSourceValidationSchema = (new FeedSource())->getSchema(); + $feedSourceValidationSchema = (object) (new FeedSource())->getSchema(); $schemaStorage->addSchema('file://contentSchema', $feedSourceValidationSchema); return new Validator(new Factory($schemaStorage)); From fd6c5e2f67347d42e991319a425c0924e32d9bed Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:41:46 +0100 Subject: [PATCH 48/52] 2829: Adjustment --- src/Dto/FeedSource.php | 1 - src/Entity/Tenant/FeedSource.php | 2 +- src/Feed/EventDatabaseApiFeedType.php | 3 +-- src/State/FeedSourceProcessor.php | 32 +++++++++------------------ 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/Dto/FeedSource.php b/src/Dto/FeedSource.php index a738598d..8ce3da9e 100644 --- a/src/Dto/FeedSource.php +++ b/src/Dto/FeedSource.php @@ -18,7 +18,6 @@ class FeedSource public string $description = ''; public string $outputType = ''; public string $feedType = ''; - public array $secrets = []; public array $feeds = []; public array $admin = []; public string $supportedFeedOutputType = ''; diff --git a/src/Entity/Tenant/FeedSource.php b/src/Entity/Tenant/FeedSource.php index 2c456595..bdd096eb 100644 --- a/src/Entity/Tenant/FeedSource.php +++ b/src/Entity/Tenant/FeedSource.php @@ -111,7 +111,7 @@ public function setSupportedFeedOutputType(string $supportedFeedOutputType): sel * * @return array The JSON schema definition */ - public function getSchema(): array + public static function getSchema(): array { return [ '$schema' => 'https://json-schema.org/draft/2020-12/schema', diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index e2d5416c..ac52b29f 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -316,8 +316,7 @@ public function getSchema(): array 'properties' => [ 'host' => [ 'type' => 'string', - 'format' => 'url', - 'pattern' => 'https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-zA-Z0-9()]{2,6}\\b([-a-zA-Z0-9()@:%_\\+~#?&//=]*)', + 'format' => 'uri', ], ], 'required' => ['host'], diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 051a3be0..03f2d628 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -50,7 +50,6 @@ public function process($data, Operation $operation, array $uriVariables = [], a /** * @throws UnknownFeedTypeException - * @throws \JsonException */ protected function fromInput(mixed $object, Operation $operation, array $uriVariables, array $context): FeedSource { @@ -76,34 +75,28 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari return $feedSource; } + /** + * @throws UnknownFeedTypeException + */ protected function updateFeedSourceProperties(FeedSource $feedSource, FeedSourceInput $object): void { - if (!empty($object->title)) { + if (isset($object->title)) { $feedSource->setTitle($object->title); } - if (!empty($object->description)) { + if (isset($object->description)) { $feedSource->setDescription($object->description); } - if (!empty($object->createdBy)) { - $feedSource->setCreatedBy($object->createdBy); - } - if (!empty($object->modifiedBy)) { - $feedSource->setModifiedBy($object->modifiedBy); - } if (!empty($object->secrets)) { $feedSource->setSecrets($object->secrets); } - if (!empty($object->feedType)) { + if (isset($object->feedType)) { $feedSource->setFeedType($object->feedType); - } - $supportedFeedOutputType = $feedSource->getSupportedFeedOutputType(); - if (null !== $supportedFeedOutputType) { - $feedSource->setSupportedFeedOutputType($supportedFeedOutputType); + $feedType = $this->feedService->getFeedType($object->feedType); + $feedSource->setSupportedFeedOutputType($feedType->getSupportedFeedOutputType()); } } /** - * @throws \JsonException * @throws UnknownFeedTypeException */ private function validateFeedSource(object $object, Operation $operation): void @@ -111,7 +104,7 @@ private function validateFeedSource(object $object, Operation $operation): void $validator = $this->prepareValidator(); // Prepare base feed source validation schema - $feedSourceValidationSchema = (new FeedSource())->getSchema(); + $feedSourceValidationSchema = FeedSource::getSchema(); // Validate base feed source $this->executeValidation($object, $validator, $feedSourceValidationSchema); @@ -135,18 +128,15 @@ private function validateFeedSource(object $object, Operation $operation): void private function prepareValidator(): Validator { $schemaStorage = new SchemaStorage(); - $feedSourceValidationSchema = (object) (new FeedSource())->getSchema(); + $feedSourceValidationSchema = (object) FeedSource::getSchema(); $schemaStorage->addSchema('file://contentSchema', $feedSourceValidationSchema); return new Validator(new Factory($schemaStorage)); } - /** - * @throws \JsonException - */ private function executeValidation(mixed $object, Validator $validator, ?array $schema = null): void { - $validator->validate($object, $schema ?? (new FeedSource())->getSchema()); + $validator->validate($object, $schema ?? FeedSource::getSchema()); if (!$validator->isValid()) { throw new InvalidArgumentException($this->getErrorMessage($validator)); } From 6722994b4161f21dc54c08257a2fe1da3fd900b5 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:00:30 +0100 Subject: [PATCH 49/52] 2829: Applied coding standards --- public/api-spec-v2.json | 24 ------------------------ public/api-spec-v2.yaml | 16 ---------------- src/State/FeedSourceProcessor.php | 25 +++++++++++-------------- tests/Api/FeedSourceTest.php | 10 +++++----- 4 files changed, 16 insertions(+), 59 deletions(-) diff --git a/public/api-spec-v2.json b/public/api-spec-v2.json index 8f67e25d..08816da0 100644 --- a/public/api-spec-v2.json +++ b/public/api-spec-v2.json @@ -8144,12 +8144,6 @@ "feedType": { "type": "string" }, - "secrets": { - "type": "array", - "items": { - "type": "string" - } - }, "feeds": { "type": "array", "items": { @@ -8207,12 +8201,6 @@ "feedType": { "type": "string" }, - "secrets": { - "type": "array", - "items": { - "type": "string" - } - }, "feeds": { "type": "array", "items": { @@ -8305,12 +8293,6 @@ "feedType": { "type": "string" }, - "secrets": { - "type": "array", - "items": { - "type": "string" - } - }, "feeds": { "type": "array", "items": { @@ -8481,12 +8463,6 @@ "feedType": { "type": "string" }, - "secrets": { - "type": "array", - "items": { - "type": "string" - } - }, "feeds": { "type": "array", "items": { diff --git a/public/api-spec-v2.yaml b/public/api-spec-v2.yaml index 5eeaf4a5..98c1364d 100644 --- a/public/api-spec-v2.yaml +++ b/public/api-spec-v2.yaml @@ -5766,10 +5766,6 @@ components: type: string feedType: type: string - secrets: - type: array - items: - type: string feeds: type: array items: @@ -5810,10 +5806,6 @@ components: type: string feedType: type: string - secrets: - type: array - items: - type: string feeds: type: array items: @@ -5877,10 +5869,6 @@ components: type: string feedType: type: string - secrets: - type: array - items: - type: string feeds: type: array items: @@ -5997,10 +5985,6 @@ components: type: string feedType: type: string - secrets: - type: array - items: - type: string feeds: type: array items: diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 03f2d628..7147b9ae 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -60,6 +60,10 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari throw new InvalidArgumentException('object must by of type FeedSource'); } + // Validate feed source + $this->validateFeedSource($object, $operation); + + // Update properties. $this->updateFeedSourceProperties($feedSource, $object); // Set tenant @@ -69,9 +73,6 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari } $feedSource->setTenant($user->getActiveTenant()); - // Validate feed source - $this->validateFeedSource($object, $operation); - return $feedSource; } @@ -80,20 +81,16 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari */ protected function updateFeedSourceProperties(FeedSource $feedSource, FeedSourceInput $object): void { - if (isset($object->title)) { - $feedSource->setTitle($object->title); - } - if (isset($object->description)) { - $feedSource->setDescription($object->description); - } + $feedSource->setTitle($object->title); + $feedSource->setDescription($object->description); + if (!empty($object->secrets)) { $feedSource->setSecrets($object->secrets); } - if (isset($object->feedType)) { - $feedSource->setFeedType($object->feedType); - $feedType = $this->feedService->getFeedType($object->feedType); - $feedSource->setSupportedFeedOutputType($feedType->getSupportedFeedOutputType()); - } + + $feedSource->setFeedType($object->feedType); + $feedType = $this->feedService->getFeedType($object->feedType); + $feedSource->setSupportedFeedOutputType($feedType->getSupportedFeedOutputType()); } /** diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index f427db7c..f72e3261 100644 --- a/tests/Api/FeedSourceTest.php +++ b/tests/Api/FeedSourceTest.php @@ -60,7 +60,7 @@ public function testCreateFeedSource(): void 'json' => [ 'title' => 'Test feed source', 'description' => 'This is a test feed source', - 'feedType' => 'App\Feed\EventDatabaseApiFeedType', + 'feedType' => \App\Feed\EventDatabaseApiFeedType::class, 'secrets' => [ 'host' => 'https://www.test.dk', ], @@ -77,7 +77,7 @@ public function testCreateFeedSource(): void '@type' => 'FeedSource', 'title' => 'Test feed source', 'description' => 'This is a test feed source', - 'feedType' => 'App\Feed\EventDatabaseApiFeedType', + 'feedType' => \App\Feed\EventDatabaseApiFeedType::class, 'secrets' => [ 'host' => 'https://www.test.dk', ], @@ -146,7 +146,7 @@ public function testCreateFeedSourceWithEventDatabaseFeedTypeWithoutRequiredSecr 'json' => [ 'title' => 'Test feed source', 'outputType' => 'This is a test output type', - 'feedType' => 'App\\Feed\\EventDatabaseApiFeedType', + 'feedType' => \App\Feed\EventDatabaseApiFeedType::class, 'secrets' => [ 'test secret', ], @@ -171,7 +171,7 @@ public function testUpdateFeedSource(): void 'title' => 'Updated title', 'description' => 'Updated description', 'outputType' => 'This is a test output type', - 'feedType' => 'App\Feed\EventDatabaseApiFeedType', + 'feedType' => \App\Feed\EventDatabaseApiFeedType::class, 'secrets' => [ ], ], @@ -198,7 +198,7 @@ public function testDeleteFeedSource(): void 'title' => 'Test feed source', 'description' => 'This is a test feed source', 'outputType' => 'This is a test output type', - 'feedType' => 'App\Feed\EventDatabaseApiFeedType', + 'feedType' => \App\Feed\EventDatabaseApiFeedType::class, 'secrets' => [ 'host' => 'https://www.test.dk', ], From ab60308ad7af355be777a93b18b245df343a322a Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:11:51 +0100 Subject: [PATCH 50/52] 2829: Fixed psalm issue --- src/State/FeedSourceProcessor.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/State/FeedSourceProcessor.php b/src/State/FeedSourceProcessor.php index 7147b9ae..17bdaab4 100644 --- a/src/State/FeedSourceProcessor.php +++ b/src/State/FeedSourceProcessor.php @@ -9,7 +9,6 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Put; use ApiPlatform\State\ProcessorInterface; -use App\Dto\FeedSourceInput; use App\Entity\Interfaces\TenantScopedUserInterface; use App\Entity\Tenant\FeedSource; use App\Exceptions\UnknownFeedTypeException; @@ -79,7 +78,7 @@ protected function fromInput(mixed $object, Operation $operation, array $uriVari /** * @throws UnknownFeedTypeException */ - protected function updateFeedSourceProperties(FeedSource $feedSource, FeedSourceInput $object): void + protected function updateFeedSourceProperties(FeedSource $feedSource, object $object): void { $feedSource->setTitle($object->title); $feedSource->setDescription($object->description); From 4e75e5e46615193a42579a292ff5646f4af04aa5 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:34:12 +0100 Subject: [PATCH 51/52] 2829: Removed secrets from required fields --- src/Entity/Tenant/FeedSource.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Entity/Tenant/FeedSource.php b/src/Entity/Tenant/FeedSource.php index bdd096eb..e52ac27c 100644 --- a/src/Entity/Tenant/FeedSource.php +++ b/src/Entity/Tenant/FeedSource.php @@ -135,11 +135,8 @@ public static function getSchema(): array 'type' => 'string', 'minLength' => 1, ], - 'secrets' => [ - 'type' => 'array', - ], ], - 'required' => ['title', 'description', 'feedType', 'secrets'], + 'required' => ['title', 'description', 'feedType'], ]; } } From fea6a7c149cad483bca7833c61ec1af695e068f9 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:53:49 +0100 Subject: [PATCH 52/52] 2829: Changed return type of interface method --- src/Feed/EventDatabaseApiFeedType.php | 3 --- src/Feed/FeedTypeInterface.php | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index ac52b29f..741a202e 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -305,9 +305,6 @@ public function getSupportedFeedOutputType(): string return self::SUPPORTED_FEED_TYPE; } - /** - * @throws \JsonException - */ public function getSchema(): array { return [ diff --git a/src/Feed/FeedTypeInterface.php b/src/Feed/FeedTypeInterface.php index 93188eab..e551deb5 100644 --- a/src/Feed/FeedTypeInterface.php +++ b/src/Feed/FeedTypeInterface.php @@ -64,7 +64,7 @@ public function getSupportedFeedOutputType(): string; /** * Get validation scheme for feed type. * - * @return mixed + * @return array */ - public function getSchema(): mixed; + public function getSchema(): array; }