From 8bd989e30e371881c14a1e1b821a50a3b334b1d1 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 26 Nov 2024 10:11:45 +0100 Subject: [PATCH 01/15] 2533: Refactored namespacing for feed types and models --- config/services.yaml | 2 +- docs/{ => feed}/calender-api-feed.md | 0 ...edFeedOutputs.php => FeedOutputModels.php} | 2 +- .../OutputModel/Calendar/Location.php} | 4 +- .../OutputModel/Calendar/Resource.php} | 4 +- src/Feed/OutputModel/Rss/Readme.md | 7 + .../Calendar}/CalendarApiFeedType.php | 31 +-- src/Feed/SourceType/Colibo/ApiClient.php | 248 ++++++++++++++++++ .../EventDatabaseApiFeedType.php | 6 +- .../{ => SourceType/Koba}/KobaFeedType.php | 6 +- .../Notified}/NotifiedFeedType.php | 6 +- src/Feed/{ => SourceType/Rss}/RssFeedType.php | 6 +- .../SparkleIO}/SparkleIOFeedType.php | 6 +- src/Model/CalendarEvent.php | 17 -- tests/Api/FeedSourceTest.php | 10 +- tests/Feed/NotifiedFeedTypeTest.php | 2 +- tests/Service/FeedServiceTest.php | 12 +- 17 files changed, 309 insertions(+), 60 deletions(-) rename docs/{ => feed}/calender-api-feed.md (100%) rename src/Feed/{SupportedFeedOutputs.php => FeedOutputModels.php} (98%) rename src/{Model/CalendarLocation.php => Feed/OutputModel/Calendar/Location.php} (71%) rename src/{Model/CalendarResource.php => Feed/OutputModel/Calendar/Resource.php} (76%) create mode 100644 src/Feed/OutputModel/Rss/Readme.md rename src/Feed/{ => SourceType/Calendar}/CalendarApiFeedType.php (95%) create mode 100644 src/Feed/SourceType/Colibo/ApiClient.php rename src/Feed/{ => SourceType/EventDatabase}/EventDatabaseApiFeedType.php (98%) rename src/Feed/{ => SourceType/Koba}/KobaFeedType.php (98%) rename src/Feed/{ => SourceType/Notified}/NotifiedFeedType.php (97%) rename src/Feed/{ => SourceType/Rss}/RssFeedType.php (95%) rename src/Feed/{ => SourceType/SparkleIO}/SparkleIOFeedType.php (98%) delete mode 100644 src/Model/CalendarEvent.php diff --git a/config/services.yaml b/config/services.yaml index 6f1a0b8c7..2cf4cf64e 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -51,7 +51,7 @@ services: Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler' Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler' - App\Feed\CalendarApiFeedType: + App\Feed\SourceType\Calendar\CalendarApiFeedType: arguments: $locationEndpoint: '%env(string:CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT)%' $resourceEndpoint: '%env(string:CALENDAR_API_FEED_SOURCE_RESOURCE_ENDPOINT)%' diff --git a/docs/calender-api-feed.md b/docs/feed/calender-api-feed.md similarity index 100% rename from docs/calender-api-feed.md rename to docs/feed/calender-api-feed.md diff --git a/src/Feed/SupportedFeedOutputs.php b/src/Feed/FeedOutputModels.php similarity index 98% rename from src/Feed/SupportedFeedOutputs.php rename to src/Feed/FeedOutputModels.php index d080b97a5..0458c7231 100644 --- a/src/Feed/SupportedFeedOutputs.php +++ b/src/Feed/FeedOutputModels.php @@ -4,7 +4,7 @@ namespace App\Feed; -class SupportedFeedOutputs +class FeedOutputModels { /** * Data example: diff --git a/src/Model/CalendarLocation.php b/src/Feed/OutputModel/Calendar/Location.php similarity index 71% rename from src/Model/CalendarLocation.php rename to src/Feed/OutputModel/Calendar/Location.php index ddc08f811..057d7e868 100644 --- a/src/Model/CalendarLocation.php +++ b/src/Feed/OutputModel/Calendar/Location.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Model; +namespace App\Feed\OutputModel\Calendar; -class CalendarLocation +class Location { public function __construct( public string $id, diff --git a/src/Model/CalendarResource.php b/src/Feed/OutputModel/Calendar/Resource.php similarity index 76% rename from src/Model/CalendarResource.php rename to src/Feed/OutputModel/Calendar/Resource.php index 48cb9b163..740f454aa 100644 --- a/src/Model/CalendarResource.php +++ b/src/Feed/OutputModel/Calendar/Resource.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace App\Model; +namespace App\Feed\OutputModel\Calendar; -class CalendarResource +class Resource { public function __construct( public string $id, diff --git a/src/Feed/OutputModel/Rss/Readme.md b/src/Feed/OutputModel/Rss/Readme.md new file mode 100644 index 000000000..eb23bf1e0 --- /dev/null +++ b/src/Feed/OutputModel/Rss/Readme.md @@ -0,0 +1,7 @@ +# RRS / FeedIO + +The output model for RSS is defined as + +`FeedIo\Reader\Result` and `FeedIo\Feed\ItemInterface` + +@see https://alexdebril.github.io/feed-io/ diff --git a/src/Feed/CalendarApiFeedType.php b/src/Feed/SourceType/Calendar/CalendarApiFeedType.php similarity index 95% rename from src/Feed/CalendarApiFeedType.php rename to src/Feed/SourceType/Calendar/CalendarApiFeedType.php index 37210cf59..b3ebd57c7 100644 --- a/src/Feed/CalendarApiFeedType.php +++ b/src/Feed/SourceType/Calendar/CalendarApiFeedType.php @@ -2,15 +2,16 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\Calendar; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Model\CalendarEvent; -use App\Model\CalendarLocation; -use App\Model\CalendarResource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; +use App\Feed\OutputModel\Calendar\Event; +use App\Feed\OutputModel\Calendar\Location; +use App\Feed\OutputModel\Calendar\Resource; use App\Service\FeedService; -use Faker\Core\DateTime; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -29,7 +30,7 @@ */ class CalendarApiFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = SupportedFeedOutputs::CALENDAR_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::CALENDAR_OUTPUT; final public const string EXCLUDE_IF_TITLE_NOT_CONTAINS = 'EXCLUDE_IF_TITLE_NOT_CONTAINS'; final public const string REPLACE_TITLE_IF_CONTAINS = 'REPLACE_TITLE_IF_CONTAINS'; @@ -79,7 +80,7 @@ public function getData(Feed $feed): array foreach ($resources as $resource) { $events = $this->getResourceEvents($resource); - /** @var CalendarEvent $event */ + /** @var Event $event */ foreach ($events as $event) { $title = $event->title; @@ -204,7 +205,7 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin $resources = array_merge($resources, $locationResources); } - $resourceOptions = array_map(fn (CalendarResource $resource) => [ + $resourceOptions = array_map(fn (Resource $resource) => [ 'id' => Ulid::generate(), 'title' => $resource->displayName, 'value' => $resource->id, @@ -215,7 +216,7 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin return $resourceOptions; } elseif ('locations' === $name) { - $locationOptions = array_map(fn (CalendarLocation $location) => [ + $locationOptions = array_map(fn (Location $location) => [ 'id' => Ulid::generate(), 'title' => $location->displayName, 'value' => $location->id, @@ -260,7 +261,7 @@ private function getLocationOptions(): array { $locations = $this->loadLocations(); - return array_reduce($locations, function (array $carry, CalendarLocation $location) { + return array_reduce($locations, function (array $carry, Location $location) { $carry[] = $location->id; return $carry; @@ -274,7 +275,7 @@ private function getResourceEvents(string $resourceId): array if (!$cacheItem->isHit()) { $allEvents = $this->loadEvents(); - $items = array_filter($allEvents, fn (CalendarEvent $item) => $item->resourceId === $resourceId); + $items = array_filter($allEvents, fn (Event $item) => $item->resourceId === $resourceId); $cacheItem->set($items); $cacheItem->expiresAfter($this->cacheExpireSeconds); @@ -291,7 +292,7 @@ private function getLocationResources(string $locationId): array if (!$cacheItem->isHit()) { $allResources = $this->loadResources(); - $items = array_filter($allResources, fn (CalendarResource $item) => $item->locationId === $locationId); + $items = array_filter($allResources, fn (Resource $item) => $item->locationId === $locationId); $cacheItem->set($items); $cacheItem->expiresAfter($this->cacheExpireSeconds); @@ -311,7 +312,7 @@ private function loadLocations(): array $LocationEntries = $response->toArray(); - $locations = array_map(fn (array $entry) => new CalendarLocation( + $locations = array_map(fn (array $entry) => new Location( $entry[$this->getMapping('locationId')], $entry[$this->getMapping('locationDisplayName')], ), $LocationEntries); @@ -346,7 +347,7 @@ private function loadResources(): array // Only include resources that are included in events endpoint. if ($includeValue) { - $resource = new CalendarResource( + $resource = new Resource( $resourceEntry[$this->getMapping('resourceId')], $resourceEntry[$this->getMapping('resourceLocationId')], $resourceEntry[$this->getMapping('resourceDisplayName')], @@ -377,7 +378,7 @@ private function loadEvents(): array $eventEntries = $response->toArray(); $events = array_reduce($eventEntries, function (array $carry, array $entry) { - $newEntry = new CalendarEvent( + $newEntry = new Event( Ulid::generate(), $entry[$this->getMapping('eventTitle')], $this->stringToUnixTimestamp($entry[$this->getMapping('eventStartTime')]), diff --git a/src/Feed/SourceType/Colibo/ApiClient.php b/src/Feed/SourceType/Colibo/ApiClient.php new file mode 100644 index 000000000..23f513147 --- /dev/null +++ b/src/Feed/SourceType/Colibo/ApiClient.php @@ -0,0 +1,248 @@ + */ + private array $apiClients = []; + + public function __construct( + private readonly CacheInterface $feedsCache, + private readonly LoggerInterface $logger, + ) {} + + public function getFeedEntriesNews(FeedSource $feedSource, array $recipients, array $publishers): mixed + { + $results = []; + + try { + $client = $this->getApiClient($feedSource); + + $response = $client->request('GET', '/api/feedentries/news', [ + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'query' => [ + 'getQuery.recipients' => $recipients, + ], + ]); + + $results = json_decode($response->getContent(), false, 512, JSON_THROW_ON_ERROR); + } catch (\Throwable $throwable) { + $this->logger->error('{code}: {message}', [ + 'code' => $throwable->getCode(), + 'message' => $throwable->getMessage(), + ]); + + throw $throwable; + } + + return $results; + } + + public function getSearchGroups(FeedSource $feedSource, string $type = 'WorkGroup'): array + { + $responseData = $this->getSearchGroupsPage($feedSource, $type)->toArray(); + + $groups = $responseData['results']; + + $total = $responseData['total']; + $pages = (int) ceil($total / self::BATCH_SIZE); + + /** @var ResponseInterface[] $responses */ + $responses = []; + for ($page = 1; $page < $pages; ++$page) { + $responses[] = $this->getSearchGroupsPage($feedSource, $type, $page); + } + + foreach ($responses as $response) { + $responseData = $response->toArray(); + $groups = array_merge($groups, $responseData['results']); + } + + return $groups; + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + */ + public function getFeedEntryPublishersGroups(FeedSource $feedSource): array + { + $client = $this->getApiClient($feedSource); + + $response = $client->request('GET', '/api/feedentries/publishers/groups', [ + 'query' => ['groupType' => 'Department'], + ]); + + $groups = []; + $childGroupIds = []; + foreach ($response->toArray() as $group) { + $groups[] = $group; + + if (isset($group['hasChildren']) && $group['hasChildren']) { + $childGroupIds[] = $group['id']; + } + } + + $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); + + return $groups; + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + private function getSearchGroupsPage(FeedSource $feedSource, string $type, int $pageIndex = 0, int $pageSize = self::BATCH_SIZE): ResponseInterface + { + $client = $this->getApiClient($feedSource); + + return $client->request('GET', '/api/search/groups', [ + 'query' => [ + 'groupSearchQuery.groupTypes' => $type, + 'groupSearchQuery.pageIndex' => $pageIndex, + 'groupSearchQuery.pageSize' => $pageSize, + ], + ]); + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + */ + private function getFeedEntryPublishersGroupsChildren(FeedSource $feedSource, array $childGroupIds, array &$groups): void + { + $client = $this->getApiClient($feedSource); + + $batches = array_chunk($childGroupIds, self::BATCH_SIZE); + + foreach ($batches as $batch) { + // @see https://symfony.com/doc/current/http_client.html#concurrent-requests + $responses = []; + foreach ($batch as $childGroupId) { + $uri = sprintf('/api/feedentries/publishers/groups/%d/children', $childGroupId); + $responses[] = $client->request('GET', $uri, []); + } + + $childGroupIds = []; + foreach ($responses as $response) { + foreach ($response->toArray() as $group) { + $groups[] = $group; + + if (isset($group['hasChildren']) && $group['hasChildren']) { + $childGroupIds[] = $group['id']; + } + } + } + } + + if (!empty($childGroupIds)) { + $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); + } + } + + private function getApiClient(FeedSource $feedSource): HttpClientInterface + { + $id = ColiboFeedType::getIdKey($feedSource); + + if (array_key_exists($id, $this->apiClients)) { + return $this->apiClients[$id]; + } + + $secrets = new SecretsDTO($feedSource); + $this->apiClients[$id] = HttpClient::createForBaseUri($secrets->apiBaseUri)->withOptions([ + 'headers' => [ + 'Authorization' => 'Bearer '.$this->fetchColiboToken($feedSource), + 'Accept' => 'application/json', + ], + ]); + + return $this->apiClients[$id]; + } + + /** + * @throws TransportExceptionInterface + * @throws \Throwable + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + * @throws \JsonException + */ + private function fetchColiboToken(FeedSource $feedSource): string + { + $id = ColiboFeedType::getIdKey($feedSource); + + /** @var CacheItemInterface $cacheItem */ + $cacheItem = $this->feedsCache->getItem('colibo_token_'.$id); + + if (false && $cacheItem->isHit()) { + /** @var string $token */ + $token = $cacheItem->get(); + } else { + try { + $secrets = new SecretsDTO($feedSource); + $client = HttpClient::createForBaseUri($secrets->apiBaseUri); + + $response = $client->request('POST', '/auth/oauth2/connect/token', [ + 'headers' => [ + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + 'body' => [ + 'grant_type' => self::GRANT_TYPE, + 'scope' => self::SCOPE, + 'client_id' => $secrets->clientId, + 'client_secret' => $secrets->clientSecret, + ], + ]); + + $content = $response->getContent(); + $contentDecoded = json_decode($content, false, 512, JSON_THROW_ON_ERROR); + + $token = $contentDecoded->access_token; + + // Expire cache 5 min before token expire + $expireSeconds = intval($contentDecoded->expires_in - 300); + + $cacheItem->set($token); + $cacheItem->expiresAfter($expireSeconds); + $this->feedsCache->save($cacheItem); + } catch (\Throwable $throwable) { + $this->logger->error('{code}: {message}', [ + 'code' => $throwable->getCode(), + 'message' => $throwable->getMessage(), + ]); + + throw $throwable; + } + } + + return $token; + } +} \ No newline at end of file diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php similarity index 98% rename from src/Feed/EventDatabaseApiFeedType.php rename to src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php index fb660ef7c..358548b96 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\EventDatabase; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedTypeInterface; +use App\Feed\FeedOutputModels; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -19,7 +21,7 @@ */ class EventDatabaseApiFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = SupportedFeedOutputs::POSTER_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::POSTER_OUTPUT; final public const int REQUEST_TIMEOUT = 10; public function __construct( diff --git a/src/Feed/KobaFeedType.php b/src/Feed/SourceType/Koba/KobaFeedType.php similarity index 98% rename from src/Feed/KobaFeedType.php rename to src/Feed/SourceType/Koba/KobaFeedType.php index 2bf319b6b..37091092a 100644 --- a/src/Feed/KobaFeedType.php +++ b/src/Feed/SourceType/Koba/KobaFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\Koba; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedTypeInterface; +use App\Feed\FeedOutputModels; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -15,7 +17,7 @@ /** @deprecated */ class KobaFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = SupportedFeedOutputs::CALENDAR_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::CALENDAR_OUTPUT; public function __construct( private readonly FeedService $feedService, diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/SourceType/Notified/NotifiedFeedType.php similarity index 97% rename from src/Feed/NotifiedFeedType.php rename to src/Feed/SourceType/Notified/NotifiedFeedType.php index 75ac8b9c1..5995920e8 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/SourceType/Notified/NotifiedFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\Notified; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedTypeInterface; +use App\Feed\FeedOutputModels; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -17,7 +19,7 @@ */ class NotifiedFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = SupportedFeedOutputs::INSTAGRAM_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::INSTAGRAM_OUTPUT; final public const int REQUEST_TIMEOUT = 10; private const string BASE_URL = 'https://api.listen.notified.com'; diff --git a/src/Feed/RssFeedType.php b/src/Feed/SourceType/Rss/RssFeedType.php similarity index 95% rename from src/Feed/RssFeedType.php rename to src/Feed/SourceType/Rss/RssFeedType.php index 4e395da7d..078f3127b 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/SourceType/Rss/RssFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\Rss; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use FeedIo\Adapter\Http\Client; use FeedIo\Feed\Item; use FeedIo\FeedIo; @@ -15,7 +17,7 @@ class RssFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = SupportedFeedOutputs::RSS_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::RSS_OUTPUT; private readonly FeedIo $feedIo; diff --git a/src/Feed/SparkleIOFeedType.php b/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php similarity index 98% rename from src/Feed/SparkleIOFeedType.php rename to src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php index 64daed7bf..692b5947b 100644 --- a/src/Feed/SparkleIOFeedType.php +++ b/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\SparkleIO; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedTypeInterface; +use App\Feed\FeedOutputModels; use App\Service\FeedService; use Psr\Cache\CacheItemInterface; use Psr\Cache\InvalidArgumentException; @@ -22,7 +24,7 @@ /** @deprecated The SparkleIO service is discontinued. */ class SparkleIOFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = SupportedFeedOutputs::INSTAGRAM_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::INSTAGRAM_OUTPUT; final public const int REQUEST_TIMEOUT = 10; diff --git a/src/Model/CalendarEvent.php b/src/Model/CalendarEvent.php deleted file mode 100644 index d8aeea9c3..000000000 --- a/src/Model/CalendarEvent.php +++ /dev/null @@ -1,17 +0,0 @@ - [ 'title' => 'Test feed source', 'description' => 'This is a test feed source', - 'feedType' => \App\Feed\EventDatabaseApiFeedType::class, + 'feedType' => \App\Feed\SourceType\EventDatabase\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::class, + 'feedType' => \App\Feed\SourceType\EventDatabase\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::class, + 'feedType' => \App\Feed\SourceType\EventDatabase\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::class, + 'feedType' => \App\Feed\SourceType\EventDatabase\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::class, + 'feedType' => \App\Feed\SourceType\EventDatabase\EventDatabaseApiFeedType::class, 'secrets' => [ 'host' => 'https://www.test.dk', ], diff --git a/tests/Feed/NotifiedFeedTypeTest.php b/tests/Feed/NotifiedFeedTypeTest.php index 5a7ef76c5..ec8203bb3 100644 --- a/tests/Feed/NotifiedFeedTypeTest.php +++ b/tests/Feed/NotifiedFeedTypeTest.php @@ -4,7 +4,7 @@ namespace App\Tests\Feed; -use App\Feed\NotifiedFeedType; +use App\Feed\SourceType\Notified\NotifiedFeedType; use App\Repository\FeedSourceRepository; use App\Repository\SlideRepository; use App\Service\FeedService; diff --git a/tests/Service/FeedServiceTest.php b/tests/Service/FeedServiceTest.php index 9cfd24b27..c28c9ca2b 100644 --- a/tests/Service/FeedServiceTest.php +++ b/tests/Service/FeedServiceTest.php @@ -6,13 +6,13 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\CalendarApiFeedType; -use App\Feed\EventDatabaseApiFeedType; use App\Feed\FeedTypeInterface; -use App\Feed\KobaFeedType; -use App\Feed\NotifiedFeedType; -use App\Feed\RssFeedType; -use App\Feed\SparkleIOFeedType; +use App\Feed\SourceType\Calendar\CalendarApiFeedType; +use App\Feed\SourceType\EventDatabase\EventDatabaseApiFeedType; +use App\Feed\SourceType\Koba\KobaFeedType; +use App\Feed\SourceType\Notified\NotifiedFeedType; +use App\Feed\Type\Rss\RssFeedType; +use App\Feed\SourceType\SparkleIO\SparkleIOFeedType; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; From aed4bfc300a0f5dd95ed48a3f375b55f2fa456aa Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 5 Dec 2024 10:33:47 +0100 Subject: [PATCH 02/15] 2533: Add Colibo Feed --- composer.json | 1 + composer.lock | 270 +++++++++--------- config/packages/nelmio_cors.yaml | 2 +- docs/feed/feed-overview.md | 33 +++ migrations/Version20241125085559.php | 36 +++ src/Feed/FeedException.php | 8 + src/Feed/OutputModel/Calendar/Event.php | 17 ++ src/Feed/SourceType/Colibo/ApiClient.php | 254 ++++++++++------ .../SourceType/Colibo/ColiboException.php | 10 + src/Feed/SourceType/Colibo/ColiboFeedType.php | 201 +++++++++++++ src/Feed/SourceType/Colibo/SecretsDTO.php | 35 +++ src/Service/FeedService.php | 2 +- 12 files changed, 642 insertions(+), 227 deletions(-) create mode 100644 docs/feed/feed-overview.md create mode 100644 migrations/Version20241125085559.php create mode 100644 src/Feed/FeedException.php create mode 100644 src/Feed/OutputModel/Calendar/Event.php create mode 100644 src/Feed/SourceType/Colibo/ColiboException.php create mode 100644 src/Feed/SourceType/Colibo/ColiboFeedType.php create mode 100644 src/Feed/SourceType/Colibo/SecretsDTO.php diff --git a/composer.json b/composer.json index f2425bda7..e4ff34207 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "rlanvin/php-rrule": "^2.2", "symfony/asset": "~6.4.0", "symfony/console": "~6.4.0", + "symfony/dom-crawler": "~6.4.0", "symfony/dotenv": "~6.4.0", "symfony/expression-language": "~6.4.0", "symfony/flex": "^2.0", diff --git a/composer.lock b/composer.lock index fa7f11b5c..ac9344e10 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bed3fa646c40854e6982154f552fcae6", + "content-hash": "67233a09d452101515a4003cb8eee218", "packages": [ { "name": "api-platform/core", @@ -3611,6 +3611,73 @@ }, "time": "2024-09-04T12:55:26+00:00" }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, { "name": "monolog/monolog", "version": "3.7.0", @@ -6316,6 +6383,73 @@ ], "time": "2024-09-08T12:31:10+00:00" }, + { + "name": "symfony/dom-crawler", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "ae074dffb018c37a57071990d16e6152728dd972" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ae074dffb018c37a57071990d16e6152728dd972", + "reference": "ae074dffb018c37a57071990d16e6152728dd972", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:07:50+00:00" + }, { "name": "symfony/dotenv", "version": "v6.4.12", @@ -11666,73 +11800,6 @@ ], "time": "2020-07-06T04:49:32+00:00" }, - { - "name": "masterminds/html5", - "version": "2.9.0", - "source": { - "type": "git", - "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Masterminds\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matt Butcher", - "email": "technosophos@gmail.com" - }, - { - "name": "Matt Farina", - "email": "matt@mattfarina.com" - }, - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - } - ], - "description": "An HTML5 parser and serializer.", - "homepage": "http://masterminds.github.io/html5-php", - "keywords": [ - "HTML5", - "dom", - "html", - "parser", - "querypath", - "serializer", - "xml" - ], - "support": { - "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" - }, - "time": "2024-03-31T07:05:07+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.12.0", @@ -13889,73 +13956,6 @@ ], "time": "2024-05-31T14:49:08+00:00" }, - { - "name": "symfony/dom-crawler", - "version": "v6.4.12", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "9d307ecbcb917001692be333cdc58f474fdb37f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/9d307ecbcb917001692be333cdc58f474fdb37f0", - "reference": "9d307ecbcb917001692be333cdc58f474fdb37f0", - "shasum": "" - }, - "require": { - "masterminds/html5": "^2.6", - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "symfony/css-selector": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases DOM navigation for HTML and XML documents", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.12" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-15T06:35:36+00:00" - }, { "name": "symfony/maker-bundle", "version": "v1.61.0", diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml index f815f4e31..7b39d06d1 100644 --- a/config/packages/nelmio_cors.yaml +++ b/config/packages/nelmio_cors.yaml @@ -1,7 +1,7 @@ nelmio_cors: defaults: origin_regex: true - allow_credentials: false + allow_credentials: true allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] allow_headers: ['Content-Type', 'Authorization', 'Authorization-Tenant-Key'] diff --git a/docs/feed/feed-overview.md b/docs/feed/feed-overview.md new file mode 100644 index 000000000..d0f0322dc --- /dev/null +++ b/docs/feed/feed-overview.md @@ -0,0 +1,33 @@ +# Feed Overview + +"Feeds" in OS2display are external data sources that can provide up-to-data to slides. The idea is that if you can set +up slide based on a feed and publish it. The Screen Client will then fetch new data from the feed whenever the Slide is +shown on screen. + +The simplest example is a classic RSS news feed. You can set up a slide based on the RSS slide template, configure the +RSS source URL, and whenever the slide is on screen it will show the latest entries from the RSS feed. + +This means that administrators can set up slides and playlists that stays up to date automatically. + +## Architecture + +The "Feed" architecture is designed to enable both generic and custom feed types. To enable this all feed based screen +templates are designed to support a given "feed output model". These are normalized data sets from a given feed type. + +Each feed implementation defines which output model it supports. Thereby multiple feed implementations can support the +same output model. This is done to enable decoupling of the screen templates from the feed implementation. + +For example: + +* If you have a news source that is not a RSS feed you can implement a "FeedSource" that fetches data from your source + then normalizes the data and outputs it as the RSS output model. When setting up RSS slides this feed source can then + be selected as the source for the slide. +* OS2display has calendar templates that can show bookings or meetings. To show data from your specific calendar or + booking system you can implement a "FeedSource" that fetches booking data from your source and normalizes it to match + the calendar output model. + +@todo + +Slide -> Feed -> FeedSource +Auth +Caching \ No newline at end of file diff --git a/migrations/Version20241125085559.php b/migrations/Version20241125085559.php new file mode 100644 index 000000000..3affb9670 --- /dev/null +++ b/migrations/Version20241125085559.php @@ -0,0 +1,36 @@ +addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Calendar\\CalendarApiFeedType\' WHERE feed_type = \'App\\Feed\\CalendarApiFeedType\''); + $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\EventDatabase\\EventDatabaseApiFeedType\' WHERE feed_type = \'App\\Feed\\EventDatabaseApiFeedType\''); + $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Koba\\KobaFeedType\' WHERE feed_type = \'App\\Feed\\KobaFeedType\''); + $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Notified\\NotifiedFeedType\' WHERE feed_type = \'App\\Feed\\NotifiedFeedType\''); + $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Rss\\RssFeedType\' WHERE feed_type = \'App\\Feed\\RssFeedType\''); + $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\SparkleIO\\SparkleIOFeedType\' WHERE feed_type = \'App\\Feed\\SparkleIOFeedType\''); + + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + + } +} diff --git a/src/Feed/FeedException.php b/src/Feed/FeedException.php new file mode 100644 index 000000000..448794a18 --- /dev/null +++ b/src/Feed/FeedException.php @@ -0,0 +1,8 @@ +getApiClient($feedSource); @@ -45,129 +51,196 @@ public function getFeedEntriesNews(FeedSource $feedSource, array $recipients, ar ], ]); - $results = json_decode($response->getContent(), false, 512, JSON_THROW_ON_ERROR); + return json_decode($response->getContent(), false, 512, JSON_THROW_ON_ERROR); + } catch (ColiboException $exception) { + return []; } catch (\Throwable $throwable) { $this->logger->error('{code}: {message}', [ 'code' => $throwable->getCode(), 'message' => $throwable->getMessage(), ]); - throw $throwable; + return []; } - - return $results; } + /** + * Retrieve search groups based on the given feed source and type. + * + * @param FeedSource $feedSource + * @param string $type + * + * @return array + */ public function getSearchGroups(FeedSource $feedSource, string $type = 'WorkGroup'): array { - $responseData = $this->getSearchGroupsPage($feedSource, $type)->toArray(); + try { + $responseData = $this->getSearchGroupsPage($feedSource, $type)->toArray(); - $groups = $responseData['results']; + $groups = $responseData['results']; - $total = $responseData['total']; - $pages = (int) ceil($total / self::BATCH_SIZE); + $total = $responseData['total']; + $pages = (int)ceil($total / self::BATCH_SIZE); - /** @var ResponseInterface[] $responses */ - $responses = []; - for ($page = 1; $page < $pages; ++$page) { - $responses[] = $this->getSearchGroupsPage($feedSource, $type, $page); - } + /** @var ResponseInterface[] $responses */ + $responses = []; + for ($page = 1; $page < $pages; ++$page) { + $responses[] = $this->getSearchGroupsPage($feedSource, $type, $page); + } - foreach ($responses as $response) { - $responseData = $response->toArray(); - $groups = array_merge($groups, $responseData['results']); - } + foreach ($responses as $response) { + $responseData = $response->toArray(); + $groups = array_merge($groups, $responseData['results']); + } + + return $groups; + } catch (ColiboException $exception) { + return []; + } catch (\Throwable $throwable) { + $this->logger->error('{code}: {message}', [ + 'code' => $throwable->getCode(), + 'message' => $throwable->getMessage(), + ]); - return $groups; + return []; + } } /** - * @throws TransportExceptionInterface - * @throws ServerExceptionInterface - * @throws RedirectionExceptionInterface - * @throws DecodingExceptionInterface - * @throws ClientExceptionInterface + * @param FeedSource $feedSource + * + * @return array */ public function getFeedEntryPublishersGroups(FeedSource $feedSource): array { - $client = $this->getApiClient($feedSource); + try { + $client = $this->getApiClient($feedSource); - $response = $client->request('GET', '/api/feedentries/publishers/groups', [ - 'query' => ['groupType' => 'Department'], - ]); + $response = $client->request('GET', '/api/feedentries/publishers/groups', [ + 'query' => ['groupType' => 'Department'], + ]); - $groups = []; - $childGroupIds = []; - foreach ($response->toArray() as $group) { - $groups[] = $group; + $groups = []; + $childGroupIds = []; + foreach ($response->toArray() as $group) { + $groups[] = $group; - if (isset($group['hasChildren']) && $group['hasChildren']) { - $childGroupIds[] = $group['id']; + if (isset($group['hasChildren']) && $group['hasChildren']) { + $childGroupIds[] = $group['id']; + } } - } - $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); + $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); + + return $groups; + } catch (ColiboException $exception) { + return []; + } catch (\Throwable $throwable) { + $this->logger->error('{code}: {message}', [ + 'code' => $throwable->getCode(), + 'message' => $throwable->getMessage(), + ]); - return $groups; + return []; + } } /** - * @throws TransportExceptionInterface - * @throws ServerExceptionInterface - * @throws RedirectionExceptionInterface - * @throws ClientExceptionInterface + * @param FeedSource $feedSource + * @param string $type + * @param int $pageIndex + * @param int $pageSize + * + * @return ResponseInterface + * + * @throws ColiboException */ private function getSearchGroupsPage(FeedSource $feedSource, string $type, int $pageIndex = 0, int $pageSize = self::BATCH_SIZE): ResponseInterface { - $client = $this->getApiClient($feedSource); + try { + $client = $this->getApiClient($feedSource); - return $client->request('GET', '/api/search/groups', [ - 'query' => [ - 'groupSearchQuery.groupTypes' => $type, - 'groupSearchQuery.pageIndex' => $pageIndex, - 'groupSearchQuery.pageSize' => $pageSize, - ], - ]); + return $client->request('GET', '/api/search/groups', [ + 'query' => [ + 'groupSearchQuery.groupTypes' => $type, + 'groupSearchQuery.pageIndex' => $pageIndex, + 'groupSearchQuery.pageSize' => $pageSize, + ], + ]); + } catch (ColiboException $exception) { + throw $exception; + } catch (\Throwable $throwable) { + $this->logger->error('{code}: {message}', [ + 'code' => $throwable->getCode(), + 'message' => $throwable->getMessage(), + ]); + + throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); + } } /** - * @throws TransportExceptionInterface - * @throws ServerExceptionInterface - * @throws RedirectionExceptionInterface - * @throws DecodingExceptionInterface - * @throws ClientExceptionInterface + * Get + * + * @param FeedSource $feedSource + * @param array $childGroupIds + * @param array $groups + * + * @return void + * + * @throws ColiboException */ private function getFeedEntryPublishersGroupsChildren(FeedSource $feedSource, array $childGroupIds, array &$groups): void { - $client = $this->getApiClient($feedSource); + try { + $client = $this->getApiClient($feedSource); - $batches = array_chunk($childGroupIds, self::BATCH_SIZE); + $batches = array_chunk($childGroupIds, self::BATCH_SIZE); - foreach ($batches as $batch) { - // @see https://symfony.com/doc/current/http_client.html#concurrent-requests - $responses = []; - foreach ($batch as $childGroupId) { - $uri = sprintf('/api/feedentries/publishers/groups/%d/children', $childGroupId); - $responses[] = $client->request('GET', $uri, []); - } + foreach ($batches as $batch) { + // @see https://symfony.com/doc/current/http_client.html#concurrent-requests + $responses = []; + foreach ($batch as $childGroupId) { + $uri = sprintf('/api/feedentries/publishers/groups/%d/children', $childGroupId); + $responses[] = $client->request('GET', $uri, []); + } - $childGroupIds = []; - foreach ($responses as $response) { - foreach ($response->toArray() as $group) { - $groups[] = $group; + $childGroupIds = []; + foreach ($responses as $response) { + foreach ($response->toArray() as $group) { + $groups[] = $group; - if (isset($group['hasChildren']) && $group['hasChildren']) { - $childGroupIds[] = $group['id']; + if (isset($group['hasChildren']) && $group['hasChildren']) { + $childGroupIds[] = $group['id']; + } } } } - } - if (!empty($childGroupIds)) { - $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); + if (!empty($childGroupIds)) { + $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); + } + } catch (ColiboException $exception) { + throw $exception; + } catch (\Throwable $throwable) { + $this->logger->error('{code}: {message}', [ + 'code' => $throwable->getCode(), + 'message' => $throwable->getMessage(), + ]); + + throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); } } + /** + * Get an authenticated scoped API client for the given FeedSource + * + * @param FeedSource $feedSource + * + * @return HttpClientInterface + * + * @throws ColiboException + */ private function getApiClient(FeedSource $feedSource): HttpClientInterface { $id = ColiboFeedType::getIdKey($feedSource); @@ -179,7 +252,7 @@ private function getApiClient(FeedSource $feedSource): HttpClientInterface $secrets = new SecretsDTO($feedSource); $this->apiClients[$id] = HttpClient::createForBaseUri($secrets->apiBaseUri)->withOptions([ 'headers' => [ - 'Authorization' => 'Bearer '.$this->fetchColiboToken($feedSource), + 'Authorization' => 'Bearer ' . $this->fetchColiboToken($feedSource), 'Accept' => 'application/json', ], ]); @@ -188,21 +261,22 @@ private function getApiClient(FeedSource $feedSource): HttpClientInterface } /** - * @throws TransportExceptionInterface - * @throws \Throwable - * @throws ServerExceptionInterface - * @throws RedirectionExceptionInterface - * @throws ClientExceptionInterface - * @throws \JsonException + * Get Colibo auth token for the given FeedSource + * + * @param FeedSource $feedSource + * + * @return string + * + * @throws ColiboException */ private function fetchColiboToken(FeedSource $feedSource): string { $id = ColiboFeedType::getIdKey($feedSource); /** @var CacheItemInterface $cacheItem */ - $cacheItem = $this->feedsCache->getItem('colibo_token_'.$id); + $cacheItem = $this->feedsCache->getItem('colibo_token_' . $id); - if (false && $cacheItem->isHit()) { + if ($cacheItem->isHit()) { /** @var string $token */ $token = $cacheItem->get(); } else { @@ -239,7 +313,7 @@ private function fetchColiboToken(FeedSource $feedSource): string 'message' => $throwable->getMessage(), ]); - throw $throwable; + throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); } } diff --git a/src/Feed/SourceType/Colibo/ColiboException.php b/src/Feed/SourceType/Colibo/ColiboException.php new file mode 100644 index 000000000..c78fe300a --- /dev/null +++ b/src/Feed/SourceType/Colibo/ColiboException.php @@ -0,0 +1,10 @@ +feedService->getFeedSourceConfigUrl($feedSource, 'FeedEntryPublishers'); + $feedEntryRecipients = $this->feedService->getFeedSourceConfigUrl($feedSource, 'FeedEntryRecipients'); + + // @TODO: Translation. + return [ + [ + 'key' => 'colibo-feed-entry-publishers-selector', + 'input' => 'multiselect-from-endpoint', + 'endpoint' => $feedEntryPublishers, + 'name' => 'colibo-feed-entry-publishers', + 'label' => 'Vælg afsender grupper for de nyheder du ønsker at vise', + 'helpText' => 'Her vælger du hvilke afsender grupper der skal hentes nyheder fra.', + 'formGroupClasses' => 'col-md-6 mb-3', + ], + [ + 'key' => 'colibo-feed-entry-recipients-selector', + 'input' => 'multiselect-from-endpoint', + 'endpoint' => $feedEntryRecipients, + 'name' => 'colibo-feed-entry-recipients', + 'label' => 'Vælg modtager grupper for de nyheder du ønsker at vise', + 'helpText' => 'Her vælger du hvilke afsender grupper der skal hentes nyheder fra.', + 'formGroupClasses' => 'col-md-6 mb-3', + ], + ]; + } + + public function getData(Feed $feed): array + { + $configuration = $feed->getConfiguration(); + $baseUri = $feed->getFeedSource()->getSecrets()['api_base_uri']; + + $entries = $this->apiClient->getFeedEntriesNews($feed->getFeedSource(), $configuration['colibo-feed-entry-recipients'], $configuration['colibo-feed-entry-publishers']); + + $result = [ + 'title' => 'Colibo Feed', + 'entries' => [], + ]; + + foreach ($entries as $entry) { + $item = new Item(); + $item->setTitle($entry->fields->title); + + $crawler = new Crawler($entry->fields->description); + $summary = ''; + foreach ($crawler as $domElement) { + $summary .= $domElement->textContent; + } + $item->setSummary($summary); + + $item->setPublicId((string) $entry->id); + + $link = sprintf('%s/feedentry/%s', $baseUri, $entry->id); + $item->setLink($link); + + if (null !== $entry->fields->body) { + $crawler = new Crawler($entry->fields->body); + $content = ''; + foreach ($crawler as $domElement) { + $content .= $domElement->textContent; + } + } else { + $content = $item->getSummary(); + } + $item->setContent($content); + + $updated = null === $entry->updated ? $entry->publishDate : $entry->updated; + $item->setLastModified(new \DateTime($updated)); + + $author = new Item\Author(); + $author->setName($entry->publisher->name); + $item->setAuthor($author); + + if ($entry->fields->galleryItems !== null) { + $galleryItems = json_decode($entry->fields->galleryItems, true, 512, JSON_THROW_ON_ERROR); + foreach ($galleryItems as $galleryItem) { + $media = new Item\Media(); + + $large = sprintf('%s/api/files/%s/thumbnail/large', $baseUri, $galleryItem['id']); + $media->setUrl($large); + + $small = sprintf('%s/api/files/%s/thumbnail/small', $baseUri, $galleryItem['id']); + $media->setThumbnail($small); + + $item->addMedia($media); + } + } + + foreach ($entry->recipients as $recipient) { + $category = new Category(); + $category->setLabel($recipient->name); + + $item->addCategory($category); + } + + $result['entries'][] = $item->toArray(); + } + + return $result; + } + + public function getConfigOptions(Request $request, FeedSource $feedSource, string $name): ?array + { + switch ($name) { + case 'FeedEntryPublishers': + case 'FeedEntryRecipients': + $id = self::getIdKey($feedSource); + + /** @var CacheItemInterface $cacheItem */ + $cacheItem = $this->feedsCache->getItem('colibo_feed_entry_publishers_groups_'.$id); + + if ($cacheItem->isHit()) { + $groups = $cacheItem->get(); + } else { + $groups = $this->apiClient->getSearchGroups($feedSource); + + $groups = array_map(fn (array $item) => [ + 'id' => Ulid::generate(), + 'title' => sprintf('%s (%d)', $item['model']['title'], $item['model']['id']), + 'value' => (string) $item['model']['id'], + ], $groups); + + usort($groups, fn ($a, $b) => strcmp($a['title'], $b['title'])); + + $cacheItem->set($groups); + $cacheItem->expiresAfter(self::CACHE_TTL); + $this->feedsCache->save($cacheItem->set($groups)); + } + + return $groups; + + default: + return null; + } + } + + public function getRequiredSecrets(): array + { + return ['client_id', 'client_secret']; + } + + public function getRequiredConfiguration(): array + { + return ['api_base_uri']; + } + + public function getSupportedFeedOutputType(): string + { + return self::SUPPORTED_FEED_TYPE; + } + + public function getSchema(): array + { + return []; + } + + public static function getIdKey(FeedSource $feedSource): string + { + $ulid = $feedSource->getId(); + assert(null !== $ulid); + + return $ulid->toBase32(); + } +} diff --git a/src/Feed/SourceType/Colibo/SecretsDTO.php b/src/Feed/SourceType/Colibo/SecretsDTO.php new file mode 100644 index 000000000..101ebcb80 --- /dev/null +++ b/src/Feed/SourceType/Colibo/SecretsDTO.php @@ -0,0 +1,35 @@ +getSecrets(); + + if (null === $secrets) { + throw new \RuntimeException('No secrets found for feed source.'); + } + + if (!isset($secrets['api_base_uri'], $secrets['client_id'], $secrets['client_secret'])) { + throw new \RuntimeException('Missing required secrets for feed source.'); + } + + if (false === filter_var($secrets['api_base_uri'], FILTER_VALIDATE_URL)) { + throw new \RuntimeException('Invalid api_endpoint.'); + } + + $this->apiBaseUri = rtrim($secrets['api_base_uri'], '/'); + $this->clientId = $secrets['client_id']; + $this->clientSecret = $secrets['client_secret']; + } +} diff --git a/src/Service/FeedService.php b/src/Service/FeedService.php index fdd17039b..71eff7e89 100644 --- a/src/Service/FeedService.php +++ b/src/Service/FeedService.php @@ -100,7 +100,7 @@ public function getData(Feed $feed): ?array /** @var CacheItemInterface $cacheItem */ $cacheItem = $this->feedsCache->getItem($feedId); - if ($cacheItem->isHit()) { + if (false && $cacheItem->isHit()) { /** @var array $data */ $data = $cacheItem->get(); } else { From c59f5bd583be3bf2b7f2f50071e375571d924f70 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:07:14 +0100 Subject: [PATCH 03/15] 3208: Fixed migration --- migrations/Version20241125085559.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/migrations/Version20241125085559.php b/migrations/Version20241125085559.php index 3affb9670..56b233bee 100644 --- a/migrations/Version20241125085559.php +++ b/migrations/Version20241125085559.php @@ -19,18 +19,21 @@ public function getDescription(): string public function up(Schema $schema): void { - $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Calendar\\CalendarApiFeedType\' WHERE feed_type = \'App\\Feed\\CalendarApiFeedType\''); - $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\EventDatabase\\EventDatabaseApiFeedType\' WHERE feed_type = \'App\\Feed\\EventDatabaseApiFeedType\''); - $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Koba\\KobaFeedType\' WHERE feed_type = \'App\\Feed\\KobaFeedType\''); - $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Notified\\NotifiedFeedType\' WHERE feed_type = \'App\\Feed\\NotifiedFeedType\''); - $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\Rss\\RssFeedType\' WHERE feed_type = \'App\\Feed\\RssFeedType\''); - $this->addSql('UPDATE feed_source SET feed_type = \'App\\Feed\\SourceType\\SparkleIO\\SparkleIOFeedType\' WHERE feed_type = \'App\\Feed\\SparkleIOFeedType\''); - + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Calendar\\\\CalendarApiFeedType" WHERE feed_type = "App\\\\Feed\\\\CalendarApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\EventDatabase\\\\EventDatabaseApiFeedType" WHERE feed_type = "App\\\\Feed\\\\EventDatabaseApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Koba\\\\KobaFeedType" WHERE feed_type = "App\\\\Feed\\\\KobaFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Notified\\\\NotifiedFeedType" WHERE feed_type = "App\\\\Feed\\\\NotifiedFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Rss\\\\RssFeedType" WHERE feed_type = "App\\\\Feed\\\\RssFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\SparkleIO\\\\SparkleIOFeedType" WHERE feed_type = "App\\\\Feed\\\\SparkleIOFeedType"'); } public function down(Schema $schema): void { - // this down() migration is auto-generated, please modify it to your needs - + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\CalendarApiFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Calendar\\\\CalendarApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\EventDatabaseApiFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\EventDatabase\\\\EventDatabaseApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\KobaFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Koba\\\\KobaFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\NotifiedFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Notified\\\\NotifiedFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\RssFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Rss\\\\RssFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SparkleIOFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\SparkleIO\\\\SparkleIOFeedType"'); } } From b6c052b91f49a07b6f37669b76ab40bfe8e83c8d Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:00:47 +0100 Subject: [PATCH 04/15] 3208: Cleaned up implementation --- .env | 2 +- src/Feed/OutputModel/ConfigOption.php | 11 ++ src/Feed/SourceType/Colibo/ApiClient.php | 132 +++--------------- src/Feed/SourceType/Colibo/ColiboFeedType.php | 99 +++++++++---- src/Feed/SourceType/Rss/RssFeedType.php | 10 +- 5 files changed, 107 insertions(+), 147 deletions(-) create mode 100644 src/Feed/OutputModel/ConfigOption.php diff --git a/.env b/.env index 114aa3363..697751b0a 100644 --- a/.env +++ b/.env @@ -93,7 +93,7 @@ REDIS_CACHE_DSN=redis://redis:6379/0 ###< redis ### ###> Calendar Api Feed Source ### -# See docs/calendar-api-feed.md for variable explainations. +# See docs/feed/calendar-api-feed.md for variable explainations. CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT= CALENDAR_API_FEED_SOURCE_RESOURCE_ENDPOINT= CALENDAR_API_FEED_SOURCE_EVENT_ENDPOINT= diff --git a/src/Feed/OutputModel/ConfigOption.php b/src/Feed/OutputModel/ConfigOption.php new file mode 100644 index 000000000..8fd9cd7cd --- /dev/null +++ b/src/Feed/OutputModel/ConfigOption.php @@ -0,0 +1,11 @@ +getApiClient($feedSource); - $response = $client->request('GET', '/api/feedentries/news', [ + $options = [ 'headers' => [ 'Content-Type' => 'application/json', ], 'query' => [ - 'getQuery.recipients' => $recipients, + 'recipients' => array_map(fn($recipient) => (object) [ + 'Id' => $recipient, + 'Type' => 'Group' + ], $recipients), + 'publishers' => array_map(fn($publisher) => (object) [ + 'Id' => $publisher, + 'Type' => 'Group' + ], $publishers), ], - ]); + ]; + + $response = $client->request('GET', '/api/feedentries/news', $options); return json_decode($response->getContent(), false, 512, JSON_THROW_ON_ERROR); - } catch (ColiboException $exception) { - return []; } catch (\Throwable $throwable) { $this->logger->error('{code}: {message}', [ 'code' => $throwable->getCode(), @@ -94,47 +101,6 @@ public function getSearchGroups(FeedSource $feedSource, string $type = 'WorkGrou } return $groups; - } catch (ColiboException $exception) { - return []; - } catch (\Throwable $throwable) { - $this->logger->error('{code}: {message}', [ - 'code' => $throwable->getCode(), - 'message' => $throwable->getMessage(), - ]); - - return []; - } - } - - /** - * @param FeedSource $feedSource - * - * @return array - */ - public function getFeedEntryPublishersGroups(FeedSource $feedSource): array - { - try { - $client = $this->getApiClient($feedSource); - - $response = $client->request('GET', '/api/feedentries/publishers/groups', [ - 'query' => ['groupType' => 'Department'], - ]); - - $groups = []; - $childGroupIds = []; - foreach ($response->toArray() as $group) { - $groups[] = $group; - - if (isset($group['hasChildren']) && $group['hasChildren']) { - $childGroupIds[] = $group['id']; - } - } - - $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); - - return $groups; - } catch (ColiboException $exception) { - return []; } catch (\Throwable $throwable) { $this->logger->error('{code}: {message}', [ 'code' => $throwable->getCode(), @@ -170,64 +136,6 @@ private function getSearchGroupsPage(FeedSource $feedSource, string $type, int $ } catch (ColiboException $exception) { throw $exception; } catch (\Throwable $throwable) { - $this->logger->error('{code}: {message}', [ - 'code' => $throwable->getCode(), - 'message' => $throwable->getMessage(), - ]); - - throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); - } - } - - /** - * Get - * - * @param FeedSource $feedSource - * @param array $childGroupIds - * @param array $groups - * - * @return void - * - * @throws ColiboException - */ - private function getFeedEntryPublishersGroupsChildren(FeedSource $feedSource, array $childGroupIds, array &$groups): void - { - try { - $client = $this->getApiClient($feedSource); - - $batches = array_chunk($childGroupIds, self::BATCH_SIZE); - - foreach ($batches as $batch) { - // @see https://symfony.com/doc/current/http_client.html#concurrent-requests - $responses = []; - foreach ($batch as $childGroupId) { - $uri = sprintf('/api/feedentries/publishers/groups/%d/children', $childGroupId); - $responses[] = $client->request('GET', $uri, []); - } - - $childGroupIds = []; - foreach ($responses as $response) { - foreach ($response->toArray() as $group) { - $groups[] = $group; - - if (isset($group['hasChildren']) && $group['hasChildren']) { - $childGroupIds[] = $group['id']; - } - } - } - } - - if (!empty($childGroupIds)) { - $this->getFeedEntryPublishersGroupsChildren($feedSource, $childGroupIds, $groups); - } - } catch (ColiboException $exception) { - throw $exception; - } catch (\Throwable $throwable) { - $this->logger->error('{code}: {message}', [ - 'code' => $throwable->getCode(), - 'message' => $throwable->getMessage(), - ]); - throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); } } @@ -252,7 +160,7 @@ private function getApiClient(FeedSource $feedSource): HttpClientInterface $secrets = new SecretsDTO($feedSource); $this->apiClients[$id] = HttpClient::createForBaseUri($secrets->apiBaseUri)->withOptions([ 'headers' => [ - 'Authorization' => 'Bearer ' . $this->fetchColiboToken($feedSource), + 'Authorization' => 'Bearer ' . $this->fetchToken($feedSource), 'Accept' => 'application/json', ], ]); @@ -261,7 +169,7 @@ private function getApiClient(FeedSource $feedSource): HttpClientInterface } /** - * Get Colibo auth token for the given FeedSource + * Get the auth token for the given FeedSource * * @param FeedSource $feedSource * @@ -269,7 +177,7 @@ private function getApiClient(FeedSource $feedSource): HttpClientInterface * * @throws ColiboException */ - private function fetchColiboToken(FeedSource $feedSource): string + private function fetchToken(FeedSource $feedSource): string { $id = ColiboFeedType::getIdKey($feedSource); @@ -287,6 +195,7 @@ private function fetchColiboToken(FeedSource $feedSource): string $response = $client->request('POST', '/auth/oauth2/connect/token', [ 'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded', + 'Accept' => 'application/json', ], 'body' => [ 'grant_type' => self::GRANT_TYPE, @@ -308,15 +217,10 @@ private function fetchColiboToken(FeedSource $feedSource): string $cacheItem->expiresAfter($expireSeconds); $this->feedsCache->save($cacheItem); } catch (\Throwable $throwable) { - $this->logger->error('{code}: {message}', [ - 'code' => $throwable->getCode(), - 'message' => $throwable->getMessage(), - ]); - throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); } } return $token; } -} \ No newline at end of file +} diff --git a/src/Feed/SourceType/Colibo/ColiboFeedType.php b/src/Feed/SourceType/Colibo/ColiboFeedType.php index 771fd641c..e72afe79c 100644 --- a/src/Feed/SourceType/Colibo/ColiboFeedType.php +++ b/src/Feed/SourceType/Colibo/ColiboFeedType.php @@ -8,9 +8,11 @@ use App\Entity\Tenant\FeedSource; use App\Feed\FeedOutputModels; use App\Feed\FeedTypeInterface; +use App\Feed\OutputModel\ConfigOption; use App\Service\FeedService; use FeedIo\Feed\Item; use FeedIo\Feed\Node\Category; +use Psr\Cache\CacheItemInterface; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Uid\Ulid; @@ -36,29 +38,28 @@ public function __construct( public function getAdminFormOptions(FeedSource $feedSource): array { - $feedEntryPublishers = $this->feedService->getFeedSourceConfigUrl($feedSource, 'FeedEntryPublishers'); - $feedEntryRecipients = $this->feedService->getFeedSourceConfigUrl($feedSource, 'FeedEntryRecipients'); + $feedEntryRecipients = $this->feedService->getFeedSourceConfigUrl($feedSource, 'recipients'); + $feedEntryPublishers = $this->feedService->getFeedSourceConfigUrl($feedSource, 'publishers'); - // @TODO: Translation. return [ [ - 'key' => 'colibo-feed-entry-publishers-selector', + 'key' => 'colibo-feed-type-publishers-selector', 'input' => 'multiselect-from-endpoint', - 'endpoint' => $feedEntryPublishers, - 'name' => 'colibo-feed-entry-publishers', - 'label' => 'Vælg afsender grupper for de nyheder du ønsker at vise', - 'helpText' => 'Her vælger du hvilke afsender grupper der skal hentes nyheder fra.', - 'formGroupClasses' => 'col-md-6 mb-3', + 'endpoint' => $feedEntryRecipients, + 'name' => 'recipients', + 'label' => 'Modtagergrupper', + 'helpText' => 'Vælg hvilke grupper, der skal hentes nyheder fra.', + 'formGroupClasses' => 'mb-3', ], [ - 'key' => 'colibo-feed-entry-recipients-selector', + 'key' => 'colibo-feed-type-publishers-selector', 'input' => 'multiselect-from-endpoint', - 'endpoint' => $feedEntryRecipients, - 'name' => 'colibo-feed-entry-recipients', - 'label' => 'Vælg modtager grupper for de nyheder du ønsker at vise', - 'helpText' => 'Her vælger du hvilke afsender grupper der skal hentes nyheder fra.', - 'formGroupClasses' => 'col-md-6 mb-3', - ], + 'endpoint' => $feedEntryPublishers, + 'name' => 'publishers', + 'label' => 'Afsendergrupper', + 'helpText' => 'Vælg afsendergrupper for at begrænse hvilke afsenderes nyheder, der vises fra modtagergruppen. Hvis den ikke er valgt vises alle nyheder fra modtagergruppen.', + 'formGroupClasses' => 'mb-3', + ] ]; } @@ -67,13 +68,21 @@ public function getData(Feed $feed): array $configuration = $feed->getConfiguration(); $baseUri = $feed->getFeedSource()->getSecrets()['api_base_uri']; - $entries = $this->apiClient->getFeedEntriesNews($feed->getFeedSource(), $configuration['colibo-feed-entry-recipients'], $configuration['colibo-feed-entry-publishers']); - $result = [ - 'title' => 'Colibo Feed', + 'title' => 'Intranet', 'entries' => [], ]; + $recipients = $configuration['recipients'] ?? null; + $publishers = $configuration['publishers'] ?? []; + + if (null === $recipients) { + return $result; + } + + $entries = $this->apiClient->getFeedEntriesNews($feed->getFeedSource(), $recipients, $publishers); + + foreach ($entries as $entry) { $item = new Item(); $item->setTitle($entry->fields->title); @@ -139,25 +148,26 @@ public function getData(Feed $feed): array public function getConfigOptions(Request $request, FeedSource $feedSource, string $name): ?array { switch ($name) { - case 'FeedEntryPublishers': - case 'FeedEntryRecipients': + case 'recipients': + case 'publishers': $id = self::getIdKey($feedSource); /** @var CacheItemInterface $cacheItem */ - $cacheItem = $this->feedsCache->getItem('colibo_feed_entry_publishers_groups_'.$id); + $cacheItem = $this->feedsCache->getItem('colibo_feed_entry_groups_'.$id); if ($cacheItem->isHit()) { $groups = $cacheItem->get(); } else { $groups = $this->apiClient->getSearchGroups($feedSource); - $groups = array_map(fn (array $item) => [ - 'id' => Ulid::generate(), - 'title' => sprintf('%s (%d)', $item['model']['title'], $item['model']['id']), - 'value' => (string) $item['model']['id'], - ], $groups); + $groups = array_map(fn (array $item) => + new ConfigOption( + Ulid::generate(), + sprintf('%s (%d)', $item['model']['title'], $item['model']['id']), + (string) $item['model']['id'] + ), $groups); - usort($groups, fn ($a, $b) => strcmp($a['title'], $b['title'])); + usort($groups, fn ($a, $b) => strcmp($a->title, $b->title)); $cacheItem->set($groups); $cacheItem->expiresAfter(self::CACHE_TTL); @@ -173,12 +183,23 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin public function getRequiredSecrets(): array { - return ['client_id', 'client_secret']; + return [ + 'api_base_uri' => [ + 'type' => 'string', + 'exposeValue' => true, + ], + 'client_id' => [ + 'type' => 'string' + ], + 'client_secret' => [ + 'type' => 'string' + ], + ]; } public function getRequiredConfiguration(): array { - return ['api_base_uri']; + return ['recipients', 'publishers']; } public function getSupportedFeedOutputType(): string @@ -188,7 +209,23 @@ public function getSupportedFeedOutputType(): string public function getSchema(): array { - return []; + return [ + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object', + 'properties' => [ + 'api_base_uri' => [ + 'type' => 'string', + 'format' => 'uri', + ], + 'client_id' => [ + 'type' => 'string', + ], + 'client_secret' => [ + 'type' => 'string', + ], + ], + 'required' => ['api_base_uri', 'client_id', 'client_secret'], + ]; } public static function getIdKey(FeedSource $feedSource): string diff --git a/src/Feed/SourceType/Rss/RssFeedType.php b/src/Feed/SourceType/Rss/RssFeedType.php index 078f3127b..90abf6907 100644 --- a/src/Feed/SourceType/Rss/RssFeedType.php +++ b/src/Feed/SourceType/Rss/RssFeedType.php @@ -56,7 +56,15 @@ public function getData(Feed $feed): array /** @var Item $item */ foreach ($feedResult->getFeed() as $item) { - $result['entries'][] = $item->toArray(); + $entry = $item->toArray(); + + if (empty($entry['author'])) { + $entry['author'] = [ + 'name' => $feedResult->getFeed()->getTitle(), + ]; + } + + $result['entries'][] = $entry; if (!is_null($numberOfEntries) && count($result['entries']) >= $numberOfEntries) { break; From e05ab3fd29ab3fe3083f7f6907b05c0ff5b8b00a Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:19:34 +0100 Subject: [PATCH 05/15] 3208: Changed to only show allowed recipients in slide creation flow --- src/Feed/SourceType/Colibo/ApiClient.php | 5 +- src/Feed/SourceType/Colibo/ColiboFeedType.php | 59 ++++++++++++------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/Feed/SourceType/Colibo/ApiClient.php b/src/Feed/SourceType/Colibo/ApiClient.php index 1ad09ac27..0c08d96b2 100644 --- a/src/Feed/SourceType/Colibo/ApiClient.php +++ b/src/Feed/SourceType/Colibo/ApiClient.php @@ -34,10 +34,12 @@ public function __construct( * An array of recipient ID's to filter by * @param array $publishers * An array of publisher ID's to filter by + * @param int $pageSize + * Number of elements to retrieve * * @return mixed */ - public function getFeedEntriesNews(FeedSource $feedSource, array $recipients = [], array $publishers = []): mixed + public function getFeedEntriesNews(FeedSource $feedSource, array $recipients = [], array $publishers = [], int $pageSize = 10): mixed { try { $client = $this->getApiClient($feedSource); @@ -55,6 +57,7 @@ public function getFeedEntriesNews(FeedSource $feedSource, array $recipients = [ 'Id' => $publisher, 'Type' => 'Group' ], $publishers), + 'pageSize' => $pageSize, ], ]; diff --git a/src/Feed/SourceType/Colibo/ColiboFeedType.php b/src/Feed/SourceType/Colibo/ColiboFeedType.php index e72afe79c..fb376c0e0 100644 --- a/src/Feed/SourceType/Colibo/ColiboFeedType.php +++ b/src/Feed/SourceType/Colibo/ColiboFeedType.php @@ -13,6 +13,7 @@ use FeedIo\Feed\Item; use FeedIo\Feed\Node\Category; use Psr\Cache\CacheItemInterface; +use Symfony\Component\BrowserKit\Exception\JsonException; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Uid\Ulid; @@ -38,28 +39,28 @@ public function __construct( public function getAdminFormOptions(FeedSource $feedSource): array { - $feedEntryRecipients = $this->feedService->getFeedSourceConfigUrl($feedSource, 'recipients'); - $feedEntryPublishers = $this->feedService->getFeedSourceConfigUrl($feedSource, 'publishers'); + $feedEntryRecipients = $this->feedService->getFeedSourceConfigUrl($feedSource, 'allowed-recipients'); return [ [ - 'key' => 'colibo-feed-type-publishers-selector', + 'key' => 'colibo-feed-type-recipient-selector', 'input' => 'multiselect-from-endpoint', 'endpoint' => $feedEntryRecipients, 'name' => 'recipients', - 'label' => 'Modtagergrupper', + 'label' => 'Grupper', 'helpText' => 'Vælg hvilke grupper, der skal hentes nyheder fra.', 'formGroupClasses' => 'mb-3', ], [ - 'key' => 'colibo-feed-type-publishers-selector', - 'input' => 'multiselect-from-endpoint', - 'endpoint' => $feedEntryPublishers, - 'name' => 'publishers', - 'label' => 'Afsendergrupper', - 'helpText' => 'Vælg afsendergrupper for at begrænse hvilke afsenderes nyheder, der vises fra modtagergruppen. Hvis den ikke er valgt vises alle nyheder fra modtagergruppen.', + 'key' => 'colibo-feed-type-page-size', + 'input' => 'input', + 'type' => 'number', + 'name' => 'page_size', + 'label' => 'Antal nyheder', + 'defaultValue' => '5', + 'helpText' => 'Vælg hvor mange nyheder der maksimalt skal hentes.', 'formGroupClasses' => 'mb-3', - ] + ], ]; } @@ -73,15 +74,15 @@ public function getData(Feed $feed): array 'entries' => [], ]; - $recipients = $configuration['recipients'] ?? null; + $recipients = $configuration['recipients'] ?? []; $publishers = $configuration['publishers'] ?? []; + $pageSize = isset($configuration['page_size']) ? (int) $configuration['page_size'] : 10; - if (null === $recipients) { + if (empty($recipients)) { return $result; } - $entries = $this->apiClient->getFeedEntriesNews($feed->getFeedSource(), $recipients, $publishers); - + $entries = $this->apiClient->getFeedEntriesNews($feed->getFeedSource(), $recipients, $publishers, $pageSize); foreach ($entries as $entry) { $item = new Item(); @@ -118,7 +119,12 @@ public function getData(Feed $feed): array $item->setAuthor($author); if ($entry->fields->galleryItems !== null) { - $galleryItems = json_decode($entry->fields->galleryItems, true, 512, JSON_THROW_ON_ERROR); + try { + $galleryItems = json_decode($entry->fields->galleryItems, true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException) { + $galleryItems = []; + } + foreach ($galleryItems as $galleryItem) { $media = new Item\Media(); @@ -148,8 +154,12 @@ public function getData(Feed $feed): array public function getConfigOptions(Request $request, FeedSource $feedSource, string $name): ?array { switch ($name) { + case 'allowed-recipients': + $allowedIds = $feedSource->getSecrets()['allowed_recipients'] ?? []; + $allGroupOptions = $this->getConfigOptions($request, $feedSource, 'recipients'); + + return array_values(array_filter($allGroupOptions, fn(ConfigOption $group) => in_array($group->value, $allowedIds))); case 'recipients': - case 'publishers': $id = self::getIdKey($feedSource); /** @var CacheItemInterface $cacheItem */ @@ -175,7 +185,6 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin } return $groups; - default: return null; } @@ -194,12 +203,16 @@ public function getRequiredSecrets(): array 'client_secret' => [ 'type' => 'string' ], + 'allowed_recipients' => [ + 'type' => 'string_array', + 'exposeValue' => true, + ] ]; } public function getRequiredConfiguration(): array { - return ['recipients', 'publishers']; + return ['recipients', 'page_size']; } public function getSupportedFeedOutputType(): string @@ -223,8 +236,14 @@ public function getSchema(): array 'client_secret' => [ 'type' => 'string', ], + 'allowed_recipients' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + ], + ], ], - 'required' => ['api_base_uri', 'client_id', 'client_secret'], + 'required' => ['api_base_uri', 'client_id', 'client_secret', 'allowed_recipients'], ]; } From 9148f421885f9609e219cbe4199b84cece2a624a Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:31:28 +0100 Subject: [PATCH 06/15] 3208: Fixed coding standards issues --- fixtures/feed_source.yaml | 4 +- psalm-baseline.xml | 27 ---------- src/Feed/FeedException.php | 5 +- src/Feed/OutputModel/ConfigOption.php | 7 ++- src/Feed/SourceType/Colibo/ApiClient.php | 33 +++++++------ .../SourceType/Colibo/ColiboException.php | 5 +- src/Feed/SourceType/Colibo/ColiboFeedType.php | 49 +++++++++++-------- src/Feed/SourceType/Colibo/SecretsDTO.php | 2 +- .../EventDatabaseApiFeedType.php | 26 +++++++--- src/Feed/SourceType/Koba/KobaFeedType.php | 6 +-- .../SourceType/Notified/NotifiedFeedType.php | 2 +- .../SparkleIO/SparkleIOFeedType.php | 6 +-- src/Service/FeedService.php | 2 +- tests/Service/FeedServiceTest.php | 2 +- 14 files changed, 87 insertions(+), 89 deletions(-) diff --git a/fixtures/feed_source.yaml b/fixtures/feed_source.yaml index 989d60c7b..4c3180b60 100644 --- a/fixtures/feed_source.yaml +++ b/fixtures/feed_source.yaml @@ -2,7 +2,7 @@ App\Entity\Tenant\FeedSource: feed (template): description: - feedType: "App\\Feed\\RssFeedType" + feedType: "App\\Feed\\SourceType\\Rss\\RssFeedType" secrets: [ ] supportedFeedOutputType: 'rss' createdAt (unique): '' @@ -16,7 +16,7 @@ App\Entity\Tenant\FeedSource: tenant: '@tenant_xyz' feed_source_abc_notified (extends feed): title: 'feed_source_abc_notified' - feedType: "App\\Feed\\RssFeedType" + feedType: "App\\Feed\\SourceType\\Rss\\RssFeedType" secrets: token: '1234567890' supportedFeedOutputType: 'instagram' diff --git a/psalm-baseline.xml b/psalm-baseline.xml index ef8d120f0..e2174f7bf 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -285,33 +285,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Feed/FeedException.php b/src/Feed/FeedException.php index 448794a18..e164db7b5 100644 --- a/src/Feed/FeedException.php +++ b/src/Feed/FeedException.php @@ -1,8 +1,9 @@ 'application/json', ], 'query' => [ - 'recipients' => array_map(fn($recipient) => (object) [ + 'recipients' => array_map(fn ($recipient) => (object) [ 'Id' => $recipient, - 'Type' => 'Group' + 'Type' => 'Group', ], $recipients), - 'publishers' => array_map(fn($publisher) => (object) [ + 'publishers' => array_map(fn ($publisher) => (object) [ 'Id' => $publisher, - 'Type' => 'Group' + 'Type' => 'Group', ], $publishers), 'pageSize' => $pageSize, ], @@ -90,7 +91,7 @@ public function getSearchGroups(FeedSource $feedSource, string $type = 'WorkGrou $groups = $responseData['results']; $total = $responseData['total']; - $pages = (int)ceil($total / self::BATCH_SIZE); + $pages = (int) ceil($total / self::BATCH_SIZE); /** @var ResponseInterface[] $responses */ $responses = []; @@ -139,12 +140,12 @@ private function getSearchGroupsPage(FeedSource $feedSource, string $type, int $ } catch (ColiboException $exception) { throw $exception; } catch (\Throwable $throwable) { - throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); + throw new ColiboException($throwable->getMessage(), (int) $throwable->getCode(), $throwable); } } /** - * Get an authenticated scoped API client for the given FeedSource + * Get an authenticated scoped API client for the given FeedSource. * * @param FeedSource $feedSource * @@ -163,7 +164,7 @@ private function getApiClient(FeedSource $feedSource): HttpClientInterface $secrets = new SecretsDTO($feedSource); $this->apiClients[$id] = HttpClient::createForBaseUri($secrets->apiBaseUri)->withOptions([ 'headers' => [ - 'Authorization' => 'Bearer ' . $this->fetchToken($feedSource), + 'Authorization' => 'Bearer '.$this->fetchToken($feedSource), 'Accept' => 'application/json', ], ]); @@ -172,7 +173,7 @@ private function getApiClient(FeedSource $feedSource): HttpClientInterface } /** - * Get the auth token for the given FeedSource + * Get the auth token for the given FeedSource. * * @param FeedSource $feedSource * @@ -185,7 +186,7 @@ private function fetchToken(FeedSource $feedSource): string $id = ColiboFeedType::getIdKey($feedSource); /** @var CacheItemInterface $cacheItem */ - $cacheItem = $this->feedsCache->getItem('colibo_token_' . $id); + $cacheItem = $this->feedsCache->getItem('colibo_token_'.$id); if ($cacheItem->isHit()) { /** @var string $token */ @@ -220,7 +221,7 @@ private function fetchToken(FeedSource $feedSource): string $cacheItem->expiresAfter($expireSeconds); $this->feedsCache->save($cacheItem); } catch (\Throwable $throwable) { - throw new ColiboException($throwable->getMessage(), $throwable->getCode(), $throwable); + throw new ColiboException($throwable->getMessage(), (int) $throwable->getCode(), $throwable); } } diff --git a/src/Feed/SourceType/Colibo/ColiboException.php b/src/Feed/SourceType/Colibo/ColiboException.php index c78fe300a..6ca7f2c53 100644 --- a/src/Feed/SourceType/Colibo/ColiboException.php +++ b/src/Feed/SourceType/Colibo/ColiboException.php @@ -1,10 +1,11 @@ getConfiguration(); - $baseUri = $feed->getFeedSource()->getSecrets()['api_base_uri']; + $secrets = $feed->getFeedSource()?->getSecrets() ?? []; $result = [ 'title' => 'Intranet', 'entries' => [], ]; + $baseUri = $secrets['api_base_uri']; $recipients = $configuration['recipients'] ?? []; $publishers = $configuration['publishers'] ?? []; $pageSize = isset($configuration['page_size']) ? (int) $configuration['page_size'] : 10; - if (empty($recipients)) { + if (empty($baseUri) || 0 === count($recipients)) { return $result; } - $entries = $this->apiClient->getFeedEntriesNews($feed->getFeedSource(), $recipients, $publishers, $pageSize); + $feedSource = $feed->getFeedSource(); + + if (null === $feedSource) { + return $result; + } + + $entries = $this->apiClient->getFeedEntriesNews($feedSource, $recipients, $publishers, $pageSize); foreach ($entries as $entry) { $item = new Item(); @@ -111,14 +117,14 @@ public function getData(Feed $feed): array } $item->setContent($content); - $updated = null === $entry->updated ? $entry->publishDate : $entry->updated; + $updated = $entry->updated ?? $entry->publishDate; $item->setLastModified(new \DateTime($updated)); $author = new Item\Author(); $author->setName($entry->publisher->name); $item->setAuthor($author); - if ($entry->fields->galleryItems !== null) { + if (null !== $entry->fields->galleryItems) { try { $galleryItems = json_decode($entry->fields->galleryItems, true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException) { @@ -155,10 +161,14 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin { switch ($name) { case 'allowed-recipients': - $allowedIds = $feedSource->getSecrets()['allowed_recipients'] ?? []; + $allowedIds = $feedSource->getSecrets()['allowed_recipients'] ?? []; $allGroupOptions = $this->getConfigOptions($request, $feedSource, 'recipients'); - return array_values(array_filter($allGroupOptions, fn(ConfigOption $group) => in_array($group->value, $allowedIds))); + if (null === $allGroupOptions) { + return []; + } + + return array_values(array_filter($allGroupOptions, fn (ConfigOption $group) => in_array($group->value, $allowedIds))); case 'recipients': $id = self::getIdKey($feedSource); @@ -170,12 +180,11 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin } else { $groups = $this->apiClient->getSearchGroups($feedSource); - $groups = array_map(fn (array $item) => - new ConfigOption( - Ulid::generate(), - sprintf('%s (%d)', $item['model']['title'], $item['model']['id']), - (string) $item['model']['id'] - ), $groups); + $groups = array_map(fn (array $item) => new ConfigOption( + Ulid::generate(), + sprintf('%s (%d)', $item['model']['title'], $item['model']['id']), + (string) $item['model']['id'] + ), $groups); usort($groups, fn ($a, $b) => strcmp($a->title, $b->title)); @@ -198,15 +207,15 @@ public function getRequiredSecrets(): array 'exposeValue' => true, ], 'client_id' => [ - 'type' => 'string' + 'type' => 'string', ], 'client_secret' => [ - 'type' => 'string' + 'type' => 'string', ], 'allowed_recipients' => [ 'type' => 'string_array', 'exposeValue' => true, - ] + ], ]; } @@ -243,7 +252,7 @@ public function getSchema(): array ], ], ], - 'required' => ['api_base_uri', 'client_id', 'client_secret', 'allowed_recipients'], + 'required' => ['api_base_uri', 'client_id', 'client_secret'], ]; } diff --git a/src/Feed/SourceType/Colibo/SecretsDTO.php b/src/Feed/SourceType/Colibo/SecretsDTO.php index 101ebcb80..761e86ffb 100644 --- a/src/Feed/SourceType/Colibo/SecretsDTO.php +++ b/src/Feed/SourceType/Colibo/SecretsDTO.php @@ -28,7 +28,7 @@ public function __construct(FeedSource $feedSource) throw new \RuntimeException('Invalid api_endpoint.'); } - $this->apiBaseUri = rtrim($secrets['api_base_uri'], '/'); + $this->apiBaseUri = rtrim((string) $secrets['api_base_uri'], '/'); $this->clientId = $secrets['client_id']; $this->clientSecret = $secrets['client_secret']; } diff --git a/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php b/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php index 358548b96..ce8cf13cb 100644 --- a/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php +++ b/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php @@ -6,8 +6,8 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedTypeInterface; use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -52,12 +52,21 @@ public function getData(Feed $feed): array $tags = $configuration['subscriptionTagValue'] ?? null; $numberOfItems = $configuration['subscriptionNumberValue'] ?? 5; - $queryParams = array_filter([ + $queryParams = [ '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), - ]); + ]; + + if (null !== $places) { + $queryParams['occurrences.place.id'] = array_map(static fn (array $place) => str_replace('/api/places/', '', (string) $place['value']), $places); + } + + if (null !== $organizers) { + $queryParams['organizer.id'] = array_map(static fn (array $organizer) => str_replace('/api/organizers/', '', (string) $organizer['value']), $organizers); + } + + if (null !== $tags) { + $queryParams['tags'] = array_map(static fn (array $tag) => str_replace('/api/tags/', '', (string) $tag['value']), $tags); + } $response = $this->client->request( 'GET', @@ -124,12 +133,13 @@ public function getData(Feed $feed): array if ($throwable instanceof ClientException && Response::HTTP_NOT_FOUND == $throwable->getCode()) { try { // Slide publishedTo is set to now. This will make the slide unpublished from this point on. - $feed->getSlide()->setPublishedTo(new \DateTime('now', new \DateTimeZone('UTC'))); + $slide = $feed->getSlide()?->setPublishedTo(new \DateTime('now', new \DateTimeZone('UTC'))); + $this->entityManager->flush(); $this->logger->info('Feed with id: {feedId} depends on an item that does not exist in Event Database. Unpublished slide with id: {slideId}', [ 'feedId' => $feed->getId(), - 'slideId' => $feed->getSlide()->getId(), + 'slideId' => $slide?->getId(), ]); } catch (\Exception $exception) { $this->logger->error('{code}: {message}', [ diff --git a/src/Feed/SourceType/Koba/KobaFeedType.php b/src/Feed/SourceType/Koba/KobaFeedType.php index 37091092a..b214b99a2 100644 --- a/src/Feed/SourceType/Koba/KobaFeedType.php +++ b/src/Feed/SourceType/Koba/KobaFeedType.php @@ -6,8 +6,8 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedTypeInterface; use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -87,7 +87,7 @@ public function getData(Feed $feed): array } // Apply list filter. If enabled it removes all events that do not have (liste) in title. - if ($filterList) { + if (true === $filterList) { if (!str_contains($title, '(liste)')) { continue; } else { @@ -96,7 +96,7 @@ public function getData(Feed $feed): array } // Apply booked title override. If enabled it changes the title to Optaget if it contains (optaget). - if ($rewriteBookedTitles) { + if (true === $rewriteBookedTitles) { if (str_contains($title, '(optaget)')) { $title = 'Optaget'; } diff --git a/src/Feed/SourceType/Notified/NotifiedFeedType.php b/src/Feed/SourceType/Notified/NotifiedFeedType.php index 5995920e8..ce3780791 100644 --- a/src/Feed/SourceType/Notified/NotifiedFeedType.php +++ b/src/Feed/SourceType/Notified/NotifiedFeedType.php @@ -6,8 +6,8 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedTypeInterface; use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php b/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php index 692b5947b..4a4329cfb 100644 --- a/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php +++ b/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php @@ -6,15 +6,15 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedTypeInterface; use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\InvalidArgumentException; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Uid\Ulid; -use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; @@ -31,7 +31,7 @@ class SparkleIOFeedType implements FeedTypeInterface public function __construct( private readonly FeedService $feedService, private readonly HttpClientInterface $client, - private readonly CacheInterface $feedsCache, + private readonly CacheItemPoolInterface $feedsCache, private readonly LoggerInterface $logger, ) {} diff --git a/src/Service/FeedService.php b/src/Service/FeedService.php index 71eff7e89..fdd17039b 100644 --- a/src/Service/FeedService.php +++ b/src/Service/FeedService.php @@ -100,7 +100,7 @@ public function getData(Feed $feed): ?array /** @var CacheItemInterface $cacheItem */ $cacheItem = $this->feedsCache->getItem($feedId); - if (false && $cacheItem->isHit()) { + if ($cacheItem->isHit()) { /** @var array $data */ $data = $cacheItem->get(); } else { diff --git a/tests/Service/FeedServiceTest.php b/tests/Service/FeedServiceTest.php index c28c9ca2b..8a95bb558 100644 --- a/tests/Service/FeedServiceTest.php +++ b/tests/Service/FeedServiceTest.php @@ -11,7 +11,7 @@ use App\Feed\SourceType\EventDatabase\EventDatabaseApiFeedType; use App\Feed\SourceType\Koba\KobaFeedType; use App\Feed\SourceType\Notified\NotifiedFeedType; -use App\Feed\Type\Rss\RssFeedType; +use App\Feed\SourceType\Rss\RssFeedType; use App\Feed\SourceType\SparkleIO\SparkleIOFeedType; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; From 2be4536e9a4f24971b824123832d5fce9b70feac Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:43:39 +0100 Subject: [PATCH 07/15] 3208: Fixed coding standards issues --- CHANGELOG.md | 2 ++ docs/feed/feed-overview.md | 20 ++++++++++---------- src/Feed/OutputModel/Rss/Readme.md | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b0eac971..c845041a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- [#226](https://github.com/os2display/display-api-service/pull/226) + - Added Colibo feed type. - [#215](https://github.com/os2display/display-api-service/pull/215) - Added calendar api feed type. - [#223](https://github.com/os2display/display-api-service/pull/223) diff --git a/docs/feed/feed-overview.md b/docs/feed/feed-overview.md index d0f0322dc..12b159b6b 100644 --- a/docs/feed/feed-overview.md +++ b/docs/feed/feed-overview.md @@ -1,33 +1,33 @@ # Feed Overview -"Feeds" in OS2display are external data sources that can provide up-to-data to slides. The idea is that if you can set -up slide based on a feed and publish it. The Screen Client will then fetch new data from the feed whenever the Slide is +"Feeds" in OS2display are external data sources that can provide up-to-data to slides. The idea is that if you can set +up slide based on a feed and publish it. The Screen Client will then fetch new data from the feed whenever the Slide is shown on screen. -The simplest example is a classic RSS news feed. You can set up a slide based on the RSS slide template, configure the +The simplest example is a classic RSS news feed. You can set up a slide based on the RSS slide template, configure the RSS source URL, and whenever the slide is on screen it will show the latest entries from the RSS feed. This means that administrators can set up slides and playlists that stays up to date automatically. ## Architecture -The "Feed" architecture is designed to enable both generic and custom feed types. To enable this all feed based screen +The "Feed" architecture is designed to enable both generic and custom feed types. To enable this all feed based screen templates are designed to support a given "feed output model". These are normalized data sets from a given feed type. -Each feed implementation defines which output model it supports. Thereby multiple feed implementations can support the -same output model. This is done to enable decoupling of the screen templates from the feed implementation. +Each feed implementation defines which output model it supports. Thereby multiple feed implementations can support the +same output model. This is done to enable decoupling of the screen templates from the feed implementation. For example: * If you have a news source that is not a RSS feed you can implement a "FeedSource" that fetches data from your source - then normalizes the data and outputs it as the RSS output model. When setting up RSS slides this feed source can then + then normalizes the data and outputs it as the RSS output model. When setting up RSS slides this feed source can then be selected as the source for the slide. -* OS2display has calendar templates that can show bookings or meetings. To show data from your specific calendar or - booking system you can implement a "FeedSource" that fetches booking data from your source and normalizes it to match +* OS2display has calendar templates that can show bookings or meetings. To show data from your specific calendar or + booking system you can implement a "FeedSource" that fetches booking data from your source and normalizes it to match the calendar output model. @todo Slide -> Feed -> FeedSource Auth -Caching \ No newline at end of file +Caching diff --git a/src/Feed/OutputModel/Rss/Readme.md b/src/Feed/OutputModel/Rss/Readme.md index eb23bf1e0..12fcd589c 100644 --- a/src/Feed/OutputModel/Rss/Readme.md +++ b/src/Feed/OutputModel/Rss/Readme.md @@ -4,4 +4,4 @@ The output model for RSS is defined as `FeedIo\Reader\Result` and `FeedIo\Feed\ItemInterface` -@see https://alexdebril.github.io/feed-io/ +@see [https://alexdebril.github.io/feed-io/](https://alexdebril.github.io/feed-io/). From 7c2e546053b7f129add86addb274a5dddd9073ca Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:57:55 +0100 Subject: [PATCH 08/15] 3208: Removed refactorings from feature --- config/packages/nelmio_cors.yaml | 2 +- config/services.yaml | 2 +- fixtures/feed_source.yaml | 4 +- migrations/Version20241125085559.php | 39 ------------------- .../Calendar => }/CalendarApiFeedType.php | 4 +- .../Colibo => }/ColiboFeedType.php | 5 +-- .../EventDatabaseApiFeedType.php | 4 +- .../{SourceType/Koba => }/KobaFeedType.php | 4 +- .../Notified => }/NotifiedFeedType.php | 4 +- src/Feed/{SourceType/Rss => }/RssFeedType.php | 4 +- src/Feed/SourceType/Colibo/ApiClient.php | 1 + .../SparkleIO => }/SparkleIOFeedType.php | 4 +- tests/Api/FeedSourceTest.php | 10 ++--- tests/Feed/NotifiedFeedTypeTest.php | 2 +- tests/Service/FeedServiceTest.php | 12 +++--- 15 files changed, 25 insertions(+), 76 deletions(-) delete mode 100644 migrations/Version20241125085559.php rename src/Feed/{SourceType/Calendar => }/CalendarApiFeedType.php (99%) rename src/Feed/{SourceType/Colibo => }/ColiboFeedType.php (98%) rename src/Feed/{SourceType/EventDatabase => }/EventDatabaseApiFeedType.php (99%) rename src/Feed/{SourceType/Koba => }/KobaFeedType.php (98%) rename src/Feed/{SourceType/Notified => }/NotifiedFeedType.php (98%) rename src/Feed/{SourceType/Rss => }/RssFeedType.php (97%) rename src/Feed/{SourceType/SparkleIO => }/SparkleIOFeedType.php (98%) diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml index 7b39d06d1..f815f4e31 100644 --- a/config/packages/nelmio_cors.yaml +++ b/config/packages/nelmio_cors.yaml @@ -1,7 +1,7 @@ nelmio_cors: defaults: origin_regex: true - allow_credentials: true + allow_credentials: false allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] allow_headers: ['Content-Type', 'Authorization', 'Authorization-Tenant-Key'] diff --git a/config/services.yaml b/config/services.yaml index 2cf4cf64e..6f1a0b8c7 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -51,7 +51,7 @@ services: Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler' Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler' - App\Feed\SourceType\Calendar\CalendarApiFeedType: + App\Feed\CalendarApiFeedType: arguments: $locationEndpoint: '%env(string:CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT)%' $resourceEndpoint: '%env(string:CALENDAR_API_FEED_SOURCE_RESOURCE_ENDPOINT)%' diff --git a/fixtures/feed_source.yaml b/fixtures/feed_source.yaml index 4c3180b60..989d60c7b 100644 --- a/fixtures/feed_source.yaml +++ b/fixtures/feed_source.yaml @@ -2,7 +2,7 @@ App\Entity\Tenant\FeedSource: feed (template): description: - feedType: "App\\Feed\\SourceType\\Rss\\RssFeedType" + feedType: "App\\Feed\\RssFeedType" secrets: [ ] supportedFeedOutputType: 'rss' createdAt (unique): '' @@ -16,7 +16,7 @@ App\Entity\Tenant\FeedSource: tenant: '@tenant_xyz' feed_source_abc_notified (extends feed): title: 'feed_source_abc_notified' - feedType: "App\\Feed\\SourceType\\Rss\\RssFeedType" + feedType: "App\\Feed\\RssFeedType" secrets: token: '1234567890' supportedFeedOutputType: 'instagram' diff --git a/migrations/Version20241125085559.php b/migrations/Version20241125085559.php deleted file mode 100644 index 56b233bee..000000000 --- a/migrations/Version20241125085559.php +++ /dev/null @@ -1,39 +0,0 @@ -addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Calendar\\\\CalendarApiFeedType" WHERE feed_type = "App\\\\Feed\\\\CalendarApiFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\EventDatabase\\\\EventDatabaseApiFeedType" WHERE feed_type = "App\\\\Feed\\\\EventDatabaseApiFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Koba\\\\KobaFeedType" WHERE feed_type = "App\\\\Feed\\\\KobaFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Notified\\\\NotifiedFeedType" WHERE feed_type = "App\\\\Feed\\\\NotifiedFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\Rss\\\\RssFeedType" WHERE feed_type = "App\\\\Feed\\\\RssFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\SparkleIO\\\\SparkleIOFeedType" WHERE feed_type = "App\\\\Feed\\\\SparkleIOFeedType"'); - } - - public function down(Schema $schema): void - { - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\CalendarApiFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Calendar\\\\CalendarApiFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\EventDatabaseApiFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\EventDatabase\\\\EventDatabaseApiFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\KobaFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Koba\\\\KobaFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\NotifiedFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Notified\\\\NotifiedFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\RssFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\Rss\\\\RssFeedType"'); - $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SparkleIOFeedType" WHERE feed_type LIKE "App\\\\Feed\\\\SourceType\\\\SparkleIO\\\\SparkleIOFeedType"'); - } -} diff --git a/src/Feed/SourceType/Calendar/CalendarApiFeedType.php b/src/Feed/CalendarApiFeedType.php similarity index 99% rename from src/Feed/SourceType/Calendar/CalendarApiFeedType.php rename to src/Feed/CalendarApiFeedType.php index b3ebd57c7..7f4c820d9 100644 --- a/src/Feed/SourceType/Calendar/CalendarApiFeedType.php +++ b/src/Feed/CalendarApiFeedType.php @@ -2,12 +2,10 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Calendar; +namespace App\Feed; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedOutputModels; -use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\Calendar\Event; use App\Feed\OutputModel\Calendar\Location; use App\Feed\OutputModel\Calendar\Resource; diff --git a/src/Feed/SourceType/Colibo/ColiboFeedType.php b/src/Feed/ColiboFeedType.php similarity index 98% rename from src/Feed/SourceType/Colibo/ColiboFeedType.php rename to src/Feed/ColiboFeedType.php index abb422d95..65526ab69 100644 --- a/src/Feed/SourceType/Colibo/ColiboFeedType.php +++ b/src/Feed/ColiboFeedType.php @@ -2,13 +2,12 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Colibo; +namespace App\Feed; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedOutputModels; -use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\ConfigOption; +use App\Feed\SourceType\Colibo\ApiClient; use App\Service\FeedService; use FeedIo\Feed\Item; use FeedIo\Feed\Node\Category; diff --git a/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php similarity index 99% rename from src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php rename to src/Feed/EventDatabaseApiFeedType.php index ce8cf13cb..250c2e404 100644 --- a/src/Feed/SourceType/EventDatabase/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -2,12 +2,10 @@ declare(strict_types=1); -namespace App\Feed\SourceType\EventDatabase; +namespace App\Feed; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedOutputModels; -use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; diff --git a/src/Feed/SourceType/Koba/KobaFeedType.php b/src/Feed/KobaFeedType.php similarity index 98% rename from src/Feed/SourceType/Koba/KobaFeedType.php rename to src/Feed/KobaFeedType.php index b214b99a2..aab326081 100644 --- a/src/Feed/SourceType/Koba/KobaFeedType.php +++ b/src/Feed/KobaFeedType.php @@ -2,12 +2,10 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Koba; +namespace App\Feed; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedOutputModels; -use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Feed/SourceType/Notified/NotifiedFeedType.php b/src/Feed/NotifiedFeedType.php similarity index 98% rename from src/Feed/SourceType/Notified/NotifiedFeedType.php rename to src/Feed/NotifiedFeedType.php index ce3780791..8de891b5e 100644 --- a/src/Feed/SourceType/Notified/NotifiedFeedType.php +++ b/src/Feed/NotifiedFeedType.php @@ -2,12 +2,10 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Notified; +namespace App\Feed; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedOutputModels; -use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Feed/SourceType/Rss/RssFeedType.php b/src/Feed/RssFeedType.php similarity index 97% rename from src/Feed/SourceType/Rss/RssFeedType.php rename to src/Feed/RssFeedType.php index 90abf6907..a1e51912f 100644 --- a/src/Feed/SourceType/Rss/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -2,12 +2,10 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Rss; +namespace App\Feed; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedOutputModels; -use App\Feed\FeedTypeInterface; use FeedIo\Adapter\Http\Client; use FeedIo\Feed\Item; use FeedIo\FeedIo; diff --git a/src/Feed/SourceType/Colibo/ApiClient.php b/src/Feed/SourceType/Colibo/ApiClient.php index 48b599423..ae98dab19 100644 --- a/src/Feed/SourceType/Colibo/ApiClient.php +++ b/src/Feed/SourceType/Colibo/ApiClient.php @@ -5,6 +5,7 @@ namespace App\Feed\SourceType\Colibo; use App\Entity\Tenant\FeedSource; +use App\Feed\ColiboFeedType; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; diff --git a/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php b/src/Feed/SparkleIOFeedType.php similarity index 98% rename from src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php rename to src/Feed/SparkleIOFeedType.php index 4a4329cfb..67787a848 100644 --- a/src/Feed/SourceType/SparkleIO/SparkleIOFeedType.php +++ b/src/Feed/SparkleIOFeedType.php @@ -2,12 +2,10 @@ declare(strict_types=1); -namespace App\Feed\SourceType\SparkleIO; +namespace App\Feed; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\FeedOutputModels; -use App\Feed\FeedTypeInterface; use App\Service\FeedService; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index 6bf70f28e..f72e32618 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\SourceType\EventDatabase\EventDatabaseApiFeedType::class, + '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\SourceType\EventDatabase\EventDatabaseApiFeedType::class, + '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\SourceType\EventDatabase\EventDatabaseApiFeedType::class, + '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\SourceType\EventDatabase\EventDatabaseApiFeedType::class, + '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\SourceType\EventDatabase\EventDatabaseApiFeedType::class, + 'feedType' => \App\Feed\EventDatabaseApiFeedType::class, 'secrets' => [ 'host' => 'https://www.test.dk', ], diff --git a/tests/Feed/NotifiedFeedTypeTest.php b/tests/Feed/NotifiedFeedTypeTest.php index ec8203bb3..5a7ef76c5 100644 --- a/tests/Feed/NotifiedFeedTypeTest.php +++ b/tests/Feed/NotifiedFeedTypeTest.php @@ -4,7 +4,7 @@ namespace App\Tests\Feed; -use App\Feed\SourceType\Notified\NotifiedFeedType; +use App\Feed\NotifiedFeedType; use App\Repository\FeedSourceRepository; use App\Repository\SlideRepository; use App\Service\FeedService; diff --git a/tests/Service/FeedServiceTest.php b/tests/Service/FeedServiceTest.php index 8a95bb558..9cfd24b27 100644 --- a/tests/Service/FeedServiceTest.php +++ b/tests/Service/FeedServiceTest.php @@ -6,13 +6,13 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\CalendarApiFeedType; +use App\Feed\EventDatabaseApiFeedType; use App\Feed\FeedTypeInterface; -use App\Feed\SourceType\Calendar\CalendarApiFeedType; -use App\Feed\SourceType\EventDatabase\EventDatabaseApiFeedType; -use App\Feed\SourceType\Koba\KobaFeedType; -use App\Feed\SourceType\Notified\NotifiedFeedType; -use App\Feed\SourceType\Rss\RssFeedType; -use App\Feed\SourceType\SparkleIO\SparkleIOFeedType; +use App\Feed\KobaFeedType; +use App\Feed\NotifiedFeedType; +use App\Feed\RssFeedType; +use App\Feed\SparkleIOFeedType; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; From 8c380bf08bbaca7a72b4b5dec37be0e85ce026cd Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:03:45 +0100 Subject: [PATCH 09/15] 3208: Removed todos --- docs/feed/feed-overview.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/feed/feed-overview.md b/docs/feed/feed-overview.md index 12b159b6b..adb2c0af5 100644 --- a/docs/feed/feed-overview.md +++ b/docs/feed/feed-overview.md @@ -25,9 +25,3 @@ For example: * OS2display has calendar templates that can show bookings or meetings. To show data from your specific calendar or booking system you can implement a "FeedSource" that fetches booking data from your source and normalizes it to match the calendar output model. - -@todo - -Slide -> Feed -> FeedSource -Auth -Caching From 7605b198e9e81aec8360de5c7b48bd2f9a84a9f8 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:14:07 +0100 Subject: [PATCH 10/15] 3208: Started on refactoring feed structure --- src/Feed/CalendarApiFeedType.php | 28 ++++---- src/Feed/ColiboFeedType.php | 67 ++++++++----------- .../OutputModel/Calendar/CalendarOutput.php | 18 +++++ src/Feed/OutputModel/Calendar/Event.php | 6 +- src/Feed/OutputModel/News/News.php | 18 +++++ src/Feed/OutputModel/News/NewsOutput.php | 18 +++++ src/Feed/OutputModel/OutputInterface.php | 8 +++ src/Feed/OutputModel/Rss/Readme.md | 7 -- src/Feed/RssFeedType.php | 42 +++++++----- 9 files changed, 131 insertions(+), 81 deletions(-) create mode 100644 src/Feed/OutputModel/Calendar/CalendarOutput.php create mode 100644 src/Feed/OutputModel/News/News.php create mode 100644 src/Feed/OutputModel/News/NewsOutput.php create mode 100644 src/Feed/OutputModel/OutputInterface.php delete mode 100644 src/Feed/OutputModel/Rss/Readme.md diff --git a/src/Feed/CalendarApiFeedType.php b/src/Feed/CalendarApiFeedType.php index 7f4c820d9..cb5717a7f 100644 --- a/src/Feed/CalendarApiFeedType.php +++ b/src/Feed/CalendarApiFeedType.php @@ -6,6 +6,7 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\OutputModel\Calendar\CalendarOutput; use App\Feed\OutputModel\Calendar\Event; use App\Feed\OutputModel\Calendar\Location; use App\Feed\OutputModel\Calendar\Resource; @@ -15,6 +16,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Uid\Ulid; use Symfony\Contracts\HttpClient\HttpClientInterface; +use function Amp\Iterator\toArray; /** * Supplies 'calendar' data based on 3 endpoints: @@ -116,21 +118,21 @@ public function getData(Feed $feed): array $title = trim($title); - $results[] = [ - 'id' => Ulid::generate(), - 'title' => $title, - 'startTime' => $event->startTimeTimestamp, - 'endTime' => $event->endTimeTimestamp, - 'resourceTitle' => $event->resourceDisplayName, - 'resourceId' => $event->resourceId, - ]; + $results[] = new Event( + Ulid::generate(), + $title, + $event->startTime, + $event->endTime, + $event->resourceTitle, + $event->resourceId, + ); } } // Sort bookings by start time. - usort($results, fn (array $a, array $b) => $a['startTime'] > $b['startTime'] ? 1 : -1); + usort($results, fn (Event $a, Event $b) => $a->startTime > $b->startTime ? 1 : -1); - return $results; + return (new CalendarOutput($results))->toArray(); } catch (\Throwable $throwable) { $this->logger->error('{code}: {message}', [ 'code' => $throwable->getCode(), @@ -387,10 +389,10 @@ private function loadEvents(): array // Filter out entries if they do not supply required data. if ( - !empty($newEntry->startTimeTimestamp) - && !empty($newEntry->endTimeTimestamp) + !empty($newEntry->startTime) + && !empty($newEntry->endTime) && !empty($newEntry->resourceId) - && !empty($newEntry->resourceDisplayName) + && !empty($newEntry->resourceTitle) ) { $carry[] = $newEntry; } diff --git a/src/Feed/ColiboFeedType.php b/src/Feed/ColiboFeedType.php index 65526ab69..e663dfdb8 100644 --- a/src/Feed/ColiboFeedType.php +++ b/src/Feed/ColiboFeedType.php @@ -7,6 +7,8 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; use App\Feed\OutputModel\ConfigOption; +use App\Feed\OutputModel\News\News; +use App\Feed\OutputModel\News\NewsOutput; use App\Feed\SourceType\Colibo\ApiClient; use App\Service\FeedService; use FeedIo\Feed\Item; @@ -67,43 +69,36 @@ public function getData(Feed $feed): array $configuration = $feed->getConfiguration(); $secrets = $feed->getFeedSource()?->getSecrets() ?? []; - $result = [ - 'title' => 'Intranet', - 'entries' => [], - ]; - $baseUri = $secrets['api_base_uri']; $recipients = $configuration['recipients'] ?? []; $publishers = $configuration['publishers'] ?? []; $pageSize = isset($configuration['page_size']) ? (int) $configuration['page_size'] : 10; if (empty($baseUri) || 0 === count($recipients)) { - return $result; + return []; } $feedSource = $feed->getFeedSource(); if (null === $feedSource) { - return $result; + return []; } + $results = []; + $entries = $this->apiClient->getFeedEntriesNews($feedSource, $recipients, $publishers, $pageSize); foreach ($entries as $entry) { - $item = new Item(); - $item->setTitle($entry->fields->title); + $categories = array_map(fn($recipient) => $recipient->name, $entry->recipients); + $title = $entry->fields->title; $crawler = new Crawler($entry->fields->description); $summary = ''; foreach ($crawler as $domElement) { $summary .= $domElement->textContent; } - $item->setSummary($summary); - - $item->setPublicId((string) $entry->id); $link = sprintf('%s/feedentry/%s', $baseUri, $entry->id); - $item->setLink($link); if (null !== $entry->fields->body) { $crawler = new Crawler($entry->fields->body); @@ -112,17 +107,15 @@ public function getData(Feed $feed): array $content .= $domElement->textContent; } } else { - $content = $item->getSummary(); + $content = $summary; } - $item->setContent($content); $updated = $entry->updated ?? $entry->publishDate; - $item->setLastModified(new \DateTime($updated)); + $lastModified = new \DateTime($updated); - $author = new Item\Author(); - $author->setName($entry->publisher->name); - $item->setAuthor($author); + $author = $entry->publisher->name; + $imageUrl = null; if (null !== $entry->fields->galleryItems) { try { $galleryItems = json_decode($entry->fields->galleryItems, true, 512, JSON_THROW_ON_ERROR); @@ -130,30 +123,25 @@ public function getData(Feed $feed): array $galleryItems = []; } - foreach ($galleryItems as $galleryItem) { - $media = new Item\Media(); - - $large = sprintf('%s/api/files/%s/thumbnail/large', $baseUri, $galleryItem['id']); - $media->setUrl($large); - - $small = sprintf('%s/api/files/%s/thumbnail/small', $baseUri, $galleryItem['id']); - $media->setThumbnail($small); - - $item->addMedia($media); - } - } - - foreach ($entry->recipients as $recipient) { - $category = new Category(); - $category->setLabel($recipient->name); - - $item->addCategory($category); + $imageUrl = count($galleryItems) > 0 ? sprintf('%s/api/files/%s/thumbnail/large', $baseUri, $galleryItems[0]['id']) : null; } - $result['entries'][] = $item->toArray(); + $publisher = ""; + + $results[] = new News( + $categories, + $title, + $content, + $summary, + $imageUrl, + $author, + $lastModified->format('c'), + $publisher, + $link, + ); } - return $result; + return (new NewsOutput($results))->toArray(); } public function getConfigOptions(Request $request, FeedSource $feedSource, string $name): ?array @@ -171,7 +159,6 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin case 'recipients': $id = self::getIdKey($feedSource); - /** @var CacheItemInterface $cacheItem */ $cacheItem = $this->feedsCache->getItem('colibo_feed_entry_groups_'.$id); if ($cacheItem->isHit()) { diff --git a/src/Feed/OutputModel/Calendar/CalendarOutput.php b/src/Feed/OutputModel/Calendar/CalendarOutput.php new file mode 100644 index 000000000..878d62b52 --- /dev/null +++ b/src/Feed/OutputModel/Calendar/CalendarOutput.php @@ -0,0 +1,18 @@ +events; + } +} diff --git a/src/Feed/OutputModel/Calendar/Event.php b/src/Feed/OutputModel/Calendar/Event.php index 90ec249d6..1c42c43fa 100644 --- a/src/Feed/OutputModel/Calendar/Event.php +++ b/src/Feed/OutputModel/Calendar/Event.php @@ -9,9 +9,9 @@ class Event public function __construct( public string $id, public string $title, - public int $startTimeTimestamp, - public int $endTimeTimestamp, + public int $startTime, + public int $endTime, public string $resourceId, - public string $resourceDisplayName, + public string $resourceTitle, ) {} } diff --git a/src/Feed/OutputModel/News/News.php b/src/Feed/OutputModel/News/News.php new file mode 100644 index 000000000..b6312d487 --- /dev/null +++ b/src/Feed/OutputModel/News/News.php @@ -0,0 +1,18 @@ +news; + } +} diff --git a/src/Feed/OutputModel/OutputInterface.php b/src/Feed/OutputModel/OutputInterface.php new file mode 100644 index 000000000..47e348892 --- /dev/null +++ b/src/Feed/OutputModel/OutputInterface.php @@ -0,0 +1,8 @@ +feedIo = new FeedIo($client, $this->logger); } @@ -46,32 +51,33 @@ public function getData(Feed $feed): array return []; } + $results = []; $feedResult = $this->feedIo->read($url); - $result = [ - 'title' => $feedResult->getFeed()->getTitle(), - 'entries' => [], - ]; /** @var Item $item */ foreach ($feedResult->getFeed() as $item) { - $entry = $item->toArray(); - - if (empty($entry['author'])) { - $entry['author'] = [ - 'name' => $feedResult->getFeed()->getTitle(), - ]; - } - - $result['entries'][] = $entry; - - if (!is_null($numberOfEntries) && count($result['entries']) >= $numberOfEntries) { + $medias = $item->getMedias(); + + $results[] = new News( + array_map(fn (CategoryInterface $category) => $category->getLabel(), $item->getCategories()), + $item->getTitle(), + $item->getContent(), + $item->getSummary(), + count($medias) > 0 ? $medias[0]->getUrl() : null, + $item->getAuthor()?->getName(), + $item->getLastModified(), + $feedResult->getFeed()->getTitle(), + $item->getLink(), + ); + + if (!is_null($numberOfEntries) && count($results) >= $numberOfEntries) { break; } } - return $result; + return (new NewsOutput($results))->toArray(); } catch (\Throwable $throwable) { - $this->logger->error($throwable->getCode().': '.$throwable->getMessage()); + $this->logger->error($throwable->getCode() . ': ' . $throwable->getMessage()); } return []; From 5931e03a644f9353d47d7d8ec994cd74eaa7882a Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:40:03 +0100 Subject: [PATCH 11/15] 3208: Adjustments to output --- src/Feed/CalendarApiFeedType.php | 1 - src/Feed/ColiboFeedType.php | 4 ++-- src/Feed/RssFeedType.php | 7 +++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Feed/CalendarApiFeedType.php b/src/Feed/CalendarApiFeedType.php index cb5717a7f..1cac7c6b9 100644 --- a/src/Feed/CalendarApiFeedType.php +++ b/src/Feed/CalendarApiFeedType.php @@ -16,7 +16,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Uid\Ulid; use Symfony\Contracts\HttpClient\HttpClientInterface; -use function Amp\Iterator\toArray; /** * Supplies 'calendar' data based on 3 endpoints: diff --git a/src/Feed/ColiboFeedType.php b/src/Feed/ColiboFeedType.php index e663dfdb8..a6f28219e 100644 --- a/src/Feed/ColiboFeedType.php +++ b/src/Feed/ColiboFeedType.php @@ -113,7 +113,7 @@ public function getData(Feed $feed): array $updated = $entry->updated ?? $entry->publishDate; $lastModified = new \DateTime($updated); - $author = $entry->publisher->name; + $author = $entry->author->firstName . ' ' . $entry->author->lastName; $imageUrl = null; if (null !== $entry->fields->galleryItems) { @@ -126,7 +126,7 @@ public function getData(Feed $feed): array $imageUrl = count($galleryItems) > 0 ? sprintf('%s/api/files/%s/thumbnail/large', $baseUri, $galleryItems[0]['id']) : null; } - $publisher = ""; + $publisher = $entry->publisher->name ?? null; $results[] = new News( $categories, diff --git a/src/Feed/RssFeedType.php b/src/Feed/RssFeedType.php index 7ab5e651e..34f45b2be 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -15,7 +15,6 @@ use Psr\Log\LoggerInterface; use Symfony\Component\HttpClient\HttplugClient; use Symfony\Component\HttpFoundation\Request; -use function Amp\Iterator\toArray; class RssFeedType implements FeedTypeInterface { @@ -59,13 +58,13 @@ public function getData(Feed $feed): array $medias = $item->getMedias(); $results[] = new News( - array_map(fn (CategoryInterface $category) => $category->getLabel(), $item->getCategories()), + array_map(fn (CategoryInterface $category) => $category->getLabel(), iterator_to_array($item->getCategories())), $item->getTitle(), - $item->getContent(), + strip_tags($item->getContent() ?? ''), $item->getSummary(), count($medias) > 0 ? $medias[0]->getUrl() : null, $item->getAuthor()?->getName(), - $item->getLastModified(), + $item->getLastModified()->format('c'), $feedResult->getFeed()->getTitle(), $item->getLink(), ); From bbec871a3d75d298692a592cbbc7e1047cae9c25 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:40:14 +0100 Subject: [PATCH 12/15] 3208: Added output models --- src/Feed/ColiboFeedType.php | 2 +- src/Feed/EventDatabaseApiFeedType.php | 52 +++++++++++--------- src/Feed/FeedOutputModels.php | 12 +++-- src/Feed/KobaFeedType.php | 23 ++++----- src/Feed/NotifiedFeedType.php | 44 +++++++++-------- src/Feed/OutputModel/Calendar/Event.php | 2 + src/Feed/OutputModel/Poster/Place.php | 15 ++++++ src/Feed/OutputModel/Poster/Poster.php | 25 ++++++++++ src/Feed/OutputModel/Poster/PosterOutput.php | 16 ++++++ src/Feed/OutputModel/Story/Story.php | 15 ++++++ src/Feed/OutputModel/Story/StoryOutput.php | 16 ++++++ src/Feed/RssFeedType.php | 2 +- src/Feed/SparkleIOFeedType.php | 32 ++++++------ src/Service/FeedService.php | 2 +- 14 files changed, 182 insertions(+), 76 deletions(-) create mode 100644 src/Feed/OutputModel/Poster/Place.php create mode 100644 src/Feed/OutputModel/Poster/Poster.php create mode 100644 src/Feed/OutputModel/Poster/PosterOutput.php create mode 100644 src/Feed/OutputModel/Story/Story.php create mode 100644 src/Feed/OutputModel/Story/StoryOutput.php diff --git a/src/Feed/ColiboFeedType.php b/src/Feed/ColiboFeedType.php index a6f28219e..264a84fe4 100644 --- a/src/Feed/ColiboFeedType.php +++ b/src/Feed/ColiboFeedType.php @@ -29,7 +29,7 @@ class ColiboFeedType implements FeedTypeInterface { public const int CACHE_TTL = 3600; - final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::RSS_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::NEWS_OUTPUT; public function __construct( private readonly FeedService $feedService, diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/EventDatabaseApiFeedType.php index 250c2e404..559d77604 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/EventDatabaseApiFeedType.php @@ -6,6 +6,9 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\OutputModel\Poster\Place; +use App\Feed\OutputModel\Poster\Poster; +use App\Feed\OutputModel\Poster\PosterOutput; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -96,33 +99,36 @@ public function getData(Feed $feed): array $baseUrl = parse_url((string) $decoded->event->{'url'}, PHP_URL_HOST); - $eventOccurrence = (object) [ - 'eventId' => $decoded->event->{'@id'}, - 'occurrenceId' => $decoded->{'@id'}, - 'ticketPurchaseUrl' => $decoded->event->{'ticketPurchaseUrl'}, - 'excerpt' => $decoded->event->{'excerpt'}, - 'name' => $decoded->event->{'name'}, - 'url' => $decoded->event->{'url'}, - 'baseUrl' => $baseUrl, - 'image' => $decoded->event->{'image'}, - 'startDate' => $decoded->{'startDate'}, - 'endDate' => $decoded->{'endDate'}, - 'ticketPriceRange' => $decoded->{'ticketPriceRange'}, - 'eventStatusText' => $decoded->{'eventStatusText'}, - ]; + $place = null; if (isset($decoded->place)) { - $eventOccurrence->place = (object) [ - 'name' => $decoded->place->name, - 'streetAddress' => $decoded->place->streetAddress, - 'addressLocality' => $decoded->place->addressLocality, - 'postalCode' => $decoded->place->postalCode, - 'image' => $decoded->place->image, - 'telephone' => $decoded->place->telephone, - ]; + $place = new Place( + $decoded->place->name, + $decoded->place->streetAddress, + $decoded->place->addressLocality, + $decoded->place->postalCode, + $decoded->place->image, + $decoded->place->telephone, + ); } - return [$eventOccurrence]; + $poster = new Poster( + $decoded->event->{'@id'}, + $decoded->{'@id'}, + $decoded->event->{'ticketPurchaseUrl'}, + $decoded->event->{'excerpt'}, + $decoded->event->{'name'}, + $decoded->event->{'url'}, + $baseUrl, + $decoded->event->{'image'}, + $decoded->{'startDate'}, + $decoded->{'endDate'}, + $decoded->{'ticketPriceRange'}, + $decoded->{'eventStatusText'}, + $place, + ); + + return (new PosterOutput([$poster]))->toArray(); } } } diff --git a/src/Feed/FeedOutputModels.php b/src/Feed/FeedOutputModels.php index 0458c7231..d9eb8e7a3 100644 --- a/src/Feed/FeedOutputModels.php +++ b/src/Feed/FeedOutputModels.php @@ -7,6 +7,8 @@ class FeedOutputModels { /** + * Output Model: src/Feed/OutputModel/Calendar/CalendarOutput.php + * * Data example: * [ * { @@ -24,11 +26,13 @@ class FeedOutputModels final public const string CALENDAR_OUTPUT = 'calendar'; /** - * TODO: Describe data structure. + * Output Model: src/Feed/OutputModel/Poster/PosterOutput.php */ final public const string POSTER_OUTPUT = 'poster'; /** + * Output Model: src/Feed/OutputModel/Story/StoryOutput.php + * * Data example: * [ * { @@ -47,9 +51,11 @@ class FeedOutputModels * } * ] */ - final public const string INSTAGRAM_OUTPUT = 'instagram'; + final public const string STORY_OUTPUT = 'instagram'; /** + * Output Model: src/Feed/OutputModel/News/NewsOutput.php + * * Data example: * * [ @@ -60,5 +66,5 @@ class FeedOutputModels * } * ] */ - final public const string RSS_OUTPUT = 'rss'; + final public const string NEWS_OUTPUT = 'rss'; } diff --git a/src/Feed/KobaFeedType.php b/src/Feed/KobaFeedType.php index aab326081..0a7a43148 100644 --- a/src/Feed/KobaFeedType.php +++ b/src/Feed/KobaFeedType.php @@ -6,6 +6,8 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\OutputModel\Calendar\CalendarOutput; +use App\Feed\OutputModel\Calendar\Event; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -100,22 +102,21 @@ public function getData(Feed $feed): array } } - $results[] = [ - 'id' => Ulid::generate(), - 'title' => $title, - 'description' => $booking['event_description'] ?? '', - 'startTime' => $booking['start_time'] ?? '', - 'endTime' => $booking['end_time'] ?? '', - 'resourceTitle' => $booking['resource_alias'] ?? '', - 'resourceId' => $booking['resource_id'] ?? '', - ]; + $results[] = new Event( + Ulid::generate(), + $title, + $booking['start_time'] ?? '', + $booking['end_time'] ?? '', + $booking['resource_alias'] ?? '', + $booking['resource_id'] ?? '', + ); } } // Sort bookings by start time. - usort($results, fn ($a, $b) => strcmp((string) $a['startTime'], (string) $b['startTime'])); + usort($results, fn (Event $a, Event $b) => $a->startTime > $b->startTime ? 1 : -1); - return $results; + return (new CalendarOutput($results))->toArray(); } catch (\Throwable $throwable) { $this->logger->error('{code}: {message}', [ 'code' => $throwable->getCode(), diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/NotifiedFeedType.php index 8de891b5e..92df93fcf 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/NotifiedFeedType.php @@ -6,6 +6,9 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\OutputModel\ConfigOption; +use App\Feed\OutputModel\Story\Story; +use App\Feed\OutputModel\Story\StoryOutput; use App\Service\FeedService; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -17,7 +20,7 @@ */ class NotifiedFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::INSTAGRAM_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::STORY_OUTPUT; final public const int REQUEST_TIMEOUT = 10; private const string BASE_URL = 'https://api.listen.notified.com'; @@ -52,19 +55,19 @@ public function getData(Feed $feed): array $feedItems = array_map(fn (array $item) => $this->getFeedItemObject($item), $data); - $result = []; + $results = []; // Check that image is accessible, otherwise leave out the feed element. foreach ($feedItems as $feedItem) { - $response = $this->client->request(Request::METHOD_HEAD, $feedItem['mediaUrl']); + // Only add item if the media can be retrieved. + $response = $this->client->request(Request::METHOD_HEAD, $feedItem->mediaUrl); $statusCode = $response->getStatusCode(); - if (200 == $statusCode) { - $result[] = $feedItem; + $results[] = $feedItem; } } - return $result; + return (new StoryOutput($results))->toArray(); } catch (\Throwable $throwable) { $this->logger->error('{code}: {message}', [ 'code' => $throwable->getCode(), @@ -113,11 +116,11 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin $data = $this->getSearchProfiles($token); - return array_map(fn (array $item) => [ - 'id' => Ulid::generate(), - 'title' => $item['name'] ?? '', - 'value' => $item['id'] ?? '', - ], $data); + return array_map(fn (array $item) => new ConfigOption( + Ulid::generate(), + $item['name'] ?? '', + $item['id'] ?? '', + ), $data); } } catch (\Throwable $throwable) { $this->logger->error('{code}: {message}', [ @@ -203,19 +206,18 @@ public function getSupportedFeedOutputType(): string /** * Parse feed item into object. */ - private function getFeedItemObject(array $item): array + private function getFeedItemObject(array $item): Story { $description = $item['description'] ?? null; - return [ - 'text' => $description, - 'textMarkup' => null !== $description ? $this->wrapTags($description) : null, - 'mediaUrl' => $item['mediaUrl'] ?? null, - // Video is not supported by the Notified Listen API. - 'videoUrl' => null, - 'username' => $item['sourceName'] ?? null, - 'createdTime' => $item['published'] ?? null, - ]; + return new Story( + $description, + null !== $description ? $this->wrapTags($description) : null, + $item['mediaUrl'] ?? null, + null, + $item['sourceName'] ?? null, + $item['published'] ?? null + ); } private function wrapTags(string $input): string diff --git a/src/Feed/OutputModel/Calendar/Event.php b/src/Feed/OutputModel/Calendar/Event.php index 1c42c43fa..8d8fe527d 100644 --- a/src/Feed/OutputModel/Calendar/Event.php +++ b/src/Feed/OutputModel/Calendar/Event.php @@ -9,7 +9,9 @@ class Event public function __construct( public string $id, public string $title, + // Unix timestamp. public int $startTime, + // Unix timestamp. public int $endTime, public string $resourceId, public string $resourceTitle, diff --git a/src/Feed/OutputModel/Poster/Place.php b/src/Feed/OutputModel/Poster/Place.php new file mode 100644 index 000000000..d80cf3226 --- /dev/null +++ b/src/Feed/OutputModel/Poster/Place.php @@ -0,0 +1,15 @@ +posters; + } +} diff --git a/src/Feed/OutputModel/Story/Story.php b/src/Feed/OutputModel/Story/Story.php new file mode 100644 index 000000000..af9119525 --- /dev/null +++ b/src/Feed/OutputModel/Story/Story.php @@ -0,0 +1,15 @@ +stories; + } +} diff --git a/src/Feed/RssFeedType.php b/src/Feed/RssFeedType.php index 34f45b2be..4d3331a4f 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/RssFeedType.php @@ -18,7 +18,7 @@ class RssFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::RSS_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::NEWS_OUTPUT; private readonly FeedIo $feedIo; diff --git a/src/Feed/SparkleIOFeedType.php b/src/Feed/SparkleIOFeedType.php index 67787a848..4627ee7de 100644 --- a/src/Feed/SparkleIOFeedType.php +++ b/src/Feed/SparkleIOFeedType.php @@ -6,6 +6,8 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\OutputModel\ConfigOption; +use App\Feed\OutputModel\Story\Story; use App\Service\FeedService; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; @@ -22,7 +24,7 @@ /** @deprecated The SparkleIO service is discontinued. */ class SparkleIOFeedType implements FeedTypeInterface { - final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::INSTAGRAM_OUTPUT; + final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::STORY_OUTPUT; final public const int REQUEST_TIMEOUT = 10; @@ -144,11 +146,11 @@ public function getConfigOptions(Request $request, FeedSource $feedSource, strin $feeds = []; foreach ($items as $item) { - $feeds[] = [ - 'id' => Ulid::generate(), - 'title' => $item->name ?? '', - 'value' => $item->id ?? '', - ]; + $feeds[] = new ConfigOption( + Ulid::generate(), + $item->name ?? '', + $item->id ?? '', + ); } return $feeds; @@ -252,16 +254,16 @@ private function getToken(string $baseUrl, string $clientId, string $clientSecre * * @return object */ - private function getFeedItemObject(object $item): object + private function getFeedItemObject(object $item): Story { - return (object) [ - 'text' => $item->text, - 'textMarkup' => null !== $item->text ? $this->wrapTags($item->text) : null, - 'mediaUrl' => $item->mediaUrl, - 'videoUrl' => $item->videoUrl, - 'username' => $item->username, - 'createdTime' => $item->createdTime, - ]; + return new Story( + $item->text, + null !== $item->text ? $this->wrapTags($item->text) : null, + $item->mediaUrl, + $item->videoUrl, + $item->username, + $item->createdTime, + ); } /** diff --git a/src/Service/FeedService.php b/src/Service/FeedService.php index fdd17039b..71eff7e89 100644 --- a/src/Service/FeedService.php +++ b/src/Service/FeedService.php @@ -100,7 +100,7 @@ public function getData(Feed $feed): ?array /** @var CacheItemInterface $cacheItem */ $cacheItem = $this->feedsCache->getItem($feedId); - if ($cacheItem->isHit()) { + if (false && $cacheItem->isHit()) { /** @var array $data */ $data = $cacheItem->get(); } else { From 05d6f93f3cb735737c0560f7230eb31b8122d8d2 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:38:18 +0100 Subject: [PATCH 13/15] 3208: Changed namespace for feed types. Added migration --- migrations/Version20241125085559.php | 41 +++++++++++++++++++ .../CalendarApi}/CalendarApiFeedType.php | 4 +- .../CalendarKoba}/KobaFeedType.php | 4 +- .../{Colibo => NewsColibo}/ApiClient.php | 3 +- .../ColiboException.php | 2 +- .../NewsColibo}/ColiboFeedType.php | 8 ++-- .../{Colibo => NewsColibo}/SecretsDTO.php | 2 +- .../{ => SourceType/NewsRss}/RssFeedType.php | 4 +- .../EventDatabaseApiFeedType.php | 4 +- .../StoryNotified}/NotifiedFeedType.php | 4 +- .../StorySparkleIO}/SparkleIOFeedType.php | 4 +- tests/Api/FeedSourceTest.php | 10 ++--- tests/Feed/NotifiedFeedTypeTest.php | 2 +- tests/Service/FeedServiceTest.php | 12 +++--- 14 files changed, 77 insertions(+), 27 deletions(-) create mode 100644 migrations/Version20241125085559.php rename src/Feed/{ => SourceType/CalendarApi}/CalendarApiFeedType.php (99%) rename src/Feed/{ => SourceType/CalendarKoba}/KobaFeedType.php (98%) rename src/Feed/SourceType/{Colibo => NewsColibo}/ApiClient.php (99%) rename src/Feed/SourceType/{Colibo => NewsColibo}/ColiboException.php (72%) rename src/Feed/{ => SourceType/NewsColibo}/ColiboFeedType.php (98%) rename src/Feed/SourceType/{Colibo => NewsColibo}/SecretsDTO.php (95%) rename src/Feed/{ => SourceType/NewsRss}/RssFeedType.php (97%) rename src/Feed/{ => SourceType/PosterEventDatabaseApi}/EventDatabaseApiFeedType.php (99%) rename src/Feed/{ => SourceType/StoryNotified}/NotifiedFeedType.php (98%) rename src/Feed/{ => SourceType/StorySparkleIO}/SparkleIOFeedType.php (98%) diff --git a/migrations/Version20241125085559.php b/migrations/Version20241125085559.php new file mode 100644 index 000000000..f2efc7a99 --- /dev/null +++ b/migrations/Version20241125085559.php @@ -0,0 +1,41 @@ +addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\CalendarApi\\\\CalendarApiFeedType" WHERE feed_type = "App\\\\Feed\\\\CalendarApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\CalendarKoba\\\\KobaFeedType" WHERE feed_type = "App\\\\Feed\\\\KobaFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\NewsColibo\\\\ColiboFeedType" WHERE feed_type = "App\\\\Feed\\\\ColiboFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\NewsRss\\\\RssFeedType" WHERE feed_type = "App\\\\Feed\\\\RssFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\PosterEventDatabase\\\\EventDatabaseApiFeedType" WHERE feed_type = "App\\\\Feed\\\\EventDatabaseApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\StoryNotified\\\\NotifiedFeedType" WHERE feed_type = "App\\\\Feed\\\\NotifiedFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SourceType\\\\StorySparkleIO\\\\SparkleIOFeedType" WHERE feed_type = "App\\\\Feed\\\\SparkleIOFeedType"'); + } + + public function down(Schema $schema): void + { + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\CalendarApiFeedType" WHERE feed_type = "App\\\\Feed\\\\SourceType\\\\CalendarApi\\\\CalendarApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\KobaFeedType" WHERE feed_type = "App\\\\Feed\\\\SourceType\\\\CalendarKoba\\\\KobaFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\ColiboFeedType" WHERE feed_type = "App\\\\Feed\\\\SourceType\\\\NewsColibo\\\\ColiboFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\RssFeedType" WHERE feed_type = "App\\\\Feed\\\\SourceType\\\\NewsRss\\\\RssFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\EventDatabaseApiFeedType" WHERE feed_type = "App\\\\Feed\\\\SourceType\\\\PosterEventDatabase\\\\EventDatabaseApiFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\NotifiedFeedType" WHERE feed_type = "App\\\\Feed\\\\SourceType\\\\StoryNotified\\\\NotifiedFeedType"'); + $this->addSql('UPDATE feed_source SET feed_type = "App\\\\Feed\\\\SparkleIOFeedType" WHERE feed_type = "App\\\\Feed\\\\SourceType\\\\StorySparkleIO\\\\SparkleIOFeedType"'); + } +} diff --git a/src/Feed/CalendarApiFeedType.php b/src/Feed/SourceType/CalendarApi/CalendarApiFeedType.php similarity index 99% rename from src/Feed/CalendarApiFeedType.php rename to src/Feed/SourceType/CalendarApi/CalendarApiFeedType.php index 1cac7c6b9..151644f3f 100644 --- a/src/Feed/CalendarApiFeedType.php +++ b/src/Feed/SourceType/CalendarApi/CalendarApiFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\CalendarApi; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\Calendar\CalendarOutput; use App\Feed\OutputModel\Calendar\Event; use App\Feed\OutputModel\Calendar\Location; diff --git a/src/Feed/KobaFeedType.php b/src/Feed/SourceType/CalendarKoba/KobaFeedType.php similarity index 98% rename from src/Feed/KobaFeedType.php rename to src/Feed/SourceType/CalendarKoba/KobaFeedType.php index 0a7a43148..54f62f4d0 100644 --- a/src/Feed/KobaFeedType.php +++ b/src/Feed/SourceType/CalendarKoba/KobaFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\CalendarKoba; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\Calendar\CalendarOutput; use App\Feed\OutputModel\Calendar\Event; use App\Service\FeedService; diff --git a/src/Feed/SourceType/Colibo/ApiClient.php b/src/Feed/SourceType/NewsColibo/ApiClient.php similarity index 99% rename from src/Feed/SourceType/Colibo/ApiClient.php rename to src/Feed/SourceType/NewsColibo/ApiClient.php index ae98dab19..fb67bdd97 100644 --- a/src/Feed/SourceType/Colibo/ApiClient.php +++ b/src/Feed/SourceType/NewsColibo/ApiClient.php @@ -2,10 +2,9 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Colibo; +namespace App\Feed\SourceType\NewsColibo; use App\Entity\Tenant\FeedSource; -use App\Feed\ColiboFeedType; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; diff --git a/src/Feed/SourceType/Colibo/ColiboException.php b/src/Feed/SourceType/NewsColibo/ColiboException.php similarity index 72% rename from src/Feed/SourceType/Colibo/ColiboException.php rename to src/Feed/SourceType/NewsColibo/ColiboException.php index 6ca7f2c53..1d1551579 100644 --- a/src/Feed/SourceType/Colibo/ColiboException.php +++ b/src/Feed/SourceType/NewsColibo/ColiboException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Colibo; +namespace App\Feed\SourceType\NewsColibo; use App\Feed\FeedException; diff --git a/src/Feed/ColiboFeedType.php b/src/Feed/SourceType/NewsColibo/ColiboFeedType.php similarity index 98% rename from src/Feed/ColiboFeedType.php rename to src/Feed/SourceType/NewsColibo/ColiboFeedType.php index 264a84fe4..e87f7c55c 100644 --- a/src/Feed/ColiboFeedType.php +++ b/src/Feed/SourceType/NewsColibo/ColiboFeedType.php @@ -2,18 +2,16 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\NewsColibo; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\ConfigOption; use App\Feed\OutputModel\News\News; use App\Feed\OutputModel\News\NewsOutput; -use App\Feed\SourceType\Colibo\ApiClient; use App\Service\FeedService; -use FeedIo\Feed\Item; -use FeedIo\Feed\Node\Category; -use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Feed/SourceType/Colibo/SecretsDTO.php b/src/Feed/SourceType/NewsColibo/SecretsDTO.php similarity index 95% rename from src/Feed/SourceType/Colibo/SecretsDTO.php rename to src/Feed/SourceType/NewsColibo/SecretsDTO.php index 761e86ffb..6b0f2f49d 100644 --- a/src/Feed/SourceType/Colibo/SecretsDTO.php +++ b/src/Feed/SourceType/NewsColibo/SecretsDTO.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Feed\SourceType\Colibo; +namespace App\Feed\SourceType\NewsColibo; use App\Entity\Tenant\FeedSource; diff --git a/src/Feed/RssFeedType.php b/src/Feed/SourceType/NewsRss/RssFeedType.php similarity index 97% rename from src/Feed/RssFeedType.php rename to src/Feed/SourceType/NewsRss/RssFeedType.php index 4d3331a4f..2e1cedb2c 100644 --- a/src/Feed/RssFeedType.php +++ b/src/Feed/SourceType/NewsRss/RssFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\NewsRss; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\News\News; use App\Feed\OutputModel\News\NewsOutput; use FeedIo\Adapter\Http\Client; diff --git a/src/Feed/EventDatabaseApiFeedType.php b/src/Feed/SourceType/PosterEventDatabaseApi/EventDatabaseApiFeedType.php similarity index 99% rename from src/Feed/EventDatabaseApiFeedType.php rename to src/Feed/SourceType/PosterEventDatabaseApi/EventDatabaseApiFeedType.php index 559d77604..a74ba3dac 100644 --- a/src/Feed/EventDatabaseApiFeedType.php +++ b/src/Feed/SourceType/PosterEventDatabaseApi/EventDatabaseApiFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\PosterEventDatabaseApi; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\Poster\Place; use App\Feed\OutputModel\Poster\Poster; use App\Feed\OutputModel\Poster\PosterOutput; diff --git a/src/Feed/NotifiedFeedType.php b/src/Feed/SourceType/StoryNotified/NotifiedFeedType.php similarity index 98% rename from src/Feed/NotifiedFeedType.php rename to src/Feed/SourceType/StoryNotified/NotifiedFeedType.php index 92df93fcf..089531764 100644 --- a/src/Feed/NotifiedFeedType.php +++ b/src/Feed/SourceType/StoryNotified/NotifiedFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\StoryNotified; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\ConfigOption; use App\Feed\OutputModel\Story\Story; use App\Feed\OutputModel\Story\StoryOutput; diff --git a/src/Feed/SparkleIOFeedType.php b/src/Feed/SourceType/StorySparkleIO/SparkleIOFeedType.php similarity index 98% rename from src/Feed/SparkleIOFeedType.php rename to src/Feed/SourceType/StorySparkleIO/SparkleIOFeedType.php index 4627ee7de..7d77fa51f 100644 --- a/src/Feed/SparkleIOFeedType.php +++ b/src/Feed/SourceType/StorySparkleIO/SparkleIOFeedType.php @@ -2,10 +2,12 @@ declare(strict_types=1); -namespace App\Feed; +namespace App\Feed\SourceType\StorySparkleIO; use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; +use App\Feed\FeedOutputModels; +use App\Feed\FeedTypeInterface; use App\Feed\OutputModel\ConfigOption; use App\Feed\OutputModel\Story\Story; use App\Service\FeedService; diff --git a/tests/Api/FeedSourceTest.php b/tests/Api/FeedSourceTest.php index f72e32618..23fc527cf 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::class, + 'feedType' => \App\Feed\SourceType\PosterEventDatabaseApi\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::class, + 'feedType' => \App\Feed\SourceType\PosterEventDatabaseApi\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::class, + 'feedType' => \App\Feed\SourceType\PosterEventDatabaseApi\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::class, + 'feedType' => \App\Feed\SourceType\PosterEventDatabaseApi\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::class, + 'feedType' => \App\Feed\SourceType\PosterEventDatabaseApi\EventDatabaseApiFeedType::class, 'secrets' => [ 'host' => 'https://www.test.dk', ], diff --git a/tests/Feed/NotifiedFeedTypeTest.php b/tests/Feed/NotifiedFeedTypeTest.php index 5a7ef76c5..e76c8d5b0 100644 --- a/tests/Feed/NotifiedFeedTypeTest.php +++ b/tests/Feed/NotifiedFeedTypeTest.php @@ -4,7 +4,7 @@ namespace App\Tests\Feed; -use App\Feed\NotifiedFeedType; +use App\Feed\SourceType\StoryNotified\NotifiedFeedType; use App\Repository\FeedSourceRepository; use App\Repository\SlideRepository; use App\Service\FeedService; diff --git a/tests/Service/FeedServiceTest.php b/tests/Service/FeedServiceTest.php index 9cfd24b27..6694b4315 100644 --- a/tests/Service/FeedServiceTest.php +++ b/tests/Service/FeedServiceTest.php @@ -6,13 +6,13 @@ use App\Entity\Tenant\Feed; use App\Entity\Tenant\FeedSource; -use App\Feed\CalendarApiFeedType; -use App\Feed\EventDatabaseApiFeedType; use App\Feed\FeedTypeInterface; -use App\Feed\KobaFeedType; -use App\Feed\NotifiedFeedType; -use App\Feed\RssFeedType; -use App\Feed\SparkleIOFeedType; +use App\Feed\SourceType\CalendarApi\CalendarApiFeedType; +use App\Feed\SourceType\CalendarKoba\KobaFeedType; +use App\Feed\SourceType\NewsRss\RssFeedType; +use App\Feed\SourceType\PosterEventDatabaseApi\EventDatabaseApiFeedType; +use App\Feed\SourceType\StoryNotified\NotifiedFeedType; +use App\Feed\SourceType\StorySparkleIO\SparkleIOFeedType; use App\Service\FeedService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; From 71aff2254bfd99c248c99fd2e8f3f6b538860fc9 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:29:33 +0100 Subject: [PATCH 14/15] 3208: Fixed service namespace --- config/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/services.yaml b/config/services.yaml index 6f1a0b8c7..975641c29 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -51,7 +51,7 @@ services: Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler' Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler' - App\Feed\CalendarApiFeedType: + App\Feed\SourceType\CalendarApi\CalendarApiFeedType: arguments: $locationEndpoint: '%env(string:CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT)%' $resourceEndpoint: '%env(string:CALENDAR_API_FEED_SOURCE_RESOURCE_ENDPOINT)%' From 866720954da9a882de4789a7d67ca5c8b9fc1646 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:37:24 +0100 Subject: [PATCH 15/15] 3208: Renamed output models --- migrations/Version20241125085560.php | 31 ++++++++++++++++++++++++++++ src/Feed/FeedOutputModels.php | 4 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 migrations/Version20241125085560.php diff --git a/migrations/Version20241125085560.php b/migrations/Version20241125085560.php new file mode 100644 index 000000000..869af5391 --- /dev/null +++ b/migrations/Version20241125085560.php @@ -0,0 +1,31 @@ +addSql('UPDATE feed_source SET supported_feed_output_type = "news" WHERE supported_feed_output_type = "rss"'); + $this->addSql('UPDATE feed_source SET supported_feed_output_type = "story" WHERE supported_feed_output_type = "instagram"'); + } + + public function down(Schema $schema): void + { + $this->addSql('UPDATE feed_source SET supported_feed_output_type = "rss" WHERE supported_feed_output_type = "news"'); + $this->addSql('UPDATE feed_source SET supported_feed_output_type = "instagram" WHERE supported_feed_output_type = "story"'); + } +} diff --git a/src/Feed/FeedOutputModels.php b/src/Feed/FeedOutputModels.php index d9eb8e7a3..43baf9b36 100644 --- a/src/Feed/FeedOutputModels.php +++ b/src/Feed/FeedOutputModels.php @@ -51,7 +51,7 @@ class FeedOutputModels * } * ] */ - final public const string STORY_OUTPUT = 'instagram'; + final public const string STORY_OUTPUT = 'story'; /** * Output Model: src/Feed/OutputModel/News/NewsOutput.php @@ -66,5 +66,5 @@ class FeedOutputModels * } * ] */ - final public const string NEWS_OUTPUT = 'rss'; + final public const string NEWS_OUTPUT = 'news'; }