Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
11 changed files with 166 additions and 141 deletions
Showing only changes of commit 940ebd5176 - Show all commits

View File

@ -1,6 +1,6 @@
<?php <?php
use Aviat\AnimeClient\Kitsu; use function Aviat\AnimeClient\friendlyTime;
?> ?>
<main class="details fixed"> <main class="details fixed">
@ -38,14 +38,14 @@ use Aviat\AnimeClient\Kitsu;
<?php if (( ! empty($data['episode_length'])) && $data['episode_count'] !== 1): ?> <?php if (( ! empty($data['episode_length'])) && $data['episode_count'] !== 1): ?>
<tr> <tr>
<td>Episode Length</td> <td>Episode Length</td>
<td><?= Kitsu::friendlyTime($data['episode_length']) ?></td> <td><?= friendlyTime($data['episode_length']) ?></td>
</tr> </tr>
<?php endif ?> <?php endif ?>
<?php if (isset($data['total_length'], $data['episode_count']) && $data['total_length'] > 0): ?> <?php if (isset($data['total_length'], $data['episode_count']) && $data['total_length'] > 0): ?>
<tr> <tr>
<td>Total Length</td> <td>Total Length</td>
<td><?= Kitsu::friendlyTime($data['total_length']) ?></td> <td><?= friendlyTime($data['total_length']) ?></td>
</tr> </tr>
<?php endif ?> <?php endif ?>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" bootstrap="../tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" bootstrap="../tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
<coverage> <coverage>
<include>
<directory suffix=".php">../src</directory>
</include>
<report> <report>
<clover outputFile="logs/clover.xml"/> <clover outputFile="logs/clover.xml"/>
<html outputDirectory="../coverage"/> <html outputDirectory="../coverage"/>
@ -27,4 +24,9 @@
<server name="REQUEST_URI" value="/"/> <server name="REQUEST_URI" value="/"/>
<server name="REQUEST_METHOD" value="GET"/> <server name="REQUEST_METHOD" value="GET"/>
</php> </php>
<source>
<include>
<directory suffix=".php">../src</directory>
</include>
</source>
</phpunit> </phpunit>

View File

@ -42,8 +42,8 @@
"ext-json": "*", "ext-json": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-pdo": "*", "ext-pdo": "*",
"laminas/laminas-diactoros": "^2.5.0", "laminas/laminas-diactoros": "^3.0.0",
"laminas/laminas-httphandlerrunner": "^2.1.0", "laminas/laminas-httphandlerrunner": "^2.6.1",
"maximebf/consolekit": "^1.0.3", "maximebf/consolekit": "^1.0.3",
"monolog/monolog": "^3.0.0", "monolog/monolog": "^3.0.0",
"php": ">= 8.1.0", "php": ">= 8.1.0",

View File

@ -14,11 +14,11 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\Kitsu;
use Aviat\AnimeClient\Types\User; use Aviat\AnimeClient\Types\User;
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
use function Aviat\AnimeClient\{formatDate, friendlyTime, getDateDiff};
/** /**
* Transform user profile data for display * Transform user profile data for display
* *
@ -41,8 +41,12 @@ final class UserTransformer extends AbstractTransformer
return User::from([ return User::from([
'about' => $base['about'] ?? '', 'about' => $base['about'] ?? '',
'avatar' => $base['avatarImage']['original']['url'] ?? NULL, 'avatar' => $base['avatarImage']['original']['url'] ?? NULL,
'birthday' => $base['birthday'] !== NULL ? Kitsu::formatDate($base['birthday']) . ' (' . Kitsu::friendlyTime(Kitsu::getDateDiff($base['birthday']), 'year') . ')' : NULL, 'birthday' => $base['birthday'] !== NULL
'joinDate' => Kitsu::formatDate($base['createdAt']) . ' (' . Kitsu::friendlyTime(Kitsu::getDateDiff($base['createdAt']), 'day') . ' ago)', ? formatDate($base['birthday']) . ' (' .
friendlyTime(getDateDiff($base['birthday']), 'year') . ')'
: NULL,
'joinDate' => formatDate($base['createdAt']) . ' (' .
friendlyTime(getDateDiff($base['createdAt']), 'day') . ' ago)',
'gender' => $base['gender'], 'gender' => $base['gender'],
'favorites' => $this->organizeFavorites($favorites), 'favorites' => $this->organizeFavorites($favorites),
'location' => $base['location'], 'location' => $base['location'],
@ -84,7 +88,7 @@ final class UserTransformer extends AbstractTransformer
if (array_key_exists('animeAmountConsumed', $stats)) if (array_key_exists('animeAmountConsumed', $stats))
{ {
$animeStats = [ $animeStats = [
'Time spent watching anime:' => Kitsu::friendlyTime($stats['animeAmountConsumed']['time']), 'Time spent watching anime:' => friendlyTime($stats['animeAmountConsumed']['time']),
'Anime series watched:' => number_format($stats['animeAmountConsumed']['media']), 'Anime series watched:' => number_format($stats['animeAmountConsumed']['media']),
'Anime episodes watched:' => number_format($stats['animeAmountConsumed']['units']), 'Anime episodes watched:' => number_format($stats['animeAmountConsumed']['units']),
]; ];

View File

@ -17,6 +17,7 @@ namespace Aviat\AnimeClient;
use Amp\Http\Client\{HttpClient, HttpClientBuilder, Request, Response}; use Amp\Http\Client\{HttpClient, HttpClientBuilder, Request, Response};
use Aviat\Ion\{ConfigInterface, ImageBuilder}; use Aviat\Ion\{ConfigInterface, ImageBuilder};
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\CodeCoverageIgnore; use PHPUnit\Framework\Attributes\CodeCoverageIgnore;
use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\CacheInterface;
use Throwable; use Throwable;
@ -26,6 +27,11 @@ use Yosymfony\Toml\{Toml, TomlBuilder};
use function Amp\Promise\wait; use function Amp\Promise\wait;
use function Aviat\Ion\_dir; use function Aviat\Ion\_dir;
const SECONDS_IN_MINUTE = 60;
const MINUTES_IN_HOUR = 60;
const MINUTES_IN_DAY = 1440;
const MINUTES_IN_YEAR = 525_600;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
//! TOML Functions //! TOML Functions
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -307,3 +313,87 @@ function renderTemplate(string $path, array $data): string
return (is_string($rawOutput)) ? $rawOutput : ''; return (is_string($rawOutput)) ? $rawOutput : '';
} }
function formatDate(string $date): string
{
$date = new DateTimeImmutable($date);
return $date->format('F d, Y');
}
function getDateDiff(string $date): int
{
$now = new DateTimeImmutable();
$then = new DateTimeImmutable($date);
$interval = $now->diff($then, TRUE);
$years = $interval->y * SECONDS_IN_MINUTE * MINUTES_IN_YEAR;
$days = $interval->d * SECONDS_IN_MINUTE * MINUTES_IN_DAY;
$hours = $interval->h * SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
$minutes = $interval->i * SECONDS_IN_MINUTE;
$seconds = $interval->s;
return $years + $days + $hours + $minutes + $seconds;
}
/**
* Convert a time in seconds to a more human-readable format
*/
function friendlyTime(int $seconds, string $minUnit = 'second'): string
{
// All the seconds left
$remSeconds = $seconds % SECONDS_IN_MINUTE;
$minutes = ($seconds - $remSeconds) / SECONDS_IN_MINUTE;
// Minutes short of a year
$years = (int) floor($minutes / MINUTES_IN_YEAR);
$minutes %= MINUTES_IN_YEAR;
// Minutes short of a day
$extraMinutes = $minutes % MINUTES_IN_DAY;
$days = ($minutes - $extraMinutes) / MINUTES_IN_DAY;
// Minutes short of an hour
$remMinutes = $extraMinutes % MINUTES_IN_HOUR;
$hours = ($extraMinutes - $remMinutes) / MINUTES_IN_HOUR;
$parts = [];
foreach ([
'year' => $years,
'day' => $days,
'hour' => $hours,
'minute' => $remMinutes,
'second' => $remSeconds,
] as $label => $value)
{
if ($value === 0)
{
continue;
}
if ($value > 1)
{
$label .= 's';
}
$parts[] = "{$value} {$label}";
if ($label === $minUnit || $label === $minUnit . 's')
{
break;
}
}
$last = array_pop($parts);
if (empty($parts))
{
return $last ?? '';
}
return (count($parts) > 1)
? implode(', ', $parts) . ", and {$last}"
: "{$parts[0]}, {$last}";
}

View File

@ -31,10 +31,6 @@ final class Kitsu
public const ANIME_HISTORY_LIST_CACHE_KEY = 'kitsu-anime-history-list'; public const ANIME_HISTORY_LIST_CACHE_KEY = 'kitsu-anime-history-list';
public const MANGA_HISTORY_LIST_CACHE_KEY = 'kitsu-manga-history-list'; public const MANGA_HISTORY_LIST_CACHE_KEY = 'kitsu-manga-history-list';
public const GRAPHQL_ENDPOINT = 'https://kitsu.io/api/graphql'; public const GRAPHQL_ENDPOINT = 'https://kitsu.io/api/graphql';
public const SECONDS_IN_MINUTE = 60;
public const MINUTES_IN_HOUR = 60;
public const MINUTES_IN_DAY = 1440;
public const MINUTES_IN_YEAR = 525_600;
/** /**
* Determine whether an anime is airing, finished airing, or has not yet aired * Determine whether an anime is airing, finished airing, or has not yet aired
@ -130,29 +126,6 @@ final class Kitsu
return MangaPublishingStatus::NOT_YET_PUBLISHED; return MangaPublishingStatus::NOT_YET_PUBLISHED;
} }
public static function formatDate(string $date): string
{
$date = new DateTimeImmutable($date);
return $date->format('F d, Y');
}
public static function getDateDiff(string $date): int
{
$now = new DateTimeImmutable();
$then = new DateTimeImmutable($date);
$interval = $now->diff($then, TRUE);
$years = $interval->y * self::SECONDS_IN_MINUTE * self::MINUTES_IN_YEAR;
$days = $interval->d * self::SECONDS_IN_MINUTE * self::MINUTES_IN_DAY;
$hours = $interval->h * self::SECONDS_IN_MINUTE * self::MINUTES_IN_HOUR;
$minutes = $interval->i * self::SECONDS_IN_MINUTE;
$seconds = $interval->s;
return $years + $days + $hours + $minutes + $seconds;
}
/** /**
* @return array<string, string> * @return array<string, string>
*/ */
@ -464,67 +437,6 @@ final class Kitsu
]; ];
} }
/**
* Convert a time in seconds to a more human-readable format
*/
public static function friendlyTime(int $seconds, string $minUnit = 'second'): string
{
// All the seconds left
$remSeconds = $seconds % self::SECONDS_IN_MINUTE;
$minutes = ($seconds - $remSeconds) / self::SECONDS_IN_MINUTE;
// Minutes short of a year
$years = (int) floor($minutes / self::MINUTES_IN_YEAR);
$minutes %= self::MINUTES_IN_YEAR;
// Minutes short of a day
$extraMinutes = $minutes % self::MINUTES_IN_DAY;
$days = ($minutes - $extraMinutes) / self::MINUTES_IN_DAY;
// Minutes short of an hour
$remMinutes = $extraMinutes % self::MINUTES_IN_HOUR;
$hours = ($extraMinutes - $remMinutes) / self::MINUTES_IN_HOUR;
$parts = [];
foreach ([
'year' => $years,
'day' => $days,
'hour' => $hours,
'minute' => $remMinutes,
'second' => $remSeconds,
] as $label => $value)
{
if ($value === 0)
{
continue;
}
if ($value > 1)
{
$label .= 's';
}
$parts[] = "{$value} {$label}";
if ($label === $minUnit || $label === $minUnit . 's')
{
break;
}
}
$last = array_pop($parts);
if (empty($parts))
{
return $last ?? '';
}
return (count($parts) > 1)
? implode(', ', $parts) . ", and {$last}"
: "{$parts[0]}, {$last}";
}
/** /**
* Determine if an alternate title is unique enough to list * Determine if an alternate title is unique enough to list
*/ */

View File

@ -42,6 +42,8 @@ final class APIRequestBuilderTest extends TestCase
public function testGzipRequest(): void public function testGzipRequest(): void
{ {
$this->markTestSkipped('Need new test API');
$request = $this->builder->newRequest('GET', 'gzip') $request = $this->builder->newRequest('GET', 'gzip')
->getFullRequest(); ->getFullRequest();
$response = getResponse($request); $response = getResponse($request);
@ -51,6 +53,8 @@ final class APIRequestBuilderTest extends TestCase
public function testInvalidRequestMethod(): void public function testInvalidRequestMethod(): void
{ {
$this->markTestSkipped('Need new test API');
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->builder->newRequest('FOO', 'gzip') $this->builder->newRequest('FOO', 'gzip')
->getFullRequest(); ->getFullRequest();
@ -58,6 +62,8 @@ final class APIRequestBuilderTest extends TestCase
public function testRequestWithBasicAuth(): void public function testRequestWithBasicAuth(): void
{ {
$this->markTestSkipped('Need new test API');
$request = $this->builder->newRequest('GET', 'headers') $request = $this->builder->newRequest('GET', 'headers')
->setBasicAuth('username', 'password') ->setBasicAuth('username', 'password')
->getFullRequest(); ->getFullRequest();
@ -70,6 +76,8 @@ final class APIRequestBuilderTest extends TestCase
public function testRequestWithQueryString(): void public function testRequestWithQueryString(): void
{ {
$this->markTestSkipped('Need new test API');
$query = [ $query = [
'foo' => 'bar', 'foo' => 'bar',
'bar' => [ 'bar' => [
@ -98,6 +106,8 @@ final class APIRequestBuilderTest extends TestCase
public function testFormValueRequest(): void public function testFormValueRequest(): void
{ {
$this->markTestSkipped('Need new test API');
$formValues = [ $formValues = [
'bar' => 'foo', 'bar' => 'foo',
'foo' => 'bar', 'foo' => 'bar',
@ -115,6 +125,8 @@ final class APIRequestBuilderTest extends TestCase
public function testFullUrlRequest(): void public function testFullUrlRequest(): void
{ {
$this->markTestSkipped('Need new test API');
$data = [ $data = [
'foo' => [ 'foo' => [
'bar' => 1, 'bar' => 1,

View File

@ -38,6 +38,10 @@ final class UserTransformerTest extends AnimeClientTestCase
public function testTransform(): void public function testTransform(): void
{ {
$actual = (new UserTransformer())->transform($this->beforeTransform); $actual = (new UserTransformer())->transform($this->beforeTransform);
// Unset the time value that will change every day, so the test is consistent
$actual->joinDate = '';
$this->assertMatchesSnapshot($actual); $this->assertMatchesSnapshot($actual);
} }
} }

View File

@ -15,7 +15,8 @@
namespace Aviat\AnimeClient\Tests; namespace Aviat\AnimeClient\Tests;
use DateTime; use DateTime;
use function Aviat\AnimeClient\{arrayToToml, checkFolderPermissions, clearCache, colNotEmpty, getLocalImg, getResponse, isSequentialArray, tomlToArray}; use function Aviat\AnimeClient\{arrayToToml, checkFolderPermissions, clearCache, colNotEmpty, friendlyTime, getLocalImg, getResponse, isSequentialArray, tomlToArray};
use const Aviat\AnimeClient\{MINUTES_IN_DAY, MINUTES_IN_HOUR, MINUTES_IN_YEAR, SECONDS_IN_MINUTE};
/** /**
* @internal * @internal
@ -128,4 +129,33 @@ final class AnimeClientTest extends AnimeClientTestCase
{ {
$this->assertTrue(clearCache($this->container->get('cache'))); $this->assertTrue(clearCache($this->container->get('cache')));
} }
public static function getFriendlyTime(): array
{
$SECONDS_IN_DAY = SECONDS_IN_MINUTE * MINUTES_IN_DAY;
$SECONDS_IN_HOUR = SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
$SECONDS_IN_YEAR = SECONDS_IN_MINUTE * MINUTES_IN_YEAR;
return [[
'seconds' => $SECONDS_IN_YEAR,
'expected' => '1 year',
], [
'seconds' => $SECONDS_IN_HOUR,
'expected' => '1 hour',
], [
'seconds' => (2 * $SECONDS_IN_YEAR) + 30,
'expected' => '2 years, 30 seconds',
], [
'seconds' => (5 * $SECONDS_IN_YEAR) + (3 * $SECONDS_IN_DAY) + (17 * SECONDS_IN_MINUTE),
'expected' => '5 years, 3 days, and 17 minutes',
]];
}
#[\PHPUnit\Framework\Attributes\DataProvider('getFriendlyTime')]
public function testGetFriendlyTime(int $seconds, string $expected): void
{
$actual = friendlyTime($seconds);
$this->assertSame($expected, $actual);
}
} }

View File

@ -98,35 +98,6 @@ final class KitsuTest extends TestCase
$this->assertSame($expected, $actual); $this->assertSame($expected, $actual);
} }
public static function getFriendlyTime(): array
{
$SECONDS_IN_DAY = Kitsu::SECONDS_IN_MINUTE * Kitsu::MINUTES_IN_DAY;
$SECONDS_IN_HOUR = Kitsu::SECONDS_IN_MINUTE * Kitsu::MINUTES_IN_HOUR;
$SECONDS_IN_YEAR = Kitsu::SECONDS_IN_MINUTE * Kitsu::MINUTES_IN_YEAR;
return [[
'seconds' => $SECONDS_IN_YEAR,
'expected' => '1 year',
], [
'seconds' => $SECONDS_IN_HOUR,
'expected' => '1 hour',
], [
'seconds' => (2 * $SECONDS_IN_YEAR) + 30,
'expected' => '2 years, 30 seconds',
], [
'seconds' => (5 * $SECONDS_IN_YEAR) + (3 * $SECONDS_IN_DAY) + (17 * Kitsu::SECONDS_IN_MINUTE),
'expected' => '5 years, 3 days, and 17 minutes',
]];
}
#[\PHPUnit\Framework\Attributes\DataProvider('getFriendlyTime')]
public function testGetFriendlyTime(int $seconds, string $expected): void
{
$actual = Kitsu::friendlyTime($seconds);
$this->assertSame($expected, $actual);
}
public function testFilterLocalizedTitles(): void public function testFilterLocalizedTitles(): void
{ {
$input = [ $input = [