mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Prototype Miniflux dispatcher
This commit is contained in:
parent
905f8938e2
commit
c92bb12a11
3 changed files with 57 additions and 15 deletions
|
@ -43,7 +43,7 @@ class REST {
|
||||||
'miniflux' => [ // Miniflux https://miniflux.app/docs/api.html
|
'miniflux' => [ // Miniflux https://miniflux.app/docs/api.html
|
||||||
'match' => '/v1/',
|
'match' => '/v1/',
|
||||||
'strip' => '/v1',
|
'strip' => '/v1',
|
||||||
'class' => REST\Miniflux\API::class,
|
'class' => REST\Miniflux\V1::class,
|
||||||
],
|
],
|
||||||
// Other candidates:
|
// Other candidates:
|
||||||
// Microsub https://indieweb.org/Microsub
|
// Microsub https://indieweb.org/Microsub
|
||||||
|
|
|
@ -23,6 +23,8 @@ use Laminas\Diactoros\Response\JsonResponse as Response;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
|
protected const ACCEPTED_TYPES_OPML = ["text/xml", "application/xml", "text/x-opml"];
|
||||||
|
protected const ACCEPTED_TYPES_JSON = ["application/json", "text/json"];
|
||||||
protected $paths = [
|
protected $paths = [
|
||||||
'/categories' => ['GET' => "getCategories", 'POST' => "createCategory"],
|
'/categories' => ['GET' => "getCategories", 'POST' => "createCategory"],
|
||||||
'/categories/1' => ['PUT' => "updateCategory", 'DELETE' => "deleteCategory"],
|
'/categories/1' => ['PUT' => "updateCategory", 'DELETE' => "deleteCategory"],
|
||||||
|
@ -55,14 +57,46 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
if ($req->getAttribute("authenticated", false)) {
|
if ($req->getAttribute("authenticated", false)) {
|
||||||
Arsse::$user->id = $req->getAttribute("authenticatedUser");
|
Arsse::$user->id = $req->getAttribute("authenticatedUser");
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Handle X-Auth-Token authentication
|
||||||
return new EmptyResponse(401);
|
return new EmptyResponse(401);
|
||||||
}
|
}
|
||||||
// get the request path only; this is assumed to already be normalized
|
// get the request path only; this is assumed to already be normalized
|
||||||
$target = parse_url($req->getRequestTarget())['path'] ?? "";
|
$target = parse_url($req->getRequestTarget())['path'] ?? "";
|
||||||
|
$method = $req->getMethod();
|
||||||
// handle HTTP OPTIONS requests
|
// handle HTTP OPTIONS requests
|
||||||
if ($req->getMethod() === "OPTIONS") {
|
if ($method === "OPTIONS") {
|
||||||
return $this->handleHTTPOptions($target);
|
return $this->handleHTTPOptions($target);
|
||||||
}
|
}
|
||||||
|
$func = $this->chooseCall($target, $method);
|
||||||
|
if ($func === "opmlImport") {
|
||||||
|
if (!HTTP::matchType($req, "", ...[self::ACCEPTED_TYPES_OPML])) {
|
||||||
|
return new EmptyResponse(415, ['Accept' => implode(", ", self::ACCEPTED_TYPES_OPML)]);
|
||||||
|
}
|
||||||
|
$data = (string) $req->getBody();
|
||||||
|
} elseif ($method === "POST" || $method === "PUT") {
|
||||||
|
if (!HTTP::matchType($req, "", ...[self::ACCEPTED_TYPES_JSON])) {
|
||||||
|
return new EmptyResponse(415, ['Accept' => implode(", ", self::ACCEPTED_TYPES_JSON)]);
|
||||||
|
}
|
||||||
|
$data = @json_decode($data, true);
|
||||||
|
if (json_last_error() !== \JSON_ERROR_NONE) {
|
||||||
|
// if the body could not be parsed as JSON, return "400 Bad Request"
|
||||||
|
return new EmptyResponse(400);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$data = null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$path = explode("/", ltrim($target, "/"));
|
||||||
|
return $this->$func($path, $req->getQueryParams(), $data);
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// if there was a REST exception return 400
|
||||||
|
return new EmptyResponse(400);
|
||||||
|
} catch (AbstractException $e) {
|
||||||
|
// if there was any other Arsse exception return 500
|
||||||
|
return new EmptyResponse(500);
|
||||||
|
}
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function normalizePathIds(string $url): string {
|
protected function normalizePathIds(string $url): string {
|
||||||
|
@ -73,6 +107,10 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$path[$a] = "1";
|
$path[$a] = "1";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// handle special case "Get User By User Name", which can have any non-numeric string, non-empty as the last component
|
||||||
|
if (sizeof($path) === 3 && $path[0] === "" && $path[1] === "users" && !preg_match("/^(?:\d+)?$/", $path[2])) {
|
||||||
|
$path[2] = "*";
|
||||||
|
}
|
||||||
return implode("/", $path);
|
return implode("/", $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +126,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
return new EmptyResponse(204, [
|
return new EmptyResponse(204, [
|
||||||
'Allow' => implode(",", $allowed),
|
'Allow' => implode(",", $allowed),
|
||||||
'Accept' => self::ACCEPTED_TYPE,
|
'Accept' => implode(", ", $url === "/import" ? self::ACCEPTED_TYPES_OPML : self::ACCEPTED_TYPES_JSON),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
// if the path is not supported, return 404
|
// if the path is not supported, return 404
|
||||||
|
@ -106,8 +144,12 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
if (isset($this->paths[$url])) {
|
if (isset($this->paths[$url])) {
|
||||||
// if the path is supported, make sure the method is allowed
|
// if the path is supported, make sure the method is allowed
|
||||||
if (isset($this->paths[$url][$method])) {
|
if (isset($this->paths[$url][$method])) {
|
||||||
// if it is allowed, return the object method to run
|
// if it is allowed, return the object method to run, assuming the method exists
|
||||||
|
if (method_exists($this, $this->paths[$url][$method])) {
|
||||||
return $this->paths[$url][$method];
|
return $this->paths[$url][$method];
|
||||||
|
} else {
|
||||||
|
throw new Exception501(); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise return 405
|
// otherwise return 405
|
||||||
throw new Exception405(implode(", ", array_keys($this->paths[$url])));
|
throw new Exception405(implode(", ", array_keys($this->paths[$url])));
|
||||||
|
|
|
@ -17,6 +17,7 @@ use JKingWeb\Arsse\Misc\HTTP;
|
||||||
use JKingWeb\Arsse\REST\Exception;
|
use JKingWeb\Arsse\REST\Exception;
|
||||||
use JKingWeb\Arsse\REST\Exception404;
|
use JKingWeb\Arsse\REST\Exception404;
|
||||||
use JKingWeb\Arsse\REST\Exception405;
|
use JKingWeb\Arsse\REST\Exception405;
|
||||||
|
use JKingWeb\Arsse\REST\Exception501;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Laminas\Diactoros\Response\JsonResponse as Response;
|
use Laminas\Diactoros\Response\JsonResponse as Response;
|
||||||
|
@ -109,20 +110,15 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// merge GET and POST data, and normalize it. POST parameters are preferred over GET parameters
|
// merge GET and POST data, and normalize it. POST parameters are preferred over GET parameters
|
||||||
$data = $this->normalizeInput(array_merge($req->getQueryParams(), $data), $this->validInput, "unix");
|
$data = $this->normalizeInput(array_merge($req->getQueryParams(), $data), $this->validInput, "unix");
|
||||||
// check to make sure the requested function is implemented
|
// check to make sure the requested function is implemented
|
||||||
|
// dispatch
|
||||||
try {
|
try {
|
||||||
$func = $this->chooseCall($target, $req->getMethod());
|
$func = $this->chooseCall($target, $req->getMethod());
|
||||||
|
$path = explode("/", ltrim($target, "/"));
|
||||||
|
return $this->$func($path, $data);
|
||||||
} catch (Exception404 $e) {
|
} catch (Exception404 $e) {
|
||||||
return new EmptyResponse(404);
|
return new EmptyResponse(404);
|
||||||
} catch (Exception405 $e) {
|
} catch (Exception405 $e) {
|
||||||
return new EmptyResponse(405, ['Allow' => $e->getMessage()]);
|
return new EmptyResponse(405, ['Allow' => $e->getMessage()]);
|
||||||
}
|
|
||||||
if (!method_exists($this, $func)) {
|
|
||||||
return new EmptyResponse(501); // @codeCoverageIgnore
|
|
||||||
}
|
|
||||||
// dispatch
|
|
||||||
try {
|
|
||||||
$path = explode("/", ltrim($target, "/"));
|
|
||||||
return $this->$func($path, $data);
|
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// if there was a REST exception return 400
|
// if there was a REST exception return 400
|
||||||
|
@ -155,8 +151,12 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
if (isset($this->paths[$url])) {
|
if (isset($this->paths[$url])) {
|
||||||
// if the path is supported, make sure the method is allowed
|
// if the path is supported, make sure the method is allowed
|
||||||
if (isset($this->paths[$url][$method])) {
|
if (isset($this->paths[$url][$method])) {
|
||||||
// if it is allowed, return the object method to run
|
// if it is allowed, return the object method to run, assuming the method exists
|
||||||
|
if (method_exists($this, $this->paths[$url][$method])) {
|
||||||
return $this->paths[$url][$method];
|
return $this->paths[$url][$method];
|
||||||
|
} else {
|
||||||
|
throw new Exception501(); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise return 405
|
// otherwise return 405
|
||||||
throw new Exception405(implode(", ", array_keys($this->paths[$url])));
|
throw new Exception405(implode(", ", array_keys($this->paths[$url])));
|
||||||
|
|
Loading…
Reference in a new issue