Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
18 changed files with 191 additions and 166 deletions
Showing only changes of commit c788cf5d87 - Show all commits

View File

@ -38,9 +38,8 @@ A self-hosted client that allows custom formatting of data from the hummingbird
### Installation
1. Install dependencies via composer: `composer install`
2. Change the `WHOSE` constant declaration in `index.php` to your name
3. Configure settings in `app/config/config.php` and `app/config/routing.php` to your liking
4. Create the following directories if they don't exist, and make sure they are world writable
2. Configure settings in `app/config/config.php` and `app/config/routing.php` to your liking
3. Create the following directories if they don't exist, and make sure they are world writable
* app/cache
* public/images/manga
* public/images/anime

View File

@ -5,6 +5,11 @@ $config = [
// ----------------------------------------------------------------------------
'hummingbird_username' => 'timw4mail',
// ----------------------------------------------------------------------------
// Whose list is it?
// ----------------------------------------------------------------------------
'whose_list' => 'Tim',
// ----------------------------------------------------------------------------
// General config
// ----------------------------------------------------------------------------

View File

@ -1,6 +1,14 @@
<?php
return [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller',
'default_controller' => '\\Aviat\\AnimeClient\\Controller\\Anime',
'default_method' => 'index'
],
'configuration' => [
],
// Routes on all controllers
'common' => [
'update' => [
@ -55,10 +63,6 @@ return [
'view' => '[a-z_]+'
]
],
],
// Routes on stats controller
'stats' => [
],
// Routes on anime controller
'anime' => [
@ -79,7 +83,6 @@ return [
'action' => ['anime_list'],
'params' => [
'type' => 'all',
'title' => WHOSE . " Anime List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
@ -89,8 +92,7 @@ return [
'path' => '/anime/watching{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'currently-watching',
'title' => WHOSE . " Anime List &middot; Watching"
'type' => 'watching',
],
'tokens' => [
'view' => '[a-z_]+'
@ -100,8 +102,7 @@ return [
'path' => '/anime/plan_to_watch{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'plan-to-watch',
'title' => WHOSE . " Anime List &middot; Plan to Watch"
'type' => 'plan_to_watch',
],
'tokens' => [
'view' => '[a-z_]+'
@ -111,8 +112,7 @@ return [
'path' => '/anime/on_hold{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'on-hold',
'title' => WHOSE . " Anime List &middot; On Hold"
'type' => 'on_hold',
],
'tokens' => [
'view' => '[a-z_]+'
@ -123,7 +123,6 @@ return [
'action' => ['anime_list'],
'params' => [
'type' => 'dropped',
'title' => WHOSE . " Anime List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
@ -134,7 +133,6 @@ return [
'action' => ['anime_list'],
'params' => [
'type' => 'completed',
'title' => WHOSE . " Anime List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
@ -156,7 +154,6 @@ return [
'action' => ['manga_list'],
'params' => [
'type' => 'all',
'title' => WHOSE . " Manga List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
@ -166,8 +163,7 @@ return [
'path' => '/manga/reading{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Reading',
'title' => WHOSE . " Manga List &middot; Reading"
'type' => 'reading',
],
'tokens' => [
'view' => '[a-z_]+'
@ -177,8 +173,7 @@ return [
'path' => '/manga/plan_to_read{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Plan to Read',
'title' => WHOSE . " Manga List &middot; Plan to Read"
'type' => 'plan_to_read',
],
'tokens' => [
'view' => '[a-z_]+'
@ -188,8 +183,7 @@ return [
'path' => '/manga/on_hold{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'On Hold',
'title' => WHOSE . " Manga List &middot; On Hold"
'type' => 'on_hold',
],
'tokens' => [
'view' => '[a-z_]+'
@ -199,8 +193,7 @@ return [
'path' => '/manga/dropped{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Dropped',
'title' => WHOSE . " Manga List &middot; Dropped"
'type' => 'dropped',
],
'tokens' => [
'view' => '[a-z_]+'
@ -210,8 +203,7 @@ return [
'path' => '/manga/completed{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Completed',
'title' => WHOSE . " Manga List &middot; Completed"
'type' => 'completed',
],
'tokens' => [
'view' => '[a-z_]+'

View File

@ -13,14 +13,14 @@
<h1 class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1">
<a href="<?= $urlGenerator->default_url($url_type) ?>">
<?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?>
<?= $config->whose_list ?>'s <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?>
</a> [<a href="<?= $urlGenerator->default_url($other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
</span>
<span class="flex-no-wrap small-font">
<?php if (is_logged_in()): ?>
[<a href="<?= $urlGenerator->url("/{$url_type}/logout", $url_type) ?>">Logout</a>]
<?php else: ?>
[<a href="<?= $urlGenerator->url("/{$url_type}/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
[<a href="<?= $urlGenerator->url("/{$url_type}/login", $url_type) ?>"><?= $config->whose_list ?>'s Login</a>]
<?php endif ?>
</span>
</h1>

View File

@ -3,20 +3,7 @@
* Here begins everything!
*/
// -----------------------------------------------------------------------------
// ! Start config
// -----------------------------------------------------------------------------
/**
* Well, whose list is it?
*/
define('WHOSE', "Tim's");
// -----------------------------------------------------------------------------
// ! End config
// -----------------------------------------------------------------------------
\session_start();
session_start();
// Work around the silly timezone error
$timezone = ini_get('date.timezone');
@ -43,29 +30,25 @@ function _dir()
return implode(DIRECTORY_SEPARATOR, func_get_args());
}
// Set up composer autoloader
require _dir(ROOT_DIR, '/vendor/autoload.php');
/**
* Set up autoloaders
*
* @codeCoverageIgnore
* @return void
*/
function _setup_autoloaders()
{
require _dir(ROOT_DIR, '/vendor/autoload.php');
spl_autoload_register(function ($class) {
$class_parts = explode('\\', $class);
$ns_path = SRC_DIR . '/' . implode('/', $class_parts) . ".php";
spl_autoload_register(function ($class) {
$class_parts = explode('\\', $class);
$ns_path = SRC_DIR . '/' . implode('/', $class_parts) . ".php";
if (file_exists($ns_path))
{
require_once($ns_path);
return;
}
});
}
// Setup autoloaders
_setup_autoloaders();
if (file_exists($ns_path))
{
require_once($ns_path);
return;
}
});
// Do dependency injection, and go!
require _dir(APP_DIR, 'bootstrap.php');

View File

@ -53,7 +53,6 @@ class Controller {
* Constructor
*
* @param Container $container
* @param array $web
*/
public function __construct(Container $container)
{
@ -150,23 +149,6 @@ class Controller {
$this->response->content->set($buffer);
}
/**
* Output json with the proper content type
*
* @param mixed $data
* @return void
*/
public function outputJSON($data)
{
if ( ! is_string($data))
{
$data = json_encode($data);
}
$this->response->content->setType('application/json');
$this->response->content->set($data);
}
/**
* Redirect to the selected page
*

View File

@ -50,6 +50,8 @@ class Anime extends BaseController {
/**
* Constructor
*
* @param Container $container
*/
public function __construct(Container $container)
{
@ -89,17 +91,38 @@ class Anime extends BaseController {
*
* @param string $type - The section of the list
* @param string $title - The title of the page
* @param string $view - List or cover view
* @return void
*/
public function anime_list($type, $title, $view)
public function anime_list($type, $view)
{
$type_title_map = [
'all' => 'All',
'watching' => 'Currently Watching',
'plan_to_watch' => 'Plan to Watch',
'on_hold' => 'On Hold',
'dropped' => 'Dropped',
'completed' => 'Completed'
];
$model_map = [
'watching' => 'currently-watching',
'plan_to_watch' => 'plan-to-watch',
'on_hold' => 'on-hold',
'all' => 'all',
'dropped' => 'dropped',
'completed' => 'completed'
];
$title = $this->config->whose_list . "'s Anime List &middot; {$type_title_map[$type]}";
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($type != 'all')
? $this->model->get_list($type)
? $this->model->get_list($model_map[$type])
: $this->model->get_all_lists();
$this->outputHTML('anime/' . $view_map[$view], [

View File

@ -8,6 +8,7 @@ namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Container;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\Config;
use Aviat\AnimeClient\UrlGenerator;
use Aviat\AnimeClient\Model\Anime as AnimeModel;
use Aviat\AnimeClient\Model\AnimeCollection as AnimeCollectionModel;
@ -28,6 +29,12 @@ class Collection extends BaseController {
*/
protected $base_data;
/**
* Url Generator class
* @var UrlGenerator
*/
protected $urlGenerator;
/**
* Route mapping for main navigation
* @var array $nav_routes
@ -56,6 +63,7 @@ class Collection extends BaseController {
unset($this->nav_routes['Collection']);
}
$this->urlGenerator = $container->get('url-generator');
$this->collection_model = new AnimeCollectionModel($container);
$this->base_data = array_merge($this->base_data, [
'message' => '',
@ -93,7 +101,7 @@ class Collection extends BaseController {
$data = $this->collection_model->get_collection();
$this->outputHTML('collection/' . $view_map[$view], [
'title' => WHOSE . " Anime Collection",
'title' => $this->config->whose_list . "'s Anime Collection",
'sections' => $data,
'genres' => $this->collection_model->get_genre_list()
]);
@ -111,8 +119,8 @@ class Collection extends BaseController {
$this->outputHTML('collection/'. strtolower($action), [
'action' => $action,
'action_url' => $this->config->full_url("collection/" . strtolower($action)),
'title' => WHOSE . " Anime Collection &middot; {$action}",
'action_url' => $this->urlGenerator->full_url("collection/" . strtolower($action)),
'title' => $this->config->whose_list . " Anime Collection &middot; {$action}",
'media_items' => $this->collection_model->get_media_type_list(),
'item' => ($action === "Edit") ? $this->collection_model->get($id) : []
]);

View File

@ -72,19 +72,29 @@ class Manga extends Controller {
* Get a section of the manga list
*
* @param string $status
* @param string $title
* @param string $view
* @return void
*/
public function manga_list($status, $title, $view)
public function manga_list($status, $view)
{
$map = [
'all' => 'All',
'plan_to_read' => 'Plan to Read',
'reading' => 'Reading',
'completed' => 'Completed',
'dropped' => 'Dropped',
'on_hold' => 'On Hold'
];
$title = $this->config->whose_list . "' Manga List &middot; {$map[$status]}";
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($status !== 'all')
? [$status => $this->model->get_list($status)]
? [$map[$status] => $this->model->get_list($map[$status])]
: $this->model->get_all_lists();
$this->outputHTML('manga/' . $view_map[$view], [

View File

@ -25,6 +25,8 @@ class Model {
/**
* Constructor
*
* @param Container $container
*/
public function __construct(Container $container)
{

View File

@ -33,6 +33,8 @@ class API extends \Aviat\AnimeClient\Model {
/**
* Constructor
*
* @param Container $container
*/
public function __construct(Container $container)
{

View File

@ -28,6 +28,8 @@ class AnimeCollection extends DB {
/**
* Constructor
*
* @param Container $container
*/
public function __construct(Container $container)
{

View File

@ -24,6 +24,8 @@ class DB extends \Aviat\AnimeClient\Model {
/**
* Constructor
*
* @param Container $container
*/
public function __construct(Container $container)
{

View File

@ -15,7 +15,7 @@ class Stats extends DB {
/**
* Constructor
*
* @param Config $config
* @param Container $container
*/
public function __construct(Container $container)
{

View File

@ -24,12 +24,6 @@ class Router extends RoutingBase {
*/
protected $request;
/**
* Array containing request and response objects
* @var array $web
*/
protected $web;
/**
* Routes added to router
* @var array $output_routes
@ -46,7 +40,6 @@ class Router extends RoutingBase {
parent::__construct($container);
$this->router = $container->get('aura-router');
$this->request = $container->get('request');
$this->web = [$this->request, $container->get('response')];
$this->output_routes = $this->_setup_routes();
}
@ -154,6 +147,35 @@ class Router extends RoutingBase {
return $controller;
}
/**
* Get the list of controllers in the default namespace
*
* @return array
*/
public function get_controller_list()
{
$convention_routing = $this->routes['convention'];
$default_namespace = $convention_routing['default_namespace'];
$path = str_replace('\\', '/', $default_namespace);
$path = trim($path, '/');
$actual_path = \_dir(SRC_DIR, $path);
$class_files = glob("{$actual_path}/*.php");
$controllers = [];
foreach($class_files as $file)
{
$raw_class_name = basename(str_replace(".php", "", $file));
$path = strtolower(basename($raw_class_name));
$class_name = trim($default_namespace . '\\' . $raw_class_name, '\\');
$controllers[$path] = $class_name;
}
return $controllers;
}
/**
* Select controller based on the current url, and apply its relevent routes
*
@ -176,7 +198,8 @@ class Router extends RoutingBase {
$path = $route['path'];
unset($route['path']);
$controller_class = '\\Aviat\\AnimeClient\\Controller\\' . ucfirst($route_type);
$controller_map = $this->get_controller_list();
$controller_class = $controller_map[$route_type];
// Prepend the controller to the route parameters
array_unshift($route['action'], $controller_class);

View File

@ -8,12 +8,16 @@ namespace Aviat\Ion\Base;
class Container {
/**
* Array with class instances
*
* @var array
*/
protected $container = [];
/**
* Constructor
*
* @param array $values (optional)
*/
public function __construct(array $values = [])
{

View File

@ -1,72 +0,0 @@
<?php
namespace Aviat\Ion\Base;
use Aura\Web\Request;
use Aura\Web\Response;
class Page {
/**
* @var Request
*/
protected $request;
/**
* @var Response
*/
protected $response;
/**
* __construct function.
*
* @param Request $request
* @param Response $response
*/
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
/**
* __destruct function.
*/
public function __destruct()
{
$this->output();
}
/**
* Output the response to the client
*/
protected function output()
{
// send status
@header($this->response->status->get(), true, $this->response->status->getCode());
// headers
foreach($this->response->headers->get() as $label => $value)
{
@header("{$label}: {$value}");
}
// cookies
foreach($this->response->cookies->get() as $name => $cookie)
{
@setcookie(
$name,
$cookie['value'],
$cookie['expire'],
$cookie['path'],
$cookie['domain'],
$cookie['secure'],
$cookie['httponly']
);
}
// send the actual response
echo $this->response->content->get();
}
}
// End of Page.php

View File

@ -58,6 +58,9 @@ class RouterTest extends AnimeClient_TestCase {
{
$default_config = array(
'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller'
],
'common' => [
'login_form' => [
'path' => '/login',
@ -171,6 +174,9 @@ class RouterTest extends AnimeClient_TestCase {
'default_list' => 'manga'
],
'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller'
],
'common' => [
'login_form' => [
'path' => '/login',
@ -207,4 +213,58 @@ class RouterTest extends AnimeClient_TestCase {
$this->assertEquals('//localhost/anime/watching', $this->urlGenerator->default_url('anime'), "Incorrect default url");
$this->assertEquals('', $this->urlGenerator->default_url('foo'), "Incorrect default url");
}
public function dataGetControllerList()
{
return array(
'controller_list_sanity_check' => [
'config' => [
'routing' => [
'anime_path' => 'anime',
'manga_path' => 'manga',
'default_anime_path' => "/anime/watching",
'default_manga_path' => '/manga/all',
'default_list' => 'manga'
],
'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller'
]
]
],
'expected' => [
'anime' => 'Aviat\AnimeClient\Controller\Anime',
'manga' => 'Aviat\AnimeClient\Controller\Manga',
'collection' => 'Aviat\AnimeClient\Controller\Collection',
'stats' => 'Aviat\AnimeClient\Controller\Stats'
]
],
'empty_controller_list' => [
'config' => [
'routing' => [
'anime_path' => 'anime',
'manga_path' => 'manga',
'default_anime_path' => "/anime/watching",
'default_manga_path' => '/manga/all',
'default_list' => 'manga'
],
'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\Ion\\Controller'
]
]
],
'expected' => []
]
);
}
/**
* @dataProvider dataGetControllerList
*/
public function testGetControllerList($config, $expected)
{
$this->_set_up($config, '/', 'localhost');
$this->assertEquals($expected, $this->router->get_controller_list());
}
}