diff --git a/appinfo/routes.php b/appinfo/routes.php index 85e2a3b..ec0fc03 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -13,7 +13,8 @@ declare(strict_types=1); return [ 'routes' => [ ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], - ['name' => 'toplist#index', 'url' => '/toplist/{count}', 'verb' => 'GET'], + ['name' => 'fetch#index', 'url' => '/fetch/{uri}', 'verb' => 'GET'], ['name' => 'search#index', 'url' => '/search/{value}', 'verb' => 'GET'], + ['name' => 'toplist#index', 'url' => '/toplist/{count}', 'verb' => 'GET'], ], ]; diff --git a/composer.json b/composer.json index 1391d21..90c989f 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "AGPL-3.0-or-later", "require-dev": { "nextcloud/ocp": "^27.0.2", - "psalm/phar": "^5.14.1", + "psalm/phar": "^5.15.0", "nextcloud/coding-standard": "^1.1.1" }, "scripts": { diff --git a/composer.lock b/composer.lock index cb655c6..7309866 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": "3fa8e5aac39e7c385a793eff9a78e763", + "content-hash": "c0c30dda23dd29e41502385edf3227da", "packages": [], "packages-dev": [ { @@ -94,16 +94,16 @@ }, { "name": "php-cs-fixer/shim", - "version": "v3.22.0", + "version": "v3.23.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "f6692934a6d1fe40fd8bc3339487490baa4a6700" + "reference": "ddca9b342374087121e44cca3b7d8aca8f121fa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/f6692934a6d1fe40fd8bc3339487490baa4a6700", - "reference": "f6692934a6d1fe40fd8bc3339487490baa4a6700", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/ddca9b342374087121e44cca3b7d8aca8f121fa7", + "reference": "ddca9b342374087121e44cca3b7d8aca8f121fa7", "shasum": "" }, "require": { @@ -140,9 +140,9 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.22.0" + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.23.0" }, - "time": "2023-07-16T23:08:49+00:00" + "time": "2023-08-14T12:28:46+00:00" }, { "name": "psalm/phar", diff --git a/lib/Controller/FetchController.php b/lib/Controller/FetchController.php new file mode 100644 index 0000000..2a06b47 --- /dev/null +++ b/lib/Controller/FetchController.php @@ -0,0 +1,47 @@ +podcastDataReader->getCachedOrFetchPodcastData($uri, $this->userService->getUserUID()); + + if ($podcastData) { + return new JSONResponse($podcastData->toArray()); + } + + $client = $this->clientService->newClient(); + $feed = $client->get($uri); + $statusCode = $feed->getStatusCode(); + + if ($statusCode < 200 || $statusCode >= 300) { + throw new \ErrorException("Web request returned non-2xx status code: {$statusCode}"); + } + + $podcastData = PodcastData::parseRssXml((string) $feed->getBody()); + + return new JSONResponse($podcastData->toArray()); + } +} diff --git a/lib/Controller/SearchController.php b/lib/Controller/SearchController.php index f9d91f2..4abdf68 100644 --- a/lib/Controller/SearchController.php +++ b/lib/Controller/SearchController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace OCA\RePod\Controller; +use OCA\GPodderSync\Core\PodcastData\PodcastData; use OCA\RePod\AppInfo\Application; use OCA\RePod\Service\FyydService; use OCA\RePod\Service\ItunesService; @@ -36,8 +37,8 @@ class SearchController extends Controller } } - usort($podcasts, fn (array $a, array $b) => new \DateTime((string) $b['lastpub']) <=> new \DateTime((string) $a['lastpub'])); - $podcasts = array_intersect_key($podcasts, array_unique(array_map(fn (array $feed) => $feed['url'], $podcasts))); + usort($podcasts, fn (PodcastData $a, PodcastData $b) => $a->getFetchedAtUnix() <=> $b->getFetchedAtUnix()); + $podcasts = array_intersect_key($podcasts, array_unique(array_map(fn (PodcastData $feed) => $feed->getLink(), $podcasts))); return new JSONResponse($podcasts); } diff --git a/lib/Service/FyydService.php b/lib/Service/FyydService.php index 2feda43..cfb5050 100644 --- a/lib/Service/FyydService.php +++ b/lib/Service/FyydService.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace OCA\RePod\Service; +use OCA\GPodderSync\Core\PodcastData\PodcastData; use OCP\Http\Client\IClientService; use OCP\IUserSession; use OCP\L10N\IFactory; use Psr\Log\LoggerInterface; -/** - * @psalm-import-type Podcast from IProvider - */ class FyydService implements IProvider { private const BASE_URL = 'https://api.fyyd.de/0.2/'; @@ -42,20 +40,14 @@ class FyydService implements IProvider if (array_key_exists('data', $json) && is_array($json['data'])) { /** @var string[] $feed */ foreach ($json['data'] as $feed) { - $podcasts[] = [ - 'id' => $feed['id'], - 'provider' => 'fyyd', - 'website' => $feed['htmlURL'], - 'description' => $feed['description'], - 'title' => $feed['title'], - 'author' => $feed['author'], - 'url' => $feed['xmlURL'], - 'position_last_week' => $feed['rank'], - 'mygpo_link' => $feed['url_fyyd'], - 'logo_url' => $feed['imgURL'], - 'lastpub' => $feed['lastpub'], - 'episode_count' => $feed['episode_count'], - ]; + $podcasts[] = new PodcastData( + $feed['title'], + $feed['author'], + $feed['xmlURL'], + $feed['description'], + $feed['imgURL'], + strtotime($feed['lastpub']) + ); } } @@ -63,7 +55,7 @@ class FyydService implements IProvider } /** - * @return Podcast[] + * @return PodcastData[] */ public function hot(int $count = 10): array { @@ -95,20 +87,14 @@ class FyydService implements IProvider if (array_key_exists('data', $postCastJson) && is_array($postCastJson['data'])) { /** @var string[] $feed */ foreach ($postCastJson['data'] as $feed) { - $podcasts[] = [ - 'id' => $feed['id'], - 'provider' => 'fyyd', - 'website' => $feed['htmlURL'], - 'description' => $feed['description'], - 'title' => $feed['title'], - 'author' => $feed['author'], - 'url' => $feed['xmlURL'], - 'position_last_week' => $feed['rank'], - 'mygpo_link' => $feed['url_fyyd'], - 'logo_url' => $feed['imgURL'], - 'lastpub' => $feed['lastpub'], - 'episode_count' => $feed['episode_count'], - ]; + $podcasts[] = new PodcastData( + $feed['title'], + $feed['author'], + $feed['xmlURL'], + $feed['description'], + $feed['imgURL'], + strtotime($feed['lastpub']) + ); } } diff --git a/lib/Service/IProvider.php b/lib/Service/IProvider.php index bbe5290..3476fc4 100644 --- a/lib/Service/IProvider.php +++ b/lib/Service/IProvider.php @@ -4,26 +4,12 @@ declare(strict_types=1); namespace OCA\RePod\Service; -/** - * @psalm-type Podcast = array{ - * id: string, - * provider: string, - * website: string, - * description: string, - * title: string, - * author: string, - * url: string, - * position_last_week: ?string, - * mygpo_link: string, - * logo_url: string, - * lastpub: string, - * episode_count: string - * } - */ +use OCA\GPodderSync\Core\PodcastData\PodcastData; + interface IProvider { /** - * @return Podcast[] + * @return PodcastData[] */ public function search(string $value): array; } diff --git a/lib/Service/ItunesService.php b/lib/Service/ItunesService.php index b86bdd3..ad08ebc 100644 --- a/lib/Service/ItunesService.php +++ b/lib/Service/ItunesService.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace OCA\RePod\Service; +use OCA\GPodderSync\Core\PodcastData\PodcastData; use OCP\Http\Client\IClientService; class ItunesService implements IProvider @@ -33,20 +34,14 @@ class ItunesService implements IProvider if (array_key_exists('results', $json) && is_array($json['results'])) { /** @var string[] $feed */ foreach ($json['results'] as $feed) { - $podcasts[] = [ - 'id' => $feed['id'], - 'provider' => 'itunes', - 'website' => $feed['trackViewUrl'], - 'description' => $feed['primaryGenreName'], - 'title' => $feed['trackName'], - 'author' => $feed['artistName'], - 'url' => $feed['feedUrl'], - 'position_last_week' => null, - 'mygpo_link' => $feed['trackViewUrl'], - 'logo_url' => $feed['artworkUrl600'], - 'lastpub' => $feed['releaseDate'], - 'episode_count' => $feed['trackCount'], - ]; + $podcasts[] = new PodcastData( + $feed['trackName'], + $feed['artistName'], + $feed['feedUrl'], + $feed['primaryGenreName'], + $feed['artworkUrl600'], + strtotime($feed['releaseDate']) + ); } } diff --git a/lib/Service/UserService.php b/lib/Service/UserService.php index 4906371..5d53e05 100644 --- a/lib/Service/UserService.php +++ b/lib/Service/UserService.php @@ -15,6 +15,13 @@ class UserService ) { } + public function getUserUID(): string + { + $user = $this->userSession->getUser(); + + return $user ? $user->getUID() : ''; + } + public function getIsoCode(): string { return $this->l10n->getUserLanguage($this->userSession->getUser()); diff --git a/psalm.xml b/psalm.xml index e55d59b..7b74fb9 100644 --- a/psalm.xml +++ b/psalm.xml @@ -6,7 +6,6 @@ findUnusedCode="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" - xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > diff --git a/stubs/OCA/GPodderSync/Core/EpisodeAction/EpisodeAction.php b/stubs/OCA/GPodderSync/Core/EpisodeAction/EpisodeAction.php index 655dcd5..a9af0d6 100644 --- a/stubs/OCA/GPodderSync/Core/EpisodeAction/EpisodeAction.php +++ b/stubs/OCA/GPodderSync/Core/EpisodeAction/EpisodeAction.php @@ -19,6 +19,19 @@ namespace OCA\GPodderSync\Core\EpisodeAction; */ class EpisodeAction { + public function __construct( + private string $podcast, + private string $episode, + private string $action, + private string $timestamp, + private int $started, + private int $position, + private int $total, + private ?string $guid, + private ?int $id + ) { + } + /** * @return string */ diff --git a/stubs/OCA/GPodderSync/Core/PodcastData/PodcastData.php b/stubs/OCA/GPodderSync/Core/PodcastData/PodcastData.php index 1bfefe2..2ab8c48 100644 --- a/stubs/OCA/GPodderSync/Core/PodcastData/PodcastData.php +++ b/stubs/OCA/GPodderSync/Core/PodcastData/PodcastData.php @@ -17,6 +17,17 @@ namespace OCA\GPodderSync\Core\PodcastData; */ class PodcastData implements \JsonSerializable { + public function __construct( + private ?string $title, + private ?string $author, + private ?string $link, + private ?string $description, + private ?string $imageUrl, + private int $fetchedAtUnix, + private ?string $imageBlob = null + ) { + } + /** * @return PodcastData * @throws \Exception if the XML data could not be parsed diff --git a/stubs/OCA/GPodderSync/Core/PodcastData/PodcastMetrics.php b/stubs/OCA/GPodderSync/Core/PodcastData/PodcastMetrics.php index ce7e0c9..3481fed 100644 --- a/stubs/OCA/GPodderSync/Core/PodcastData/PodcastMetrics.php +++ b/stubs/OCA/GPodderSync/Core/PodcastData/PodcastMetrics.php @@ -13,6 +13,13 @@ namespace OCA\GPodderSync\Core\PodcastData; */ class PodcastMetrics implements \JsonSerializable { + public function __construct( + private string $url, + private int $listenedSeconds = 0, + private ?PodcastActionCounts $actionCounts = null + ) { + } + /** * @return string */ diff --git a/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChange.php b/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChange.php index 7ef7f85..02857fc 100644 --- a/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChange.php +++ b/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChange.php @@ -6,6 +6,12 @@ namespace OCA\GPodderSync\Core\SubscriptionChange; class SubscriptionChange { + public function __construct( + private string $url, + private bool $isSubscribed + ) { + } + /** * @return bool */ diff --git a/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChangeRequestParser.php b/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChangeRequestParser.php index eef272f..5b83720 100644 --- a/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChangeRequestParser.php +++ b/stubs/OCA/GPodderSync/Core/SubscriptionChange/SubscriptionChangeRequestParser.php @@ -6,6 +6,10 @@ namespace OCA\GPodderSync\Core\SubscriptionChange; class SubscriptionChangeRequestParser { + public function __construct(private SubscriptionChangesReader $subscriptionChangeReader) + { + } + /** * @return SubscriptionChange[] */