Version 5.1 - All the GraphQL #32
5
.gitignore
vendored
5
.gitignore
vendored
@ -25,4 +25,7 @@ app/config/*.toml
|
|||||||
phinx.yml
|
phinx.yml
|
||||||
.idea/
|
.idea/
|
||||||
Caddyfile
|
Caddyfile
|
||||||
build/humbuglog.txt
|
build/humbuglog.txt
|
||||||
|
public/images/anime/**
|
||||||
|
public/images/manga/**
|
||||||
|
public/images/characters/**
|
@ -147,6 +147,16 @@ return [
|
|||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
// Default / Shared routes
|
// Default / Shared routes
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
'image_proxy' => [
|
||||||
|
'path' => '/public/images/{type}/{file}',
|
||||||
|
'action' => 'images',
|
||||||
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
|
'verb' => 'get',
|
||||||
|
'tokens' => [
|
||||||
|
'type' => '[a-z0-9\-]+',
|
||||||
|
'file' => '[a-z0-9\-]+\.[a-z]{3}'
|
||||||
|
]
|
||||||
|
],
|
||||||
'cache_purge' => [
|
'cache_purge' => [
|
||||||
'path' => '/cache_purge',
|
'path' => '/cache_purge',
|
||||||
'action' => 'clearCache',
|
'action' => 'clearCache',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<main class="details fixed">
|
<main class="details fixed">
|
||||||
<section class="flex flex-no-wrap">
|
<section class="flex flex-no-wrap">
|
||||||
<div>
|
<div>
|
||||||
<img class="cover" width="402" height="284" src="<?= $data['cover_image'] ?>" alt="" />
|
<img class="cover" width="402" height="284" src="<?= $urlGenerator->assetUrl("images/anime/{$data['id']}.jpg") ?>" alt="" />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<table class="media_details">
|
<table class="media_details">
|
||||||
@ -80,7 +80,7 @@
|
|||||||
<?php if (count($characters) > 0): ?>
|
<?php if (count($characters) > 0): ?>
|
||||||
<h2>Characters</h2>
|
<h2>Characters</h2>
|
||||||
<section class="align_left media-wrap">
|
<section class="align_left media-wrap">
|
||||||
<?php foreach($characters as $char): ?>
|
<?php foreach($characters as $id => $char): ?>
|
||||||
<?php if ( ! empty($char['image']['original'])): ?>
|
<?php if ( ! empty($char['image']['original'])): ?>
|
||||||
<article class="character">
|
<article class="character">
|
||||||
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
|
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
|
||||||
@ -88,7 +88,7 @@
|
|||||||
<?= $helper->a($link, $char['name']); ?>
|
<?= $helper->a($link, $char['name']); ?>
|
||||||
</div>
|
</div>
|
||||||
<a href="<?= $link ?>">
|
<a href="<?= $link ?>">
|
||||||
<?= $helper->img($char['image']['original'], [
|
<?= $helper->img($urlGenerator->assetUrl("images/characters/{$id}.jpg"), [
|
||||||
'width' => '225'
|
'width' => '225'
|
||||||
]) ?>
|
]) ?>
|
||||||
</a>
|
</a>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<main class="details">
|
<main class="details">
|
||||||
<section class="flex flex-no-wrap">
|
<section class="flex flex-no-wrap">
|
||||||
<div>
|
<div>
|
||||||
<img class="cover" width="284" src="<?= $data[0]['attributes']['image']['original'] ?>" alt="" />
|
<img class="cover" width="284" src="<?= $urlGenerator->assetUrl("images/characters/{$data[0]['id']}.jpg") ?>" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2><?= $data[0]['attributes']['name'] ?></h2>
|
<h2><?= $data[0]['attributes']['name'] ?></h2>
|
||||||
@ -18,14 +18,14 @@
|
|||||||
<div>
|
<div>
|
||||||
<h4>Anime</h4>
|
<h4>Anime</h4>
|
||||||
<section class="align_left media-wrap">
|
<section class="align_left media-wrap">
|
||||||
<?php foreach($data['included']['anime'] as $anime): ?>
|
<?php foreach($data['included']['anime'] as $id => $anime): ?>
|
||||||
<article class="media">
|
<article class="media">
|
||||||
<?php
|
<?php
|
||||||
$link = $url->generate('anime.details', ['id' => $anime['attributes']['slug']]);
|
$link = $url->generate('anime.details', ['id' => $anime['attributes']['slug']]);
|
||||||
$titles = Kitsu::filterTitles($anime['attributes']);
|
$titles = Kitsu::filterTitles($anime['attributes']);
|
||||||
?>
|
?>
|
||||||
<a href="<?= $link ?>">
|
<a href="<?= $link ?>">
|
||||||
<img src="<?= $anime['attributes']['posterImage']['small'] ?>" width="220" alt="" />
|
<img src="<?= $urlGenerator->assetUrl("images/anime/{$id}.jpg") ?>" width="220" alt="" />
|
||||||
</a>
|
</a>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<a href="<?= $link ?>">
|
<a href="<?= $link ?>">
|
||||||
@ -45,14 +45,14 @@
|
|||||||
<h4>Manga</h4>
|
<h4>Manga</h4>
|
||||||
<section class="align_left media-wrap">
|
<section class="align_left media-wrap">
|
||||||
|
|
||||||
<?php foreach($data['included']['manga'] as $manga): ?>
|
<?php foreach($data['included']['manga'] as $id => $manga): ?>
|
||||||
<article class="media">
|
<article class="media">
|
||||||
<?php
|
<?php
|
||||||
$link = $url->generate('manga.details', ['id' => $manga['attributes']['slug']]);
|
$link = $url->generate('manga.details', ['id' => $manga['attributes']['slug']]);
|
||||||
$titles = Kitsu::filterTitles($manga['attributes']);
|
$titles = Kitsu::filterTitles($manga['attributes']);
|
||||||
?>
|
?>
|
||||||
<a href="<?= $link ?>">
|
<a href="<?= $link ?>">
|
||||||
<img src="<?= $manga['attributes']['posterImage']['small'] ?>" width="220" alt="" />
|
<img src="<?= $urlGenerator->assetUrl("images/manga/{$id}.jpg") ?>" width="220" alt="" />
|
||||||
</a>
|
</a>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<a href="<?= $link ?>">
|
<a href="<?= $link ?>">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<section class="media-wrap">
|
<section class="media-wrap">
|
||||||
<?php foreach($items as $item): ?>
|
<?php foreach($items as $item): ?>
|
||||||
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
|
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
|
||||||
<img src="https://media.kitsu.io/anime/poster_images/<?= $item['hummingbird_id'] ?>/small.jpg"
|
<img src="<?= $urlGenerator->assetUrl("images/anime/{$item['hummingbird_id']}.jpg") ?>"
|
||||||
alt="<?= $item['title'] ?> cover image" />
|
alt="<?= $item['title'] ?> cover image" />
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
|
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
|
||||||
|
@ -11,10 +11,11 @@ const css = fs.readFileSync('css/base.css', 'utf8');
|
|||||||
|
|
||||||
postcss()
|
postcss()
|
||||||
.use(atImport())
|
.use(atImport())
|
||||||
.use(cssNext({
|
.use(cssNext())
|
||||||
warnForDuplicates: false
|
|
||||||
}))
|
|
||||||
.use(cssNano({
|
.use(cssNano({
|
||||||
|
autoprefixer: false,
|
||||||
|
colormin: false,
|
||||||
|
minifyFontValues: false,
|
||||||
options: {
|
options: {
|
||||||
sourcemap: false
|
sourcemap: false
|
||||||
}
|
}
|
||||||
|
2
public/css/app.min.css
vendored
2
public/css/app.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
:root {
|
:root {
|
||||||
--default-font-list:system-ui,sans-serif;
|
--default-font-list: system-ui,sans-serif;
|
||||||
--monospace-font-list:'Anonymous Pro','Fira Code',Menlo,Monaco,Consolas,'Courier New',monospace;
|
--monospace-font-list:'Anonymous Pro','Fira Code',Menlo,Monaco,Consolas,'Courier New',monospace;
|
||||||
--serif-font-list:Georgia,Times,'Times New Roman',serif;
|
--serif-font-list:Georgia,Times,'Times New Roman',serif;
|
||||||
-ms-text-size-adjust:100%;
|
-ms-text-size-adjust:100%;
|
||||||
|
0
public/images/characters/.gitkeep
Normal file
0
public/images/characters/.gitkeep
Normal file
@ -829,6 +829,7 @@ class Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$baseData = $data['data']['attributes'];
|
$baseData = $data['data']['attributes'];
|
||||||
|
$baseData['id'] = $data['id'];
|
||||||
$baseData['included'] = $data['included'];
|
$baseData['included'] = $data['included'];
|
||||||
return $baseData;
|
return $baseData;
|
||||||
}
|
}
|
||||||
@ -864,6 +865,7 @@ class Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$baseData = $data['data'][0]['attributes'];
|
$baseData = $data['data'][0]['attributes'];
|
||||||
|
$baseData['id'] = $data['data'][0]['id'];
|
||||||
$baseData['included'] = $data['included'];
|
$baseData['included'] = $data['included'];
|
||||||
return $baseData;
|
return $baseData;
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,11 @@ class AnimeTransformer extends AbstractTransformer {
|
|||||||
$item['included'] = JsonAPI::organizeIncludes($item['included']);
|
$item['included'] = JsonAPI::organizeIncludes($item['included']);
|
||||||
$item['genres'] = array_column($item['included']['genres'], 'name') ?? [];
|
$item['genres'] = array_column($item['included']['genres'], 'name') ?? [];
|
||||||
sort($item['genres']);
|
sort($item['genres']);
|
||||||
|
|
||||||
$titles = Kitsu::filterTitles($item);
|
$titles = Kitsu::filterTitles($item);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'id' => $item['id'],
|
||||||
'slug' => $item['slug'],
|
'slug' => $item['slug'],
|
||||||
'title' => $titles[0],
|
'title' => $titles[0],
|
||||||
'titles' => $titles,
|
'titles' => $titles,
|
||||||
|
@ -16,10 +16,16 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Controller;
|
namespace Aviat\AnimeClient\Controller;
|
||||||
|
|
||||||
|
use function Amp\wait;
|
||||||
|
|
||||||
|
use Amp\Artax\Client;
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
use Aviat\AnimeClient\API\JsonAPI;
|
use Aviat\AnimeClient\API\JsonAPI;
|
||||||
use Aviat\Ion\View\HtmlView;
|
use Aviat\Ion\View\HtmlView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for handling routes that don't fit elsewhere
|
||||||
|
*/
|
||||||
class Index extends BaseController {
|
class Index extends BaseController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,6 +119,44 @@ class Index extends BaseController {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get image covers from kitsu
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function images($type, $file)
|
||||||
|
{
|
||||||
|
$kitsuUrl = 'https://media.kitsu.io/';
|
||||||
|
list($id, $ext) = explode('.', basename($file));
|
||||||
|
switch ($type)
|
||||||
|
{
|
||||||
|
case 'anime':
|
||||||
|
$kitsuUrl .= "anime/poster_images/{$id}/small.{$ext}";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'manga':
|
||||||
|
$kitsuUrl .= "manga/poster_images/{$id}/small.{$ext}";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'characters':
|
||||||
|
$kitsuUrl .= "characters/images/{$id}/original.{$ext}";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->notFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$promise = (new Client)->request($kitsuUrl);
|
||||||
|
$response = wait($promise);
|
||||||
|
$data = (string) $response->getBody();
|
||||||
|
|
||||||
|
$baseSavePath = $this->config->get('img_cache_path');
|
||||||
|
file_put_contents("{$baseSavePath}/{$type}/{$id}.{$ext}", $data);
|
||||||
|
header('Content-type: ' . $response->getHeader('content-type')[0]);
|
||||||
|
echo (string) $response->getBody();
|
||||||
|
}
|
||||||
|
|
||||||
private function organizeFavorites(array $rawfavorites): array
|
private function organizeFavorites(array $rawfavorites): array
|
||||||
{
|
{
|
||||||
// return $rawfavorites;
|
// return $rawfavorites;
|
||||||
|
@ -22,29 +22,27 @@ use Aviat\Ion\Friend;
|
|||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
class AnimeTransformerTest extends AnimeClientTestCase {
|
class AnimeTransformerTest extends AnimeClientTestCase {
|
||||||
|
|
||||||
protected $dir;
|
protected $dir;
|
||||||
protected $beforeTransform;
|
protected $beforeTransform;
|
||||||
protected $afterTransform;
|
protected $afterTransform;
|
||||||
protected $transformer;
|
protected $transformer;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
$this->beforeTransform = Json::decodeFile("{$this->dir}/animeBeforeTransform.json");
|
$this->beforeTransform = Json::decodeFile("{$this->dir}/animeBeforeTransform.json");
|
||||||
$this->afterTransform = Json::decodeFile("{$this->dir}/animeAfterTransform.json");
|
|
||||||
|
|
||||||
$this->transformer = new AnimeTransformer();
|
$this->transformer = new AnimeTransformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransform()
|
public function testTransform()
|
||||||
{
|
{
|
||||||
$expected = $this->afterTransform;
|
$expected = $this->afterTransform;
|
||||||
$actual = $this->transformer->transform($this->beforeTransform);
|
$actual = $this->transformer->transform($this->beforeTransform);
|
||||||
// Json::encodeFile("{$this->dir}/animeAfterTransform.json", $actual);
|
|
||||||
|
$this->assertMatchesSnapshot($actual);
|
||||||
$this->assertEquals($expected, $actual);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
<?php return array (
|
||||||
|
'id' => 32344,
|
||||||
|
'slug' => 'attack-on-titan',
|
||||||
|
'title' => 'Attack on Titan',
|
||||||
|
'titles' =>
|
||||||
|
array (
|
||||||
|
0 => 'Attack on Titan',
|
||||||
|
1 => 'Shingeki no Kyojin',
|
||||||
|
2 => '進撃の巨人',
|
||||||
|
),
|
||||||
|
'status' => 'Finished Airing',
|
||||||
|
'cover_image' => 'https://media.kitsu.io/anime/poster_images/7442/small.jpg?1418580054',
|
||||||
|
'show_type' => 'TV',
|
||||||
|
'episode_count' => 25,
|
||||||
|
'episode_length' => 24,
|
||||||
|
'synopsis' => 'Several hundred years ago, humans were nearly exterminated by titans. Titans are typically several stories tall, seem to have no intelligence, devour human beings and, worst of all, seem to do it for the pleasure rather than as a food source. A small percentage of humanity survived by enclosing themselves in a city protected by extremely high walls, even taller than the biggest of titans. Flash forward to the present and the city has not seen a titan in over 100 years. Teenage boy Eren and his foster sister Mikasa witness something horrific as the city walls are destroyed by a colossal titan that appears out of thin air. As the smaller titans flood the city, the two kids watch in horror as their mother is eaten alive. Eren vows that he will murder every single titan and take revenge for all of mankind.
|
||||||
|
|
||||||
|
(Source: ANN)',
|
||||||
|
'age_rating' => 'R',
|
||||||
|
'age_rating_guide' => 'Violence, Profanity',
|
||||||
|
'url' => 'https://kitsu.io/anime/attack-on-titan',
|
||||||
|
'genres' =>
|
||||||
|
array (
|
||||||
|
0 => 'Action',
|
||||||
|
1 => 'Drama',
|
||||||
|
2 => 'Fantasy',
|
||||||
|
3 => 'Super Power',
|
||||||
|
),
|
||||||
|
'streaming_links' =>
|
||||||
|
array (
|
||||||
|
0 =>
|
||||||
|
array (
|
||||||
|
'meta' =>
|
||||||
|
array (
|
||||||
|
'name' => 'Crunchyroll',
|
||||||
|
'link' => true,
|
||||||
|
'image' => 'streaming-logos/crunchyroll.svg',
|
||||||
|
),
|
||||||
|
'link' => 'http://www.crunchyroll.com/attack-on-titan',
|
||||||
|
'subs' =>
|
||||||
|
array (
|
||||||
|
0 => 'en',
|
||||||
|
),
|
||||||
|
'dubs' =>
|
||||||
|
array (
|
||||||
|
0 => 'ja',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
1 =>
|
||||||
|
array (
|
||||||
|
'meta' =>
|
||||||
|
array (
|
||||||
|
'name' => 'Hulu',
|
||||||
|
'link' => true,
|
||||||
|
'image' => 'streaming-logos/hulu.svg',
|
||||||
|
),
|
||||||
|
'link' => 'http://www.hulu.com/attack-on-titan',
|
||||||
|
'subs' =>
|
||||||
|
array (
|
||||||
|
0 => 'en',
|
||||||
|
),
|
||||||
|
'dubs' =>
|
||||||
|
array (
|
||||||
|
0 => 'ja',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
2 =>
|
||||||
|
array (
|
||||||
|
'meta' =>
|
||||||
|
array (
|
||||||
|
'name' => 'Funimation',
|
||||||
|
'link' => true,
|
||||||
|
'image' => 'streaming-logos/funimation.svg',
|
||||||
|
),
|
||||||
|
'link' => 'http://www.funimation.com/shows/attack-on-titan/videos/episodes',
|
||||||
|
'subs' =>
|
||||||
|
array (
|
||||||
|
0 => 'en',
|
||||||
|
),
|
||||||
|
'dubs' =>
|
||||||
|
array (
|
||||||
|
0 => 'ja',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
3 =>
|
||||||
|
array (
|
||||||
|
'meta' =>
|
||||||
|
array (
|
||||||
|
'name' => 'Netflix',
|
||||||
|
'link' => false,
|
||||||
|
'image' => 'streaming-logos/netflix.svg',
|
||||||
|
),
|
||||||
|
'link' => 't',
|
||||||
|
'subs' =>
|
||||||
|
array (
|
||||||
|
0 => 'en',
|
||||||
|
),
|
||||||
|
'dubs' =>
|
||||||
|
array (
|
||||||
|
0 => 'ja',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"id": 32344,
|
||||||
"slug": "attack-on-titan",
|
"slug": "attack-on-titan",
|
||||||
"synopsis": "Several hundred years ago, humans were nearly exterminated by titans. Titans are typically several stories tall, seem to have no intelligence, devour human beings and, worst of all, seem to do it for the pleasure rather than as a food source. A small percentage of humanity survived by enclosing themselves in a city protected by extremely high walls, even taller than the biggest of titans. Flash forward to the present and the city has not seen a titan in over 100 years. Teenage boy Eren and his foster sister Mikasa witness something horrific as the city walls are destroyed by a colossal titan that appears out of thin air. As the smaller titans flood the city, the two kids watch in horror as their mother is eaten alive. Eren vows that he will murder every single titan and take revenge for all of mankind.\n\n(Source: ANN)",
|
"synopsis": "Several hundred years ago, humans were nearly exterminated by titans. Titans are typically several stories tall, seem to have no intelligence, devour human beings and, worst of all, seem to do it for the pleasure rather than as a food source. A small percentage of humanity survived by enclosing themselves in a city protected by extremely high walls, even taller than the biggest of titans. Flash forward to the present and the city has not seen a titan in over 100 years. Teenage boy Eren and his foster sister Mikasa witness something horrific as the city walls are destroyed by a colossal titan that appears out of thin air. As the smaller titans flood the city, the two kids watch in horror as their mother is eaten alive. Eren vows that he will murder every single titan and take revenge for all of mankind.\n\n(Source: ANN)",
|
||||||
"coverImageTopOffset": 263,
|
"coverImageTopOffset": 263,
|
||||||
|
Loading…
Reference in New Issue
Block a user