mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-23 05:34:55 +00:00
REST functions for article listing and marking
Needs testing
This commit is contained in:
parent
761c1054f3
commit
3fad820be4
4 changed files with 342 additions and 71 deletions
|
@ -6,24 +6,24 @@ abstract class AbstractHandler implements Handler {
|
||||||
abstract function __construct();
|
abstract function __construct();
|
||||||
abstract function dispatch(Request $req): Response;
|
abstract function dispatch(Request $req): Response;
|
||||||
|
|
||||||
protected function mapFieldNames(array $data, array $map, bool $overwrite = false): array {
|
protected function fieldMapNames(array $data, array $map): array {
|
||||||
foreach($map as $from => $to) {
|
$out = [];
|
||||||
|
foreach($map as $to => $from) {
|
||||||
if(array_key_exists($from, $data)) {
|
if(array_key_exists($from, $data)) {
|
||||||
if($overwrite || !array_key_exists($to, $data)) $data[$to] = $data[$from];
|
$out[$to] = $data[$from];
|
||||||
unset($data[$from]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $data;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function mapFieldTypes(array $data, array $map): array {
|
protected function fieldMapTypes(array $data, array $map): array {
|
||||||
foreach($map as $key => $type) {
|
foreach($map as $key => $type) {
|
||||||
if(array_key_exists($key, $data)) settype($data[$key], $type);
|
if(array_key_exists($key, $data)) settype($data[$key], $type);
|
||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function validateId($id): bool {
|
protected function validateInt($id): bool {
|
||||||
try {
|
try {
|
||||||
$ch1 = strval(intval($id));
|
$ch1 = strval(intval($id));
|
||||||
$ch2 = strval($id);
|
$ch2 = strval($id);
|
||||||
|
@ -33,4 +33,47 @@ abstract class AbstractHandler implements Handler {
|
||||||
return ($ch1 === $ch2);
|
return ($ch1 === $ch2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function NormalizeInput(array $data, array $types, string $dateFormat = "Y-m-d\TH:i:sP"): array {
|
||||||
|
$out = [];
|
||||||
|
foreach($data as $key => $value) {
|
||||||
|
if(!isset($types[$key])) {
|
||||||
|
$out[$key] = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(is_null($value)) {
|
||||||
|
$out[$key] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch($types[$key]) {
|
||||||
|
case "int":
|
||||||
|
if($this->validateInt($value)) $out[$key] = (int) $value;
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
$out[$key] = (string) $value;
|
||||||
|
break;
|
||||||
|
case "bool":
|
||||||
|
if(is_bool($value)) {
|
||||||
|
$out[$key] = $value;
|
||||||
|
} else if($this->validateInt($value)) {
|
||||||
|
$value = (int) $value;
|
||||||
|
if($value > -1 && $value < 2) $out[$key] = $value;
|
||||||
|
} else if(is_string($value)) {
|
||||||
|
$value = trim(strtolower($value));
|
||||||
|
if($value=="false") $out[$key] = false;
|
||||||
|
if($value=="true") $out[$key] = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "float":
|
||||||
|
if(is_numeric($value)) $out[$key] = (float) $value;
|
||||||
|
break;
|
||||||
|
case "datetime":
|
||||||
|
$t = \DateTime::createFromFormat($dateFormat, (string) $value);
|
||||||
|
if($t) $out[$key] = $t;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("typeUnknown", $types[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,24 @@ use JKingWeb\Arsse\REST\Exception405;
|
||||||
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
const REALM = "NextCloud News API v1-2";
|
const REALM = "NextCloud News API v1-2";
|
||||||
|
|
||||||
|
protected $validInput = [
|
||||||
|
'name' => "string",
|
||||||
|
'url' => "string",
|
||||||
|
'folderId' => "int",
|
||||||
|
'feedTitle' => "string",
|
||||||
|
'userId' => "string",
|
||||||
|
'feedId' => "int",
|
||||||
|
'newestItemId' => "int",
|
||||||
|
'batchSize' => "int",
|
||||||
|
'offset' => "int",
|
||||||
|
'type' => "int",
|
||||||
|
'id' => "int",
|
||||||
|
'getRead' => "bool",
|
||||||
|
'oldestFirst' => "bool",
|
||||||
|
'lastModified' => "datetime",
|
||||||
|
// 'items' => "array int", // just pass these through
|
||||||
|
];
|
||||||
|
|
||||||
function __construct() {
|
function __construct() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +54,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$data = [];
|
$data = [];
|
||||||
}
|
}
|
||||||
// FIXME: Do query parameters take precedence in NextCloud? Is there a conflict error when values differ?
|
// FIXME: Do query parameters take precedence in NextCloud? Is there a conflict error when values differ?
|
||||||
$data = array_merge($data, $req->query);
|
$data = $this->normalizeInput($data, $this->validInput, "U");
|
||||||
|
$query = $this->normalizeInput($req->query, $this->validInput, "U");
|
||||||
|
$data = array_merge($data, $query);
|
||||||
|
unset($query);
|
||||||
// check to make sure the requested function is implemented
|
// check to make sure the requested function is implemented
|
||||||
try {
|
try {
|
||||||
$func = $this->chooseCall($req->paths, $req->method);
|
$func = $this->chooseCall($req->paths, $req->method);
|
||||||
|
@ -76,6 +97,19 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
'all' => ['GET' => "feedListStale"],
|
'all' => ['GET' => "feedListStale"],
|
||||||
'update' => ['GET' => "feedUpdate"],
|
'update' => ['GET' => "feedUpdate"],
|
||||||
],
|
],
|
||||||
|
'items' => [
|
||||||
|
'' => ['GET' => "articleList"],
|
||||||
|
'updated' => ['GET' => "articleList"],
|
||||||
|
'read' => ['PUT' => "articleMarkReadAll"],
|
||||||
|
'0/read' => ['PUT' => "articleMarkRead"],
|
||||||
|
'0/unread' => ['PUT' => "articleMarkRead"],
|
||||||
|
'read/multiple' => ['PUT' => "articleMarkReadMulti"],
|
||||||
|
'unread/multiple' => ['PUT' => "articleMarkReadMulti"],
|
||||||
|
'0/0/star' => ['PUT' => "articleMarkStarred"],
|
||||||
|
'0/0/unstar' => ['PUT' => "articleMarkStarred"],
|
||||||
|
'star/multiple' => ['PUT' => "articleMarkStarredMulti"],
|
||||||
|
'unstar/multiple' => ['PUT' => "articleMarkStarredMulti"],
|
||||||
|
],
|
||||||
'cleanup' => [],
|
'cleanup' => [],
|
||||||
'version' => [
|
'version' => [
|
||||||
'' => ['GET' => "versionReport"],
|
'' => ['GET' => "versionReport"],
|
||||||
|
@ -87,7 +121,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
$scope = $url[0];
|
$scope = $url[0];
|
||||||
// any URL components which are only digits should be replaced with "#", for easier comparison (integer segments are IDs, and we don't care about the specific ID)
|
// any URL components which are only digits should be replaced with "#", for easier comparison (integer segments are IDs, and we don't care about the specific ID)
|
||||||
for($a = 0; $a < sizeof($url); $a++) {
|
for($a = 0; $a < sizeof($url); $a++) {
|
||||||
if($this->validateId($url[$a])) $url[$a] = "0";
|
if($this->validateInt($url[$a])) $url[$a] = "0";
|
||||||
}
|
}
|
||||||
// normalize the HTTP method to uppercase
|
// normalize the HTTP method to uppercase
|
||||||
$method = strtoupper($method);
|
$method = strtoupper($method);
|
||||||
|
@ -115,6 +149,57 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
throw new Exception501();
|
throw new Exception501();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function feedTranslate(array $feed): array {
|
||||||
|
// map fields to proper names
|
||||||
|
$feed = $this->fieldMapNames($feed, [
|
||||||
|
'id' => "id",
|
||||||
|
'url' => "url",
|
||||||
|
'title' => "title",
|
||||||
|
'added' => "added",
|
||||||
|
'pinned' => "pinned",
|
||||||
|
'link' => "source",
|
||||||
|
'faviconLink' => "favicon",
|
||||||
|
'folderId' => "top_folder",
|
||||||
|
'unreadCount' => "unread",
|
||||||
|
'ordering' => "order_type",
|
||||||
|
'updateErrorCount' => "err_count",
|
||||||
|
'lastUpdateError' => "err_msg",
|
||||||
|
]);
|
||||||
|
// cast values
|
||||||
|
$feed = $this->fieldMapTypes($feed, [
|
||||||
|
'folderId' => "int",
|
||||||
|
'pinned' => "bool",
|
||||||
|
]);
|
||||||
|
return $feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function articleTranslate(array $article) :array {
|
||||||
|
// map fields to proper names
|
||||||
|
$article = $this->fieldMapNames($article, [
|
||||||
|
'id' => "edition",
|
||||||
|
'guid' => "guid",
|
||||||
|
'guidHash' => "id",
|
||||||
|
'url' => "url",
|
||||||
|
'title' => "title",
|
||||||
|
'author' => "author",
|
||||||
|
'pubDate' => "edited_date",
|
||||||
|
'body' => "content",
|
||||||
|
'enclsoureMime' => "media_type",
|
||||||
|
'enclosureLink' => "media_url",
|
||||||
|
'feedId' => "feed",
|
||||||
|
'unread' => "unread",
|
||||||
|
'starred' => "starred",
|
||||||
|
'lastModified' => "modified_date",
|
||||||
|
'fingerprint' => "fingerprint",
|
||||||
|
]);
|
||||||
|
// cast values
|
||||||
|
$article = $this->fieldMapTypes($article, [
|
||||||
|
'unread' => "bool",
|
||||||
|
'starred' => "bool",
|
||||||
|
]);
|
||||||
|
return $article;
|
||||||
|
}
|
||||||
|
|
||||||
// list folders
|
// list folders
|
||||||
protected function folderList(array $url, array $data): Response {
|
protected function folderList(array $url, array $data): Response {
|
||||||
$folders = Data::$db->folderList(Data::$user->id, null, false)->getAll();
|
$folders = Data::$db->folderList(Data::$user->id, null, false)->getAll();
|
||||||
|
@ -175,47 +260,32 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return new Response(204);
|
return new Response(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function folderMarkRead(array $url, array $data): Response {
|
||||||
|
$c = new Context;
|
||||||
|
if(isset($data['newestItemId'])) {
|
||||||
|
// if the item ID is valid (i.e. an integer), add it to the context
|
||||||
|
$c->latestEdition($data['newestItemId']);
|
||||||
|
} else {
|
||||||
|
// otherwise return an error
|
||||||
|
return new Response(422);
|
||||||
|
}
|
||||||
|
// add the folder ID to the context
|
||||||
|
$c->folder((int) $url[1]);
|
||||||
|
// perform the operation
|
||||||
|
try {
|
||||||
|
Data::$db->articleMark(Data::$user->id, ['read' => true], $c);
|
||||||
|
} catch(ExceptionInput $e) {
|
||||||
|
// folder does not exist
|
||||||
|
return new Response(404);
|
||||||
|
}
|
||||||
|
return new Response(204);
|
||||||
|
}
|
||||||
|
|
||||||
// return the server version
|
// return the server version
|
||||||
protected function versionReport(array $url, array $data): Response {
|
protected function versionReport(array $url, array $data): Response {
|
||||||
return new Response(200, ['version' => \JKingWeb\Arsse\VERSION]);
|
return new Response(200, ['version' => \JKingWeb\Arsse\VERSION]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function feedTranslate(array $feed, bool $overwrite = false): array {
|
|
||||||
// cast values
|
|
||||||
$feed = $this->mapFieldTypes($feed, [
|
|
||||||
'top_folder' => "int",
|
|
||||||
'pinned' => "bool",
|
|
||||||
]);
|
|
||||||
// map fields to proper names
|
|
||||||
$feed = $this->mapFieldNames($feed, [
|
|
||||||
'source' => "link",
|
|
||||||
'favicon' => "faviconLink",
|
|
||||||
'top_folder' => "folderId",
|
|
||||||
'unread' => "unreadCount",
|
|
||||||
'order_type' => "ordering",
|
|
||||||
'err_count' => "updateErrorCount",
|
|
||||||
'err_msg' => "lastUpdateError",
|
|
||||||
], $overwrite);
|
|
||||||
// remove the true folder since the protocol does not support nesting
|
|
||||||
unset($feed['folder']);
|
|
||||||
return $feed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return list of feeds for the logged-in user
|
|
||||||
protected function subscriptionList(array $url, array $data): Response {
|
|
||||||
$subs = Data::$db->subscriptionList(Data::$user->id);
|
|
||||||
$out = [];
|
|
||||||
foreach($subs as $sub) {
|
|
||||||
$sub = $this->feedTranslate($sub);
|
|
||||||
$out[] = $sub;
|
|
||||||
}
|
|
||||||
$out = ['feeds' => $out];
|
|
||||||
$out['starredCount'] = Data::$db->articleStarredCount(Data::$user->id);
|
|
||||||
$newest = Data::$db->editionLatest(Data::$user->id);
|
|
||||||
if($newest) $out['newestItemId'] = $newest;
|
|
||||||
return new Response(200, $out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return list of feeds which should be refreshed
|
// return list of feeds which should be refreshed
|
||||||
protected function feedListStale(array $url, array $data): Response {
|
protected function feedListStale(array $url, array $data): Response {
|
||||||
// function requires admin rights per spec
|
// function requires admin rights per spec
|
||||||
|
@ -235,10 +305,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// function requires admin rights per spec
|
// function requires admin rights per spec
|
||||||
if(Data::$user->rightsGet(Data::$user->id)==User::RIGHTS_NONE) return new Response(403);
|
if(Data::$user->rightsGet(Data::$user->id)==User::RIGHTS_NONE) return new Response(403);
|
||||||
// perform an update of a single feed
|
// perform an update of a single feed
|
||||||
if(!array_key_exists("feedId", $data)) return new Response(422);
|
if(!isset($data['feedId'])) return new Response(422);
|
||||||
if(!$this->validateId($data['feedId'])) return new Response(404);
|
|
||||||
try {
|
try {
|
||||||
Data::$db->feedUpdate((int) $data['feedId']);
|
Data::$db->feedUpdate($data['feedId']);
|
||||||
} catch(ExceptionInput $e) {
|
} catch(ExceptionInput $e) {
|
||||||
return new Response(404);
|
return new Response(404);
|
||||||
}
|
}
|
||||||
|
@ -248,22 +317,13 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// add a new feed
|
// add a new feed
|
||||||
protected function subscriptionAdd(array $url, array $data): Response {
|
protected function subscriptionAdd(array $url, array $data): Response {
|
||||||
// normalize the feed URL
|
// normalize the feed URL
|
||||||
if(!array_key_exists("url", $data)) {
|
if(!isset($data['url'])) return new Response(422);
|
||||||
$url = "";
|
// normalize the folder ID, if specified; zero should be transformed to null
|
||||||
} else {
|
$folder = (isset($data['folderId']) && $data['folderId']) ? $data['folderId'] : null;
|
||||||
$url = $data['url'];
|
|
||||||
}
|
|
||||||
// normalize the folder ID, if specified
|
|
||||||
if(!array_key_exists("folderId", $data)) {
|
|
||||||
$folder = null;
|
|
||||||
} else {
|
|
||||||
$folder = $data['folderId'];
|
|
||||||
$folder = $folder ? $folder : null;
|
|
||||||
}
|
|
||||||
// try to add the feed
|
// try to add the feed
|
||||||
$tr = Data::$db->begin();
|
$tr = Data::$db->begin();
|
||||||
try {
|
try {
|
||||||
$id = Data::$db->subscriptionAdd(Data::$user->id, $url);
|
$id = Data::$db->subscriptionAdd(Data::$user->id, $data['url']);
|
||||||
} catch(ExceptionInput $e) {
|
} catch(ExceptionInput $e) {
|
||||||
// feed already exists
|
// feed already exists
|
||||||
return new Response(409);
|
return new Response(409);
|
||||||
|
@ -287,6 +347,20 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
return new Response(200, $out);
|
return new Response(200, $out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return list of feeds for the logged-in user
|
||||||
|
protected function subscriptionList(array $url, array $data): Response {
|
||||||
|
$subs = Data::$db->subscriptionList(Data::$user->id);
|
||||||
|
$out = [];
|
||||||
|
foreach($subs as $sub) {
|
||||||
|
$out[] = $this->feedTranslate($sub);
|
||||||
|
}
|
||||||
|
$out = ['feeds' => $out];
|
||||||
|
$out['starredCount'] = Data::$db->articleStarredCount(Data::$user->id);
|
||||||
|
$newest = Data::$db->editionLatest(Data::$user->id);
|
||||||
|
if($newest) $out['newestItemId'] = $newest;
|
||||||
|
return new Response(200, $out);
|
||||||
|
}
|
||||||
|
|
||||||
// delete a feed
|
// delete a feed
|
||||||
protected function subscriptionRemove(array $url, array $data): Response {
|
protected function subscriptionRemove(array $url, array $data): Response {
|
||||||
try {
|
try {
|
||||||
|
@ -302,7 +376,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
protected function subscriptionRename(array $url, array $data): Response {
|
protected function subscriptionRename(array $url, array $data): Response {
|
||||||
// normalize input
|
// normalize input
|
||||||
$in = [];
|
$in = [];
|
||||||
if(array_key_exists("feedTitle", $data)) {
|
if(array_key_exists('feedTitle', $data)) { // we use array_key_exists because null is a valid input
|
||||||
$in['title'] = $data['feedTitle'];
|
$in['title'] = $data['feedTitle'];
|
||||||
} else {
|
} else {
|
||||||
return new Response(422);
|
return new Response(422);
|
||||||
|
@ -326,13 +400,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
|
|
||||||
// move a feed to a folder
|
// move a feed to a folder
|
||||||
protected function subscriptionMove(array $url, array $data): Response {
|
protected function subscriptionMove(array $url, array $data): Response {
|
||||||
// normalize input for move and rename
|
// normalize input
|
||||||
$in = [];
|
$in = [];
|
||||||
if(array_key_exists("folderId", $data)) {
|
if(isset($data['folderId'])) {
|
||||||
$folder = $data['folderId'];
|
$in['folder'] = $data['folderId'] ? $data['folderId'] : null;
|
||||||
if(!$this->validateId($folder)) return new Response(422);
|
|
||||||
if(!$folder) $folder = null;
|
|
||||||
$in['folder'] = $folder;
|
|
||||||
} else {
|
} else {
|
||||||
return new Response(422);
|
return new Response(422);
|
||||||
}
|
}
|
||||||
|
@ -351,4 +422,161 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
}
|
}
|
||||||
return new Response(204);
|
return new Response(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function subscriptionMarkRead(array $url, array $data): Response {
|
||||||
|
$c = new Context;
|
||||||
|
if(isset($data['newestItemId'])) {
|
||||||
|
$c->latestEdition($data['newestItemId']);
|
||||||
|
} else {
|
||||||
|
// otherwise return an error
|
||||||
|
return new Response(422);
|
||||||
|
}
|
||||||
|
// add the subscription ID to the context
|
||||||
|
$c->subscription((int) $url[1]);
|
||||||
|
// perform the operation
|
||||||
|
try {
|
||||||
|
Data::$db->articleMark(Data::$user->id, ['read' => true], $c);
|
||||||
|
} catch(ExceptionInput $e) {
|
||||||
|
// subscription does not exist
|
||||||
|
return new Response(404);
|
||||||
|
}
|
||||||
|
return new Response(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function articleList(array $url, array $data): Response {
|
||||||
|
// set the context options supplied by the client
|
||||||
|
$c = new Context;
|
||||||
|
// set the batch size
|
||||||
|
if(isset($data['batchSize']) && $data['batchSize'] > 0) $c->limit($data['batchSize']);
|
||||||
|
// set the order of returned items
|
||||||
|
if(isset($data['oldestFirst']) && $data['oldestFirst']) {
|
||||||
|
$c->reverse(false);
|
||||||
|
} else {
|
||||||
|
$c->reverse(true);
|
||||||
|
}
|
||||||
|
// set the edition mark-off; the database uses an or-equal comparison for internal consistency, but the protocol does not, so we must adjust by one
|
||||||
|
if(isset($data['offset'])) {
|
||||||
|
if($c->reverse) {
|
||||||
|
$c->latestEdition($data['offset'] - 1);
|
||||||
|
} else {
|
||||||
|
$c->oldestEdition($data['offset'] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set whether to only return unread
|
||||||
|
if(isset($data['getRead']) && !$data['getRead']) $c->unread(true);
|
||||||
|
// if no type is specified assume 3 (All)
|
||||||
|
if(!isset($data['type'])) $data['type'] = 3;
|
||||||
|
switch($data['type']) {
|
||||||
|
case 0: // feed
|
||||||
|
if(isset($data['id'])) $c->subscription($data['id']);
|
||||||
|
break;
|
||||||
|
case 1: // folder
|
||||||
|
if(isset($data['id'])) $c->folder($data['id']);
|
||||||
|
break;
|
||||||
|
case 2: // starred
|
||||||
|
$c->starred(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// return all items
|
||||||
|
}
|
||||||
|
// whether to return only updated items
|
||||||
|
if(isset($data['lastModified'])) $c->modifiedSince($data['lastModified']);
|
||||||
|
// perform the fetch
|
||||||
|
try {
|
||||||
|
$items = Data::$db->articleList(Data::$user->id, $c);
|
||||||
|
} catch(ExceptionInput $e) {
|
||||||
|
// ID of subscription or folder is not valid
|
||||||
|
return new Response(422);
|
||||||
|
}
|
||||||
|
$out = [];
|
||||||
|
foreach($items as $item) {
|
||||||
|
$out[] = $this->articleTranslate($item);
|
||||||
|
}
|
||||||
|
$out = ['items' => $out];
|
||||||
|
return new Response(200, $out);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function articleMarkReadAll(array $url, array $data): Response {
|
||||||
|
$c = new Context;
|
||||||
|
if(isset($data['newestItemId'])) {
|
||||||
|
// set the newest item ID as specified
|
||||||
|
$c->latestEdition($data['newestItemId']);
|
||||||
|
} else {
|
||||||
|
// otherwise return an error
|
||||||
|
return new Response(422);
|
||||||
|
}
|
||||||
|
// perform the operation
|
||||||
|
Data::$db->articleMark(Data::$user->id, ['read' => true], $c);
|
||||||
|
return new Response(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function articleMarkRead(array $url, array $data): Response {
|
||||||
|
// initialize the matching context
|
||||||
|
$c = new Context;
|
||||||
|
$c->edition((int) $url[1]);
|
||||||
|
// determine whether to mark read or unread
|
||||||
|
$set = ($url[2]=="read");
|
||||||
|
try {
|
||||||
|
Data::$db->articleMark(Data::$user->id, ['read' => $set], $c);
|
||||||
|
} catch(ExceptionInput $e) {
|
||||||
|
// ID is not valid
|
||||||
|
return new Response(404);
|
||||||
|
}
|
||||||
|
return new Response(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function articleMarkStarred(array $url, array $data): Response {
|
||||||
|
// initialize the matching context
|
||||||
|
$c = new Context;
|
||||||
|
$c->article((int) $url[2]);
|
||||||
|
// determine whether to mark read or unread
|
||||||
|
$set = ($url[3]=="star");
|
||||||
|
try {
|
||||||
|
Data::$db->articleMark(Data::$user->id, ['star' => $set], $c);
|
||||||
|
} catch(ExceptionInput $e) {
|
||||||
|
// ID is not valid
|
||||||
|
return new Response(404);
|
||||||
|
}
|
||||||
|
return new Response(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function articleMarkReadMulti(array $url, array $data): Response {
|
||||||
|
// initialize the matching context
|
||||||
|
$c = new Context;
|
||||||
|
// determine whether to mark read or unread
|
||||||
|
$set = ($url[1]=="read");
|
||||||
|
// if the input data is not at all valid, return an error
|
||||||
|
if(!isset($data['items']) || !is_array($data['items'])) return new Response(422);
|
||||||
|
// start a transaction and loop through the items
|
||||||
|
$t = Data::$db->begin();
|
||||||
|
$in = array_chunk($data['items'], 50);
|
||||||
|
for($a = 0; $a < sizeof($in); $a++) {
|
||||||
|
$c->editions($in[$a]);
|
||||||
|
try {
|
||||||
|
Data::$db->articleMark(Data::$user->id, ['read' => $set], $c);
|
||||||
|
} catch(ExceptionInput $e) {}
|
||||||
|
}
|
||||||
|
$t->commit();
|
||||||
|
return new Response(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function articleMarkStarredMulti(array $url, array $data): Response {
|
||||||
|
// initialize the matching context
|
||||||
|
$c = new Context;
|
||||||
|
// determine whether to mark read or unread
|
||||||
|
$set = ($url[1]=="star");
|
||||||
|
// if the input data is not at all valid, return an error
|
||||||
|
if(!isset($data['items']) || !is_array($data['items'])) return new Response(422);
|
||||||
|
// start a transaction and loop through the items
|
||||||
|
$t = Data::$db->begin();
|
||||||
|
$in = array_chunk(array_column($data['items'], "guidHash"), 50);
|
||||||
|
for($a = 0; $a < sizeof($in); $a++) {
|
||||||
|
$c->articles($in[$a]);
|
||||||
|
try {
|
||||||
|
Data::$db->articleMark(Data::$user->id, ['starred' => $set], $c);
|
||||||
|
} catch(ExceptionInput $e) {}
|
||||||
|
}
|
||||||
|
$t->commit();
|
||||||
|
return new Response(204);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,11 +3,11 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\REST\NextCloudNews;
|
namespace JKingWeb\Arsse\REST\NextCloudNews;
|
||||||
use JKingWeb\Arsse\REST\Response;
|
use JKingWeb\Arsse\REST\Response;
|
||||||
|
|
||||||
class Versions extends \JKingWeb\Arsse\REST\AbstractHandler {
|
class Versions implements \JKingWeb\Arsse\REST\Handler {
|
||||||
function __construct() {
|
function __construct() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatch(\JKingWeb\Arsse\REST\Request $req): \JKingWeb\Arsse\REST\Response {
|
function dispatch(\JKingWeb\Arsse\REST\Request $req): Response {
|
||||||
// if a method other than GET was used, this is an error
|
// if a method other than GET was used, this is an error
|
||||||
if($req->method != "GET") {
|
if($req->method != "GET") {
|
||||||
return new Response(405);
|
return new Response(405);
|
||||||
|
|
|
@ -390,7 +390,7 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
|
||||||
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[0]), 'application/json')));
|
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[0]), 'application/json')));
|
||||||
$exp = new Response(404);
|
$exp = new Response(404);
|
||||||
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[1]), 'application/json')));
|
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[1]), 'application/json')));
|
||||||
$exp = new Response(404);
|
$exp = new Response(422);
|
||||||
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[2]), 'application/json')));
|
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[2]), 'application/json')));
|
||||||
$exp = new Response(422);
|
$exp = new Response(422);
|
||||||
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[3]), 'application/json')));
|
$this->assertEquals($exp, $this->h->dispatch(new Request("GET", "/feeds/update", json_encode($in[3]), 'application/json')));
|
||||||
|
|
Loading…
Reference in a new issue