mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Implemented NCN API v1-2 folder list
- Fixes #2 - Also re-organized REST handling
This commit is contained in:
parent
dc9f5e545e
commit
842e277d43
9 changed files with 184 additions and 36 deletions
|
@ -30,11 +30,14 @@ class REST {
|
|||
|
||||
function dispatch(REST\Request $req = null): bool {
|
||||
if($req===null) $req = new REST\Request();
|
||||
$api = $this->apiMatch($url, $this->apis);
|
||||
$req->url = substr($url,strlen($this->apis[$api]['strip']));
|
||||
$api = $this->apiMatch($req->url, $this->apis);
|
||||
$req->url = substr($req->url,strlen($this->apis[$api]['strip']));
|
||||
$req->refreshURL();
|
||||
$class = $this->apis[$api]['class'];
|
||||
$drv = new $class();
|
||||
$drv->dispatch($req);
|
||||
$out = $drv->dispatch($req);
|
||||
echo "Status: ".$out->code."\n";
|
||||
echo json_encode($out->payload,\JSON_PRETTY_PRINT);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,29 +5,4 @@ namespace JKingWeb\Arsse\REST;
|
|||
abstract class AbstractHandler implements Handler {
|
||||
abstract function __construct();
|
||||
abstract function dispatch(Request $req): Response;
|
||||
|
||||
protected function parseURL(string $url): array {
|
||||
// split the query string from the path
|
||||
$parts = explode("?", $url);
|
||||
$out = ['path' => $parts[0], 'query' => []];
|
||||
// if there is a query string, parse it
|
||||
if(isset($parts[1])) {
|
||||
// split along & to get key-value pairs
|
||||
$query = explode("&", $parts[1]);
|
||||
for($a = 0; $a < sizeof($query); $a++) {
|
||||
// split each pair, into no more than two parts
|
||||
$data = explode("=", $query[$a], 2);
|
||||
// decode the key
|
||||
$key = rawurldecode($data[0]);
|
||||
// decode the value if there is one
|
||||
$value = "";
|
||||
if(isset($data[1])) {
|
||||
$value = rawurldecode($data[1]);
|
||||
}
|
||||
// add the pair to the query output, overwriting earlier values for the same key, is present
|
||||
$out['query'][$key] = $value;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\REST\NextCloudNews;
|
||||
use JKingWeb\Arsse\Data;
|
||||
use JKingWeb\Arsse\REST\Response;
|
||||
|
||||
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||
|
@ -8,13 +9,31 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
}
|
||||
|
||||
function dispatch(\JKingWeb\Arsse\REST\Request $req): \JKingWeb\Arsse\REST\Response {
|
||||
// parse the URL and populate $path and $query
|
||||
extract($this->parseURL($req->url));
|
||||
if(preg_match("<^/(items|folders|feeds|cleanup|version|status|user)(?:/([^/]+))?(?:/([^/]+))?(?:/([^/]+))?/?$>", $path, $matches)) {
|
||||
$scope = $matches[1];
|
||||
var_export($scope);
|
||||
// try to authenticate
|
||||
if(!Data::$user->authHTTP()) return new Response(401, "", null, ['WWW-Authenticate: Basic realm="NextCloud News API"']);
|
||||
// only accept GET, POST, or PUT
|
||||
if(!in_array($req->method, ["GET", "POST", "PUT"])) return new Response(405);
|
||||
// match the path
|
||||
if(preg_match("<^/(items|folders|feeds|cleanup|version|status|user)(?:/([^/]+))?(?:/([^/]+))?(?:/([^/]+))?/?$>", $req->path, $match)) {
|
||||
// dispatch
|
||||
switch($match[1]) {
|
||||
case "folders":
|
||||
switch($req->method) {
|
||||
case "GET": return $this->folderList();
|
||||
case "POST": return $this->folderAdd($this->normalizeInput($req));
|
||||
case "PUT":
|
||||
list($path, $scope, $id, $action) = $match;
|
||||
return $this->folderEdit($id, $action, $this->normalizeInput($req));
|
||||
}
|
||||
default: return new Response(404);
|
||||
}
|
||||
} else {
|
||||
return new Response(404);
|
||||
}
|
||||
}
|
||||
|
||||
protected function folderList(): Response {
|
||||
$folders = Data::$db->folderList(Data::$user->id, null, false)->getAll();
|
||||
return new Response(200, ['folders' => $folders]);
|
||||
}
|
||||
}
|
|
@ -8,13 +8,11 @@ class Versions extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
}
|
||||
|
||||
function dispatch(\JKingWeb\Arsse\REST\Request $req): \JKingWeb\Arsse\REST\Response {
|
||||
// parse the URL and populate $path and $query
|
||||
extract($this->parseURL($req->url));
|
||||
// if a method other than GET was used, this is an error
|
||||
if($req->method != "GET") {
|
||||
return new Response(405);
|
||||
}
|
||||
if(preg_match("<^/?$>",$path)) {
|
||||
if(preg_match("<^/?$>",$req->path)) {
|
||||
// if the request path is an empty string or just a slash, return the supported versions
|
||||
$out = [
|
||||
'apiLevels' => [
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace JKingWeb\Arsse\REST;
|
|||
class Request {
|
||||
public $method = "GET";
|
||||
public $url = "";
|
||||
public $path ="";
|
||||
public $query = "";
|
||||
public $type ="";
|
||||
public $stream = "php://input";
|
||||
|
||||
|
@ -19,5 +21,37 @@ class Request {
|
|||
$this->url = $url;
|
||||
$this->stream = $bodyStream;
|
||||
$this->type = $contentType;
|
||||
$this->refreshURL();
|
||||
}
|
||||
|
||||
public function refreshURL() {
|
||||
$url = $this->parseURL($this->url);
|
||||
$this->path = $url['path'];
|
||||
$this->query = $url['query'];
|
||||
}
|
||||
|
||||
protected function parseURL(string $url): array {
|
||||
// split the query string from the path
|
||||
$parts = explode("?", $url);
|
||||
$out = ['path' => $parts[0], 'query' => []];
|
||||
// if there is a query string, parse it
|
||||
if(isset($parts[1])) {
|
||||
// split along & to get key-value pairs
|
||||
$query = explode("&", $parts[1]);
|
||||
for($a = 0; $a < sizeof($query); $a++) {
|
||||
// split each pair, into no more than two parts
|
||||
$data = explode("=", $query[$a], 2);
|
||||
// decode the key
|
||||
$key = rawurldecode($data[0]);
|
||||
// decode the value if there is one
|
||||
$value = "";
|
||||
if(isset($data[1])) {
|
||||
$value = rawurldecode($data[1]);
|
||||
}
|
||||
// add the pair to the query output, overwriting earlier values for the same key, is present
|
||||
$out['query'][$key] = $value;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,12 @@ return [
|
|||
|
||||
'Driver.Db.SQLite3.Name' => 'SQLite 3',
|
||||
|
||||
'HTTP.Status.200' => 'OK',
|
||||
'HTTP.Status.204' => 'No Content',
|
||||
'HTTP.Status.401' => 'Unauthorized',
|
||||
'HTTP.Status.404' => 'Not Found',
|
||||
'HTTP.Status.405' => 'Method Not Allowed',
|
||||
|
||||
// this should only be encountered in testing (because tests should cover all exceptions!)
|
||||
'Exception.JKingWeb/Arsse/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in AbstractException.php',
|
||||
// this should not usually be encountered
|
||||
|
|
39
tests/REST/NextCloudNews/TestNCNV1_2.php
Normal file
39
tests/REST/NextCloudNews/TestNCNV1_2.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse;
|
||||
use JKingWeb\Arsse\Rest\Request;
|
||||
use JKingWeb\Arsse\Rest\Response;
|
||||
use JKingWeb\Arsse\Test\Result;
|
||||
use Phake;
|
||||
|
||||
|
||||
class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
|
||||
use Test\Tools;
|
||||
|
||||
protected $h;
|
||||
|
||||
function setUp() {
|
||||
$this->clearData();
|
||||
// create a mock user manager
|
||||
Data::$user = Phake::mock(User::class);
|
||||
Phake::when(Data::$user)->authHTTP->thenReturn(true);
|
||||
Data::$user->id = "john.doe@example.com";
|
||||
// create a mock database interface
|
||||
Data::$db = Phake::mock(Database::Class);
|
||||
$this->h = new REST\NextCloudNews\V1_2();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
$this->clearData();
|
||||
}
|
||||
|
||||
function testListFolders() {
|
||||
$list = [
|
||||
['id' => 1, 'name' => "Software", 'parent' => null],
|
||||
['id' => 12, 'name' => "Hardware", 'parent' => null],
|
||||
];
|
||||
Phake::when(Data::$db)->folderList(Data::$user->id, null, false)->thenReturn(new Result($list));
|
||||
$exp = new Response(200, ['folders' => $list]);
|
||||
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/folders")));
|
||||
}
|
||||
}
|
73
tests/lib/Result.php
Normal file
73
tests/lib/Result.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Test;
|
||||
|
||||
class Result implements \JKingWeb\Arsse\Db\Result {
|
||||
protected $st;
|
||||
protected $set;
|
||||
protected $pos = 0;
|
||||
protected $cur = null;
|
||||
protected $rows = 0;
|
||||
protected $id = 0;
|
||||
|
||||
// actual public methods
|
||||
|
||||
public function getValue() {
|
||||
$arr = $this->next();
|
||||
if($this->valid()) {
|
||||
$keys = array_keys($arr);
|
||||
return $arr[array_shift($keys)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRow() {
|
||||
$arr = $this->next();
|
||||
return ($this->valid() ? $arr : null);
|
||||
}
|
||||
|
||||
public function getAll(): array {
|
||||
return $this->set;
|
||||
}
|
||||
|
||||
public function changes() {
|
||||
return $this->rows;
|
||||
}
|
||||
|
||||
public function lastId() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
// constructor/destructor
|
||||
|
||||
public function __construct(array $result, int $changes = 0, int $lastID = 0) {
|
||||
$this->set = $result;
|
||||
$this->rows = $changes;
|
||||
$this->id = $lastID;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
}
|
||||
|
||||
// PHP iterator methods
|
||||
|
||||
public function valid() {
|
||||
return !is_null(key($this->set));
|
||||
}
|
||||
|
||||
public function next() {
|
||||
return next($this->set);
|
||||
}
|
||||
|
||||
public function current() {
|
||||
return current($this->set);
|
||||
}
|
||||
|
||||
public function key() {
|
||||
return key($this->set);
|
||||
}
|
||||
|
||||
public function rewind() {
|
||||
rewind($this->set);
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@
|
|||
</testsuite>
|
||||
<testsuite name="NextCloud News API">
|
||||
<file>REST/NextCloudNews/TestNCNVersionDiscovery.php</file>
|
||||
<file>REST/NextCloudNews/TestNCNV1_2.php</file>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
Loading…
Reference in a new issue