simplify actions, move ViewHttpResponse into src/App
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
de75577f3d
commit
d20cfd2486
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\Api;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use IQBall\Core\Action;
|
||||||
|
use IQBall\Core\Connection;
|
||||||
|
use IQBall\Core\Data\Account;
|
||||||
|
use IQBall\Core\Gateway\AccountGateway;
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Http\JsonHttpResponse;
|
||||||
|
use IQBall\Core\Session\PhpSessionHandle;
|
||||||
|
use IQBall\Core\Validation\ValidationFail;
|
||||||
|
|
||||||
|
class API {
|
||||||
|
public static function render(HttpResponse $response): void {
|
||||||
|
http_response_code($response->getCode());
|
||||||
|
|
||||||
|
foreach ($response->getHeaders() as $header => $value) {
|
||||||
|
header("$header: $value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response instanceof JsonHttpResponse) {
|
||||||
|
header('Content-type: application/json');
|
||||||
|
echo $response->getJson();
|
||||||
|
} else {
|
||||||
|
throw new Exception("API returned a non-json response.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed[] $match
|
||||||
|
* @return HttpResponse
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function handleMatch(array $match): HttpResponse {
|
||||||
|
if (!$match) {
|
||||||
|
return new JsonHttpResponse([ValidationFail::notFound("not found")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $match['target'];
|
||||||
|
if (!$action instanceof Action) {
|
||||||
|
throw new Exception("routed action is not an AppAction object.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth = null;
|
||||||
|
|
||||||
|
if ($action->isAuthRequired()) {
|
||||||
|
$auth = self::tryGetAuthorization();
|
||||||
|
if ($auth == null) {
|
||||||
|
return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header.")]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $action->run($match['params'], $auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function tryGetAuthorization(): ?Account {
|
||||||
|
$headers = getallheaders();
|
||||||
|
|
||||||
|
// If no authorization header is set, try fallback to php session.
|
||||||
|
if (!isset($headers['Authorization'])) {
|
||||||
|
$session = PhpSessionHandle::init();
|
||||||
|
return $session->getAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $headers['Authorization'];
|
||||||
|
$gateway = new AccountGateway(new Connection(get_database()));
|
||||||
|
return $gateway->getAccountFromToken($token);
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api;
|
|
||||||
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Route\AbstractAction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends AbstractAction<?Account>
|
|
||||||
*/
|
|
||||||
class ApiAction extends AbstractAction {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed[] $params
|
|
||||||
* @param ?Account $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function run(array $params, $session): HttpResponse {
|
|
||||||
$params = array_values($params);
|
|
||||||
if ($this->isAuthRequired()) {
|
|
||||||
if ($session == null) {
|
|
||||||
throw new \Exception("action requires authorization.");
|
|
||||||
}
|
|
||||||
$params[] = $session;
|
|
||||||
}
|
|
||||||
|
|
||||||
return call_user_func_array($this->action, $params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[]): HttpResponse $action
|
|
||||||
* @return ApiAction an action that does not require to have an authorization.
|
|
||||||
*/
|
|
||||||
public static function noAuth(callable $action): ApiAction {
|
|
||||||
return new ApiAction($action, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[]): HttpResponse $action
|
|
||||||
* @return ApiAction an action that does require to have an authorization.
|
|
||||||
*/
|
|
||||||
public static function auth(callable $action): ApiAction {
|
|
||||||
return new ApiAction($action, true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace IQBall\Api;
|
namespace IQBall\Api\Controller;
|
||||||
|
|
||||||
use IQBall\Core\Route\Control;
|
use IQBall\Core\Route\Control;
|
||||||
use IQBall\Core\Http\HttpRequest;
|
use IQBall\Core\Http\HttpRequest;
|
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\App;
|
||||||
|
|
||||||
|
use IQBall\Core\Action;
|
||||||
|
use IQBall\Core\Http\HttpCodes;
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Http\JsonHttpResponse;
|
||||||
|
use IQBall\Core\Session\MutableSessionHandle;
|
||||||
|
use IQBall\Core\Validation\ValidationFail;
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Error\LoaderError;
|
||||||
|
use Twig\Error\RuntimeError;
|
||||||
|
use Twig\Error\SyntaxError;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
|
class App {
|
||||||
|
public static function render(HttpResponse $response): void {
|
||||||
|
http_response_code($response->getCode());
|
||||||
|
|
||||||
|
foreach ($response->getHeaders() as $header => $value) {
|
||||||
|
header("$header: $value");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response instanceof ViewHttpResponse) {
|
||||||
|
self::renderView($response);
|
||||||
|
} elseif ($response instanceof JsonHttpResponse) {
|
||||||
|
header('Content-type: application/json');
|
||||||
|
echo $response->getJson();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function renderView(ViewHttpResponse $response): void {
|
||||||
|
$file = $response->getFile();
|
||||||
|
$args = $response->getArguments();
|
||||||
|
|
||||||
|
switch ($response->getViewKind()) {
|
||||||
|
case ViewHttpResponse::REACT_VIEW:
|
||||||
|
send_react_front($file, $args);
|
||||||
|
break;
|
||||||
|
case ViewHttpResponse::TWIG_VIEW:
|
||||||
|
try {
|
||||||
|
$loader = new FilesystemLoader('../src/App/Views/');
|
||||||
|
$twig = new Environment($loader);
|
||||||
|
$twig->display($file, $args);
|
||||||
|
} catch (RuntimeError | SyntaxError | LoaderError $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s");
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Action<MutableSessionHandle> $action
|
||||||
|
* @param mixed[] $params
|
||||||
|
* @param MutableSessionHandle $session
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
private static function runAction(Action $action, array $params, MutableSessionHandle $session): HttpResponse {
|
||||||
|
global $basePath;
|
||||||
|
if ($action->isAuthRequired()) {
|
||||||
|
$account = $session->getAccount();
|
||||||
|
if ($account == null) {
|
||||||
|
// put in the session the initial url the user wanted to get
|
||||||
|
$session->setInitialTarget($_SERVER['REQUEST_URI']);
|
||||||
|
return HttpResponse::redirect($basePath . "/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $action->run($params, $session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed>|false $match
|
||||||
|
* @param MutableSessionHandle $session
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public static function runMatch($match, MutableSessionHandle $session): HttpResponse {
|
||||||
|
if (!$match) {
|
||||||
|
return ViewHttpResponse::twig("error.html.twig", [
|
||||||
|
'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")],
|
||||||
|
], HttpCodes::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::runAction($match['target'], $match['params'], $session);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App;
|
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Session\MutableSessionHandle;
|
|
||||||
use Exception;
|
|
||||||
use IQBall\Core\Route\AbstractAction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Front controller action
|
|
||||||
* @extends AbstractAction<MutableSessionHandle>
|
|
||||||
*/
|
|
||||||
class AppAction extends AbstractAction {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed[] $params
|
|
||||||
* @param MutableSessionHandle $session
|
|
||||||
* @return HttpResponse
|
|
||||||
* @throws Exception <p>
|
|
||||||
* thrown if this action is required to be authenticated, but the given session does not contain a logged-in account.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before
|
|
||||||
* running this action.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public function run(array $params, $session): HttpResponse {
|
|
||||||
$params = array_values($params);
|
|
||||||
if ($this->isAuthRequired()) {
|
|
||||||
if ($session->getAccount() == null) {
|
|
||||||
throw new Exception("action requires authorization.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$params[] = $session;
|
|
||||||
return call_user_func_array($this->action, $params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[]): HttpResponse $action
|
|
||||||
* @return AppAction an action that does not require to have an authorization.
|
|
||||||
*/
|
|
||||||
public static function noAuth(callable $action): AppAction {
|
|
||||||
return new AppAction($action, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[]): HttpResponse $action
|
|
||||||
* @return AppAction an action that does require to have an authorization.
|
|
||||||
*/
|
|
||||||
public static function auth(callable $action): AppAction {
|
|
||||||
return new AppAction($action, true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace IQBall\Core\Http;
|
namespace IQBall\App;
|
||||||
|
|
||||||
|
use IQBall\Core\Http\HttpCodes;
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
|
||||||
class ViewHttpResponse extends HttpResponse {
|
class ViewHttpResponse extends HttpResponse {
|
||||||
public const TWIG_VIEW = 0;
|
public const TWIG_VIEW = 0;
|
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\Core;
|
||||||
|
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template S session
|
||||||
|
*/
|
||||||
|
class Action {
|
||||||
|
/**
|
||||||
|
* @var callable(mixed[], S): HttpResponse $action action to call
|
||||||
|
*/
|
||||||
|
protected $action;
|
||||||
|
|
||||||
|
private bool $isAuthRequired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(mixed[], S): HttpResponse $action
|
||||||
|
*/
|
||||||
|
protected function __construct(callable $action, bool $isAuthRequired) {
|
||||||
|
$this->action = $action;
|
||||||
|
$this->isAuthRequired = $isAuthRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAuthRequired(): bool {
|
||||||
|
return $this->isAuthRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed[] $params
|
||||||
|
* @param S $session
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public function run(array $params, $session): HttpResponse {
|
||||||
|
$params = array_values($params);
|
||||||
|
$params[] = $session;
|
||||||
|
return call_user_func_array($this->action, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(mixed[], S): HttpResponse $action
|
||||||
|
* @return Action<S> an action that does not require to have an authorization.
|
||||||
|
*/
|
||||||
|
public static function noAuth(callable $action): Action {
|
||||||
|
return new Action($action, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(mixed[], S): HttpResponse $action
|
||||||
|
* @return Action<S> an action that does require to have an authorization.
|
||||||
|
*/
|
||||||
|
public static function auth(callable $action): Action {
|
||||||
|
return new Action($action, true);
|
||||||
|
}
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Route;
|
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template S session
|
|
||||||
*/
|
|
||||||
abstract class AbstractAction {
|
|
||||||
/**
|
|
||||||
* @var callable(mixed[]): HttpResponse $action action to call
|
|
||||||
*/
|
|
||||||
protected $action;
|
|
||||||
|
|
||||||
private bool $isAuthRequired;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[]): HttpResponse $action
|
|
||||||
*/
|
|
||||||
protected function __construct(callable $action, bool $isAuthRequired) {
|
|
||||||
$this->action = $action;
|
|
||||||
$this->isAuthRequired = $isAuthRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthRequired(): bool {
|
|
||||||
return $this->isAuthRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed[] $params
|
|
||||||
* @param S $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public abstract function run(array $params, $session): HttpResponse;
|
|
||||||
}
|
|
Loading…
Reference in new issue