Refacto API to be pseudo compatible with gpodder
All checks were successful
repod / nextcloud (push) Successful in 1m3s
repod / nodejs (push) Successful in 1m20s

This commit is contained in:
Michel Roux 2023-08-01 23:18:37 +02:00
parent 6d6f8b4cf7
commit 098db9f09e
15 changed files with 5101 additions and 1377 deletions

View File

@ -13,7 +13,7 @@ declare(strict_types=1);
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'top#index', 'url' => '/top', 'verb' => 'GET'],
['name' => 'toplist#index', 'url' => '/toplist/{count}', 'verb' => 'GET'],
['name' => 'search#index', 'url' => '/search/{value}', 'verb' => 'GET'],
],
];

View File

@ -5,7 +5,7 @@
"license": "AGPL-3.0-or-later",
"require-dev": {
"nextcloud/ocp": "^27.0.1",
"psalm/phar": "^5.13.1",
"psalm/phar": "^5.14.1",
"nextcloud/coding-standard": "^1.1.1"
},
"scripts": {

2
composer.lock generated
View File

@ -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": "a5d69457fcb9b9b73dd75bcd2b7c9791",
"content-hash": "817269c30922145fbaf4270b2cb22d8e",
"packages": [],
"packages-dev": [
{

View File

@ -36,8 +36,8 @@ class SearchController extends Controller
}
}
usort($podcasts, fn (array $a, array $b) => new \DateTime((string) $b['last_pub']) <=> new \DateTime((string) $a['last_pub']));
$podcasts = array_intersect_key($podcasts, array_unique(array_map(fn (array $feed) => $feed['feed_url'], $podcasts)));
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)));
return new JSONResponse($podcasts);
}

View File

@ -11,7 +11,7 @@ use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
class TopController extends Controller
class ToplistController extends Controller
{
public function __construct(
IRequest $request,
@ -27,10 +27,7 @@ class TopController extends Controller
public function index(): JSONResponse
{
try {
$response = $this->fyydService->hot();
$json = (array) json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
return new JSONResponse($json, $response->getStatusCode());
return new JSONResponse($this->fyydService->hot());
} catch (\Exception $e) {
return new JSONResponse([$e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}

View File

@ -5,11 +5,13 @@ declare(strict_types=1);
namespace OCA\RePod\Service;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
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/';
@ -41,15 +43,18 @@ class FyydService implements IProvider
/** @var string[] $feed */
foreach ($json['data'] as $feed) {
$podcasts[] = [
'provider' => 'fyyd',
'id' => $feed['id'],
'provider' => 'fyyd',
'website' => $feed['htmlURL'],
'description' => $feed['description'],
'title' => $feed['title'],
'author' => $feed['author'],
'image' => $feed['imgURL'],
'provider_url' => $feed['htmlURL'],
'feed_url' => $feed['xmlURL'],
'last_pub' => $feed['lastpub'],
'nb_episodes' => $feed['episode_count'],
'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'],
];
}
}
@ -57,8 +62,12 @@ class FyydService implements IProvider
return $podcasts;
}
public function hot(): IResponse
/**
* @return Podcast[]
*/
public function hot(int $count = 10): array
{
$podcasts = [];
$language = 'en';
$userLang = $this->userService->getLangCode();
@ -75,10 +84,34 @@ class FyydService implements IProvider
$podcastClient = $this->clientService->newClient();
return $podcastClient->get(self::BASE_URL.'feature/podcast/hot', [
$podcastResponse = $podcastClient->get(self::BASE_URL.'feature/podcast/hot', [
'query' => [
'count' => $count,
'language' => $language,
],
]);
$postCastJson = (array) json_decode((string) $podcastResponse->getBody(), true, flags: JSON_THROW_ON_ERROR);
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'],
];
}
}
return $podcasts;
}
}

View File

@ -4,20 +4,26 @@ 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
* }
*/
interface IProvider
{
/**
* @return array<array{
* provider: string,
* id: string,
* title: string,
* author: string,
* image: string,
* provider_url: string,
* feed_url: string,
* last_pub: string,
* nb_episodes: string
* }>
* @return Podcast[]
*/
public function search(string $value): array;
}

View File

@ -34,15 +34,18 @@ class ItunesService implements IProvider
/** @var string[] $feed */
foreach ($json['results'] as $feed) {
$podcasts[] = [
'provider' => 'itunes',
'id' => $feed['id'],
'provider' => 'itunes',
'website' => $feed['trackViewUrl'],
'description' => $feed['primaryGenreName'],
'title' => $feed['trackName'],
'author' => $feed['artistName'],
'image' => $feed['artworkUrl600'],
'provider_url' => $feed['trackViewUrl'],
'feed_url' => $feed['feedUrl'],
'last_pub' => $feed['releaseDate'],
'nb_episodes' => $feed['trackCount'],
'url' => $feed['feedUrl'],
'position_last_week' => null,
'mygpo_link' => $feed['trackViewUrl'],
'logo_url' => $feed['artworkUrl600'],
'lastpub' => $feed['releaseDate'],
'episode_count' => $feed['trackCount'],
];
}
}

6302
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,7 @@
"@nextcloud/babel-config": "^1.0.0",
"@nextcloud/browserslist-config": "^2.3.0",
"@nextcloud/eslint-config": "^8.2.1",
"@nextcloud/stylelint-config": "^2.3.0",
"@nextcloud/stylelint-config": "^2.3.1",
"@nextcloud/webpack-vue-config": "^5.5.1"
}
}

View File

@ -1,7 +1,7 @@
<template>
<NcAppNavigationItem :loading="loading"
:title="feed ? feed.title : subscriptionUrl"
:to="`/${subscriptionUrl}`">
:title="feed ? feed.title : url"
:to="`/${url}`">
<template #icon>
<NcAvatar v-if="feed"
:display-name="feed.author"
@ -29,7 +29,7 @@ import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'
export default {
name: 'SubscriptionListItem',
name: 'FeedListItem',
components: {
Alert,
Delete,
@ -38,7 +38,7 @@ export default {
NcAvatar,
},
props: {
subscriptionUrl: {
url: {
type: String,
required: true,
},
@ -52,7 +52,7 @@ export default {
},
async mounted() {
try {
const podcastData = await axios.get(generateUrl('/apps/gpoddersync/personal_settings/podcast_data?url={url}', { url: this.subscriptionUrl }))
const podcastData = await axios.get(generateUrl('/apps/gpoddersync/personal_settings/podcast_data?url={url}', { url: this.url }))
this.feed = podcastData.data.data
} catch (e) {
this.failed = true
@ -65,7 +65,7 @@ export default {
async deleteSubscription() {
try {
this.loading = true
await axios.post(generateUrl('/apps/gpoddersync/subscription_change/create'), { add: [], remove: [this.subscriptionUrl] })
await axios.post(generateUrl('/apps/gpoddersync/subscription_change/create'), { add: [], remove: [this.url] })
} catch (e) {
console.error(e)
showError(t('Error while removing the feed'))

View File

@ -2,14 +2,14 @@
<ul>
<NcListItem v-for="feed in feeds"
:key="`${feed.provider}_${feed.id}`"
:counter-number="feed.nb_episodes"
:details="formatTimeAgo(new Date(feed.last_pub))"
:counter-number="feed.episode_count"
:details="formatTimeAgo(new Date(feed.lastpub))"
:title="feed.title"
:to="`/${feed.feed_url}`">
:to="`/${feed.url}`">
<template #icon>
<NcAvatar :display-name="feed.author"
:is-no-user="true"
:url="feed.image" />
:url="feed.logo_url" />
</template>
<template #subtitle>
{{ feed.author }}

View File

@ -1,7 +1,7 @@
<template>
<a @click="addSubscription">
<img :alt="author"
:src="imgUrl"
<img :alt="`${title} - ${author}`"
:src="logo"
:title="author">
</a>
</template>
@ -15,23 +15,27 @@ export default {
name: 'TopItem',
components: {},
props: {
xmlUrl: {
type: String,
required: true,
},
imgUrl: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
logo: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
},
methods: {
async addSubscription() {
try {
await axios.post(generateUrl('/apps/gpoddersync/subscription_change/create'), { add: [this.xmlUrl], remove: [] })
await axios.post(generateUrl('/apps/gpoddersync/subscription_change/create'), { add: [this.url], remove: [] })
} catch (e) {
console.error(e)
showError(t('Error while adding the feed'))

View File

@ -8,8 +8,9 @@
<ul v-if="!loading" class="tops">
<li v-for="top in tops" :key="top.id">
<TopItem :author="top.author"
:img-url="top.imgURL"
:xml-url="top.xmlURL" />
:logo="top.logo_url"
:title="top.title"
:url="top.url" />
</li>
</ul>
<span class="caption">{{ t('Suggests by fyyd') }}</span>
@ -39,8 +40,8 @@ export default {
async mounted() {
try {
this.loading = true
const top = await axios.get(generateUrl('/apps/repod/top'))
this.tops = top.data.data
const toplist = await axios.get(generateUrl('/apps/repod/toplist/10'))
this.tops = toplist.data
} catch (e) {
console.error(e)
showError(t('Could not fetch tops'))

View File

@ -11,9 +11,9 @@
</router-link>
<NcLoadingIcon v-if="loading" />
<ul v-if="!loading">
<SubscriptionListItem v-for="subscriptionUrl of subscriptions"
<FeedListItem v-for="subscriptionUrl of subscriptions"
:key="subscriptionUrl"
:subscription-url="subscriptionUrl" />
:url="subscriptionUrl" />
</ul>
</NcAppContentList>
</NcAppNavigation>
@ -31,20 +31,20 @@ import {
NcAppNavigationNew,
NcLoadingIcon,
} from '@nextcloud/vue'
import FeedListItem from '../components/FeedListItem.vue'
import Plus from 'vue-material-design-icons/Plus.vue'
import SubscriptionListItem from '../components/SubscriptionListItem.vue'
import { showError } from '@nextcloud/dialogs'
export default {
name: 'Index',
components: {
FeedListItem,
NcAppContent,
NcAppContentList,
NcAppNavigation,
NcAppNavigationNew,
NcLoadingIcon,
Plus,
SubscriptionListItem,
},
data() {
return {