Full Anilist settings page OAuth flow, ability to run app without manually editing config files. See #7. Resolves #5
This commit is contained in:
parent
7a2bb1ba05
commit
fec30b7e36
@ -30,6 +30,14 @@ return array_merge($tomlConfig, [
|
||||
'asset_dir' => "{$ROOT_DIR}/public",
|
||||
'base_config_dir' => __DIR__,
|
||||
'config_dir' => "{$APP_DIR}/config",
|
||||
|
||||
// No config defaults
|
||||
'kitsu_username' => 'timw4mail',
|
||||
'whose_list' => 'Someone',
|
||||
'cache' => [
|
||||
'connection' => [],
|
||||
'driver' => 'null',
|
||||
],
|
||||
|
||||
// Routing defaults
|
||||
'asset_path' => '/public',
|
||||
|
@ -187,11 +187,13 @@ return [
|
||||
'path' => '/anilist-redirect',
|
||||
'action' => 'anilistRedirect',
|
||||
'controller' => DEFAULT_CONTROLLER,
|
||||
'verb' => 'get',
|
||||
],
|
||||
'anilist-oauth' => [
|
||||
'anilist-callback' => [
|
||||
'path' => '/anilist-oauth',
|
||||
'action' => 'anilistCallback',
|
||||
'controller' => DEFAULT_CONTROLLER,
|
||||
'verb' => 'get',
|
||||
],
|
||||
'image_proxy' => [
|
||||
'path' => '/public/images/{type}/{file}',
|
||||
|
@ -3,14 +3,6 @@
|
||||
// $fields
|
||||
// $hiddenFields
|
||||
// $nestedPrefix
|
||||
if ( ! function_exists('subfieldRender'))
|
||||
{
|
||||
function subfieldRender ($nestedPrefix, $fields, &$hiddenFields, $helper, $section)
|
||||
{
|
||||
include '_form.php';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<?php foreach ($fields as $name => $field): ?>
|
||||
@ -18,7 +10,7 @@ if ( ! function_exists('subfieldRender'))
|
||||
<?php if ($field['type'] === 'subfield'): ?>
|
||||
<section>
|
||||
<h4><?= $field['title'] ?></h4>
|
||||
<?php subfieldRender($fieldname, $field['fields'], $hiddenFields, $helper, $section); ?>
|
||||
<?php include_once '_form.php'; ?>
|
||||
</section>
|
||||
<?php elseif ( ! empty($field['display'])): ?>
|
||||
<article>
|
||||
|
@ -43,7 +43,7 @@ $hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE;
|
||||
[<?= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
|
||||
[<?= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
|
||||
<?php endif ?>
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
<?php if ($auth->isAuthenticated() && $config->get(['cache', 'driver']) !== 'null'): ?>
|
||||
<span class="flex-no-wrap small-font">
|
||||
<button type="button" class="js-clear-cache user-btn">Clear API Cache</button>
|
||||
</span>
|
||||
|
@ -29,13 +29,34 @@ $nestedPrefix = 'config';
|
||||
<label for="settings-tab<?= $i ?>"><h3><?= $sectionMapping[$section] ?></h3></label>
|
||||
<section class="content">
|
||||
<?php require __DIR__ . '/_form.php' ?>
|
||||
<?php if ($section === 'anilist'): ?>
|
||||
<hr />
|
||||
<?php $auth = $anilistModel->checkAuth(); ?>
|
||||
<?php if (array_key_exists('errors', $auth)): ?>
|
||||
<p class="static-message error">Not Authorized.</p>
|
||||
<?= $helper->a(
|
||||
$url->generate('anilist-redirect'),
|
||||
'Link Anilist Account'
|
||||
) ?>
|
||||
<?php else: ?>
|
||||
<?php $expires = $config->get(['anilist', 'access_token_expires']); ?>
|
||||
<p class="static-message info">
|
||||
Linked to Anilist. Your access token will expire around <?= date('F j, Y, g:i a T', $expires) ?>
|
||||
</p>
|
||||
<?= $helper->a(
|
||||
$url->generate('anilist-redirect'),
|
||||
'Update Access Token'
|
||||
) ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php endif ?>
|
||||
</section>
|
||||
<?php $i++; ?>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<br />
|
||||
<?php foreach ($hiddenFields as $field): ?>
|
||||
<?= $field ?>
|
||||
<?= $field->__toString() ?>
|
||||
<?php endforeach ?>
|
||||
<button type="submit">Save Changes</button>
|
||||
</main>
|
||||
|
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
@ -310,7 +310,7 @@ a:hover, a:active {
|
||||
Message boxes
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
.message {
|
||||
.message, .static-message {
|
||||
position: relative;
|
||||
margin: 0.5em auto;
|
||||
padding: 0.5em;
|
||||
@ -342,7 +342,7 @@ a:hover, a:active {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
.message.error, .static-message.error {
|
||||
border: 1px solid #924949;
|
||||
background: #f3e6e6;
|
||||
}
|
||||
@ -351,7 +351,7 @@ a:hover, a:active {
|
||||
content: '✘';
|
||||
}
|
||||
|
||||
.message.success {
|
||||
.message.success, .static-message.success {
|
||||
border: 1px solid #1f8454;
|
||||
background: #70dda9;
|
||||
}
|
||||
@ -360,7 +360,7 @@ a:hover, a:active {
|
||||
content: '✔'
|
||||
}
|
||||
|
||||
.message.info {
|
||||
.message.info, .static-message.info {
|
||||
border: 1px solid #bfbe3a;
|
||||
background: #FFFFCC;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ use Aviat\AnimeClient\API\Enum\{
|
||||
*/
|
||||
final class Anilist {
|
||||
public const AUTH_URL = 'https://anilist.co/api/v2/oauth/authorize';
|
||||
public const TOKEN_URL = 'https://anilist.co/api/v2/oauth/token';
|
||||
public const BASE_URL = 'https://graphql.anilist.co';
|
||||
|
||||
public const KITSU_ANILIST_WATCHING_STATUS_MAP = [
|
||||
|
@ -16,15 +16,17 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Anilist;
|
||||
|
||||
use const Aviat\AnimeClient\USER_AGENT;
|
||||
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
use Amp\Artax\Request;
|
||||
use Amp\Artax\Response;
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
use Aviat\AnimeClient\API\{
|
||||
Anilist,
|
||||
HummingbirdClient
|
||||
};
|
||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
||||
use Aviat\Ion\Json;
|
||||
use Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
@ -52,7 +54,7 @@ trait AnilistTrait {
|
||||
'Accept' => 'application/json',
|
||||
'Accept-Encoding' => 'gzip',
|
||||
'Content-type' => 'application/json',
|
||||
'User-Agent' => "Tim's Anime Client/4.0"
|
||||
'User-Agent' => USER_AGENT,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -80,13 +82,10 @@ trait AnilistTrait {
|
||||
$anilistConfig = $config->get('anilist');
|
||||
|
||||
$request = $this->requestBuilder->newRequest('POST', $url);
|
||||
$sessionSegment = $this->getContainer()
|
||||
->get('session')
|
||||
->getSegment(SESSION_SEGMENT);
|
||||
|
||||
//$authenticated = $sessionSegment->get('auth_token') !== NULL;
|
||||
|
||||
//if ($authenticated)
|
||||
// You can only authenticate the request if you
|
||||
// actually have an access_token saved
|
||||
if ($config->has(['anilist', 'access_token']))
|
||||
{
|
||||
$request = $request->setAuth('bearer', $anilistConfig['access_token']);
|
||||
}
|
||||
@ -245,7 +244,7 @@ trait AnilistTrait {
|
||||
{
|
||||
$response = $this->getResponse(Anilist::BASE_URL, $options);
|
||||
$validResponseCodes = [200, 201];
|
||||
|
||||
|
||||
$logger = NULL;
|
||||
if ($this->getContainer())
|
||||
{
|
||||
|
6
src/API/Anilist/GraphQL/Queries/CheckLogin.graphql
Normal file
6
src/API/Anilist/GraphQL/Queries/CheckLogin.graphql
Normal file
@ -0,0 +1,6 @@
|
||||
query {
|
||||
Viewer {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
@ -16,11 +16,15 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Anilist;
|
||||
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
use Amp\Artax\Request;
|
||||
use Aviat\AnimeClient\API\Anilist;
|
||||
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
use Aviat\Ion\Json;
|
||||
|
||||
/**
|
||||
* Anilist API Model
|
||||
@ -47,6 +51,42 @@ final class Model
|
||||
// ! Generic API calls
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Attempt to get an auth token
|
||||
*
|
||||
* @param string $code - The request token
|
||||
* @param string $redirectUri - The oauth callback url
|
||||
* @return array
|
||||
*/
|
||||
public function authenticate(string $code, string $redirectUri): array
|
||||
{
|
||||
$config = $this->getContainer()->get('config');
|
||||
$request = $this->requestBuilder
|
||||
->newRequest('POST', Anilist::TOKEN_URL)
|
||||
->setJsonBody([
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $config->get(['anilist', 'client_id']),
|
||||
'client_secret' => $config->get(['anilist', 'client_secret']),
|
||||
'redirect_uri' => $redirectUri,
|
||||
'code' => $code,
|
||||
])
|
||||
->getFullRequest();
|
||||
|
||||
$response = $this->getResponseFromRequest($request);
|
||||
|
||||
return Json::decode(wait($response->getBody()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check auth status with simple API call
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkAuth(): array
|
||||
{
|
||||
return $this->runQuery('CheckLogin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user list data for syncing with Kitsu
|
||||
*
|
||||
|
@ -170,9 +170,11 @@ final class Auth {
|
||||
*/
|
||||
public function get_auth_token()
|
||||
{
|
||||
$now = time();
|
||||
|
||||
$token = $this->segment->get('auth_token', FALSE);
|
||||
$refreshToken = $this->segment->get('refresh_token', FALSE);
|
||||
$isExpired = time() >= $this->segment->get('auth_token_expires', 0);
|
||||
$isExpired = time() > $this->segment->get('auth_token_expires', $now + 5000);
|
||||
|
||||
// Attempt to re-authenticate with refresh token
|
||||
/* if ($isExpired && $refreshToken)
|
||||
|
0
src/API/Kitsu/GraphQL/Mutations/.gitkeep
Normal file
0
src/API/Kitsu/GraphQL/Mutations/.gitkeep
Normal file
0
src/API/Kitsu/GraphQL/Queries/.gitkeep
Normal file
0
src/API/Kitsu/GraphQL/Queries/.gitkeep
Normal file
@ -56,26 +56,6 @@ function loadToml(string $path): array
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from toml files, keyed by the original file
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
function loadTomlByFile(string $path): array
|
||||
{
|
||||
$output = [];
|
||||
$files = glob("{$path}/*.toml");
|
||||
|
||||
foreach ($files as $file)
|
||||
{
|
||||
$config = Toml::parseFile($file);
|
||||
$output[basename($file)] = $config;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load config from one specific TOML file
|
||||
*
|
||||
@ -179,6 +159,7 @@ function checkFolderPermissions(ConfigInterface $config): array
|
||||
$publicDir = $config->get('asset_dir');
|
||||
|
||||
$pathMap = [
|
||||
'app/config' => realpath(__DIR__ . '/../app/config'),
|
||||
'app/logs' => realpath(__DIR__ . '/../app/logs'),
|
||||
'public/images/avatars' => "{$publicDir}/images/avatars",
|
||||
'public/images/anime' => "{$publicDir}/images/anime",
|
||||
|
@ -70,12 +70,18 @@ class BaseCommand extends Command {
|
||||
$APP_DIR = realpath(__DIR__ . '/../../app');
|
||||
$APPCONF_DIR = realpath("{$APP_DIR}/appConf/");
|
||||
$CONF_DIR = realpath("{$APP_DIR}/config/");
|
||||
$base_config = require $APPCONF_DIR . '/base_config.php';
|
||||
$baseConfig = require $APPCONF_DIR . '/base_config.php';
|
||||
|
||||
$config = loadToml($CONF_DIR);
|
||||
$config_array = array_merge($base_config, $config);
|
||||
|
||||
$di = function ($config_array) use ($APP_DIR) {
|
||||
$overrideFile = $CONF_DIR . '/admin-override.toml';
|
||||
$overrideConfig = file_exists($overrideFile)
|
||||
? loadTomlFile($overrideFile)
|
||||
: [];
|
||||
|
||||
$configArray = array_replace_recursive($baseConfig, $config, $overrideConfig);
|
||||
|
||||
$di = function ($configArray) use ($APP_DIR) {
|
||||
$container = new Container();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -93,8 +99,8 @@ class BaseCommand extends Command {
|
||||
$container->setLogger($kitsu_request_logger, 'kitsu-request');
|
||||
|
||||
// Create Config Object
|
||||
$container->set('config', function() use ($config_array) {
|
||||
return new Config($config_array);
|
||||
$container->set('config', function() use ($configArray) {
|
||||
return new Config($configArray);
|
||||
});
|
||||
|
||||
// Create Cache Object
|
||||
@ -148,6 +154,6 @@ class BaseCommand extends Command {
|
||||
return $container;
|
||||
};
|
||||
|
||||
return $di($config_array);
|
||||
return $di($configArray);
|
||||
}
|
||||
}
|
@ -60,6 +60,16 @@ final class SyncLists extends BaseCommand {
|
||||
{
|
||||
$this->setContainer($this->setupContainer());
|
||||
$this->setCache($this->container->get('cache'));
|
||||
|
||||
$config = $this->container->get('config');
|
||||
$anilistEnabled = $config->get(['anilist', 'enabled']);
|
||||
|
||||
if ( ! $anilistEnabled)
|
||||
{
|
||||
$this->echoBox('Anlist API is not enabled. Can not sync.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->anilistModel = $this->container->get('anilist-model');
|
||||
$this->kitsuModel = $this->container->get('kitsu-model');
|
||||
|
||||
|
@ -27,12 +27,21 @@ use Aviat\Ion\View\HtmlView;
|
||||
* Controller for handling routes that don't fit elsewhere
|
||||
*/
|
||||
final class Index extends BaseController {
|
||||
/**
|
||||
* @var \Aviat\API\Anilist\Model
|
||||
*/
|
||||
private $anilistModel;
|
||||
|
||||
/**
|
||||
* @var \Aviat\AnimeClient\Model\Settings
|
||||
*/
|
||||
private $settingsModel;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
$this->anilistModel = $container->get('anilist-model');
|
||||
$this->settingsModel = $container->get('settings-model');
|
||||
}
|
||||
|
||||
@ -83,10 +92,11 @@ final class Index extends BaseController {
|
||||
$redirectUrl = 'https://anilist.co/api/v2/oauth/authorize?' .
|
||||
http_build_query([
|
||||
'client_id' => $this->config->get(['anilist', 'client_id']),
|
||||
'redirect_uri' => $this->urlGenerator->url('/anilist-oauth'),
|
||||
'response_type' => 'code',
|
||||
]);
|
||||
|
||||
$this->redirect($redirectUrl, 301);
|
||||
$this->redirect($redirectUrl, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,10 +104,47 @@ final class Index extends BaseController {
|
||||
*/
|
||||
public function anilistCallback()
|
||||
{
|
||||
dump($_GET);
|
||||
$this->outputHTML('blank', [
|
||||
'title' => 'Oauth!'
|
||||
]);
|
||||
$query = $this->request->getQueryParams();
|
||||
$authCode = $query['code'];
|
||||
$uri = $this->urlGenerator->url('/anilist-oauth');
|
||||
|
||||
$authData = $this->anilistModel->authenticate($authCode, $uri);
|
||||
$settings = $this->settingsModel->getSettings();
|
||||
|
||||
if (array_key_exists('error', $authData))
|
||||
{
|
||||
$this->errorPage(400, 'Error Linking Account', $authData['hint']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the override config file
|
||||
$anilistSettings = [
|
||||
'access_token' => $authData['access_token'],
|
||||
'access_token_expires' => (time() - 10) + $authData['expires_in'],
|
||||
'refresh_token' => $authData['refresh_token'],
|
||||
];
|
||||
|
||||
$newSettings = $settings;
|
||||
$newSettings['anilist'] = array_merge($settings['anilist'], $anilistSettings);
|
||||
|
||||
foreach($newSettings['config'] as $key => $value)
|
||||
{
|
||||
$newSettings[$key] = $value;
|
||||
}
|
||||
unset($newSettings['config']);
|
||||
|
||||
$saved = $this->settingsModel->saveSettingsFile($newSettings);
|
||||
|
||||
if ($saved)
|
||||
{
|
||||
$this->setFlashMessage('Linked Anilist Account', 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setFlashMessage('Error Linking Anilist Account', 'error');
|
||||
}
|
||||
|
||||
$this->redirect($this->url->generate('settings'), 303);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,11 +212,13 @@ final class Index extends BaseController {
|
||||
$auth = $this->container->get('auth');
|
||||
$form = $this->settingsModel->getSettingsForm();
|
||||
|
||||
// dump($this->session->getFlash('message'));
|
||||
$hasAnilistLogin = $this->config->has(['anilist','access_token']);
|
||||
|
||||
$this->outputHTML('settings', [
|
||||
'anilistModel' => $this->anilistModel,
|
||||
'auth' => $auth,
|
||||
'form' => $form,
|
||||
'hasAnilistLogin' => $hasAnilistLogin,
|
||||
'config' => $this->config,
|
||||
'title' => $this->config->get('whose_list') . "'s Settings",
|
||||
]);
|
||||
@ -183,6 +232,7 @@ final class Index extends BaseController {
|
||||
public function settings_post()
|
||||
{
|
||||
$post = $this->request->getParsedBody();
|
||||
unset($post['settings-tabs']);
|
||||
|
||||
// dump($post);
|
||||
$saved = $this->settingsModel->saveSettingsFile($post);
|
||||
|
@ -79,11 +79,6 @@ final class FormGenerator {
|
||||
switch($type)
|
||||
{
|
||||
case 'boolean':
|
||||
/* $params['type'] = 'checkbox';
|
||||
$params['attribs']['label'] = $form['description'];
|
||||
$params['attribs']['value'] = TRUE;
|
||||
$params['attribs']['value_unchecked'] = '0'; */
|
||||
|
||||
$params['type'] = 'radio';
|
||||
$params['options'] = [
|
||||
'1' => 'Yes',
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use const Aviat\AnimeClient\SETTINGS_MAP;
|
||||
|
||||
use function Aviat\AnimeClient\arrayToToml;
|
||||
use function Aviat\Ion\_dir;
|
||||
|
||||
@ -34,177 +36,6 @@ final class Settings {
|
||||
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Map the config values to types and form fields
|
||||
*/
|
||||
private const SETTINGS_MAP = [
|
||||
'anilist' => [
|
||||
'enabled' => [
|
||||
'type' => 'boolean',
|
||||
'title' => 'Enable Anilist Integration',
|
||||
'default' => FALSE,
|
||||
'description' => 'Enable syncing data between Kitsu and Anilist. Requires appropriate API keys to be set in config',
|
||||
],
|
||||
'client_id' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Anilist API Client ID',
|
||||
'default' => '',
|
||||
'description' => 'The client id for your Anilist API application',
|
||||
],
|
||||
'client_secret' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Anilist API Client Secret',
|
||||
'default' => '',
|
||||
'description' => 'The client secret for your Anilist API application',
|
||||
],
|
||||
],
|
||||
'config' => [
|
||||
'kitsu_username' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Kitsu Username',
|
||||
'default' => '',
|
||||
'description' => 'Username of the account to pull list data from.',
|
||||
],
|
||||
'whose_list' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Whose List',
|
||||
'default' => 'Somebody',
|
||||
'description' => 'Name of the owner of the list data.',
|
||||
],
|
||||
'show_anime_collection' => [
|
||||
'type' => 'boolean',
|
||||
'title' => 'Show Anime Collection',
|
||||
'default' => FALSE,
|
||||
'description' => 'Should the anime collection be shown?',
|
||||
],
|
||||
'show_manga_collection' => [
|
||||
'type' => 'boolean',
|
||||
'title' => 'Show Manga Collection',
|
||||
'default' => FALSE,
|
||||
'description' => 'Should the manga collection be shown?',
|
||||
],
|
||||
'default_list' => [
|
||||
'type' => 'select',
|
||||
'title' => 'Default List',
|
||||
'description' => 'Which list to show by default.',
|
||||
'options' => [
|
||||
'Anime' => 'anime',
|
||||
'Manga' => 'manga',
|
||||
],
|
||||
],
|
||||
'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all
|
||||
'type' => 'select',
|
||||
'title' => 'Default Anime List Section',
|
||||
'description' => 'Which part of the anime list to show by default.',
|
||||
'options' => [
|
||||
'Watching' => 'watching',
|
||||
'Plan to Watch' => 'plan_to_watch',
|
||||
'On Hold' => 'on_hold',
|
||||
'Dropped' => 'dropped',
|
||||
'Completed' => 'completed',
|
||||
'All' => 'all',
|
||||
]
|
||||
],
|
||||
'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all
|
||||
'type' => 'select',
|
||||
'title' => 'Default Manga List Section',
|
||||
'description' => 'Which part of the manga list to show by default.',
|
||||
'options' => [
|
||||
'Reading' => 'reading',
|
||||
'Plan to Read' => 'plan_to_read',
|
||||
'On Hold' => 'on_hold',
|
||||
'Dropped' => 'dropped',
|
||||
'Completed' => 'completed',
|
||||
'All' => 'all',
|
||||
]
|
||||
]
|
||||
],
|
||||
'cache' => [
|
||||
'driver' => [
|
||||
'type' => 'select',
|
||||
'title' => 'Cache Type',
|
||||
'description' => 'The Cache backend',
|
||||
'options' => [
|
||||
'APCu' => 'apcu',
|
||||
'Memcached' => 'memcached',
|
||||
'Redis' => 'redis',
|
||||
'No Cache' => 'null'
|
||||
],
|
||||
],
|
||||
'connection' => [
|
||||
'type' => 'subfield',
|
||||
'title' => 'Connection',
|
||||
'fields' => [
|
||||
'host' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Host',
|
||||
'description' => 'Host of the cache backend to connect to',
|
||||
],
|
||||
'port' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Port',
|
||||
'description' => 'Port of the cache backend to connect to',
|
||||
'default' => NULL,
|
||||
],
|
||||
'password' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Password',
|
||||
'description' => 'Password to connect to cache backend',
|
||||
'default' => NULL,
|
||||
],
|
||||
'database' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Database',
|
||||
'description' => 'Cache database number for Redis',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'database' => [
|
||||
'type' => [
|
||||
'type' => 'select',
|
||||
'title' => 'Database Type',
|
||||
'options' => [
|
||||
'MySQL' => 'mysql',
|
||||
'PostgreSQL' => 'pgsql',
|
||||
'SQLite' => 'sqlite',
|
||||
],
|
||||
'description' => 'Type of database to connect to',
|
||||
],
|
||||
'host' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Host',
|
||||
'description' => 'The host of the database server',
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'string',
|
||||
'title' => 'User',
|
||||
'description' => 'Database connection user',
|
||||
],
|
||||
'pass' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Password',
|
||||
'description' => 'Database connection password'
|
||||
],
|
||||
'port' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Port',
|
||||
'description' => 'Database connection port',
|
||||
'default' => NULL,
|
||||
],
|
||||
'database' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Database Name',
|
||||
'description' => 'Name of the database/schema to connect to',
|
||||
],
|
||||
'file' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Database File',
|
||||
'description' => 'Path to the database file, if required by the current database type.'
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(ConfigInterface $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
@ -216,7 +47,7 @@ final class Settings {
|
||||
'config' => [],
|
||||
];
|
||||
|
||||
foreach(static::SETTINGS_MAP as $file => $values)
|
||||
foreach(SETTINGS_MAP as $file => $values)
|
||||
{
|
||||
if ($file === 'config')
|
||||
{
|
||||
@ -243,7 +74,9 @@ final class Settings {
|
||||
|
||||
foreach($settings as $file => $values)
|
||||
{
|
||||
foreach(static::SETTINGS_MAP[$file] as $key => $value)
|
||||
$values = $values ?? [];
|
||||
|
||||
foreach(SETTINGS_MAP[$file] as $key => $value)
|
||||
{
|
||||
if ($value['type'] === 'subfield')
|
||||
{
|
||||
@ -317,7 +150,7 @@ final class Settings {
|
||||
$looseConfig[$key] = $val;
|
||||
}
|
||||
}
|
||||
elseif (is_array($val))
|
||||
elseif (is_array($val) && ! empty($val))
|
||||
{
|
||||
foreach($val as $k => $v)
|
||||
{
|
||||
@ -357,7 +190,11 @@ final class Settings {
|
||||
|
||||
public function saveSettingsFile(array $settings): bool
|
||||
{
|
||||
$settings = $settings['config'];
|
||||
$configWrapped = (count(array_keys($settings)) === 1 && array_key_exists('config', $settings));
|
||||
if ($configWrapped)
|
||||
{
|
||||
$settings = $settings['config'];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@ -365,6 +202,9 @@ final class Settings {
|
||||
}
|
||||
catch (UndefinedPropertyException $e)
|
||||
{
|
||||
dump($e);
|
||||
dump($settings);
|
||||
die();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,10 @@ class Anilist extends AbstractType {
|
||||
|
||||
public $client_id;
|
||||
public $client_secret;
|
||||
public $redirect_uri;
|
||||
|
||||
public $access_token;
|
||||
public $access_token_expires;
|
||||
public $refresh_token;
|
||||
|
||||
public $user_id;
|
||||
public $username;
|
||||
}
|
@ -88,7 +88,9 @@ class UrlGenerator extends RoutingBase {
|
||||
}
|
||||
$path = implode('/', $path_segments);
|
||||
|
||||
return "//{$this->host}/{$path}";
|
||||
$scheme = ($_SERVER['HTTPS'] === 'on') ? 'https:' : '';
|
||||
|
||||
return "{$scheme}//{$this->host}/{$path}";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,4 +27,218 @@ const SRC_DIR = __DIR__;
|
||||
const USER_AGENT = "Tim's Anime Client/4.0";
|
||||
|
||||
// Why doesn't this already exist?
|
||||
const MILLI_FROM_NANO = 1000 * 1000;
|
||||
const MILLI_FROM_NANO = 1000 * 1000;
|
||||
|
||||
/**
|
||||
* Map config settings to form fields
|
||||
*/
|
||||
const SETTINGS_MAP = [
|
||||
'anilist' => [
|
||||
'enabled' => [
|
||||
'type' => 'boolean',
|
||||
'title' => 'Enable Anilist Integration',
|
||||
'default' => FALSE,
|
||||
'description' => 'Enable syncing data between Kitsu and Anilist. Requires appropriate API keys to be set in config',
|
||||
],
|
||||
'client_id' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Anilist API Client ID',
|
||||
'default' => '',
|
||||
'description' => 'The client id for your Anilist API application',
|
||||
],
|
||||
'client_secret' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Anilist API Client Secret',
|
||||
'default' => '',
|
||||
'description' => 'The client secret for your Anilist API application',
|
||||
],
|
||||
'username' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Anilist Username',
|
||||
'default' => '',
|
||||
'readonly' => TRUE,
|
||||
'description' => 'Login username for Anilist account to integrate with',
|
||||
],
|
||||
'access_token' => [
|
||||
'type' => 'hidden',
|
||||
'title' => 'API Access Token',
|
||||
'default' => '',
|
||||
'description' => 'The Access code for accessing the Anilist API',
|
||||
'readonly' => TRUE,
|
||||
],
|
||||
'access_token_expires' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Expiration timestamp of the access token',
|
||||
'default' => '0',
|
||||
'description' => 'The unix timestamp of when the access token expires.',
|
||||
'readonly' => TRUE,
|
||||
],
|
||||
'refresh_token' => [
|
||||
'type' => 'string',
|
||||
'title' => 'API Refresh Token',
|
||||
'default' => '',
|
||||
'description' => 'Token to refresh the access token before it expires',
|
||||
'readonly' => TRUE,
|
||||
],
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'driver' => [
|
||||
'type' => 'select',
|
||||
'title' => 'Cache Type',
|
||||
'description' => 'The Cache backend',
|
||||
'options' => [
|
||||
'APCu' => 'apcu',
|
||||
'Memcached' => 'memcached',
|
||||
'Redis' => 'redis',
|
||||
'No Cache' => 'null'
|
||||
],
|
||||
],
|
||||
'connection' => [
|
||||
'type' => 'subfield',
|
||||
'title' => 'Connection',
|
||||
'fields' => [
|
||||
'host' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Host',
|
||||
'description' => 'Host of the cache backend to connect to',
|
||||
],
|
||||
'port' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Port',
|
||||
'description' => 'Port of the cache backend to connect to',
|
||||
'default' => NULL,
|
||||
],
|
||||
'password' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Password',
|
||||
'description' => 'Password to connect to cache backend',
|
||||
'default' => NULL,
|
||||
],
|
||||
'persistent' => [
|
||||
'type' => 'boolean',
|
||||
'title' => 'Persistent Cache Connection',
|
||||
'description' => 'Whether to have a persistent connection to the cache',
|
||||
'default' => FALSE,
|
||||
],
|
||||
'database' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Cache Database',
|
||||
'default' => '1',
|
||||
'description' => 'Cache database number for Redis',
|
||||
],
|
||||
],
|
||||
],
|
||||
/*'options' => [
|
||||
'type' => 'subfield',
|
||||
'title' => 'Options',
|
||||
'fields' => [],
|
||||
] */
|
||||
],
|
||||
'config' => [
|
||||
'kitsu_username' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Kitsu Username',
|
||||
'default' => '',
|
||||
'description' => 'Username of the account to pull list data from.',
|
||||
],
|
||||
'whose_list' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Whose List',
|
||||
'default' => 'Somebody',
|
||||
'description' => 'Name of the owner of the list data.',
|
||||
],
|
||||
'show_anime_collection' => [
|
||||
'type' => 'boolean',
|
||||
'title' => 'Show Anime Collection',
|
||||
'default' => FALSE,
|
||||
'description' => 'Should the anime collection be shown?',
|
||||
],
|
||||
'show_manga_collection' => [
|
||||
'type' => 'boolean',
|
||||
'title' => 'Show Manga Collection',
|
||||
'default' => FALSE,
|
||||
'description' => 'Should the manga collection be shown?',
|
||||
],
|
||||
'default_list' => [
|
||||
'type' => 'select',
|
||||
'title' => 'Default List',
|
||||
'description' => 'Which list to show by default.',
|
||||
'options' => [
|
||||
'Anime' => 'anime',
|
||||
'Manga' => 'manga',
|
||||
],
|
||||
],
|
||||
'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all
|
||||
'type' => 'select',
|
||||
'title' => 'Default Anime List Section',
|
||||
'description' => 'Which part of the anime list to show by default.',
|
||||
'options' => [
|
||||
'Watching' => 'watching',
|
||||
'Plan to Watch' => 'plan_to_watch',
|
||||
'On Hold' => 'on_hold',
|
||||
'Dropped' => 'dropped',
|
||||
'Completed' => 'completed',
|
||||
'All' => 'all',
|
||||
]
|
||||
],
|
||||
'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all
|
||||
'type' => 'select',
|
||||
'title' => 'Default Manga List Section',
|
||||
'description' => 'Which part of the manga list to show by default.',
|
||||
'options' => [
|
||||
'Reading' => 'reading',
|
||||
'Plan to Read' => 'plan_to_read',
|
||||
'On Hold' => 'on_hold',
|
||||
'Dropped' => 'dropped',
|
||||
'Completed' => 'completed',
|
||||
'All' => 'all',
|
||||
]
|
||||
]
|
||||
],
|
||||
'database' => [
|
||||
'type' => [
|
||||
'type' => 'select',
|
||||
'title' => 'Database Type',
|
||||
'options' => [
|
||||
'MySQL' => 'mysql',
|
||||
'PostgreSQL' => 'pgsql',
|
||||
'SQLite' => 'sqlite',
|
||||
],
|
||||
'default' => 'sqlite',
|
||||
'description' => 'Type of database to connect to',
|
||||
],
|
||||
'host' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Host',
|
||||
'description' => 'The host of the database server',
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'string',
|
||||
'title' => 'User',
|
||||
'description' => 'Database connection user',
|
||||
],
|
||||
'pass' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Password',
|
||||
'description' => 'Database connection password'
|
||||
],
|
||||
'port' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Port',
|
||||
'description' => 'Database connection port',
|
||||
'default' => NULL,
|
||||
],
|
||||
'database' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Database Name',
|
||||
'description' => 'Name of the database/schema to connect to',
|
||||
],
|
||||
'file' => [
|
||||
'type' => 'string',
|
||||
'title' => 'Database File',
|
||||
'description' => 'Path to the database file, if required by the current database type.',
|
||||
'default' => 'anime_collection.sqlite',
|
||||
],
|
||||
],
|
||||
];
|
Loading…
Reference in New Issue
Block a user