Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
73bbc569a7
@ -5,22 +5,12 @@ use PhpCsFixer\{Config, Finder};
|
|||||||
|
|
||||||
$finder = Finder::create()
|
$finder = Finder::create()
|
||||||
->in([
|
->in([
|
||||||
__DIR__,
|
__DIR__ . '/src',
|
||||||
__DIR__ . '/app',
|
__DIR__ . '/tests',
|
||||||
__DIR__ . '/tools',
|
__DIR__ . '/tools',
|
||||||
])
|
])
|
||||||
->exclude([
|
->exclude([
|
||||||
'apidocs',
|
|
||||||
'build',
|
|
||||||
'coverage',
|
|
||||||
'frontEndSrc',
|
|
||||||
'phinx',
|
|
||||||
'public',
|
|
||||||
'tools',
|
|
||||||
'tmp',
|
|
||||||
'vendor',
|
'vendor',
|
||||||
'views',
|
|
||||||
'templates',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (new Config())
|
return (new Config())
|
||||||
@ -45,7 +35,7 @@ return (new Config())
|
|||||||
'blank_line_after_opening_tag' => false,
|
'blank_line_after_opening_tag' => false,
|
||||||
'blank_line_before_statement' => [
|
'blank_line_before_statement' => [
|
||||||
'statements' => [
|
'statements' => [
|
||||||
'case',
|
// 'case',
|
||||||
'continue',
|
'continue',
|
||||||
'declare',
|
'declare',
|
||||||
'default',
|
'default',
|
||||||
@ -128,12 +118,12 @@ return (new Config())
|
|||||||
'noise_remaining_usages_exclude' => [],
|
'noise_remaining_usages_exclude' => [],
|
||||||
],
|
],
|
||||||
'escape_implicit_backslashes' => [
|
'escape_implicit_backslashes' => [
|
||||||
'double_quoted' => true,
|
'double_quoted' => false,
|
||||||
'heredoc_syntax' => true,
|
'heredoc_syntax' => false,
|
||||||
'single_quoted' => false,
|
'single_quoted' => false,
|
||||||
],
|
],
|
||||||
'explicit_indirect_variable' => true,
|
'explicit_indirect_variable' => false,
|
||||||
'explicit_string_variable' => true,
|
'explicit_string_variable' => false,
|
||||||
'final_class' => false,
|
'final_class' => false,
|
||||||
'final_internal_class' => [
|
'final_internal_class' => [
|
||||||
'annotation_exclude' => ['@no-final'],
|
'annotation_exclude' => ['@no-final'],
|
||||||
@ -167,7 +157,7 @@ return (new Config())
|
|||||||
],
|
],
|
||||||
'group_import' => true,
|
'group_import' => true,
|
||||||
'header_comment' => false, // false by default
|
'header_comment' => false, // false by default
|
||||||
'heredoc_indentation' => ['indentation' => 'start_plus_one'],
|
// 'heredoc_indentation' => ['indentation' => 'start_plus_one'],
|
||||||
'heredoc_to_nowdoc' => true,
|
'heredoc_to_nowdoc' => true,
|
||||||
'implode_call' => true,
|
'implode_call' => true,
|
||||||
'include' => true,
|
'include' => true,
|
||||||
@ -232,8 +222,7 @@ return (new Config())
|
|||||||
'allow_unused_params' => true,
|
'allow_unused_params' => true,
|
||||||
'remove_inheritdoc' => false,
|
'remove_inheritdoc' => false,
|
||||||
],
|
],
|
||||||
'no_trailing_comma_in_list_call' => true,
|
'no_trailing_comma_in_singleline' => true,
|
||||||
'no_trailing_comma_in_singleline_array' => true,
|
|
||||||
'no_trailing_whitespace' => true,
|
'no_trailing_whitespace' => true,
|
||||||
'no_trailing_whitespace_in_comment' => true,
|
'no_trailing_whitespace_in_comment' => true,
|
||||||
'no_trailing_whitespace_in_string' => true,
|
'no_trailing_whitespace_in_string' => true,
|
||||||
@ -270,9 +259,16 @@ return (new Config())
|
|||||||
'ordered_class_elements' => [
|
'ordered_class_elements' => [
|
||||||
'order' => [
|
'order' => [
|
||||||
'use_trait',
|
'use_trait',
|
||||||
'constant',
|
'case',
|
||||||
'property',
|
'constant_public',
|
||||||
'method',
|
'constant_protected',
|
||||||
|
'constant_private',
|
||||||
|
'property_public',
|
||||||
|
'property_protected',
|
||||||
|
'property_private',
|
||||||
|
'construct',
|
||||||
|
'destruct',
|
||||||
|
'magic',
|
||||||
],
|
],
|
||||||
'sort_algorithm' => 'none',
|
'sort_algorithm' => 'none',
|
||||||
],
|
],
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Version 5.2
|
## Version 5.2
|
||||||
* Updated PHP requirement to 8
|
* Updated PHP requirement to 8.1
|
||||||
* Updated to support PHP 8.1
|
* Updated to support PHP 8.2
|
||||||
|
* Improve Anilist <-> Kitsu mappings to be more reliable
|
||||||
|
|
||||||
## Version 5.1
|
## Version 5.1
|
||||||
* Added session check, so when coming back to a page, if the session is expired, the page will refresh.
|
* Added session check, so when coming back to a page, if the session is expired, the page will refresh.
|
||||||
|
0
app/logs/.gitkeep
Normal file → Executable file
0
app/logs/.gitkeep
Normal file → Executable 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 ?>
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ use Aviat\AnimeClient\Kitsu;
|
|||||||
<br />
|
<br />
|
||||||
<hr />
|
<hr />
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<p><?= str_replace("\n", '</p><p>', $data['description']) ?></p>
|
<p><?= nl2br($data['description']) ?></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
<?php foreach ($data['names'] as $name): ?>
|
<?php foreach ($data['names'] as $name): ?>
|
||||||
<h3><?= $name ?></h3>
|
<h3><?= $name ?></h3>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
|
<?php if ( ! empty($data['birthday'])): ?>
|
||||||
|
<h4><?= $data['birthday'] ?></h4>
|
||||||
|
<?php endif ?>
|
||||||
<br />
|
<br />
|
||||||
<hr />
|
<hr />
|
||||||
<div class="description">
|
<div class="description">
|
||||||
|
@ -3,40 +3,58 @@ use Aviat\AnimeClient\Kitsu;
|
|||||||
?>
|
?>
|
||||||
<main class="user-page details">
|
<main class="user-page details">
|
||||||
<h2 class="toph">
|
<h2 class="toph">
|
||||||
|
About
|
||||||
<?= $helper->a(
|
<?= $helper->a(
|
||||||
"https://kitsu.io/users/{$data['slug']}",
|
"https://kitsu.io/users/{$data['slug']}",
|
||||||
$data['name'], [
|
$data['name'], [
|
||||||
'title' => 'View profile on Kitsu'
|
'title' => 'View profile on Kitsu'
|
||||||
])
|
])
|
||||||
?>
|
?>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p><?= $escape->html($data['about']) ?></p>
|
|
||||||
|
|
||||||
<section class="flex flex-no-wrap">
|
<section class="flex flex-no-wrap">
|
||||||
<aside class="info">
|
<aside class="info">
|
||||||
<center>
|
<table class="media-details invisible">
|
||||||
<?= $helper->img($data['avatar'], ['alt' => '']); ?>
|
<tr>
|
||||||
</center>
|
<?php if($data['avatar'] !== null): ?>
|
||||||
|
<td><?= $helper->img($data['avatar'], ['alt' => '', 'width' => '225']); ?></td>
|
||||||
|
<?php endif ?>
|
||||||
|
<td><?= $escape->html($data['about']) ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
<br />
|
<br />
|
||||||
<table class="media-details">
|
<table class="media-details">
|
||||||
|
<?php foreach ([
|
||||||
|
'joinDate' => 'Joined',
|
||||||
|
'birthday' => 'Birthday',
|
||||||
|
'gender' => 'Gender',
|
||||||
|
'location' => 'Location'
|
||||||
|
] as $key => $label): ?>
|
||||||
|
<?php if ($data[$key] !== null): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Location</td>
|
<td><?= $label ?></td>
|
||||||
<td><?= $data['location'] ?></td>
|
<td><?= $data[$key] ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if ($data['website'] !== null): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Website</td>
|
<td>Website</td>
|
||||||
<td><?= $helper->a($data['website'], $data['website']) ?></td>
|
<td><?= $helper->a($data['website'], $data['website']) ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php if ( ! empty($data['waifu'])): ?>
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ($data['waifu']['character'] !== null): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?= $escape->html($data['waifu']['label']) ?></td>
|
<td><?= $escape->html($data['waifu']['label']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?php
|
<?php
|
||||||
$character = $data['waifu']['character'];
|
$character = $data['waifu']['character'];
|
||||||
echo $helper->a(
|
echo $component->character(
|
||||||
$url->generate('character', ['slug' => $character['slug']]),
|
$character['names']['canonical'],
|
||||||
$character['names']['canonical']
|
$url->generate('character', ['slug' => $character['slug']]),
|
||||||
|
$helper->img(Kitsu::getImage($character))
|
||||||
);
|
);
|
||||||
?>
|
?>
|
||||||
</td>
|
</td>
|
||||||
@ -75,7 +93,7 @@ use Aviat\AnimeClient\Kitsu;
|
|||||||
$rendered[] = $component->character(
|
$rendered[] = $component->character(
|
||||||
$item['names']['canonical'],
|
$item['names']['canonical'],
|
||||||
$url->generate('character', ['slug' => $item['slug']]),
|
$url->generate('character', ['slug' => $item['slug']]),
|
||||||
$helper->img($item['image']['original']['url'])
|
$helper->img(Kitsu::getImage($item))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -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"/>
|
||||||
@ -14,12 +11,12 @@
|
|||||||
<directory>../tests/AnimeClient</directory>
|
<directory>../tests/AnimeClient</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="Ion">
|
<testsuite name="Ion">
|
||||||
<directory>../tests/Ion</directory>
|
<directory>../tests/Ion</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<logging>
|
<logging>
|
||||||
<junit outputFile="logs/junit.xml"/>
|
<junit outputFile="logs/junit.xml"/>
|
||||||
</logging>
|
</logging>
|
||||||
<php>
|
<php>
|
||||||
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0"/>
|
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0"/>
|
||||||
<server name="HTTP_HOST" value="localhost"/>
|
<server name="HTTP_HOST" value="localhost"/>
|
||||||
@ -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>
|
||||||
|
@ -30,21 +30,20 @@
|
|||||||
"lock": false
|
"lock": false
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"amphp/amp": "^2.5.0",
|
|
||||||
"amphp/http-client": "^4.5.0",
|
"amphp/http-client": "^4.5.0",
|
||||||
"aura/html": "^2.5.0",
|
"aura/html": "^2.5.0",
|
||||||
"aura/router": "^3.1.0",
|
"aura/router": "^3.1.0",
|
||||||
"aura/session": "^2.1.0",
|
"aura/session": "^2.1.0",
|
||||||
"aviat/banker": "^4.1.2",
|
"aviat/banker": "^4.1.2",
|
||||||
"aviat/query": "^4.0.0",
|
"aviat/query": "^4.1.0",
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
"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",
|
||||||
@ -56,9 +55,9 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.2.0",
|
"phpstan/phpstan": "^1.2.0",
|
||||||
"phpunit/phpunit": "^9.5.0",
|
"phpunit/phpunit": "^10.0.0",
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"spatie/phpunit-snapshot-assertions": "^4.1.0"
|
"spatie/phpunit-snapshot-assertions": "^5.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:css": "cd public && npm run build:css && cd ..",
|
"build:css": "cd public && npm run build:css && cd ..",
|
||||||
|
@ -333,7 +333,8 @@ td.danger, td.danger:hover, td.danger:active {
|
|||||||
.borderless th,
|
.borderless th,
|
||||||
.invisible tr,
|
.invisible tr,
|
||||||
.invisible td,
|
.invisible td,
|
||||||
.invisible th {
|
.invisible th,
|
||||||
|
table.invisible {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
@ -836,19 +837,11 @@ aside.info {
|
|||||||
max-width: 390px;
|
max-width: 390px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .fixed aside.info + article {
|
|
||||||
max-width: inherit;
|
|
||||||
} */
|
|
||||||
|
|
||||||
aside picture, aside img {
|
aside picture, aside img {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* aside.info + article {
|
|
||||||
max-width: 66%;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
User page styles
|
User page styles
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import _ from './anime-client.js'
|
import _ from './anime-client.js'
|
||||||
import { renderSearchResults } from './template-helpers.js'
|
import { renderSearchResults } from './template-helpers.js'
|
||||||
|
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||||
|
|
||||||
const search = (query, isCollection = false) => {
|
const search = (query, isCollection = false) => {
|
||||||
// Show the loader
|
// Show the loader
|
||||||
@ -70,6 +71,14 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const displayMessage = (type, message) => {
|
||||||
|
_.hide('#loading-shadow');
|
||||||
|
_.showMessage(type, `${message} ${title}`);
|
||||||
|
_.scrollToTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const showError = () => displayMessage('error', 'Failed to update');
|
||||||
|
|
||||||
// If the episode count is 0, and incremented,
|
// If the episode count is 0, and incremented,
|
||||||
// change status to currently watching
|
// change status to currently watching
|
||||||
if (isNaN(watchedCount) || watchedCount === 0) {
|
if (isNaN(watchedCount) || watchedCount === 0) {
|
||||||
@ -89,36 +98,31 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const resData = JSON.parse(res);
|
try {
|
||||||
|
const resData = JSON.parse(res);
|
||||||
|
|
||||||
if (resData.error) {
|
// Do a rough sanity check for weird errors
|
||||||
_.hide('#loading-shadow');
|
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||||
_.showMessage('error', `Failed to update ${title}. `);
|
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||||
_.scrollToTop();
|
showError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
// We've completed the series
|
||||||
|
if (getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.status') === 'COMPLETED') {
|
||||||
|
_.hide(parentSel);
|
||||||
|
displayMessage('success', 'Completed')
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just a normal update
|
||||||
|
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
|
||||||
|
displayMessage('success', 'Updated');
|
||||||
|
} catch (_) {
|
||||||
|
showError();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've completed the series
|
|
||||||
if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') {
|
|
||||||
_.hide(parentSel);
|
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('success', `Successfully completed ${title}`);
|
|
||||||
_.scrollToTop();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_.hide('#loading-shadow');
|
|
||||||
|
|
||||||
_.showMessage('success', `Successfully updated ${title}`);
|
|
||||||
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
|
|
||||||
_.scrollToTop();
|
|
||||||
},
|
},
|
||||||
error: () => {
|
error: showError,
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('error', `Failed to update ${title}. `);
|
|
||||||
_.scrollToTop();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
103
frontEndSrc/js/fns.js
Normal file
103
frontEndSrc/js/fns.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Make sure properties are in an easily splittable format
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {String} props
|
||||||
|
* @param {String} [sep='.'] The default separator
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
function _normalizeProperty(props, sep = '.') {
|
||||||
|
// Since we split by period, and property lookup
|
||||||
|
// is the same by dot or [], replace bracket lookups
|
||||||
|
// with periods
|
||||||
|
return props.replace(/\[(.*?)]/g, sep + '$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell if a nested object has a given property (or array a given index)
|
||||||
|
* given an object such as a.b.c.d = 5, hasNestedProperty(a, 'b.c.d') will return true.
|
||||||
|
*
|
||||||
|
* @param {Object} object the object to get the property from
|
||||||
|
* @param {String} property the path to the property as a string
|
||||||
|
* @returns {boolean} true when property in object, false otherwise
|
||||||
|
*/
|
||||||
|
export function hasNestedProperty(object, property) {
|
||||||
|
if (object && typeof object === 'object') {
|
||||||
|
if (typeof property === 'string' && property !== '') {
|
||||||
|
property = _normalizeProperty(property);
|
||||||
|
|
||||||
|
let split = property.split('.');
|
||||||
|
return split.reduce((obj, prop, idx, array) => {
|
||||||
|
if (idx === array.length - 1) {
|
||||||
|
return !!(obj && obj.hasOwnProperty(prop));
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj && obj[prop];
|
||||||
|
}, object);
|
||||||
|
} else if (typeof property === 'number') {
|
||||||
|
return property in object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of a deeply nested property in an object
|
||||||
|
*
|
||||||
|
* @param {Object} object the object to get the property
|
||||||
|
* @param {string} property the path to the property as a string
|
||||||
|
* @param {string} [sep='.'] The default separator to split on
|
||||||
|
* @return {*} the value of the property
|
||||||
|
*/
|
||||||
|
export function getNestedProperty(object, property, sep = '.') {
|
||||||
|
if (isType('string', property) && property !== '') {
|
||||||
|
// convert numbers to dot syntax
|
||||||
|
property = _normalizeProperty(property, sep);
|
||||||
|
const levels = property.split(sep);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return levels.reduce((obj, prop) => obj[prop], object);
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reliably get the type of the value of a variable
|
||||||
|
*
|
||||||
|
* @param {*} x The variable to get the type of
|
||||||
|
* @return {string} The name of the type
|
||||||
|
*/
|
||||||
|
export function getType(x) {
|
||||||
|
// is it an array?
|
||||||
|
if (Array.isArray(x)) {
|
||||||
|
return 'array';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use typeof for truthy primitives
|
||||||
|
if (typeof x !== 'object') {
|
||||||
|
return (typeof x).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = function () {
|
||||||
|
return Object.prototype.toString.call(this).slice(8, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, strip the type out of the '[Object x]' toString value
|
||||||
|
return type.call(x).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the value matches the passed type name
|
||||||
|
*
|
||||||
|
* @param {string} type Javascript type name
|
||||||
|
* @param {*} val The value to type check
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isType(type, val) {
|
||||||
|
return getType(val) === String(type).toLowerCase();
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import _ from './anime-client.js'
|
import _ from './anime-client.js'
|
||||||
import { renderSearchResults } from './template-helpers.js'
|
import { renderSearchResults } from './template-helpers.js'
|
||||||
|
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||||
|
|
||||||
const search = (query) => {
|
const search = (query) => {
|
||||||
_.show('.cssload-loader');
|
_.show('.cssload-loader');
|
||||||
@ -36,7 +37,7 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
|||||||
let type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
|
let type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
|
||||||
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
||||||
let total = parseInt(_.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
let total = parseInt(_.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
||||||
let mangaName = _.$('.name', parentSel)[ 0 ].textContent;
|
let title = _.$('.name', parentSel)[ 0 ].textContent;
|
||||||
|
|
||||||
if (isNaN(completed)) {
|
if (isNaN(completed)) {
|
||||||
completed = 0;
|
completed = 0;
|
||||||
@ -45,12 +46,21 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
|||||||
// Setup the update data
|
// Setup the update data
|
||||||
let data = {
|
let data = {
|
||||||
id: parentSel.dataset.kitsuId,
|
id: parentSel.dataset.kitsuId,
|
||||||
|
anilist_id: parentSel.dataset.anilistId,
|
||||||
mal_id: parentSel.dataset.malId,
|
mal_id: parentSel.dataset.malId,
|
||||||
data: {
|
data: {
|
||||||
progress: completed
|
progress: completed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const displayMessage = (type, message) => {
|
||||||
|
_.hide('#loading-shadow');
|
||||||
|
_.showMessage(type, `${message} ${title}`);
|
||||||
|
_.scrollToTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const showError = () => displayMessage('error', 'Failed to update');
|
||||||
|
|
||||||
// If the episode count is 0, and incremented,
|
// If the episode count is 0, and incremented,
|
||||||
// change status to currently reading
|
// change status to currently reading
|
||||||
if (isNaN(completed) || completed === 0) {
|
if (isNaN(completed) || completed === 0) {
|
||||||
@ -73,33 +83,32 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
mimeType: 'application/json',
|
mimeType: 'application/json',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const resData = JSON.parse(res)
|
try {
|
||||||
if (resData.error) {
|
const resData = JSON.parse(res);
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('error', `Failed to update ${mangaName}. `);
|
// Do a rough sanity check for weird errors
|
||||||
_.scrollToTop();
|
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||||
return;
|
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||||
|
showError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've completed the series
|
||||||
|
if (getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.status') === 'COMPLETED') {
|
||||||
|
_.hide(parentSel);
|
||||||
|
displayMessage('success', 'Completed')
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just a normal update
|
||||||
|
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
||||||
|
displayMessage('success', 'Updated');
|
||||||
|
|
||||||
|
} catch (_) {
|
||||||
|
showError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (String(data.data.status).toUpperCase() === 'COMPLETED') {
|
|
||||||
_.hide(parentSel);
|
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('success', `Successfully completed ${mangaName}`);
|
|
||||||
_.scrollToTop();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_.hide('#loading-shadow');
|
|
||||||
|
|
||||||
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
|
||||||
_.showMessage('success', `Successfully updated ${mangaName}`);
|
|
||||||
_.scrollToTop();
|
|
||||||
},
|
},
|
||||||
error: () => {
|
error: showError,
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('error', `Failed to update ${mangaName}`);
|
|
||||||
_.scrollToTop();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -15,7 +15,7 @@
|
|||||||
"cssnano": "^5.0.1",
|
"cssnano": "^5.0.1",
|
||||||
"postcss": "^8.2.6",
|
"postcss": "^8.2.6",
|
||||||
"postcss-import": "^15.0.0",
|
"postcss-import": "^15.0.0",
|
||||||
"postcss-preset-env": "^7.8.2",
|
"postcss-preset-env": "^8.0.1",
|
||||||
"watch": "^1.0.2"
|
"watch": "^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
module.exports = {
|
const { config } = require("@swc/core/spack");
|
||||||
|
|
||||||
|
module.exports = config({
|
||||||
entry: {
|
entry: {
|
||||||
'scripts.min': __dirname + '/js/index.js',
|
'scripts.min': __dirname + '/js/index.js',
|
||||||
'tables.min': __dirname + '/js/base/sort-tables.js',
|
'tables.min': __dirname + '/js/base/sort-tables.js',
|
||||||
@ -8,12 +10,15 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
jsc: {
|
jsc: {
|
||||||
target: 'es3',
|
parser: {
|
||||||
loose: true,
|
syntax: "ecmascript",
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
target: 'es2016',
|
||||||
|
loose: false,
|
||||||
},
|
},
|
||||||
minify: true,
|
minify: true,
|
||||||
module: {
|
sourceMaps: false,
|
||||||
type: 'es6'
|
isModule: true,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
File diff suppressed because it is too large
Load Diff
54
justfile
54
justfile
@ -2,13 +2,25 @@
|
|||||||
default:
|
default:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
# Runs rector, showing what changes will be make
|
# -------------------------------------------------------------------
|
||||||
rector-dry-run:
|
# Front-end stuff
|
||||||
tools/vendor/bin/rector process --config=tools/rector.php --dry-run src
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
# Runs rector, and updates the files
|
# Builds/optimizes JS and CSS
|
||||||
rector:
|
build:
|
||||||
tools/vendor/bin/rector process --config=tools/rector.php src
|
cd frontEndSrc && npm run build && cd ..
|
||||||
|
|
||||||
|
# Builds/optimizes CSS
|
||||||
|
css:
|
||||||
|
composer run-script build:css
|
||||||
|
|
||||||
|
# Builds/optimizes JS
|
||||||
|
js:
|
||||||
|
composer run-script build:js
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# Code Quality and Formatting
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
# Check code formatting
|
# Check code formatting
|
||||||
check-fmt:
|
check-fmt:
|
||||||
@ -18,6 +30,22 @@ check-fmt:
|
|||||||
fmt:
|
fmt:
|
||||||
tools/vendor/bin/php-cs-fixer fix --verbose
|
tools/vendor/bin/php-cs-fixer fix --verbose
|
||||||
|
|
||||||
|
# Runs phpstan code check
|
||||||
|
phpstan:
|
||||||
|
composer run-script phpstan
|
||||||
|
|
||||||
|
# Runs rector, showing what changes will be make
|
||||||
|
rector-dry-run:
|
||||||
|
tools/vendor/bin/rector process --config=tools/rector.php --dry-run src tests
|
||||||
|
|
||||||
|
# Runs rector, and updates the source files
|
||||||
|
rector:
|
||||||
|
tools/vendor/bin/rector process --config=tools/rector.php src tests
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# Testing
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
test:
|
test:
|
||||||
composer run-script test
|
composer run-script test
|
||||||
@ -26,10 +54,14 @@ test:
|
|||||||
test-update:
|
test-update:
|
||||||
composer run-script test-update
|
composer run-script test-update
|
||||||
|
|
||||||
# Update the per-file header comments
|
|
||||||
update-headers:
|
|
||||||
php tools/update_header_comments.php
|
|
||||||
|
|
||||||
# Run unit tests and generate test-coverage report
|
# Run unit tests and generate test-coverage report
|
||||||
coverage:
|
coverage:
|
||||||
composer run-script coverage
|
composer run-script coverage
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# Misc
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Update the per-file header comments
|
||||||
|
update-headers:
|
||||||
|
php tools/update_header_comments.php
|
@ -9,11 +9,12 @@ parameters:
|
|||||||
- ./console
|
- ./console
|
||||||
- index.php
|
- index.php
|
||||||
ignoreErrors:
|
ignoreErrors:
|
||||||
- "#Offset 'fields' does not exist on array#"
|
- '#Unable to resolve the template type T#'
|
||||||
- '#Function imagepalletetotruecolor not found#'
|
- '#imagepalletetotruecolor not found#'
|
||||||
- '#Call to an undefined method Aura\\\Html\\\HelperLocator::[a-zA-Z0-9_]+\(\)#'
|
- '#Call to an undefined method Aura\\\Html\\\HelperLocator::[a-zA-Z0-9_]+\(\)#'
|
||||||
- '#Call to an undefined method Query\\QueryBuilderInterface::[a-zA-Z0-9_]+\(\)#'
|
- '#Call to an undefined method Query\\QueryBuilderInterface::[a-zA-Z0-9_]+\(\)#'
|
||||||
excludes_analyse:
|
excludePaths:
|
||||||
|
- src/Ion/Type/Stringy.php
|
||||||
- tests/mocks.php
|
- tests/mocks.php
|
||||||
- vendor
|
- vendor
|
||||||
# These are objects that basically can return anything
|
# These are objects that basically can return anything
|
||||||
|
2
public/css/auto.min.css
vendored
2
public/css/auto.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/dark.min.css
vendored
2
public/css/dark.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/light.min.css
vendored
2
public/css/light.min.css
vendored
File diff suppressed because one or more lines are too long
39
public/js/scripts.min.js
vendored
39
public/js/scripts.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/js/tables.min.js
vendored
2
public/js/tables.min.js
vendored
@ -1 +1 @@
|
|||||||
var LightTableSorter=function(){var th=null;var cellIndex=null;var order="";var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){var arrayA=textA.replace("episodes: ","").replace("-",0).split("/");var arrayB=textB.replace("episodes: ","").replace("-",0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};var toggle=function(){var c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=c};var reset=function(){th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName("tbody")[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(function(row){tbody.appendChild(row)})}}};return{init:function(){var ths=document.getElementsByTagName("th");var results=[];for(var i=0,len=ths.length;i<len;i++){var th=ths[i];th.classList.add("sorting");th.classList.add("testing");results.push(th.onclick=onClickEvent)}return results}}}();LightTableSorter.init();
|
const LightTableSorter=(()=>{let th=null;let cellIndex=null;let order="";const text=row=>row.cells.item(cellIndex).textContent.toLowerCase();const sort=(a,b)=>{let textA=text(a);let textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){let arrayA=textA.replace("episodes: ","").replace("-",0).split("/");let arrayB=textB.replace("episodes: ","").replace("-",0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};const toggle=()=>{const c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=c};const reset=()=>{th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};const onClickEvent=e=>{if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;const tbody=th.offsetParent.getElementsByTagName("tbody")[0];let rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(row=>{tbody.appendChild(row)})}}};return{init:()=>{let ths=document.getElementsByTagName("th");let results=[];for(let i=0,len=ths.length;i<len;i++){let th=ths[i];th.classList.add("sorting");th.classList.add("testing");results.push(th.onclick=onClickEvent)}return results}}})();LightTableSorter.init();
|
File diff suppressed because one or more lines are too long
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Anilist Schema",
|
|
||||||
"schemaPath": "schema.graphql",
|
|
||||||
"extensions": {
|
|
||||||
"endpoints": {
|
|
||||||
"Anilist": {
|
|
||||||
"url": "https://graphql.anilist.co",
|
|
||||||
"headers": {
|
|
||||||
"user-agent": "JS GraphQL"
|
|
||||||
},
|
|
||||||
"introspect": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -189,7 +189,7 @@ final class Model
|
|||||||
*/
|
*/
|
||||||
public function deleteItem(FormItem $data, string $type): ?Request
|
public function deleteItem(FormItem $data, string $type): ?Request
|
||||||
{
|
{
|
||||||
$mediaId = $this->getMediaId((array)$data, $type);
|
$mediaId = $this->getMediaId((array) $data, $type);
|
||||||
if ($mediaId === NULL)
|
if ($mediaId === NULL)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -209,7 +209,7 @@ final class Model
|
|||||||
*/
|
*/
|
||||||
public function getListIdFromData(FormItem $data, string $type = 'ANIME'): ?string
|
public function getListIdFromData(FormItem $data, string $type = 'ANIME'): ?string
|
||||||
{
|
{
|
||||||
$mediaId = $this->getMediaId((array)$data, $type);
|
$mediaId = $this->getMediaId((array) $data, $type);
|
||||||
if ($mediaId === NULL)
|
if ($mediaId === NULL)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -244,7 +244,7 @@ final class Model
|
|||||||
/**
|
/**
|
||||||
* Find the id to update by
|
* Find the id to update by
|
||||||
*/
|
*/
|
||||||
private function getMediaId (array $data, string $type = 'ANIME'): ?string
|
private function getMediaId(array $data, string $type = 'ANIME'): ?string
|
||||||
{
|
{
|
||||||
if (isset($data['anilist_id']))
|
if (isset($data['anilist_id']))
|
||||||
{
|
{
|
||||||
|
8
src/AnimeClient/API/Anilist/graphql.config.yml
Normal file
8
src/AnimeClient/API/Anilist/graphql.config.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
schema: schema.graphql
|
||||||
|
extensions:
|
||||||
|
endpoints:
|
||||||
|
Anilist:
|
||||||
|
url: https://graphql.anilist.co
|
||||||
|
headers:
|
||||||
|
user-agent: JS GraphQL
|
||||||
|
introspect: true
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Kitsu Schema",
|
|
||||||
"schemaPath": "schema.graphql",
|
|
||||||
"extensions": {
|
|
||||||
"endpoints": {
|
|
||||||
"Kitsu": {
|
|
||||||
"url": "https://kitsu.io/api/graphql",
|
|
||||||
"headers": {
|
|
||||||
"user-agent": "JS GraphQL"
|
|
||||||
},
|
|
||||||
"introspect": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -52,7 +52,7 @@ final class Auth
|
|||||||
->getSegment(SESSION_SEGMENT);
|
->getSegment(SESSION_SEGMENT);
|
||||||
$this->model = $container->get('kitsu-model');
|
$this->model = $container->get('kitsu-model');
|
||||||
|
|
||||||
Event::on('::unauthorized::', [$this, 'reAuthenticate']);
|
Event::on('::unauthorized::', $this->reAuthenticate(...));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,7 +133,7 @@ final class Auth
|
|||||||
/**
|
/**
|
||||||
* Save the new authentication information
|
* Save the new authentication information
|
||||||
*/
|
*/
|
||||||
private function storeAuth(array|FALSE $auth): bool
|
private function storeAuth(array|false $auth): bool
|
||||||
{
|
{
|
||||||
if (FALSE !== $auth)
|
if (FALSE !== $auth)
|
||||||
{
|
{
|
||||||
|
@ -34,6 +34,7 @@ use Aviat\AnimeClient\API\{
|
|||||||
use Aviat\AnimeClient\Enum\MediaType;
|
use Aviat\AnimeClient\Enum\MediaType;
|
||||||
use Aviat\AnimeClient\Kitsu as K;
|
use Aviat\AnimeClient\Kitsu as K;
|
||||||
use Aviat\AnimeClient\Types\{Anime, MangaPage};
|
use Aviat\AnimeClient\Types\{Anime, MangaPage};
|
||||||
|
use Aviat\AnimeClient\Types\{AnimeListItem, MangaListItem};
|
||||||
use Aviat\Ion\{
|
use Aviat\Ion\{
|
||||||
Di\ContainerAware,
|
Di\ContainerAware,
|
||||||
Json
|
Json
|
||||||
@ -282,7 +283,7 @@ final class Model
|
|||||||
|
|
||||||
if ($list === NULL)
|
if ($list === NULL)
|
||||||
{
|
{
|
||||||
$data = $this->getList(MediaType::ANIME, $status) ?? [];
|
$data = $this->getList(MediaType::ANIME, $status);
|
||||||
|
|
||||||
// Bail out on no data
|
// Bail out on no data
|
||||||
if (empty($data))
|
if (empty($data))
|
||||||
@ -319,7 +320,7 @@ final class Model
|
|||||||
/**
|
/**
|
||||||
* Get all the anime entries, that are organized for output to html
|
* Get all the anime entries, that are organized for output to html
|
||||||
*
|
*
|
||||||
* @return array<string, mixed[]>
|
* @return array<string, array>
|
||||||
*/
|
*/
|
||||||
public function getFullOrganizedAnimeList(): array
|
public function getFullOrganizedAnimeList(): array
|
||||||
{
|
{
|
||||||
@ -330,7 +331,7 @@ final class Model
|
|||||||
foreach ($statuses as $status)
|
foreach ($statuses as $status)
|
||||||
{
|
{
|
||||||
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
|
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
|
||||||
$output[$mappedStatus] = $this->getAnimeList($status) ?? [];
|
$output[$mappedStatus] = $this->getAnimeList($status);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
@ -412,7 +413,7 @@ final class Model
|
|||||||
|
|
||||||
if ($list === NULL)
|
if ($list === NULL)
|
||||||
{
|
{
|
||||||
$data = $this->getList(MediaType::MANGA, $status) ?? [];
|
$data = $this->getList(MediaType::MANGA, $status);
|
||||||
|
|
||||||
// Bail out on no data
|
// Bail out on no data
|
||||||
if (empty($data))
|
if (empty($data))
|
||||||
@ -534,14 +535,14 @@ final class Model
|
|||||||
* Get the data for a specific list item, generally for editing
|
* Get the data for a specific list item, generally for editing
|
||||||
*
|
*
|
||||||
* @param string $listId - The unique identifier of that list item
|
* @param string $listId - The unique identifier of that list item
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
public function getListItem(string $listId)
|
public function getListItem(string $listId): AnimeListItem|MangaListItem|array
|
||||||
{
|
{
|
||||||
$baseData = $this->listItem->get($listId);
|
$baseData = $this->listItem->get($listId);
|
||||||
if ( ! isset($baseData['data']['findLibraryEntryById']))
|
if ( ! isset($baseData['data']['findLibraryEntryById']))
|
||||||
{
|
{
|
||||||
return [];
|
// We need to get the errors...
|
||||||
|
return $baseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (new LibraryEntryTransformer())->transform($baseData['data']['findLibraryEntryById']);
|
return (new LibraryEntryTransformer())->transform($baseData['data']['findLibraryEntryById']);
|
||||||
@ -566,7 +567,7 @@ final class Model
|
|||||||
// this way is much faster...
|
// this way is much faster...
|
||||||
foreach ($statuses as $status)
|
foreach ($statuses as $status)
|
||||||
{
|
{
|
||||||
foreach ($this->getPages([$this, 'getThumbListPages'], strtoupper($type), $status) as $page)
|
foreach ($this->getPages($this->getThumbListPages(...), strtoupper($type), $status) as $page)
|
||||||
{
|
{
|
||||||
$pages[] = $page;
|
$pages[] = $page;
|
||||||
}
|
}
|
||||||
@ -596,7 +597,7 @@ final class Model
|
|||||||
// this way is much faster...
|
// this way is much faster...
|
||||||
foreach ($statuses as $status)
|
foreach ($statuses as $status)
|
||||||
{
|
{
|
||||||
foreach ($this->getPages([$this, 'getSyncPages'], strtoupper($type), $status) as $page)
|
foreach ($this->getPages($this->getSyncPages(...), strtoupper($type), $status) as $page)
|
||||||
{
|
{
|
||||||
$pages[] = $page;
|
$pages[] = $page;
|
||||||
}
|
}
|
||||||
@ -626,7 +627,7 @@ final class Model
|
|||||||
{
|
{
|
||||||
$pages = [];
|
$pages = [];
|
||||||
|
|
||||||
foreach ($this->getPages([$this, 'getListPages'], strtoupper($type), strtoupper($status)) as $page)
|
foreach ($this->getPages($this->getListPages(...), strtoupper($type), strtoupper($status)) as $page)
|
||||||
{
|
{
|
||||||
$pages[] = $page;
|
$pages[] = $page;
|
||||||
}
|
}
|
||||||
@ -786,7 +787,7 @@ final class Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getUserId(): string
|
protected function getUserId(): string
|
||||||
{
|
{
|
||||||
static $userId = NULL;
|
static $userId = NULL;
|
||||||
|
|
||||||
|
@ -67,9 +67,6 @@ trait MutationTrait
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a list item
|
* Remove a list item
|
||||||
*
|
|
||||||
* @param FormItem $data
|
|
||||||
* @return Request
|
|
||||||
*/
|
*/
|
||||||
public function deleteItem(FormItem $data): Request
|
public function deleteItem(FormItem $data): Request
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ query ($slug: String!) {
|
|||||||
}
|
}
|
||||||
categories(first: 100) {
|
categories(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
title
|
title(locales: "en")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
characters(first: 100) {
|
characters(first: 100) {
|
||||||
@ -29,7 +29,7 @@ query ($slug: String!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
original {
|
original {
|
||||||
@ -50,7 +50,7 @@ query ($slug: String!) {
|
|||||||
startCursor
|
startCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
description
|
description(locales: "en")
|
||||||
startDate
|
startDate
|
||||||
endDate
|
endDate
|
||||||
episodeCount
|
episodeCount
|
||||||
@ -87,7 +87,7 @@ query ($slug: String!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ query ($slug: String!) {
|
|||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
totalLength
|
totalLength
|
||||||
youtubeTrailerVideoId
|
youtubeTrailerVideoId
|
||||||
|
@ -19,7 +19,7 @@ query ($id: ID!) {
|
|||||||
}
|
}
|
||||||
categories(first: 100) {
|
categories(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
title
|
title(locales: "en")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
characters(first: 100) {
|
characters(first: 100) {
|
||||||
@ -29,7 +29,7 @@ query ($id: ID!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
original {
|
original {
|
||||||
@ -50,7 +50,7 @@ query ($id: ID!) {
|
|||||||
startCursor
|
startCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
description
|
description(locales: "en")
|
||||||
startDate
|
startDate
|
||||||
endDate
|
endDate
|
||||||
episodeCount
|
episodeCount
|
||||||
@ -87,7 +87,7 @@ query ($id: ID!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ query ($id: ID!) {
|
|||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
totalLength
|
totalLength
|
||||||
youtubeTrailerVideoId
|
youtubeTrailerVideoId
|
||||||
|
@ -6,12 +6,12 @@ query ($slug: String!) {
|
|||||||
url
|
url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
description
|
description(locales: "en")
|
||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: "*")
|
||||||
},
|
},
|
||||||
media(first: 100) {
|
media(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
@ -22,7 +22,7 @@ query ($slug: String!) {
|
|||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
posterImage {
|
posterImage {
|
||||||
original {
|
original {
|
||||||
@ -41,7 +41,7 @@ query ($slug: String!) {
|
|||||||
type
|
type
|
||||||
}
|
}
|
||||||
role
|
role
|
||||||
voices(first: 10) {
|
voices(first: 10, locale:"*", sort:{direction:ASCENDING, on: UPDATED_AT}) {
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
locale
|
locale
|
||||||
@ -53,7 +53,7 @@ query ($slug: String!) {
|
|||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
original {
|
original {
|
||||||
@ -70,4 +70,4 @@ query ($slug: String!) {
|
|||||||
}
|
}
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ query (
|
|||||||
type
|
type
|
||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
alternatives
|
alternatives
|
||||||
}
|
}
|
||||||
...on Anime {
|
...on Anime {
|
||||||
|
@ -16,7 +16,7 @@ query($id: ID!) {
|
|||||||
ageRating
|
ageRating
|
||||||
categories(first: 100) {
|
categories(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
title
|
title(locales: "*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mappings(first: 10) {
|
mappings(first: 10) {
|
||||||
@ -41,7 +41,7 @@ query($id: ID!) {
|
|||||||
endDate
|
endDate
|
||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
}
|
}
|
||||||
type
|
type
|
||||||
|
@ -33,7 +33,7 @@ query ($slug: String!) {
|
|||||||
titles {
|
titles {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
...on Anime {
|
...on Anime {
|
||||||
episodeCount
|
episodeCount
|
||||||
|
@ -19,7 +19,7 @@ query ($slug: String!) {
|
|||||||
}
|
}
|
||||||
categories(first: 100) {
|
categories(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
title
|
title(locales: "en")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chapterCount
|
chapterCount
|
||||||
@ -51,7 +51,7 @@ query ($slug: String!) {
|
|||||||
startCursor
|
startCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
description
|
description(locales: "en")
|
||||||
startDate
|
startDate
|
||||||
endDate
|
endDate
|
||||||
mappings(first: 10) {
|
mappings(first: 10) {
|
||||||
@ -98,7 +98,7 @@ query ($slug: String!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@ query ($slug: String!) {
|
|||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ query ($id: ID!) {
|
|||||||
}
|
}
|
||||||
categories(first: 100) {
|
categories(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
title
|
title(locales: "*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chapterCount
|
chapterCount
|
||||||
@ -116,7 +116,7 @@ query ($id: ID!) {
|
|||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
query ($slug: String!) {
|
query ($slug: String!) {
|
||||||
findPersonBySlug(slug: $slug) {
|
findPersonBySlug(slug: $slug) {
|
||||||
id
|
id
|
||||||
description
|
description(locales: "en")
|
||||||
birthday
|
birthday
|
||||||
image {
|
image {
|
||||||
original {
|
original {
|
||||||
@ -20,7 +20,7 @@ query ($slug: String!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
mediaStaff(first: 100) {
|
mediaStaff(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
@ -47,7 +47,7 @@ query ($slug: String!) {
|
|||||||
titles {
|
titles {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ query ($slug: String!) {
|
|||||||
}
|
}
|
||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ query ($type: MediaTypeEnum!) {
|
|||||||
}
|
}
|
||||||
categories(first: 100) {
|
categories(first: 100) {
|
||||||
nodes {
|
nodes {
|
||||||
title
|
title(locales: "*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
characters(first: 100) {
|
characters(first: 100) {
|
||||||
@ -36,7 +36,7 @@ query ($type: MediaTypeEnum!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
image {
|
image {
|
||||||
original {
|
original {
|
||||||
@ -90,7 +90,7 @@ query ($type: MediaTypeEnum!) {
|
|||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ query ($type: MediaTypeEnum!) {
|
|||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
...on Anime {
|
...on Anime {
|
||||||
episodeCount
|
episodeCount
|
||||||
|
@ -19,7 +19,7 @@ query ($query: String!) {
|
|||||||
slug
|
slug
|
||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
alternatives
|
alternatives
|
||||||
}
|
}
|
||||||
myLibraryEntry {
|
myLibraryEntry {
|
||||||
|
@ -19,7 +19,7 @@ query ($query: String!) {
|
|||||||
slug
|
slug
|
||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
alternatives
|
alternatives
|
||||||
}
|
}
|
||||||
myLibraryEntry {
|
myLibraryEntry {
|
||||||
|
@ -18,8 +18,10 @@ query ($slug: String!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
birthday
|
birthday
|
||||||
|
createdAt
|
||||||
id
|
id
|
||||||
location
|
location
|
||||||
|
gender
|
||||||
name
|
name
|
||||||
proMessage
|
proMessage
|
||||||
proTier
|
proTier
|
||||||
@ -52,7 +54,7 @@ query ($slug: String!) {
|
|||||||
}
|
}
|
||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
...on Manga {
|
...on Manga {
|
||||||
@ -72,7 +74,7 @@ query ($slug: String!) {
|
|||||||
}
|
}
|
||||||
titles {
|
titles {
|
||||||
canonical
|
canonical
|
||||||
localized
|
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
...on Person {
|
...on Person {
|
||||||
@ -88,11 +90,12 @@ query ($slug: String!) {
|
|||||||
width
|
width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
name,
|
||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: "*")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
...on Character {
|
...on Character {
|
||||||
@ -107,12 +110,12 @@ query ($slug: String!) {
|
|||||||
height
|
height
|
||||||
width
|
width
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
names {
|
names {
|
||||||
alternatives
|
alternatives
|
||||||
canonical
|
canonical
|
||||||
canonicalLocale
|
canonicalLocale
|
||||||
localized
|
localized(locales: "*")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,7 +153,7 @@ query ($slug: String!) {
|
|||||||
names {
|
names {
|
||||||
canonical
|
canonical
|
||||||
alternatives
|
alternatives
|
||||||
localized
|
localized(locales: "*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
waifuOrHusbando
|
waifuOrHusbando
|
||||||
|
@ -78,7 +78,7 @@ final class RequestBuilder extends APIRequestBuilder
|
|||||||
elseif ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
elseif ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
||||||
{
|
{
|
||||||
$token = $sessionSegment->get('auth_token');
|
$token = $sessionSegment->get('auth_token');
|
||||||
if ( ! (empty($token) || $cache->has(K::AUTH_TOKEN_CACHE_KEY)))
|
if ( ! empty($token))
|
||||||
{
|
{
|
||||||
$cache->set(K::AUTH_TOKEN_CACHE_KEY, $token);
|
$cache->set(K::AUTH_TOKEN_CACHE_KEY, $token);
|
||||||
}
|
}
|
||||||
@ -239,43 +239,4 @@ final class RequestBuilder extends APIRequestBuilder
|
|||||||
'body' => $body,
|
'body' => $body,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a request
|
|
||||||
*/
|
|
||||||
private function request(string $type, string $url, array $options = []): array
|
|
||||||
{
|
|
||||||
$logger = $this->container->getLogger('kitsu-request');
|
|
||||||
$response = $this->getResponse($type, $url, $options);
|
|
||||||
$statusCode = $response->getStatus();
|
|
||||||
|
|
||||||
// Check for requests that are unauthorized
|
|
||||||
if ($statusCode === 401 || $statusCode === 403)
|
|
||||||
{
|
|
||||||
Event::emit(EventType::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rawBody = wait($response->getBody()->buffer());
|
|
||||||
|
|
||||||
// Any other type of failed request
|
|
||||||
if ($statusCode > 299 || $statusCode < 200)
|
|
||||||
{
|
|
||||||
if ($logger !== NULL)
|
|
||||||
{
|
|
||||||
$logger->warning('Non 2xx response for api call', (array) $response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Json::decode($rawBody);
|
|
||||||
}
|
|
||||||
catch (JsonException)
|
|
||||||
{
|
|
||||||
// dump($e);
|
|
||||||
dump($rawBody);
|
|
||||||
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@ trait RequestBuilderTrait
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the request builder object
|
* Set the request builder object
|
||||||
*
|
|
||||||
* @return ListItem|Model|RequestBuilderTrait
|
|
||||||
*/
|
*/
|
||||||
public function setRequestBuilder(RequestBuilder $requestBuilder): self
|
public function setRequestBuilder(RequestBuilder $requestBuilder): self
|
||||||
{
|
{
|
||||||
|
@ -36,7 +36,8 @@ final class AnimeTransformer extends AbstractTransformer
|
|||||||
$characters = [];
|
$characters = [];
|
||||||
$links = [];
|
$links = [];
|
||||||
$staff = [];
|
$staff = [];
|
||||||
$genres = array_map(static fn ($genre) => $genre['title']['en'], $base['categories']['nodes']);
|
$rawGenres = array_filter($base['categories']['nodes'], static fn ($c) => $c !== NULL);
|
||||||
|
$genres = array_map(static fn ($genre) => $genre['title']['en'], $rawGenres);
|
||||||
|
|
||||||
sort($genres);
|
sort($genres);
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ final class AnimeTransformer extends AbstractTransformer
|
|||||||
|
|
||||||
$details = $rawCharacter['character'];
|
$details = $rawCharacter['character'];
|
||||||
$characters[$type][$details['id']] = [
|
$characters[$type][$details['id']] = [
|
||||||
'image' => $details['image']['original']['url'] ?? '',
|
'image' => Kitsu::getImage($details),
|
||||||
'name' => $details['names']['canonical'],
|
'name' => $details['names']['canonical'],
|
||||||
'slug' => $details['slug'],
|
'slug' => $details['slug'],
|
||||||
];
|
];
|
||||||
@ -100,7 +101,7 @@ final class AnimeTransformer extends AbstractTransformer
|
|||||||
$staff[$role][$person['id']] = [
|
$staff[$role][$person['id']] = [
|
||||||
'id' => $person['id'],
|
'id' => $person['id'],
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'image' => $person['image']['original']['url'],
|
'image' => Kitsu::getImage($person),
|
||||||
'slug' => $person['slug'],
|
'slug' => $person['slug'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ final class CharacterTransformer extends AbstractTransformer
|
|||||||
'castings' => $castings,
|
'castings' => $castings,
|
||||||
'description' => $data['description']['en'],
|
'description' => $data['description']['en'],
|
||||||
'id' => $data['id'],
|
'id' => $data['id'],
|
||||||
'image' => $data['image']['original']['url'] ?? 'images/placeholder.png',
|
'image' => Kitsu::getImage($data),
|
||||||
'media' => $media,
|
'media' => $media,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'names' => $names,
|
'names' => $names,
|
||||||
@ -130,7 +130,7 @@ final class CharacterTransformer extends AbstractTransformer
|
|||||||
'person' => [
|
'person' => [
|
||||||
'id' => $voice['person']['id'],
|
'id' => $voice['person']['id'],
|
||||||
'slug' => $voice['person']['slug'],
|
'slug' => $voice['person']['slug'],
|
||||||
'image' => $voice['person']['image']['original']['url'],
|
'image' => Kitsu::getImage($voice['person']),
|
||||||
'name' => $voice['person']['name'],
|
'name' => $voice['person']['name'],
|
||||||
],
|
],
|
||||||
'series' => [],
|
'series' => [],
|
||||||
|
@ -54,10 +54,10 @@ final class MangaTransformer extends AbstractTransformer
|
|||||||
}
|
}
|
||||||
|
|
||||||
$details = $rawCharacter['character'];
|
$details = $rawCharacter['character'];
|
||||||
if (array_key_exists($details['id'], $characters[$type]))
|
if (array_key_exists($details['id'], (array)$characters[$type]))
|
||||||
{
|
{
|
||||||
$characters[$type][$details['id']] = [
|
$characters[$type][$details['id']] = [
|
||||||
'image' => $details['image']['original']['url'],
|
'image' => Kitsu::getImage($details),
|
||||||
'name' => $details['names']['canonical'],
|
'name' => $details['names']['canonical'],
|
||||||
'slug' => $details['slug'],
|
'slug' => $details['slug'],
|
||||||
];
|
];
|
||||||
@ -103,7 +103,7 @@ final class MangaTransformer extends AbstractTransformer
|
|||||||
'id' => $person['id'],
|
'id' => $person['id'],
|
||||||
'slug' => $person['slug'],
|
'slug' => $person['slug'],
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'image' => $person['image']['original']['url'],
|
'image' => Kitsu::getImage($person),
|
||||||
];
|
];
|
||||||
|
|
||||||
usort($staff[$role], static fn ($a, $b) => $a['name'] <=> $b['name']);
|
usort($staff[$role], static fn ($a, $b) => $a['name'] <=> $b['name']);
|
||||||
|
@ -35,7 +35,8 @@ final class PersonTransformer extends AbstractTransformer
|
|||||||
return Person::from([
|
return Person::from([
|
||||||
'id' => $data['id'],
|
'id' => $data['id'],
|
||||||
'name' => $canonicalName,
|
'name' => $canonicalName,
|
||||||
'image' => $data['image']['original']['url'],
|
'birthday' => $data['birthday'],
|
||||||
|
'image' => Kitsu::getImage($data),
|
||||||
'names' => array_diff($data['names']['localized'], [$canonicalName]),
|
'names' => array_diff($data['names']['localized'], [$canonicalName]),
|
||||||
'description' => $data['description']['en'] ?? '',
|
'description' => $data['description']['en'] ?? '',
|
||||||
'characters' => $orgData['characters'],
|
'characters' => $orgData['characters'],
|
||||||
@ -97,7 +98,12 @@ final class PersonTransformer extends AbstractTransformer
|
|||||||
{
|
{
|
||||||
foreach ($data['voices']['nodes'] as $voicing)
|
foreach ($data['voices']['nodes'] as $voicing)
|
||||||
{
|
{
|
||||||
$character = $voicing['mediaCharacter']['character'];
|
if ($voicing === NULL)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$character = $voicing['mediaCharacter']['character'] ?? [];
|
||||||
$charId = $character['id'];
|
$charId = $character['id'];
|
||||||
$rawMedia = $voicing['mediaCharacter']['media'];
|
$rawMedia = $voicing['mediaCharacter']['media'];
|
||||||
$role = strtolower($voicing['mediaCharacter']['role']);
|
$role = strtolower($voicing['mediaCharacter']['role']);
|
||||||
@ -123,7 +129,7 @@ final class PersonTransformer extends AbstractTransformer
|
|||||||
'character' => [
|
'character' => [
|
||||||
'id' => $character['id'],
|
'id' => $character['id'],
|
||||||
'slug' => $character['slug'],
|
'slug' => $character['slug'],
|
||||||
'image' => $character['image']['original']['url'],
|
'image' => Kitsu::getImage($character),
|
||||||
'canonicalName' => $character['names']['canonical'],
|
'canonicalName' => $character['names']['canonical'],
|
||||||
],
|
],
|
||||||
'media' => [
|
'media' => [
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
@ -39,15 +39,22 @@ final class UserTransformer extends AbstractTransformer
|
|||||||
] : [];
|
] : [];
|
||||||
|
|
||||||
return User::from([
|
return User::from([
|
||||||
'about' => $base['about'],
|
'about' => $base['about'] ?? '',
|
||||||
'avatar' => $base['avatarImage']['original']['url'],
|
'avatar' => $base['avatarImage']['original']['url'] ?? NULL,
|
||||||
|
'birthday' => $base['birthday'] !== NULL
|
||||||
|
? formatDate($base['birthday']) . ' (' .
|
||||||
|
friendlyTime(getDateDiff($base['birthday']), 'year') . ')'
|
||||||
|
: NULL,
|
||||||
|
'joinDate' => formatDate($base['createdAt']) . ' (' .
|
||||||
|
friendlyTime(getDateDiff($base['createdAt']), 'day') . ' ago)',
|
||||||
|
'gender' => $base['gender'],
|
||||||
'favorites' => $this->organizeFavorites($favorites),
|
'favorites' => $this->organizeFavorites($favorites),
|
||||||
'location' => $base['location'],
|
'location' => $base['location'],
|
||||||
'name' => $base['name'],
|
'name' => $base['name'],
|
||||||
'slug' => $base['slug'],
|
'slug' => $base['slug'],
|
||||||
'stats' => $this->organizeStats($stats),
|
'stats' => $this->organizeStats($stats),
|
||||||
'waifu' => $waifu,
|
'waifu' => $waifu,
|
||||||
'website' => $base['siteLinks']['nodes'][0]['url'],
|
'website' => $base['siteLinks']['nodes'][0]['url'] ?? NULL,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,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']),
|
||||||
];
|
];
|
||||||
|
8
src/AnimeClient/API/Kitsu/graphql.config.yml
Normal file
8
src/AnimeClient/API/Kitsu/graphql.config.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
schema: schema.graphql
|
||||||
|
extensions:
|
||||||
|
endpoints:
|
||||||
|
Kitsu:
|
||||||
|
url: https://kitsu.io/api/graphql
|
||||||
|
headers:
|
||||||
|
user-agent: JS GraphQL
|
||||||
|
introspect: true
|
@ -35,7 +35,7 @@ final class ParallelAPIRequest
|
|||||||
/**
|
/**
|
||||||
* Add a request
|
* Add a request
|
||||||
*/
|
*/
|
||||||
public function addRequest(string|Request $request, string|int|NULL $key = NULL): self
|
public function addRequest(string|Request $request, string|int|null $key = NULL): self
|
||||||
{
|
{
|
||||||
if ($key !== NULL)
|
if ($key !== NULL)
|
||||||
{
|
{
|
||||||
@ -56,7 +56,7 @@ final class ParallelAPIRequest
|
|||||||
*/
|
*/
|
||||||
public function addRequests(array $requests): self
|
public function addRequests(array $requests): self
|
||||||
{
|
{
|
||||||
array_walk($requests, [$this, 'addRequest']);
|
array_walk($requests, $this->addRequest(...));
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -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 Psr\SimpleCache\CacheInterface;
|
use Psr\SimpleCache\CacheInterface;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@ -25,13 +26,17 @@ 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
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
/**
|
/**
|
||||||
* Load configuration options from .toml files
|
* Load configuration options from .toml files
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @param string $path - Path to load config
|
* @param string $path - Path to load config
|
||||||
*/
|
*/
|
||||||
function loadConfig(string $path): array
|
function loadConfig(string $path): array
|
||||||
@ -72,8 +77,6 @@ function loadConfig(string $path): array
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load config from one specific TOML file
|
* Load config from one specific TOML file
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
*/
|
||||||
function loadTomlFile(string $filename): array
|
function loadTomlFile(string $filename): array
|
||||||
{
|
{
|
||||||
@ -131,19 +134,6 @@ function tomlToArray(string $toml): array
|
|||||||
//! Misc Functions
|
//! Misc Functions
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
if ( ! function_exists('array_is_list'))
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Polyfill for PHP 8
|
|
||||||
*
|
|
||||||
* @see https://www.php.net/manual/en/function.array-is-list
|
|
||||||
*/
|
|
||||||
function array_is_list(array $a): bool
|
|
||||||
{
|
|
||||||
return $a === [] || (array_keys($a) === range(0, count($a) - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the array sequential, not associative?
|
* Is the array sequential, not associative?
|
||||||
*/
|
*/
|
||||||
@ -256,8 +246,6 @@ function getLocalImg(string $kitsuUrl, bool $webp = TRUE): string
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a transparent placeholder image
|
* Create a transparent placeholder image
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
*/
|
||||||
function createPlaceholderImage(string $path, int $width = 200, int $height = 200, string $text = 'Image Unavailable'): bool
|
function createPlaceholderImage(string $path, int $width = 200, int $height = 200, string $text = 'Image Unavailable'): bool
|
||||||
{
|
{
|
||||||
@ -303,15 +291,13 @@ function clearCache(CacheInterface $cache): bool
|
|||||||
|
|
||||||
$cleared = $cache->clear();
|
$cleared = $cache->clear();
|
||||||
|
|
||||||
$saved = (empty($userData)) ? TRUE : $cache->setMultiple($userData);
|
$saved = empty($userData) || $cache->setMultiple($userData);
|
||||||
|
|
||||||
return $cleared && $saved;
|
return $cleared && $saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a PHP code template as a string
|
* Render a PHP code template as a string
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
*/
|
||||||
function renderTemplate(string $path, array $data): string
|
function renderTemplate(string $path, array $data): string
|
||||||
{
|
{
|
||||||
@ -322,3 +308,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}";
|
||||||
|
}
|
||||||
|
@ -44,7 +44,7 @@ abstract class BaseCommand extends Command
|
|||||||
/**
|
/**
|
||||||
* Echo text in a box
|
* Echo text in a box
|
||||||
*/
|
*/
|
||||||
public function echoBox(string|array $message, string|int|NULL $fgColor = NULL, string|int|NULL $bgColor = NULL): void
|
public function echoBox(string|array $message, string|int|null $fgColor = NULL, string|int|null $bgColor = NULL): void
|
||||||
{
|
{
|
||||||
if (is_array($message))
|
if (is_array($message))
|
||||||
{
|
{
|
||||||
@ -131,7 +131,7 @@ abstract class BaseCommand extends Command
|
|||||||
return $this->_di($configArray, $APP_DIR);
|
return $this->_di($configArray, $APP_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function _line(string $message, int|string|NULL $fgColor = NULL, int|string|NULL $bgColor = NULL): void
|
private function _line(string $message, int|string|null $fgColor = NULL, int|string|null $bgColor = NULL): void
|
||||||
{
|
{
|
||||||
if ($fgColor !== NULL)
|
if ($fgColor !== NULL)
|
||||||
{
|
{
|
||||||
|
@ -98,21 +98,23 @@ final class SyncLists extends BaseCommand
|
|||||||
if ( ! $anilistEnabled)
|
if ( ! $anilistEnabled)
|
||||||
{
|
{
|
||||||
$this->echoErrorBox('Anlist API is not enabled. Can not sync.');
|
$this->echoErrorBox('Anlist API is not enabled. Can not sync.');
|
||||||
return false;
|
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authentication is required to update Kitsu
|
// Authentication is required to update Kitsu
|
||||||
$isKitsuAuthenticated = $this->container->get('auth')->isAuthenticated();
|
$isKitsuAuthenticated = $this->container->get('auth')->isAuthenticated();
|
||||||
if ( !$isKitsuAuthenticated)
|
if ( ! $isKitsuAuthenticated)
|
||||||
{
|
{
|
||||||
$this->echoErrorBox('Kitsu is not authenticated. Kitsu list can not be updated.');
|
$this->echoErrorBox('Kitsu is not authenticated. Kitsu list can not be updated.');
|
||||||
return false;
|
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->anilistModel = $this->container->get('anilist-model');
|
$this->anilistModel = $this->container->get('anilist-model');
|
||||||
$this->kitsuModel = $this->container->get('kitsu-model');
|
$this->kitsuModel = $this->container->get('kitsu-model');
|
||||||
|
|
||||||
return true;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -148,7 +150,7 @@ final class SyncLists extends BaseCommand
|
|||||||
*/
|
*/
|
||||||
protected function fetch(string $type): array
|
protected function fetch(string $type): array
|
||||||
{
|
{
|
||||||
$this->echo("Fetching $type List Data");
|
$this->echo("Fetching {$type} List Data");
|
||||||
$progress = new Widgets\ProgressBar($this->getConsole(), 2, 50, FALSE);
|
$progress = new Widgets\ProgressBar($this->getConsole(), 2, 50, FALSE);
|
||||||
|
|
||||||
$anilist = $this->fetchAnilist($type);
|
$anilist = $this->fetchAnilist($type);
|
||||||
|
@ -42,6 +42,11 @@ class Controller
|
|||||||
{
|
{
|
||||||
use ContainerAware;
|
use ContainerAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global configuration object
|
||||||
|
*/
|
||||||
|
public ConfigInterface $config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The authentication object
|
* The authentication object
|
||||||
*/
|
*/
|
||||||
@ -52,11 +57,6 @@ class Controller
|
|||||||
*/
|
*/
|
||||||
protected CacheInterface $cache;
|
protected CacheInterface $cache;
|
||||||
|
|
||||||
/**
|
|
||||||
* The global configuration object
|
|
||||||
*/
|
|
||||||
public ConfigInterface $config;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request object
|
* Request object
|
||||||
*/
|
*/
|
||||||
@ -120,197 +120,194 @@ class Controller
|
|||||||
Event::on(EventType::RESET_CACHE_KEY, fn (string $key) => $this->cache->delete($key));
|
Event::on(EventType::RESET_CACHE_KEY, fn (string $key) => $this->cache->delete($key));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the current url in the session as the target of a future redirect
|
* Set the current url in the session as the target of a future redirect
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @throws ContainerException
|
||||||
* @throws ContainerException
|
* @throws NotFoundException
|
||||||
* @throws NotFoundException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
public function setSessionRedirect(?string $url = NULL): void
|
public function setSessionRedirect(?string $url = NULL): void
|
||||||
{
|
{
|
||||||
$serverParams = $this->request->getServerParams();
|
$serverParams = $this->request->getServerParams();
|
||||||
|
|
||||||
if ( ! array_key_exists('HTTP_REFERER', $serverParams))
|
if ( ! array_key_exists('HTTP_REFERER', $serverParams))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$util = $this->container->get('util');
|
$util = $this->container->get('util');
|
||||||
$doubleFormPage = $serverParams['HTTP_REFERER'] === $this->request->getUri();
|
$doubleFormPage = $serverParams['HTTP_REFERER'] === $this->request->getUri();
|
||||||
$isLoginPage = str_contains($serverParams['HTTP_REFERER'], 'login');
|
$isLoginPage = str_contains($serverParams['HTTP_REFERER'], 'login');
|
||||||
|
|
||||||
// Don't attempt to set the redirect url if
|
// Don't attempt to set the redirect url if
|
||||||
// the page is one of the form type pages,
|
// the page is one of the form type pages,
|
||||||
// and the previous page is also a form type
|
// and the previous page is also a form type
|
||||||
if ($doubleFormPage || $isLoginPage)
|
if ($doubleFormPage || $isLoginPage)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NULL === $url)
|
if (NULL === $url)
|
||||||
{
|
{
|
||||||
$url = $util->isViewPage()
|
$url = $util->isViewPage()
|
||||||
? (string) $this->request->getUri()
|
? (string) $this->request->getUri()
|
||||||
: $serverParams['HTTP_REFERER'];
|
: $serverParams['HTTP_REFERER'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->session->set('redirect_url', $url);
|
$this->session->set('redirect_url', $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to the url previously set in the session
|
* Redirect to the url previously set in the session
|
||||||
*
|
*
|
||||||
* If one is not set, redirect to default url
|
* If one is not set, redirect to default url
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @throws InvalidArgumentException
|
||||||
* @throws InvalidArgumentException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
public function sessionRedirect(): void
|
public function sessionRedirect(): void
|
||||||
{
|
{
|
||||||
$target = $this->session->get('redirect_url') ?? '/';
|
$target = $this->session->get('redirect_url') ?? '/';
|
||||||
|
|
||||||
$this->redirect($target, 303);
|
$this->redirect($target, 303);
|
||||||
$this->session->set('redirect_url', NULL);
|
$this->session->set('redirect_url', NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current user is authenticated, else error and exit
|
* Check if the current user is authenticated, else error and exit
|
||||||
* @codeCoverageIgnore
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
protected function checkAuth(): void
|
protected function checkAuth(): void
|
||||||
{
|
{
|
||||||
if ( ! $this->auth->isAuthenticated())
|
if ( ! $this->auth->isAuthenticated())
|
||||||
{
|
{
|
||||||
$this->errorPage(
|
$this->errorPage(
|
||||||
403,
|
403,
|
||||||
'Forbidden',
|
'Forbidden',
|
||||||
'You must <a href="/login">log in</a> to perform this action.'
|
'You must <a href="/login">log in</a> to perform this action.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the string output of a partial template
|
* Get the string output of a partial template
|
||||||
*
|
*/
|
||||||
* @codeCoverageIgnore
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
*/
|
protected function loadPartial(HtmlView $view, string $template, array $data = []): string
|
||||||
protected function loadPartial(HtmlView $view, string $template, array $data = []): string
|
{
|
||||||
{
|
$router = $this->container->get('dispatcher');
|
||||||
$router = $this->container->get('dispatcher');
|
|
||||||
|
|
||||||
if (isset($this->baseData))
|
if (isset($this->baseData))
|
||||||
{
|
{
|
||||||
$data = array_merge($this->baseData, $data);
|
$data = array_merge($this->baseData, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$route = $router->getRoute();
|
$route = $router->getRoute();
|
||||||
$data['route_path'] = $route !== FALSE ? $route->path : '';
|
$data['route_path'] = $route !== FALSE ? $route->path : '';
|
||||||
|
|
||||||
$templatePath = _dir($this->config->get('view_path'), "{$template}.php");
|
$templatePath = _dir($this->config->get('view_path'), "{$template}.php");
|
||||||
|
|
||||||
if ( ! is_file($templatePath))
|
if ( ! is_file($templatePath))
|
||||||
{
|
{
|
||||||
throw new InvalidArgumentException("Invalid template : {$template}");
|
throw new InvalidArgumentException("Invalid template : {$template}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $view->renderTemplate($templatePath, $data);
|
return $view->renderTemplate($templatePath, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a template with header and footer
|
* Render a template with header and footer
|
||||||
*
|
*/
|
||||||
* @codeCoverageIgnore
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
*/
|
protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
|
||||||
protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
|
{
|
||||||
{
|
$csp = [
|
||||||
$csp = [
|
"default-src 'self' media.kitsu.io kitsu-production-media.s3.us-west-002.backblazeb2.com",
|
||||||
"default-src 'self' media.kitsu.io kitsu-production-media.s3.us-west-002.backblazeb2.com",
|
"object-src 'none'",
|
||||||
"object-src 'none'",
|
"child-src 'self' *.youtube.com polyfill.io",
|
||||||
"child-src 'self' *.youtube.com polyfill.io",
|
];
|
||||||
];
|
|
||||||
|
|
||||||
$view->addHeader('Content-Security-Policy', implode('; ', $csp));
|
$view->addHeader('Content-Security-Policy', implode('; ', $csp));
|
||||||
$view->appendOutput($this->loadPartial($view, 'header', $data));
|
$view->appendOutput($this->loadPartial($view, 'header', $data));
|
||||||
|
|
||||||
if (array_key_exists('message', $data) && is_array($data['message']))
|
if (array_key_exists('message', $data) && is_array($data['message']))
|
||||||
{
|
{
|
||||||
$view->appendOutput($this->loadPartial($view, 'message', $data['message']));
|
$view->appendOutput($this->loadPartial($view, 'message', $data['message']));
|
||||||
}
|
}
|
||||||
|
|
||||||
$view->appendOutput($this->loadPartial($view, $template, $data));
|
$view->appendOutput($this->loadPartial($view, $template, $data));
|
||||||
$view->appendOutput($this->loadPartial($view, 'footer', $data));
|
$view->appendOutput($this->loadPartial($view, 'footer', $data));
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 404 action
|
* 404 action
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @throws InvalidArgumentException
|
||||||
* @throws InvalidArgumentException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
public function notFound(
|
public function notFound(
|
||||||
string $title = 'Sorry, page not found',
|
string $title = 'Sorry, page not found',
|
||||||
string $message = 'Page Not Found'
|
string $message = 'Page Not Found'
|
||||||
): void {
|
): never {
|
||||||
$this->outputHTML('404', [
|
$this->outputHTML('404', [
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
], NULL, 404);
|
], NULL, 404);
|
||||||
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a generic error page
|
* Display a generic error page
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @throws InvalidArgumentException
|
||||||
* @throws InvalidArgumentException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void
|
public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void
|
||||||
{
|
{
|
||||||
$this->outputHTML('error', [
|
$this->outputHTML('error', [
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
'long_message' => $longMessage,
|
'long_message' => $longMessage,
|
||||||
], NULL, $httpCode);
|
], NULL, $httpCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to the default controller/url from an empty path
|
* Redirect to the default controller/url from an empty path
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @throws InvalidArgumentException
|
||||||
* @throws InvalidArgumentException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
public function redirectToDefaultRoute(): void
|
public function redirectToDefaultRoute(): void
|
||||||
{
|
{
|
||||||
$defaultType = $this->config->get('default_list');
|
$defaultType = $this->config->get('default_list');
|
||||||
$this->redirect($this->urlGenerator->defaultUrl($defaultType), 303);
|
$this->redirect($this->urlGenerator->defaultUrl($defaultType), 303);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a session flash variable to display a message on
|
* Set a session flash variable to display a message on
|
||||||
* next page load
|
* next page load
|
||||||
*
|
*/
|
||||||
* @codeCoverageIgnore
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
*/
|
public function setFlashMessage(string $message, string $type = 'info'): void
|
||||||
public function setFlashMessage(string $message, string $type = 'info'): void
|
{
|
||||||
{
|
static $messages;
|
||||||
static $messages;
|
|
||||||
|
|
||||||
if ( ! $messages)
|
if ( ! $messages)
|
||||||
{
|
{
|
||||||
$messages = [];
|
$messages = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$messages[] = [
|
$messages[] = [
|
||||||
'message_type' => $type,
|
'message_type' => $type,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->session->setFlash('message', $messages);
|
$this->session->setFlash('message', $messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for consistent page titles
|
* Helper for consistent page titles
|
||||||
@ -322,63 +319,62 @@ class Controller
|
|||||||
return implode(' · ', $parts);
|
return implode(' · ', $parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a message box to the page
|
* Add a message box to the page
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @throws InvalidArgumentException
|
||||||
* @throws InvalidArgumentException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
protected function showMessage(HtmlView $view, string $type, string $message): string
|
protected function showMessage(HtmlView $view, string $type, string $message): string
|
||||||
{
|
{
|
||||||
return $this->loadPartial($view, 'message', [
|
return $this->loadPartial($view, 'message', [
|
||||||
'message_type' => $type,
|
'message_type' => $type,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output a template to HTML, using the provided data
|
* Output a template to HTML, using the provided data
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @throws InvalidArgumentException
|
||||||
* @throws InvalidArgumentException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
protected function outputHTML(string $template, array $data = [], ?HtmlView $view = NULL, int $code = 200): void
|
protected function outputHTML(string $template, array $data = [], ?HtmlView $view = NULL, int $code = 200): void
|
||||||
{
|
{
|
||||||
if (NULL === $view)
|
if (NULL === $view)
|
||||||
{
|
{
|
||||||
$view = new HtmlView($this->container);
|
$view = new HtmlView($this->container);
|
||||||
}
|
}
|
||||||
|
|
||||||
$view->setStatusCode($code);
|
$view->setStatusCode($code);
|
||||||
$this->renderFullPage($view, $template, $data)->send();
|
$this->renderFullPage($view, $template, $data)->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output a JSON Response
|
* Output a JSON Response
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @param int $code - the http status code
|
||||||
* @param int $code - the http status code
|
* @throws DoubleRenderException
|
||||||
* @throws DoubleRenderException
|
*/
|
||||||
*/
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
protected function outputJSON(mixed $data, int $code): void
|
protected function outputJSON(mixed $data, int $code): void
|
||||||
{
|
{
|
||||||
JsonView::new()
|
JsonView::new()
|
||||||
->setOutput($data)
|
->setOutput($data)
|
||||||
->setStatusCode($code)
|
->setStatusCode($code)
|
||||||
->send();
|
->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to the selected page
|
* Redirect to the selected page
|
||||||
*
|
*/
|
||||||
* @codeCoverageIgnore
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
*/
|
protected function redirect(string $url, int $code): void
|
||||||
protected function redirect(string $url, int $code): void
|
{
|
||||||
{
|
HttpView::new()
|
||||||
HttpView::new()
|
->redirect($url, $code)
|
||||||
->redirect($url, $code)
|
->send();
|
||||||
->send();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of BaseController.php
|
// End of BaseController.php
|
||||||
|
@ -21,8 +21,7 @@ use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
|
|||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||||
use Aviat\AnimeClient\Types\FormItem;
|
use Aviat\AnimeClient\Types\FormItem;
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
@ -307,8 +306,6 @@ final class Anime extends BaseController
|
|||||||
'Anime not found',
|
'Anime not found',
|
||||||
'Anime Not Found'
|
'Anime Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->outputHTML('anime/details', [
|
$this->outputHTML('anime/details', [
|
||||||
@ -346,8 +343,6 @@ final class Anime extends BaseController
|
|||||||
'Anime not found',
|
'Anime not found',
|
||||||
'Anime Not Found'
|
'Anime Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->outputHTML('anime/details', [
|
$this->outputHTML('anime/details', [
|
||||||
|
@ -20,11 +20,9 @@ use Aviat\AnimeClient\Model\{
|
|||||||
Anime as AnimeModel,
|
Anime as AnimeModel,
|
||||||
AnimeCollection as AnimeCollectionModel
|
AnimeCollection as AnimeCollectionModel
|
||||||
};
|
};
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
use Aviat\Ion\Json;
|
|
||||||
use Aviat\Ion\Exception\DoubleRenderException;
|
use Aviat\Ion\Exception\DoubleRenderException;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
@ -114,7 +112,6 @@ final class AnimeCollection extends BaseController
|
|||||||
/**
|
/**
|
||||||
* Show the anime collection add/edit form
|
* Show the anime collection add/edit form
|
||||||
*
|
*
|
||||||
* @param int|null $id
|
|
||||||
* @throws ContainerException
|
* @throws ContainerException
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
|
@ -18,8 +18,7 @@ use Aviat\AnimeClient\API\Kitsu\Model;
|
|||||||
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
|
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
|
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
|
|
||||||
@ -60,8 +59,6 @@ final class Character extends BaseController
|
|||||||
),
|
),
|
||||||
'Character Not Found'
|
'Character Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = (new CharacterTransformer())->transform($rawData)->toArray();
|
$data = (new CharacterTransformer())->transform($rawData)->toArray();
|
||||||
|
@ -14,9 +14,8 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Controller;
|
namespace Aviat\AnimeClient\Controller;
|
||||||
|
|
||||||
use Aviat\Ion\Attribute\Controller;
|
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\AnimeClient\{Controller as BaseController, Model};
|
use Aviat\AnimeClient\{Controller as BaseController, Model};
|
||||||
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
|
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
namespace Aviat\AnimeClient\Controller;
|
namespace Aviat\AnimeClient\Controller;
|
||||||
|
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use function Amp\Promise\wait;
|
use function Amp\Promise\wait;
|
||||||
use function Aviat\AnimeClient\{createPlaceholderImage, getResponse};
|
use function Aviat\AnimeClient\{createPlaceholderImage, getResponse};
|
||||||
@ -97,7 +96,7 @@ final class Images extends BaseController
|
|||||||
|
|
||||||
$kitsuUrl .= $imageType['kitsuUrl'];
|
$kitsuUrl .= $imageType['kitsuUrl'];
|
||||||
$width = $imageType['width'];
|
$width = $imageType['width'];
|
||||||
$height = $imageType['height'];
|
$height = $imageType['height'] ?? 225;
|
||||||
$filePrefix = "{$baseSavePath}/{$type}/{$id}";
|
$filePrefix = "{$baseSavePath}/{$type}/{$id}";
|
||||||
|
|
||||||
$response = getResponse($kitsuUrl);
|
$response = getResponse($kitsuUrl);
|
||||||
@ -121,11 +120,11 @@ final class Images extends BaseController
|
|||||||
|
|
||||||
if ($display)
|
if ($display)
|
||||||
{
|
{
|
||||||
$this->getPlaceholder("{$baseSavePath}/{$type}", $width, $height);
|
$this->getPlaceholder("{$baseSavePath}/{$type}", $width ?? 225, $height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
createPlaceholderImage("{$baseSavePath}/{$type}", $width, $height);
|
createPlaceholderImage("{$baseSavePath}/{$type}", $width ?? 225, $height);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -133,7 +132,13 @@ final class Images extends BaseController
|
|||||||
|
|
||||||
$data = wait($response->getBody()->buffer());
|
$data = wait($response->getBody()->buffer());
|
||||||
|
|
||||||
[$origWidth] = getimagesizefromstring($data);
|
$size = getimagesizefromstring($data);
|
||||||
|
if ($size === FALSE)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$origWidth] = $size;
|
||||||
$gdImg = imagecreatefromstring($data);
|
$gdImg = imagecreatefromstring($data);
|
||||||
if ($gdImg === FALSE)
|
if ($gdImg === FALSE)
|
||||||
{
|
{
|
||||||
@ -183,15 +188,15 @@ final class Images extends BaseController
|
|||||||
/**
|
/**
|
||||||
* Get a placeholder for a missing image
|
* Get a placeholder for a missing image
|
||||||
*/
|
*/
|
||||||
private function getPlaceholder(string $path, ?int $width = 200, ?int $height = NULL): void
|
private function getPlaceholder(string $path, ?int $width = NULL, ?int $height = NULL): void
|
||||||
{
|
{
|
||||||
$height ??= $width;
|
$height ??= $width ?? 200;
|
||||||
|
|
||||||
$filename = $path . '/placeholder.png';
|
$filename = $path . '/placeholder.png';
|
||||||
|
|
||||||
if ( ! file_exists($path . '/placeholder.png'))
|
if ( ! file_exists($path . '/placeholder.png'))
|
||||||
{
|
{
|
||||||
createPlaceholderImage($path, $width, $height);
|
createPlaceholderImage($path, $width ?? 200, $height);
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Content-Type: image/png');
|
header('Content-Type: image/png');
|
||||||
|
@ -14,21 +14,16 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Controller;
|
namespace Aviat\AnimeClient\Controller;
|
||||||
|
|
||||||
use Aura\Router\Exception\RouteNotFound;
|
|
||||||
use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer;
|
use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer;
|
||||||
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
use Aviat\AnimeClient\Model\Manga as MangaModel;
|
use Aviat\AnimeClient\Model\Manga as MangaModel;
|
||||||
use Aviat\AnimeClient\Types\FormItem;
|
use Aviat\AnimeClient\Types\FormItem;
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for manga list
|
* Controller for manga list
|
||||||
*/
|
*/
|
||||||
@ -282,8 +277,6 @@ final class Manga extends BaseController
|
|||||||
'Manga not found',
|
'Manga not found',
|
||||||
'Manga Not Found'
|
'Manga Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->outputHTML('manga/details', [
|
$this->outputHTML('manga/details', [
|
||||||
@ -311,8 +304,6 @@ final class Manga extends BaseController
|
|||||||
'Manga not found',
|
'Manga not found',
|
||||||
'Manga Not Found'
|
'Manga Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->outputHTML('manga/details', [
|
$this->outputHTML('manga/details', [
|
||||||
|
@ -15,12 +15,10 @@
|
|||||||
namespace Aviat\AnimeClient\Controller;
|
namespace Aviat\AnimeClient\Controller;
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\Kitsu\Model;
|
use Aviat\AnimeClient\API\Kitsu\Model;
|
||||||
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
|
use Aviat\AnimeClient\API\Kitsu\Transformer\{CharacterTransformer, PersonTransformer};
|
||||||
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
|
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
use Aviat\AnimeClient\Enum\EventType;
|
use Aviat\AnimeClient\Enum\EventType;
|
||||||
use Aviat\Ion\Attribute\DefaultController;
|
use Aviat\Ion\Attribute\{DefaultController, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Event;
|
use Aviat\Ion\Event;
|
||||||
use Aviat\Ion\View\HtmlView;
|
use Aviat\Ion\View\HtmlView;
|
||||||
@ -103,11 +101,7 @@ final class Misc extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->setFlashMessage('Invalid username or password.');
|
$this->setFlashMessage('Invalid username or password.');
|
||||||
|
$this->redirect($this->url->generate('login'), 303);
|
||||||
$redirectUrl = $this->url->generate('login');
|
|
||||||
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
|
|
||||||
|
|
||||||
$this->redirect($redirectUrl, 303);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,8 +141,6 @@ final class Misc extends BaseController
|
|||||||
),
|
),
|
||||||
'Character Not Found'
|
'Character Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = (new CharacterTransformer())->transform($rawData)->toArray();
|
$data = (new CharacterTransformer())->transform($rawData)->toArray();
|
||||||
@ -180,8 +172,6 @@ final class Misc extends BaseController
|
|||||||
),
|
),
|
||||||
'Person Not Found'
|
'Person Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->outputHTML('person/details', [
|
$this->outputHTML('person/details', [
|
||||||
|
@ -18,8 +18,7 @@ use Aviat\AnimeClient\API\Kitsu\Model;
|
|||||||
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
|
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
|
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
|
|
||||||
@ -61,8 +60,6 @@ final class People extends BaseController
|
|||||||
),
|
),
|
||||||
'Person Not Found'
|
'Person Not Found'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->outputHTML('person/details', [
|
$this->outputHTML('person/details', [
|
||||||
|
@ -18,8 +18,7 @@ use Aura\Router\Exception\RouteNotFound;
|
|||||||
use Aviat\AnimeClient\API\Anilist\Model as AnilistModel;
|
use Aviat\AnimeClient\API\Anilist\Model as AnilistModel;
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
use Aviat\AnimeClient\Model\Settings as SettingsModel;
|
use Aviat\AnimeClient\Model\Settings as SettingsModel;
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
|
|
||||||
@ -87,10 +86,7 @@ final class Settings extends BaseController
|
|||||||
? $this->setFlashMessage('Saved config settings.', 'success')
|
? $this->setFlashMessage('Saved config settings.', 'success')
|
||||||
: $this->setFlashMessage('Failed to save config file.', 'error');
|
: $this->setFlashMessage('Failed to save config file.', 'error');
|
||||||
|
|
||||||
$redirectUrl = $this->url->generate('settings');
|
$this->redirect($this->url->generate('settings'), 303);
|
||||||
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
|
|
||||||
|
|
||||||
$this->redirect($redirectUrl, 303);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -153,9 +149,6 @@ final class Settings extends BaseController
|
|||||||
? $this->setFlashMessage('Linked Anilist Account', 'success')
|
? $this->setFlashMessage('Linked Anilist Account', 'success')
|
||||||
: $this->setFlashMessage('Error Linking Anilist Account', 'error');
|
: $this->setFlashMessage('Error Linking Anilist Account', 'error');
|
||||||
|
|
||||||
$redirectUrl = $this->url->generate('settings');
|
$this->redirect($this->url->generate('settings'), 303);
|
||||||
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
|
|
||||||
|
|
||||||
$this->redirect($redirectUrl, 303);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@ use Aviat\AnimeClient\API\Kitsu\Model;
|
|||||||
use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer;
|
use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer;
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
|
|
||||||
use Aviat\Ion\Attribute\Controller;
|
use Aviat\Ion\Attribute\{Controller, Route};
|
||||||
use Aviat\Ion\Attribute\Route;
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
|
|
||||||
@ -70,6 +69,11 @@ final class User extends BaseController
|
|||||||
: $username;
|
: $username;
|
||||||
|
|
||||||
$rawData = $this->kitsuModel->getUserData($username);
|
$rawData = $this->kitsuModel->getUserData($username);
|
||||||
|
if ($rawData['data']['findProfileBySlug'] === NULL)
|
||||||
|
{
|
||||||
|
$this->notFound('Sorry, user not found', "The user '{$username}' does not seem to exist.");
|
||||||
|
}
|
||||||
|
|
||||||
$data = (new UserTransformer())->transform($rawData)->toArray();
|
$data = (new UserTransformer())->transform($rawData)->toArray();
|
||||||
|
|
||||||
$this->outputHTML('user/details', [
|
$this->outputHTML('user/details', [
|
||||||
|
@ -69,6 +69,44 @@ final class Dispatcher extends RoutingBase
|
|||||||
$this->outputRoutes = $this->setupRoutes();
|
$this->outputRoutes = $this->setupRoutes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the current route
|
||||||
|
*
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
public function __invoke(?object $route = NULL): void
|
||||||
|
{
|
||||||
|
$logger = $this->container->getLogger();
|
||||||
|
|
||||||
|
if ($route === NULL)
|
||||||
|
{
|
||||||
|
$route = $this->getRoute();
|
||||||
|
|
||||||
|
$logger?->info('Dispatcher - Route invoke arguments');
|
||||||
|
$logger?->info(print_r($route, TRUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $route)
|
||||||
|
{
|
||||||
|
// If not route was matched, return an appropriate http
|
||||||
|
// error message
|
||||||
|
$errorRoute = $this->getErrorParams();
|
||||||
|
$controllerName = DEFAULT_CONTROLLER;
|
||||||
|
$actionMethod = $errorRoute['action_method'];
|
||||||
|
$params = $errorRoute['params'];
|
||||||
|
$this->call($controllerName, $actionMethod, $params);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = $this->processRoute(new Friend($route));
|
||||||
|
$controllerName = $parsed['controller_name'];
|
||||||
|
$actionMethod = $parsed['action_method'];
|
||||||
|
$params = $parsed['params'];
|
||||||
|
|
||||||
|
$this->call($controllerName, $actionMethod, $params);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current route object, if one matches
|
* Get the current route object, if one matches
|
||||||
*/
|
*/
|
||||||
@ -100,47 +138,6 @@ final class Dispatcher extends RoutingBase
|
|||||||
return $this->outputRoutes;
|
return $this->outputRoutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the current route
|
|
||||||
*
|
|
||||||
* @throws ReflectionException
|
|
||||||
*/
|
|
||||||
public function __invoke(?object $route = NULL): void
|
|
||||||
{
|
|
||||||
$logger = $this->container->getLogger();
|
|
||||||
|
|
||||||
if ($route === NULL)
|
|
||||||
{
|
|
||||||
$route = $this->getRoute();
|
|
||||||
|
|
||||||
if ($logger !== NULL)
|
|
||||||
{
|
|
||||||
$logger->info('Dispatcher - Route invoke arguments');
|
|
||||||
$logger->info(print_r($route, TRUE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $route)
|
|
||||||
{
|
|
||||||
// If not route was matched, return an appropriate http
|
|
||||||
// error message
|
|
||||||
$errorRoute = $this->getErrorParams();
|
|
||||||
$controllerName = DEFAULT_CONTROLLER;
|
|
||||||
$actionMethod = $errorRoute['action_method'];
|
|
||||||
$params = $errorRoute['params'];
|
|
||||||
$this->call($controllerName, $actionMethod, $params);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$parsed = $this->processRoute(new Friend($route));
|
|
||||||
$controllerName = $parsed['controller_name'];
|
|
||||||
$actionMethod = $parsed['action_method'];
|
|
||||||
$params = $parsed['params'];
|
|
||||||
|
|
||||||
$this->call($controllerName, $actionMethod, $params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse out the arguments for the appropriate controller for
|
* Parse out the arguments for the appropriate controller for
|
||||||
* the current route
|
* the current route
|
||||||
@ -183,10 +180,7 @@ final class Dispatcher extends RoutingBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
$logger = $this->container->getLogger();
|
$logger = $this->container->getLogger();
|
||||||
if ($logger !== NULL)
|
$logger?->info(Json::encode($params));
|
||||||
{
|
|
||||||
$logger->info(Json::encode($params));
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'controller_name' => $controllerName,
|
'controller_name' => $controllerName,
|
||||||
@ -208,10 +202,7 @@ final class Dispatcher extends RoutingBase
|
|||||||
$controller = reset($segments);
|
$controller = reset($segments);
|
||||||
|
|
||||||
$logger = $this->container->getLogger();
|
$logger = $this->container->getLogger();
|
||||||
if ($logger !== NULL)
|
$logger?->info('Controller: ' . $controller);
|
||||||
{
|
|
||||||
$logger->info('Controller: ' . $controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($controller))
|
if (empty($controller))
|
||||||
{
|
{
|
||||||
@ -224,7 +215,7 @@ final class Dispatcher extends RoutingBase
|
|||||||
/**
|
/**
|
||||||
* Get the list of controllers in the default namespace
|
* Get the list of controllers in the default namespace
|
||||||
*
|
*
|
||||||
* @return mixed[]
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getControllerList(): array
|
public function getControllerList(): array
|
||||||
{
|
{
|
||||||
@ -300,7 +291,6 @@ final class Dispatcher extends RoutingBase
|
|||||||
/**
|
/**
|
||||||
* Get the appropriate params for the error page
|
* Get the appropriate params for the error page
|
||||||
* passed on the failed route
|
* passed on the failed route
|
||||||
* @return mixed[][]
|
|
||||||
*/
|
*/
|
||||||
protected function getErrorParams(): array
|
protected function getErrorParams(): array
|
||||||
{
|
{
|
||||||
@ -317,14 +307,15 @@ final class Dispatcher extends RoutingBase
|
|||||||
|
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
switch ($failure->failedRule) {
|
switch ($failure?->failedRule)
|
||||||
|
{
|
||||||
case Rule\Allows::class:
|
case Rule\Allows::class:
|
||||||
$params = [
|
$params = [
|
||||||
'http_code' => 405,
|
'http_code' => 405,
|
||||||
'title' => '405 Method Not Allowed',
|
'title' => '405 Method Not Allowed',
|
||||||
'message' => 'Invalid HTTP Verb',
|
'message' => 'Invalid HTTP Verb',
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Rule\Accepts::class:
|
case Rule\Accepts::class:
|
||||||
$params = [
|
$params = [
|
||||||
@ -332,12 +323,12 @@ final class Dispatcher extends RoutingBase
|
|||||||
'title' => '406 Not Acceptable',
|
'title' => '406 Not Acceptable',
|
||||||
'message' => 'Unacceptable content type',
|
'message' => 'Unacceptable content type',
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Fall back to a 404 message
|
// Fall back to a 404 message
|
||||||
$actionMethod = NOT_FOUND_METHOD;
|
$actionMethod = NOT_FOUND_METHOD;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -348,8 +339,6 @@ final class Dispatcher extends RoutingBase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Select controller based on the current url, and apply its relevant routes
|
* Select controller based on the current url, and apply its relevant routes
|
||||||
*
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
*/
|
||||||
protected function setupRoutes(): array
|
protected function setupRoutes(): array
|
||||||
{
|
{
|
||||||
|
@ -83,16 +83,16 @@ final class FormGenerator
|
|||||||
];
|
];
|
||||||
$params['strict'] = TRUE;
|
$params['strict'] = TRUE;
|
||||||
unset($params['attribs']['id']);
|
unset($params['attribs']['id']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'string':
|
case 'string':
|
||||||
$params['type'] = 'text';
|
$params['type'] = 'text';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'select':
|
case 'select':
|
||||||
$params['type'] = 'select';
|
$params['type'] = 'select';
|
||||||
$params['options'] = array_flip($form['options']);
|
$params['options'] = array_flip($form['options']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -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
|
||||||
@ -72,18 +68,18 @@ final class Kitsu
|
|||||||
}
|
}
|
||||||
|
|
||||||
$monthMap = [
|
$monthMap = [
|
||||||
'01' => 'Jan',
|
'01' => 'January',
|
||||||
'02' => 'Feb',
|
'02' => 'February',
|
||||||
'03' => 'Mar',
|
'03' => 'March',
|
||||||
'04' => 'Apr',
|
'04' => 'April',
|
||||||
'05' => 'May',
|
'05' => 'May',
|
||||||
'06' => 'Jun',
|
'06' => 'June',
|
||||||
'07' => 'Jul',
|
'07' => 'July',
|
||||||
'08' => 'Aug',
|
'08' => 'August',
|
||||||
'09' => 'Sep',
|
'09' => 'September',
|
||||||
'10' => 'Oct',
|
'10' => 'October',
|
||||||
'11' => 'Nov',
|
'11' => 'November',
|
||||||
'12' => 'Dec',
|
'12' => 'December',
|
||||||
];
|
];
|
||||||
|
|
||||||
[$startYear, $startMonth, $startDay] = explode('-', $startDate);
|
[$startYear, $startMonth, $startDay] = explode('-', $startDate);
|
||||||
@ -305,7 +301,16 @@ final class Kitsu
|
|||||||
{
|
{
|
||||||
// Really don't care about languages that aren't english
|
// Really don't care about languages that aren't english
|
||||||
// or Japanese for titles
|
// or Japanese for titles
|
||||||
if ( ! in_array($locale, ['en', 'en_us', 'en_jp', 'ja_jp'], TRUE))
|
if ( ! in_array($locale, [
|
||||||
|
'en',
|
||||||
|
'en-jp',
|
||||||
|
'en-us',
|
||||||
|
'en_jp',
|
||||||
|
'en_us',
|
||||||
|
'ja-jp',
|
||||||
|
'ja_jp',
|
||||||
|
'jp',
|
||||||
|
], TRUE))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -326,15 +331,29 @@ final class Kitsu
|
|||||||
/**
|
/**
|
||||||
* Get the url of the posterImage from Kitsu, with fallbacks
|
* Get the url of the posterImage from Kitsu, with fallbacks
|
||||||
*/
|
*/
|
||||||
public static function getPosterImage(array $base, int $size = 1): string
|
public static function getPosterImage(array $base, int $sizeId = 1): string
|
||||||
{
|
{
|
||||||
$rawUrl = $base['posterImage']['views'][$size]['url']
|
$rawUrl = $base['posterImage']['views'][$sizeId]['url']
|
||||||
?? $base['posterImage']['original']['url']
|
?? $base['posterImage']['original']['url']
|
||||||
?? '/public/images/placeholder.png';
|
?? '/public/images/placeholder.png';
|
||||||
|
|
||||||
$parts = explode('?', $rawUrl);
|
$parts = explode('?', $rawUrl);
|
||||||
|
|
||||||
return (empty($parts)) ? $rawUrl : $parts[0];
|
return $parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url of the image from Kitsu, with fallbacks
|
||||||
|
*/
|
||||||
|
public static function getImage(array $base, int $sizeId = 1): string
|
||||||
|
{
|
||||||
|
$rawUrl = $base['image']['original']['url']
|
||||||
|
?? $base['image']['views'][$sizeId]['url']
|
||||||
|
?? '/public/images/placeholder.png';
|
||||||
|
|
||||||
|
$parts = explode('?', $rawUrl);
|
||||||
|
|
||||||
|
return $parts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -418,62 +437,6 @@ final class Kitsu
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a time in seconds to a more human-readable format
|
|
||||||
*/
|
|
||||||
public static function friendlyTime(int $seconds): 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}";
|
|
||||||
}
|
|
||||||
|
|
||||||
$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
|
||||||
*/
|
*/
|
||||||
@ -486,7 +449,7 @@ final class Kitsu
|
|||||||
|
|
||||||
foreach ($existingTitles as $existing)
|
foreach ($existingTitles as $existing)
|
||||||
{
|
{
|
||||||
$isSubset = mb_substr_count($existing, $title) > 0;
|
$isSubset = mb_substr_count(mb_strtolower($existing), mb_strtolower($title)) > 0;
|
||||||
$diff = levenshtein(mb_strtolower($existing), mb_strtolower($title));
|
$diff = levenshtein(mb_strtolower($existing), mb_strtolower($title));
|
||||||
|
|
||||||
if ($diff <= 4 || $isSubset || mb_strlen($title) > 45 || mb_strlen($existing) > 50)
|
if ($diff <= 4 || $isSubset || mb_strlen($title) > 45 || mb_strlen($existing) > 50)
|
||||||
|
@ -36,6 +36,19 @@ final class MenuGenerator extends UrlGenerator
|
|||||||
*/
|
*/
|
||||||
protected ServerRequestInterface $request;
|
protected ServerRequestInterface $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MenuGenerator constructor.
|
||||||
|
*
|
||||||
|
* @throws ContainerException
|
||||||
|
* @throws NotFoundException
|
||||||
|
*/
|
||||||
|
private function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->helper = $container->get('html-helper');
|
||||||
|
$this->request = $container->get('request');
|
||||||
|
}
|
||||||
|
|
||||||
public static function new(ContainerInterface $container): self
|
public static function new(ContainerInterface $container): self
|
||||||
{
|
{
|
||||||
return new self($container);
|
return new self($container);
|
||||||
@ -80,19 +93,6 @@ final class MenuGenerator extends UrlGenerator
|
|||||||
return (string) $this->helper->ul();
|
return (string) $this->helper->ul();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* MenuGenerator constructor.
|
|
||||||
*
|
|
||||||
* @throws ContainerException
|
|
||||||
* @throws NotFoundException
|
|
||||||
*/
|
|
||||||
private function __construct(ContainerInterface $container)
|
|
||||||
{
|
|
||||||
parent::__construct($container);
|
|
||||||
$this->helper = $container->get('html-helper');
|
|
||||||
$this->request = $container->get('request');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the full menu structure from the config files
|
* Generate the full menu structure from the config files
|
||||||
*
|
*
|
||||||
|
@ -91,7 +91,7 @@ final class AnimeCollection extends Collection
|
|||||||
$genres = $this->getGenreList();
|
$genres = $this->getGenreList();
|
||||||
$media = $this->getMediaList();
|
$media = $this->getMediaList();
|
||||||
|
|
||||||
if ($rows === FALSE)
|
if (empty($rows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ final class AnimeCollection extends Collection
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if ($rows === FALSE)
|
if (empty($rows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -349,7 +349,7 @@ final class AnimeCollection extends Collection
|
|||||||
->get()
|
->get()
|
||||||
->fetchAll(PDO::FETCH_ASSOC);
|
->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($mediaRows === FALSE)
|
if (empty($mediaRows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -411,7 +411,7 @@ final class AnimeCollection extends Collection
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if ($rows === FALSE)
|
if (empty($rows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -479,7 +479,7 @@ final class AnimeCollection extends Collection
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if ($rows === FALSE)
|
if (empty($rows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -659,7 +659,7 @@ final class AnimeCollection extends Collection
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if ($rows === FALSE)
|
if (empty($rows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -691,7 +691,7 @@ final class AnimeCollection extends Collection
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if ($rows === FALSE)
|
if (empty($rows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -737,7 +737,7 @@ final class AnimeCollection extends Collection
|
|||||||
|
|
||||||
// Add genres associated with each item
|
// Add genres associated with each item
|
||||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if ($rows === FALSE)
|
if (empty($rows))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ trait MediaTrait
|
|||||||
* Get information about a specific list item
|
* Get information about a specific list item
|
||||||
* for editing/updating that item
|
* for editing/updating that item
|
||||||
*/
|
*/
|
||||||
public function getItem(string $itemId): AnimeListItem|MangaListItem
|
public function getItem(string $itemId): AnimeListItem|MangaListItem|array
|
||||||
{
|
{
|
||||||
return $this->kitsuModel->getListItem($itemId);
|
return $this->kitsuModel->getListItem($itemId);
|
||||||
}
|
}
|
||||||
|
@ -95,14 +95,7 @@ final class Settings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (array_key_exists($key, $values) && is_scalar($values[$key]))
|
$value['value'] = array_key_exists($key, $values) && is_scalar($values[$key]) ? $values[$key] : $value['default'] ?? '';
|
||||||
{
|
|
||||||
$value['value'] = $values[$key];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$value['value'] = $value['default'] ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (['readonly', 'disabled'] as $flag)
|
foreach (['readonly', 'disabled'] as $flag)
|
||||||
{
|
{
|
||||||
|
@ -20,37 +20,6 @@ use Stringable;
|
|||||||
|
|
||||||
abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Populate values for un-serializing data
|
|
||||||
*/
|
|
||||||
public static function __set_state(mixed $properties): self
|
|
||||||
{
|
|
||||||
return new static($properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the shape of the object, and return the array equivalent
|
|
||||||
*/
|
|
||||||
final public static function check(array $data = []): ?array
|
|
||||||
{
|
|
||||||
$currentClass = static::class;
|
|
||||||
|
|
||||||
if (get_parent_class($currentClass) !== FALSE)
|
|
||||||
{
|
|
||||||
return static::class::from($data)->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static constructor
|
|
||||||
*/
|
|
||||||
final public static function from(mixed $data): static
|
|
||||||
{
|
|
||||||
return new static($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the properties by using the constructor
|
* Sets the properties by using the constructor
|
||||||
*/
|
*/
|
||||||
@ -73,6 +42,14 @@ abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate values for un-serializing data
|
||||||
|
*/
|
||||||
|
public static function __set_state(mixed $properties): self
|
||||||
|
{
|
||||||
|
return new static($properties);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See if a property is set
|
* See if a property is set
|
||||||
*/
|
*/
|
||||||
@ -123,6 +100,29 @@ abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
|||||||
return print_r($this, TRUE);
|
return print_r($this, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the shape of the object, and return the array equivalent
|
||||||
|
*/
|
||||||
|
final public static function check(array $data = []): ?array
|
||||||
|
{
|
||||||
|
$currentClass = static::class;
|
||||||
|
|
||||||
|
if (get_parent_class($currentClass) !== FALSE)
|
||||||
|
{
|
||||||
|
return static::class::from($data)->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static constructor
|
||||||
|
*/
|
||||||
|
final public static function from(mixed $data): static
|
||||||
|
{
|
||||||
|
return new static($data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementing ArrayAccess
|
* Implementing ArrayAccess
|
||||||
*/
|
*/
|
||||||
@ -201,27 +201,25 @@ abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||||
* @codeCoverageIgnore
|
final protected function fromObject(mixed $parent = NULL): float|null|bool|int|array|string
|
||||||
*/
|
{
|
||||||
final protected function fromObject(mixed $parent = NULL): float|NULL|bool|int|array|string
|
$object = $parent ?? $this;
|
||||||
{
|
|
||||||
$object = $parent ?? $this;
|
|
||||||
|
|
||||||
if (is_scalar($object) || $object === NULL)
|
if (is_scalar($object) || $object === NULL)
|
||||||
{
|
{
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = [];
|
$output = [];
|
||||||
|
|
||||||
foreach ($object as $key => $value)
|
foreach ($object as $key => $value)
|
||||||
{
|
{
|
||||||
$output[$key] = (is_scalar($value) || empty($value))
|
$output[$key] = (is_scalar($value) || empty($value))
|
||||||
? $value
|
? $value
|
||||||
: $this->fromObject((array) $value);
|
: $this->fromObject((array) $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ final class Person extends AbstractType
|
|||||||
{
|
{
|
||||||
public string $id;
|
public string $id;
|
||||||
public ?string $name;
|
public ?string $name;
|
||||||
|
public ?string $birthday;
|
||||||
public string $image;
|
public string $image;
|
||||||
public array $names = [];
|
public array $names = [];
|
||||||
public ?string $description;
|
public ?string $description;
|
||||||
|
@ -21,11 +21,14 @@ final class User extends AbstractType
|
|||||||
{
|
{
|
||||||
public ?string $about;
|
public ?string $about;
|
||||||
public ?string $avatar;
|
public ?string $avatar;
|
||||||
|
public ?string $birthday;
|
||||||
|
public string $joinDate;
|
||||||
|
public ?string $gender;
|
||||||
public ?array $favorites;
|
public ?array $favorites;
|
||||||
public ?string $location;
|
public ?string $location;
|
||||||
public ?string $name;
|
public ?string $name;
|
||||||
public ?string $slug;
|
public ?string $slug;
|
||||||
public ?array $stats;
|
public ?array $stats;
|
||||||
public ?array $waifu;
|
public array $waifu;
|
||||||
public ?string $website;
|
public ?string $website;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@ namespace Aviat\Ion\Attribute;
|
|||||||
use Attribute;
|
use Attribute;
|
||||||
|
|
||||||
#[Attribute(Attribute::TARGET_CLASS)]
|
#[Attribute(Attribute::TARGET_CLASS)]
|
||||||
class Controller {
|
class Controller
|
||||||
public function __construct(public string $prefix = '') {}
|
{
|
||||||
}
|
public function __construct(public string $prefix = '')
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,4 +17,6 @@ namespace Aviat\Ion\Attribute;
|
|||||||
use Attribute;
|
use Attribute;
|
||||||
|
|
||||||
#[Attribute(Attribute::TARGET_CLASS)]
|
#[Attribute(Attribute::TARGET_CLASS)]
|
||||||
class DefaultController {}
|
class DefaultController
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@ -17,16 +17,15 @@ namespace Aviat\Ion\Attribute;
|
|||||||
use Attribute;
|
use Attribute;
|
||||||
|
|
||||||
#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||||
class Route {
|
class Route
|
||||||
public const GET = 'get';
|
{
|
||||||
public const POST = 'post';
|
final public const GET = 'get';
|
||||||
|
final public const POST = 'post';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $name,
|
public string $name,
|
||||||
public string $path,
|
public string $path,
|
||||||
public string $verb = self::GET,
|
public string $verb = self::GET,
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ class Container implements ContainerInterface
|
|||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
/**
|
/**
|
||||||
* Array of container Generator functions
|
* Array of container Generator functions
|
||||||
*/
|
*/
|
||||||
protected array $container = []
|
protected array $container = []
|
||||||
) {
|
) {
|
||||||
$this->loggers = [];
|
$this->loggers = [];
|
||||||
|
@ -25,7 +25,10 @@ class Json
|
|||||||
/**
|
/**
|
||||||
* Encode data in json format
|
* Encode data in json format
|
||||||
*
|
*
|
||||||
* @throws JsonException
|
* @param mixed $data
|
||||||
|
* @param int $options
|
||||||
|
* @param int<1, max> $depth
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function encode(mixed $data, int $options = 0, int $depth = 512): string
|
public static function encode(mixed $data, int $options = 0, int $depth = 512): string
|
||||||
{
|
{
|
||||||
@ -54,7 +57,11 @@ class Json
|
|||||||
/**
|
/**
|
||||||
* Decode data from json
|
* Decode data from json
|
||||||
*
|
*
|
||||||
* @throws JsonException
|
* @param string|null $json
|
||||||
|
* @param bool $assoc
|
||||||
|
* @param int<1, max> $depth
|
||||||
|
* @param int $options
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public static function decode(?string $json, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
|
public static function decode(?string $json, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
|
||||||
{
|
{
|
||||||
@ -74,7 +81,11 @@ class Json
|
|||||||
/**
|
/**
|
||||||
* Decode json data loaded from the passed filename
|
* Decode json data loaded from the passed filename
|
||||||
*
|
*
|
||||||
* @throws JsonException
|
* @param string $filename
|
||||||
|
* @param bool $assoc
|
||||||
|
* @param int<1, max> $depth
|
||||||
|
* @param int $options
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public static function decodeFile(string $filename, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
|
public static function decodeFile(string $filename, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,7 @@ abstract class AbstractTransformer implements TransformerInterface
|
|||||||
{
|
{
|
||||||
$list = (array) $collection;
|
$list = (array) $collection;
|
||||||
|
|
||||||
return array_map([$this, 'transform'], $list);
|
return array_map($this->transform(...), $list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,14 +65,6 @@ class ArrayType
|
|||||||
'pop' => 'array_pop',
|
'pop' => 'array_pop',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an ArrayType wrapper class from an array
|
|
||||||
*/
|
|
||||||
public static function from(array $arr): ArrayType
|
|
||||||
{
|
|
||||||
return new ArrayType($arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an ArrayType wrapper class
|
* Create an ArrayType wrapper class
|
||||||
*/
|
*/
|
||||||
@ -108,6 +100,14 @@ class ArrayType
|
|||||||
throw new InvalidArgumentException("Method '{$method}' does not exist");
|
throw new InvalidArgumentException("Method '{$method}' does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an ArrayType wrapper class from an array
|
||||||
|
*/
|
||||||
|
public static function from(array $arr): ArrayType
|
||||||
|
{
|
||||||
|
return new ArrayType($arr);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the passed key exist in the current array?
|
* Does the passed key exist in the current array?
|
||||||
*/
|
*/
|
||||||
@ -156,7 +156,7 @@ class ArrayType
|
|||||||
/**
|
/**
|
||||||
* Find an array key by its associated value
|
* Find an array key by its associated value
|
||||||
*/
|
*/
|
||||||
public function search(mixed $value, bool $strict = TRUE): int|string|FALSE|null
|
public function search(mixed $value, bool $strict = TRUE): int|string|false|null
|
||||||
{
|
{
|
||||||
return array_search($value, $this->arr, $strict);
|
return array_search($value, $this->arr, $strict);
|
||||||
}
|
}
|
||||||
@ -172,7 +172,7 @@ class ArrayType
|
|||||||
/**
|
/**
|
||||||
* Return the array, or a key
|
* Return the array, or a key
|
||||||
*/
|
*/
|
||||||
public function &get(string|int|NULL $key = NULL): mixed
|
public function &get(string|int|null $key = NULL): mixed
|
||||||
{
|
{
|
||||||
$value = NULL;
|
$value = NULL;
|
||||||
if ($key === NULL)
|
if ($key === NULL)
|
||||||
|
@ -24,9 +24,9 @@ final class StringType extends Stringy
|
|||||||
/**
|
/**
|
||||||
* Alias for `create` static constructor
|
* Alias for `create` static constructor
|
||||||
*/
|
*/
|
||||||
public static function from(string $str): self
|
public static function from(string $str = '', ?string $encoding = NULL): self
|
||||||
{
|
{
|
||||||
return self::create($str);
|
return self::create($str, $encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -34,11 +34,11 @@ class HtmlView extends HttpView
|
|||||||
/**
|
/**
|
||||||
* Create the Html View
|
* Create the Html View
|
||||||
*/
|
*/
|
||||||
public function __construct(ContainerInterface $container)
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->setContainer($container);
|
$this->setContainer(func_get_arg(0));
|
||||||
$this->response = new HtmlResponse('');
|
$this->response = new HtmlResponse('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ use InvalidArgumentException;
|
|||||||
|
|
||||||
use Laminas\Diactoros\Response;
|
use Laminas\Diactoros\Response;
|
||||||
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||||||
|
use PHPUnit\Framework\Attributes\CodeCoverageIgnore;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Stringable;
|
use Stringable;
|
||||||
|
|
||||||
@ -168,10 +169,10 @@ class HttpView implements HttpViewInterface, Stringable
|
|||||||
/**
|
/**
|
||||||
* Send the appropriate response
|
* Send the appropriate response
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @throws DoubleRenderException
|
* @throws DoubleRenderException
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
|
#[CodeCoverageIgnore]
|
||||||
protected function output(): void
|
protected function output(): void
|
||||||
{
|
{
|
||||||
if ($this->hasRendered)
|
if ($this->hasRendered)
|
||||||
|
@ -40,8 +40,10 @@ final class APIRequestBuilderTest extends TestCase
|
|||||||
$this->builder->setLogger(new NullLogger());
|
$this->builder->setLogger(new NullLogger());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGzipRequest(): void
|
public function testGzipRequest(): never
|
||||||
{
|
{
|
||||||
|
$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);
|
||||||
@ -49,15 +51,19 @@ final class APIRequestBuilderTest extends TestCase
|
|||||||
$this->assertTrue($body['gzipped']);
|
$this->assertTrue($body['gzipped']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvalidRequestMethod(): void
|
public function testInvalidRequestMethod(): never
|
||||||
{
|
{
|
||||||
|
$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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRequestWithBasicAuth(): void
|
public function testRequestWithBasicAuth(): never
|
||||||
{
|
{
|
||||||
|
$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();
|
||||||
@ -68,8 +74,10 @@ final class APIRequestBuilderTest extends TestCase
|
|||||||
$this->assertSame('Basic dXNlcm5hbWU6cGFzc3dvcmQ=', $body['headers']['Authorization']);
|
$this->assertSame('Basic dXNlcm5hbWU6cGFzc3dvcmQ=', $body['headers']['Authorization']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRequestWithQueryString(): void
|
public function testRequestWithQueryString(): never
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Need new test API');
|
||||||
|
|
||||||
$query = [
|
$query = [
|
||||||
'foo' => 'bar',
|
'foo' => 'bar',
|
||||||
'bar' => [
|
'bar' => [
|
||||||
@ -96,8 +104,10 @@ final class APIRequestBuilderTest extends TestCase
|
|||||||
$this->assertSame($expected, $body['args']);
|
$this->assertSame($expected, $body['args']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFormValueRequest(): void
|
public function testFormValueRequest(): never
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Need new test API');
|
||||||
|
|
||||||
$formValues = [
|
$formValues = [
|
||||||
'bar' => 'foo',
|
'bar' => 'foo',
|
||||||
'foo' => 'bar',
|
'foo' => 'bar',
|
||||||
@ -113,8 +123,10 @@ final class APIRequestBuilderTest extends TestCase
|
|||||||
$this->assertSame($formValues, $body['form']);
|
$this->assertSame($formValues, $body['form']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFullUrlRequest(): void
|
public function testFullUrlRequest(): never
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Need new test API');
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'foo' => [
|
'foo' => [
|
||||||
'bar' => 1,
|
'bar' => 1,
|
||||||
|
@ -38,7 +38,7 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
|||||||
$this->transformer = new AnimeListTransformer();
|
$this->transformer = new AnimeListTransformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransform(): void
|
public function testTransform(): never
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('Old test data');
|
$this->markTestSkipped('Old test data');
|
||||||
|
|
||||||
@ -46,11 +46,11 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
|||||||
$this->assertMatchesSnapshot($actual);
|
$this->assertMatchesSnapshot($actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dataUntransform(): array
|
public static function dataUntransform(): array
|
||||||
{
|
{
|
||||||
return [[
|
return [[
|
||||||
'input' => [
|
'input' => [
|
||||||
'id' => 14047981,
|
'id' => 14_047_981,
|
||||||
'watching_status' => 'current',
|
'watching_status' => 'current',
|
||||||
'user_rating' => 8,
|
'user_rating' => 8,
|
||||||
'episodes_watched' => 38,
|
'episodes_watched' => 38,
|
||||||
@ -60,7 +60,7 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
|||||||
],
|
],
|
||||||
], [
|
], [
|
||||||
'input' => [
|
'input' => [
|
||||||
'id' => 14047981,
|
'id' => 14_047_981,
|
||||||
'mal_id' => '12345',
|
'mal_id' => '12345',
|
||||||
'watching_status' => 'current',
|
'watching_status' => 'current',
|
||||||
'user_rating' => 8,
|
'user_rating' => 8,
|
||||||
@ -73,7 +73,7 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
|||||||
],
|
],
|
||||||
], [
|
], [
|
||||||
'input' => [
|
'input' => [
|
||||||
'id' => 14047983,
|
'id' => 14_047_983,
|
||||||
'mal_id' => '12347',
|
'mal_id' => '12347',
|
||||||
'watching_status' => 'current',
|
'watching_status' => 'current',
|
||||||
'user_rating' => 0,
|
'user_rating' => 0,
|
||||||
@ -87,12 +87,10 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
|||||||
]];
|
]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[\PHPUnit\Framework\Attributes\DataProvider('dataUntransform')]
|
||||||
* @dataProvider dataUntransform
|
public function testUntransform(array $input): void
|
||||||
*/
|
{
|
||||||
public function testUntransform(array $input): void
|
$actual = $this->transformer->untransform($input);
|
||||||
{
|
$this->assertMatchesSnapshot($actual);
|
||||||
$actual = $this->transformer->untransform($input);
|
}
|
||||||
$this->assertMatchesSnapshot($actual);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ final class AnimeTransformerTest extends AnimeClientTestCase
|
|||||||
$this->transformer = new AnimeTransformer();
|
$this->transformer = new AnimeTransformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransform()
|
public function testTransform(): never
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('May fail on CI');
|
$this->markTestSkipped('May fail on CI');
|
||||||
$actual = $this->transformer->transform($this->beforeTransform);
|
$actual = $this->transformer->transform($this->beforeTransform);
|
||||||
|
@ -35,7 +35,7 @@ final class CharacterTransformerTest extends AnimeClientTestCase
|
|||||||
$this->beforeTransform = $raw;
|
$this->beforeTransform = $raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransform(): void
|
public function testTransform(): never
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('Fails on CI');
|
$this->markTestSkipped('Fails on CI');
|
||||||
$actual = (new CharacterTransformer())->transform($this->beforeTransform);
|
$actual = (new CharacterTransformer())->transform($this->beforeTransform);
|
||||||
|
@ -35,7 +35,7 @@ final class HistoryTransformerTest extends AnimeClientTestCase
|
|||||||
$this->beforeTransform = $raw;
|
$this->beforeTransform = $raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAnimeTransform(): void
|
public function testAnimeTransform(): never
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('Old test data');
|
$this->markTestSkipped('Old test data');
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ final class PersonTransformerTest extends AnimeClientTestCase
|
|||||||
$this->beforeTransform = $raw;
|
$this->beforeTransform = $raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransform(): void
|
public function testTransform(): never
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('Fails on CI');
|
$this->markTestSkipped('Fails on CI');
|
||||||
$actual = (new PersonTransformer())->transform($this->beforeTransform);
|
$actual = (new PersonTransformer())->transform($this->beforeTransform);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ id: '20286'
|
|||||||
manga_type: MANGA
|
manga_type: MANGA
|
||||||
status: Completed
|
status: Completed
|
||||||
staff:
|
staff:
|
||||||
'Story & Art': [{ id: '8712', slug: ruri-miyahara, name: 'Ruri Miyahara', image: 'https://media.kitsu.io/people/images/8712/original.jpg?1533271952' }]
|
'Story & Art': [{ id: '8712', slug: ruri-miyahara, name: 'Ruri Miyahara', image: 'https://media.kitsu.io/people/images/8712/original.jpg' }]
|
||||||
synopsis: "Usa, a high-school student aspiring to begin a bachelor lifestyle, moves into a new apartment only to discover that he not only shares a room with a perverted roommate that has an obsession for underaged girls, but also that another girl, Ritsu, a love-at-first-sight, is living in the same building as well!\r\n(Source: Kirei Cake)"
|
synopsis: "Usa, a high-school student aspiring to begin a bachelor lifestyle, moves into a new apartment only to discover that he not only shares a room with a perverted roommate that has an obsession for underaged girls, but also that another girl, Ritsu, a love-at-first-sight, is living in the same building as well!\r\n(Source: Kirei Cake)"
|
||||||
title: 'Bokura wa Minna Kawai-sou'
|
title: 'Bokura wa Minna Kawai-sou'
|
||||||
titles:
|
titles:
|
||||||
|
File diff suppressed because one or more lines are too long
@ -15,11 +15,17 @@
|
|||||||
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 PHPUnit\Framework\Attributes\IgnoreFunctionForCodeCoverage;
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
|
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\loadConfig')]
|
||||||
|
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\createPlaceholderImage')]
|
||||||
|
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\renderTemplate')]
|
||||||
|
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\getLocalImg')]
|
||||||
final class AnimeClientTest extends AnimeClientTestCase
|
final class AnimeClientTest extends AnimeClientTestCase
|
||||||
{
|
{
|
||||||
public function testArrayToToml(): void
|
public function testArrayToToml(): void
|
||||||
@ -128,4 +134,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,10 @@ class AnimeClientTestCase extends TestCase
|
|||||||
use MatchesSnapshots;
|
use MatchesSnapshots;
|
||||||
|
|
||||||
// Test directory constants
|
// Test directory constants
|
||||||
public const ROOT_DIR = AC_TEST_ROOT_DIR;
|
final public const ROOT_DIR = AC_TEST_ROOT_DIR;
|
||||||
public const SRC_DIR = SRC_DIR;
|
final public const SRC_DIR = SRC_DIR;
|
||||||
public const TEST_DATA_DIR = __DIR__ . '/test_data';
|
final public const TEST_DATA_DIR = __DIR__ . '/test_data';
|
||||||
public const TEST_VIEW_DIR = __DIR__ . '/test_views';
|
final public const TEST_VIEW_DIR = __DIR__ . '/test_views';
|
||||||
|
|
||||||
protected ContainerInterface $container;
|
protected ContainerInterface $container;
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ class AnimeClientTestCase extends TestCase
|
|||||||
$container = $di($config_array);
|
$container = $di($config_array);
|
||||||
|
|
||||||
// Use mock session handler
|
// Use mock session handler
|
||||||
$container->set('session-handler', static function () {
|
$container->set('session-handler', static function (): TestSessionHandler {
|
||||||
$session_handler = new TestSessionHandler();
|
$session_handler = new TestSessionHandler();
|
||||||
session_set_save_handler($session_handler, TRUE);
|
session_set_save_handler($session_handler, TRUE);
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ class AnimeClientTestCase extends TestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$request = call_user_func_array(
|
$request = call_user_func_array(
|
||||||
[ServerRequestFactory::class, 'fromGlobals'],
|
ServerRequestFactory::fromGlobals(...),
|
||||||
array_values(array_merge($default, $supers)),
|
array_values(array_merge($default, $supers)),
|
||||||
);
|
);
|
||||||
$this->container->setInstance('request', $request);
|
$this->container->setInstance('request', $request);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user