Anime and Manga editing, incrementing, and deletion

This commit is contained in:
Timothy Warren 2017-01-09 20:36:48 -05:00
parent 187812576c
commit 08b4227b34
19 changed files with 192 additions and 184 deletions

View File

@ -25,9 +25,9 @@
<td><label for="status">Reading Status</label></td>
<td>
<select name="status" id="status">
<?php foreach($status_list as $status): ?>
<option <?php if($item['reading_status'] === $status): ?>selected="selected"<?php endif ?>
value="<?= $status ?>"><?= $status ?></option>
<?php foreach($status_list as $val => $status): ?>
<option <?php if($item['reading_status'] === $val): ?>selected="selected"<?php endif ?>
value="<?= $val ?>"><?= $status ?></option>
<?php endforeach ?>
</select>
</td>

View File

@ -1174,8 +1174,8 @@ a:hover, a:active {
position:absolute;
top: 86px;
top: calc(50% - 58.5px);
left: 5px;
left: calc(50% - 95px);
left: 43.5px;
left: calc(50% - 66.5px);
}
/* -----------------------------------------------------------------------------
@ -1222,11 +1222,16 @@ a:hover, a:active {
position:absolute;
display:block;
top:0;
left:0;
height:100%;
width:100%;
vertical-align:middle;
}
#series_list .name small {
color: #fff;
}
/* ----------------------------------------------------------------------------
Details page styles
-----------------------------------------------------------------------------*/

View File

@ -439,8 +439,8 @@ a:hover, a:active {
position:absolute;
top: 86px;
top: calc(50% - 58.5px);
left: 5px;
left: calc(50% - 95px);
left: 43.5px;
left: calc(50% - 66.5px);
}
@ -484,10 +484,14 @@ a:hover, a:active {
position:absolute;
display:block;
top:0;
left:0;
height:100%;
width:100%;
vertical-align:middle;
}
#series_list .name small {
color: #fff;
}
/* ----------------------------------------------------------------------------
Details page styles

8
public/js/manga_edit.js Executable file → Normal file
View File

@ -18,12 +18,16 @@
completed = 0;
}
// Setup the update data
let data = {
id: manga_id
id: manga_id,
data: {
progress: completed
}
};
// Update the total count
data[type + "s_read"] = ++completed;
data['data']['progress'] = ++completed;
_.ajax(_.url('/manga/update'), {
data: data,

View File

@ -4,10 +4,13 @@
<input type="radio" class="big-check" id="{{attributes.slug}}" name="id" value="{{id}}" />
<label for="{{attributes.slug}}">
<span class="name">
{{attributes.canonicalTitle}}<br />
{{attributes.titles.en}}<br />
{{attributes.titles.en_jp}}<br />
{{attributes.titles.ja_jp}}
{{attributes.canonicalTitle}}
<br />
<small>
{{#attributes.titles}}
{{.}}<br />
{{/attributes.titles}}
</small>
</span>
</label>
</div>

View File

@ -1,10 +1,18 @@
{{#search}}
{{#data}}
<article class="media search">
<div class="name" style="background-image:url({{image}})">
<input type="radio" class="big-check" id="{{link}}" name="id" value="{{link}}" />
<label for="{{link}}">
<span>{{title}}</span>
<div class="name" style="background-image:url({{attributes.posterImage.small}})">
<input type="radio" class="big-check" id="{{attributes.slug}}" name="id" value="{{id}}" />
<label for="{{attributes.slug}}">
<span class="name">
{{attributes.canonicalTitle}}
<br />
<small>
{{#attributes.titles}}
{{.}}<br />
{{/attributes.titles}}
</small>
</span>
</label>
</div>
</article>
{{/search}}
{{/data}}

View File

@ -16,7 +16,11 @@
namespace Aviat\AnimeClient\API;
use Aviat\AnimeClient\API\Kitsu\Enum\{AnimeAiringStatus, AnimeWatchingStatus};
use Aviat\AnimeClient\API\Kitsu\Enum\{
AnimeAiringStatus,
AnimeWatchingStatus,
MangaReadingStatus
};
use DateTimeImmutable;
/**
@ -41,6 +45,17 @@ class Kitsu {
];
}
public static function getStatusToMangaSelectMap()
{
return [
MangaReadingStatus::READING => 'Currently Reading',
MangaReadingStatus::PLAN_TO_READ => 'Plan to Read',
MangaReadingStatus::COMPLETED => 'Completed',
MangaReadingStatus::ON_HOLD => 'On Hold',
MangaReadingStatus::DROPPED => 'Dropped'
];
}
/**
* Determine whether an anime is airing, finished airing, or has not yet aired
*

View File

@ -22,10 +22,10 @@ use Aviat\Ion\Enum as BaseEnum;
* Possible values for current reading status of manga
*/
class MangaReadingStatus extends BaseEnum {
const READING = 1;
const PLAN_TO_READ = 2;
const DROPPED = 5;
const ON_HOLD = 4;
const COMPLETED = 3;
const READING = 'current';
const PLAN_TO_READ = 'planned';
const DROPPED = 'dropped';
const ON_HOLD = 'on_hold';
const COMPLETED = 'completed';
}
// End of MangaReadingStatus.php

View File

@ -24,6 +24,7 @@ use Aviat\AnimeClient\API\Kitsu\Transformer\{
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json;
use GuzzleHttp\Exception\ClientException;
use PHP_CodeSniffer\Tokenizers\JS;
/**
* Kitsu API Model
@ -32,9 +33,6 @@ class KitsuModel {
use ContainerAware;
use KitsuTrait;
const CLIENT_ID = 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd';
const CLIENT_SECRET = '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151';
/**
* Class to map anime list items
* to a common format used by
@ -101,7 +99,8 @@ class KitsuModel {
*/
public function authenticate(string $username, string $password)
{
$data = $this->postRequest(K::AUTH_URL, [
$response = $this->getResponse('POST', K::AUTH_URL, [
'headers' => [],
'form_params' => [
'grant_type' => 'password',
'username' => $username,
@ -109,6 +108,8 @@ class KitsuModel {
]
]);
$data = Json::decode((string)$response->getBody());
if (array_key_exists('access_token', $data))
{
return $data['access_token'];
@ -161,7 +162,6 @@ class KitsuModel {
$data = $this->getRequest('library-entries', $options);
$included = K::organizeIncludes($data['included']);
/*?><pre><?= print_r($included, TRUE) ?></pre><?php*/
foreach($data['data'] as $i => &$item)
{
@ -173,8 +173,6 @@ class KitsuModel {
{
$item['genres'][] = $included['genres'][$id]['name'];
}
// $item['genres'] = array_pluck($genres, 'name');
}
$transformed = $this->animeListTransformer->transformCollection($data['data']);
@ -223,7 +221,15 @@ class KitsuModel {
'include' => 'media'
];
return $this->getRequest($type, $options);
$raw = $this->getRequest($type, $options);
foreach ($raw['data'] as &$item)
{
$item['attributes']['titles'] = K::filterTitles($item['attributes']);
array_shift($item['attributes']['titles']);
}
return $raw;
}
public function getListItem(string $listId): array
@ -264,6 +270,11 @@ class KitsuModel {
}
}
public function deleteListItem(string $id): bool
{
return $this->listItem->delete($id);
}
private function getUsername(): string
{
return $this->getContainer()

View File

@ -18,12 +18,12 @@ namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\AnimeClient;
use Aviat\AnimeClient\API\GuzzleTrait;
use Aviat\AnimeClient\API\Kitsu as K;
use Aviat\Ion\Json;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Psr7\Response;
use InvalidArgumentException;
use PHP_CodeSniffer\Tokenizers\JS;
use RuntimeException;
trait KitsuTrait {
@ -43,7 +43,7 @@ trait KitsuTrait {
protected $defaultHeaders = [
'User-Agent' => "Tim's Anime Client/4.0",
'Accept-Encoding' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json; charset=utf-8',
'Content-Type' => 'application/vnd.api+json',
'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd',
'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151',
];
@ -93,23 +93,22 @@ trait KitsuTrait {
'headers' => $this->defaultHeaders
];
if ($this->getContainer());
{
$logger = $this->container->getLogger('request');
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(AnimeClient::SESSION_SEGMENT);
$logger = $this->container->getLogger('request');
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(AnimeClient::SESSION_SEGMENT);
if ($sessionSegment->get('auth_token') !== null)
{
$token = $sessionSegment->get('auth_token');
$defaultOptions['headers']['Authorization'] = "bearer {$token}";
}
$logger->debug(Json::encode(func_get_args()));
if ($sessionSegment->get('auth_token') !== null && $url !== K::AUTH_URL)
{
$token = $sessionSegment->get('auth_token');
$defaultOptions['headers']['Authorization'] = "bearer {$token}";
}
$options = array_merge($defaultOptions, $options);
$logger->debug(Json::encode([$type, $url]));
$logger->debug(Json::encode($options));
return $this->client->request($type, $url, $options);
}
@ -131,7 +130,7 @@ trait KitsuTrait {
$response = $this->getResponse($type, $url, $options);
if ((int) $response->getStatusCode() !== 200)
if ((int) $response->getStatusCode() > 299 || (int) $response->getStatusCode() < 200)
{
if ($logger)
{
@ -192,7 +191,7 @@ trait KitsuTrait {
$logger->warning($response->getBody());
}
throw new RuntimeException($response->getBody());
// throw new RuntimeException($response->getBody());
}
return JSON::decode($response->getBody(), TRUE);

View File

@ -37,14 +37,33 @@ class ListItem extends AbstractListItem {
public function create(array $data): bool
{
// TODO: Implement create() method.
return false;
$response = $this->getResponse('post', 'library-entries', [
'body' => [
'type' => 'libraryEntries',
'attributes' => [
'status' => $data['status'],
'progress' => $data['progress'] ?? 0
],
'relationships' => [
'user' => [
'id' => $data['user_id'],
'type' => 'users'
],
'media' => [
'id' => $data['id'],
'type' => $data['type']
]
]
]
]);
return ($response->getStatusCode() === 201);
}
public function delete(string $id): bool
{
// TODO: Implement delete() method.
return false;
$response = $this->getResponse('DELETE', "library-entries/{$id}");
return ($response->getStatusCode() === 204);
}
public function get(string $id): array

View File

@ -91,18 +91,18 @@ class MangaListTransformer extends AbstractTransformer {
$map = [
'id' => $item['id'],
'manga_id' => $item['manga_id'],
'status' => $item['status'],
'chapters_read' => (int)$item['chapters_read'],
'volumes_read' => (int)$item['volumes_read'],
'rereading' => $rereading,
'reread_count' => (int)$item['reread_count'],
'notes' => $item['notes'],
'data' => [
'status' => $item['status'],
'progress' => (int)$item['chapters_read'],
'reconsuming' => $rereading,
'reconsumeCount' => (int)$item['reread_count'],
'notes' => $item['notes'],
],
];
if ($item['new_rating'] !== $item['old_rating'] && $item['new_rating'] !== "")
{
$map['rating'] = ($item['new_rating'] > 0)
$map['data']['rating'] = ($item['new_rating'] > 0)
? $item['new_rating'] / 2
: $item['old_rating'] / 2;
}

View File

@ -260,8 +260,6 @@ class Anime extends BaseController {
}
$response = $this->model->updateLibraryItem($data);
//echo JSON::encode($response);
//die();
// $this->cache->purge();
$this->outputJSON($response['body'], $response['statusCode']);
@ -274,12 +272,13 @@ class Anime extends BaseController {
*/
public function delete()
{
$response = $this->model->delete($this->request->getParsedBody());
$body = $this->request->getParsedBody();
$response = $this->model->deleteLibraryItem($body['id']);
if ((bool)$response['body'] === TRUE)
if ((bool)$response === TRUE)
{
$this->set_flash_message("Successfully deleted anime.", 'success');
$this->cache->purge();
// $this->cache->purge();
}
else
{

View File

@ -17,6 +17,7 @@
namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\Kitsu\Enum\MangaReadingStatus;
use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer;
use Aviat\AnimeClient\Model\Manga as MangaModel;
@ -166,7 +167,7 @@ class Manga extends Controller {
$this->outputHTML('manga/edit', [
'title' => $title,
'status_list' => MangaReadingStatus::getConstList(),
'status_list' => Kitsu::getStatusToMangaSelectMap(),
'item' => $item,
'action' => $this->container->get('url-generator')
->url('/manga/update_form'),
@ -185,50 +186,54 @@ class Manga extends Controller {
}
/**
* Update an anime item via a form submission
* Update an manga item via a form submission
*
* @return void
*/
public function form_update()
{
$post_data = $this->request->getParsedBody();
$data = $this->request->getParsedBody();
// Do some minor data manipulation for
// large form-based updates
$transformer = new MangaListTransformer();
$post_data = $transformer->untransform($post_data);
$full_result = $this->model->update($post_data);
$post_data = $transformer->untransform($data);
$full_result = $this->model->updateLibraryItem($post_data);
$result = Json::decode((string)$full_result['body']);
if ((int)$full_result['statusCode'] === 200)
if ($full_result['statusCode'] === 200)
{
$m =& $result['manga'][0];
$title = ( ! empty($m['english_title']))
? "{$m['romaji_title']} ({$m['english_title']})"
: "{$m['romaji_title']}";
$this->set_flash_message("Successfully updated {$title}.", 'success');
$this->cache->purge();
$this->set_flash_message("Successfully updated manga.", 'success');
// $this->cache->purge();
}
else
{
$this->set_flash_message('Failed to update manga.', 'error');
}
$this->session_redirect();
}
/**
* Update an anime item
* Update a manga item
*
* @return boolean|null
* @return void
*/
public function update()
{
$result = $this->model->update($this->request->getParsedBody());
$this->cache->purge();
$this->outputJSON($result['body'], $result['statusCode']);
if ($this->request->getHeader('content-type')[0] === 'application/json')
{
$data = JSON::decode((string)$this->request->getBody());
}
else
{
$data = $this->request->getParsedBody();
}
$response = $this->model->updateLibraryItem($data);
// $this->cache->purge();
$this->outputJSON($response['body'], $response['statusCode']);
}
/**

View File

@ -38,10 +38,10 @@ class API extends Model {
*/
protected $cache;
/**
* Default settings for Guzzle
* @var array
*/
/**
* Default settings for Guzzle
* @var array
*/
protected $connectionDefaults = [];
/**
@ -75,4 +75,4 @@ class API extends Model {
array_multisort($sort, SORT_ASC, $array);
}
}
// End of BaseApiModel.php
// End of BaseApiModel.php

View File

@ -115,5 +115,10 @@ class Anime extends API {
{
return $this->kitsuModel->updateListItem($data);
}
public function deleteLibraryItem($id): bool
{
return $this->kitsuModel->deleteListItem($id);
}
}
// End of AnimeModel.php

View File

@ -18,6 +18,7 @@ namespace Aviat\AnimeClient\Model;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Model\DB;
use PDO;
use PDOException;
/**

View File

@ -95,6 +95,28 @@ class Manga extends API
return $this->kitsuModel->getListItem($itemId);
}
/**
* Update a list entry
*
* @param array $data
* @return array
*/
public function updateLibraryItem(array $data): array
{
return $this->kitsuModel->updateListItem($data);
}
/**
* Search for anime by name
*
* @param string $name
* @return array
*/
public function search($name)
{
return $this->kitsuModel->search('manga', $name);
}
/**
* Map transformed anime data to be organized by reading status
*

View File

@ -109,97 +109,5 @@ class Util {
{
return ! $this->is_view_page();
}
}
/**
* Get the path of the cached version of the image. Create the cached image
* if the file does not already exist
*
* @codeCoverageIgnore
* @param string $api_path - The original image url
* @param string $series_slug - The part of the url with the series name, becomes the image name
* @param string $type - Anime or Manga, controls cache path
* @return string - the frontend path for the cached image
* @throws \DomainException
*/
public function get_cached_image($api_path, $series_slug, $type = "anime")
{
$path_parts = explode('?', basename($api_path));
$path = current($path_parts);
$ext_parts = explode('.', $path);
$ext = end($ext_parts);
// Workaround for some broken file extensions
if ($ext === "jjpg")
{
$ext = "jpg";
}
// Failsafe for weird urls
if (strlen($ext) > 3)
{
return $api_path;
}
$img_cache_path = $this->config->get('img_cache_path');
$cached_image = "{$series_slug}.{$ext}";
$cached_path = "{$img_cache_path}/{$type}/{$cached_image}";
// Cache the file if it doesn't already exist
if ( ! file_exists($cached_path))
{
if (function_exists('curl_init'))
{
$ch = curl_init($api_path);
$fp = fopen($cached_path, 'wb');
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_HEADER => 0
]);
curl_exec($ch);
curl_close($ch);
fclose($fp);
}
else if (ini_get('allow_url_fopen'))
{
copy($api_path, $cached_path);
}
else
{
throw new DomainException("Couldn't cache images because they couldn't be downloaded.");
}
// Resize the image
if ($type === 'anime')
{
$resize_width = 220;
$resize_height = 319;
$this->_resize($cached_path, $resize_width, $resize_height);
}
}
return "/public/images/{$type}/{$cached_image}";
}
/**
* Resize an image
*
* @codeCoverageIgnore
* @param string $path
* @param string $width
* @param string $height
* @return void
*/
private function _resize($path, $width, $height)
{
try
{
$img = new SimpleImage($path);
$img->resize($width, $height)->save();
}
catch (Exception $e)
{
// Catch image errors, since they don't otherwise affect
// functionality
}
}
}