Implement feed listing by category
Also modify user list to reflect changes in Miniflux 2.0.27.
This commit is contained in:
parent
4972c79e32
commit
727864f401
3 changed files with 102 additions and 123 deletions
|
@ -54,17 +54,17 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
'fetch_via_proxy' => "boolean",
|
||||
];
|
||||
protected const USER_META_MAP = [
|
||||
// Miniflux ID // Arsse ID Default value Extra
|
||||
'is_admin' => ["admin", false, false],
|
||||
'theme' => ["theme", "light_serif", false],
|
||||
'language' => ["lang", "en_US", false],
|
||||
'timezone' => ["tz", "UTC", false],
|
||||
'entry_sorting_direction' => ["sort_asc", false, false],
|
||||
'entries_per_page' => ["page_size", 100, false],
|
||||
'keyboard_shortcuts' => ["shortcuts", true, false],
|
||||
'show_reading_time' => ["reading_time", true, false],
|
||||
'entry_swipe' => ["swipe", true, false],
|
||||
'custom_css' => ["stylesheet", "", true],
|
||||
// Miniflux ID // Arsse ID Default value
|
||||
'is_admin' => ["admin", false],
|
||||
'theme' => ["theme", "light_serif"],
|
||||
'language' => ["lang", "en_US"],
|
||||
'timezone' => ["tz", "UTC"],
|
||||
'entry_sorting_direction' => ["sort_asc", false],
|
||||
'entries_per_page' => ["page_size", 100],
|
||||
'keyboard_shortcuts' => ["shortcuts", true],
|
||||
'show_reading_time' => ["reading_time", true],
|
||||
'entry_swipe' => ["swipe", true],
|
||||
'stylesheet' => ["stylesheet", ""],
|
||||
];
|
||||
protected const CALLS = [ // handler method Admin Path Body Query Required fields
|
||||
'/categories' => [
|
||||
|
@ -76,13 +76,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
'DELETE' => ["deleteCategory", false, true, false, false, []],
|
||||
],
|
||||
'/categories/1/entries' => [
|
||||
'GET' => ["getCategoryEntries", false, false, false, true, []],
|
||||
'GET' => ["getCategoryEntries", false, true, false, false, []],
|
||||
],
|
||||
'/categories/1/entries/1' => [
|
||||
'GET' => ["getCategoryEntry", false, false, false, true, []],
|
||||
'GET' => ["getCategoryEntry", false, true, false, false, []],
|
||||
],
|
||||
'/categories/1/feeds' => [
|
||||
'GET' => ["getCategoryFeeds", false, false, false, true, []],
|
||||
'GET' => ["getCategoryFeeds", false, true, false, false, []],
|
||||
],
|
||||
'/categories/1/mark-all-as-read' => [
|
||||
'PUT' => ["markCategory", false, true, false, false, []],
|
||||
|
@ -354,16 +354,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
'id' => $info['num'],
|
||||
'username' => $u,
|
||||
'last_login_at' => $now,
|
||||
'google_id' => "",
|
||||
'openid_connect_id' => "",
|
||||
];
|
||||
foreach (self::USER_META_MAP as $ext => [$int, $default, $extra]) {
|
||||
if (!$extra) {
|
||||
$entry[$ext] = $info[$int] ?? $default;
|
||||
} else {
|
||||
if (!isset($entry['extra'])) {
|
||||
$entry['extra'] = [];
|
||||
}
|
||||
$entry['extra'][$ext] = $info[$int] ?? $default;
|
||||
}
|
||||
foreach (self::USER_META_MAP as $ext => [$int, $default]) {
|
||||
$entry[$ext] = $info[$int] ?? $default;
|
||||
}
|
||||
$entry['entry_sorting_direction'] = ($entry['entry_sorting_direction']) ? "asc" : "desc";
|
||||
$out[] = $entry;
|
||||
|
@ -530,15 +525,21 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
return new EmptyResponse(204);
|
||||
}
|
||||
|
||||
protected function getCategories(): ResponseInterface {
|
||||
$out = [];
|
||||
protected function baseCategory(): array {
|
||||
// the root folder is always a category and is always ID 1
|
||||
// the specific formulation is verbose, so a function makes sense
|
||||
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
|
||||
return ['id' => 1, 'title' => $meta['root_folder_name'] ?? Arsse::$lang->msg("API.Miniflux.DefaultCategoryName"), 'user_id' => $meta['num']];
|
||||
}
|
||||
|
||||
protected function getCategories(): ResponseInterface {
|
||||
// add the root folder as a category
|
||||
$out[] = ['id' => 1, 'title' => $meta['root_folder_name'] ?? Arsse::$lang->msg("API.Miniflux.DefaultCategoryName"), 'user_id' => $meta['num']];
|
||||
$out = [$this->baseCategory()];
|
||||
$num = $out[0]['user_id'];
|
||||
// add other top folders as categories
|
||||
foreach (Arsse::$db->folderList(Arsse::$user->id, null, false) as $f) {
|
||||
// always add 1 to the ID since the root folder will always be 1 instead of 0.
|
||||
$out[] = ['id' => $f['id'] + 1, 'title' => $f['name'], 'user_id' => $meta['num']];
|
||||
$out[] = ['id' => $f['id'] + 1, 'title' => $f['name'], 'user_id' => $num];
|
||||
}
|
||||
return new Response($out);
|
||||
}
|
||||
|
@ -622,13 +623,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
}
|
||||
|
||||
protected function mapFolders(): array {
|
||||
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
|
||||
$folders = [0 => ['id' => 1, 'title' => $meta['root_folder_name'] ?? Arsse::$lang->msg("API.Miniflux.DefaultCategoryName"), 'user_id' => $meta['num']]];
|
||||
$folders = [0 => $this->baseCategory()];
|
||||
$num = $folders[0]['user_id'];
|
||||
foreach (Arsse::$db->folderList(Arsse::$user->id, null, false) as $r) {
|
||||
$folders[(int) $r['id']] = [
|
||||
'id' => ((int) $r['id']) + 1,
|
||||
'title' => $r['name'],
|
||||
'user_id' => $meta['num'],
|
||||
'user_id' => $num,
|
||||
];
|
||||
}
|
||||
return $folders;
|
||||
|
@ -676,6 +677,30 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
return new Response($out);
|
||||
}
|
||||
|
||||
protected function getCategoryFeeds(array $path): ResponseInterface {
|
||||
// transform the category number into a folder number by subtracting one
|
||||
$folder = ((int) $path[1]) - 1;
|
||||
// unless the folder is root, list recursive
|
||||
$recursive = $folder > 0;
|
||||
$tr = Arsse::$db->begin();
|
||||
// get the list of subscriptions, or bail\
|
||||
try {
|
||||
$subs = Arsse::$db->subscriptionList(Arsse::$user->id, $folder, $recursive)->getAll();
|
||||
} catch (ExceptionInput $e) {
|
||||
// the folder does not exist
|
||||
return new EmptyResponse(404);
|
||||
}
|
||||
// compile the list of folders; the feed list includes folder names
|
||||
// NOTE: We compile the full list of folders in case someone has manually selected a non-top folder
|
||||
$folders = $this->mapFolders();
|
||||
// next compile the list of feeds
|
||||
$out = [];
|
||||
foreach ($subs as $r) {
|
||||
$out[] = $this->transformFeed($r, $folders);
|
||||
}
|
||||
return new Response($out);
|
||||
}
|
||||
|
||||
protected function createFeed(array $data): ResponseInterface {
|
||||
$props = [
|
||||
'keep_rule' => $data['keeplist_rules'],
|
||||
|
|
|
@ -15,6 +15,7 @@ use JKingWeb\Arsse\Db\ExceptionInput;
|
|||
use JKingWeb\Arsse\Misc\Date;
|
||||
use JKingWeb\Arsse\REST\Miniflux\V1;
|
||||
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
|
||||
use JKingWeb\Arsse\Test\FeedException;
|
||||
use JKingWeb\Arsse\User\ExceptionConflict;
|
||||
use JKingWeb\Arsse\User\ExceptionInput as UserExceptionInput;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
@ -34,6 +35,8 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
'id' => 1,
|
||||
'username' => "john.doe@example.com",
|
||||
'last_login_at' => self::NOW,
|
||||
'google_id' => "",
|
||||
'openid_connect_id' => "",
|
||||
'is_admin' => true,
|
||||
'theme' => "custom",
|
||||
'language' => "fr_CA",
|
||||
|
@ -43,14 +46,14 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
'keyboard_shortcuts' => false,
|
||||
'show_reading_time' => false,
|
||||
'entry_swipe' => false,
|
||||
'extra' => [
|
||||
'custom_css' => "p {}",
|
||||
],
|
||||
'stylesheet' => "p {}",
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'username' => "jane.doe@example.com",
|
||||
'last_login_at' => self::NOW,
|
||||
'google_id' => "",
|
||||
'openid_connect_id' => "",
|
||||
'is_admin' => false,
|
||||
'theme' => "light_serif",
|
||||
'language' => "en_US",
|
||||
|
@ -60,11 +63,17 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
'keyboard_shortcuts' => true,
|
||||
'show_reading_time' => true,
|
||||
'entry_swipe' => true,
|
||||
'extra' => [
|
||||
'custom_css' => "",
|
||||
],
|
||||
'stylesheet' => "",
|
||||
],
|
||||
];
|
||||
protected $feeds = [
|
||||
['id' => 1, 'feed' => 12, 'url' => "http://example.com/ook", 'title' => "Ook", 'source' => "http://example.com/", 'icon_id' => 47, 'icon_url' => "http://example.com/icon", 'folder' => 2112, 'top_folder' => 5, 'pinned' => 0, 'err_count' => 1, 'err_msg' => "Oopsie", 'order_type' => 0, 'keep_rule' => "this|that", 'block_rule' => "both", 'added' => "2020-12-21 21:12:00", 'updated' => "2021-01-05 13:51:32", 'edited' => "2021-01-01 00:00:00", 'modified' => "2020-11-30 04:08:52", 'next_fetch' => "2021-01-20 00:00:00", 'etag' => "OOKEEK", 'scrape' => 0, 'unread' => 42],
|
||||
['id' => 55, 'feed' => 12, 'url' => "http://j%20k:super%20secret@example.com/eek", 'title' => "Eek", 'source' => "http://example.com/", 'icon_id' => null, 'icon_url' => null, 'folder' => null, 'top_folder' => null, 'pinned' => 0, 'err_count' => 0, 'err_msg' => null, 'order_type' => 0, 'keep_rule' => null, 'block_rule' => null, 'added' => "2020-12-21 21:12:00", 'updated' => "2021-01-05 13:51:32", 'edited' => null, 'modified' => "2020-11-30 04:08:52", 'next_fetch' => null, 'etag' => null, 'scrape' => 1, 'unread' => 0],
|
||||
];
|
||||
protected $feedsOut = [
|
||||
['id' => 1, 'user_id' => 42, 'feed_url' => "http://example.com/ook", 'site_url' => "http://example.com/", 'title' => "Ook", 'checked_at' => "2021-01-05T13:51:32.000000Z", 'next_check_at' => "2021-01-20T00:00:00.000000Z", 'etag_header' => "OOKEEK", 'last_modified_header' => "Fri, 01 Jan 2021 00:00:00 GMT", 'parsing_error_message' => "Oopsie", 'parsing_error_count' => 1, 'scraper_rules' => "", 'rewrite_rules' => "", 'crawler' => false, 'blocklist_rules' => "both", 'keeplist_rules' => "this|that", 'user_agent' => "", 'username' => "", 'password' => "", 'disabled' => false, 'ignore_http_cache' => false, 'fetch_via_proxy' => false, 'category' => ['id' => 6, 'title' => "Cat Ook", 'user_id' => 42], 'icon' => ['feed_id' => 1,'icon_id' => 47]],
|
||||
['id' => 55, 'user_id' => 42, 'feed_url' => "http://example.com/eek", 'site_url' => "http://example.com/", 'title' => "Eek", 'checked_at' => "2021-01-05T13:51:32.000000Z", 'next_check_at' => "0001-01-01T00:00:00.000000Z", 'etag_header' => "", 'last_modified_header' => "", 'parsing_error_message' => "", 'parsing_error_count' => 0, 'scraper_rules' => "", 'rewrite_rules' => "", 'crawler' => true, 'blocklist_rules' => "", 'keeplist_rules' => "", 'user_agent' => "", 'username' => "j k", 'password' => "super secret", 'disabled' => false, 'ignore_http_cache' => false, 'fetch_via_proxy' => false, 'category' => ['id' => 1,'title' => "All", 'user_id' => 42], 'icon' => null],
|
||||
];
|
||||
|
||||
protected function req(string $method, string $target, $data = "", array $headers = [], ?string $user = "john.doe@example.com", bool $body = true): ResponseInterface {
|
||||
$prefix = "/v1";
|
||||
|
@ -535,82 +544,42 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
);
|
||||
}
|
||||
|
||||
public function testListReeds(): void {
|
||||
\Phake::when(Arsse::$db)->folderList->thenReturn(new Result([
|
||||
public function testListFeeds(): void {
|
||||
\Phake::when(Arsse::$db)->folderList->thenReturn(new Result($this->v([
|
||||
['id' => 5, 'name' => "Cat Ook"],
|
||||
]));
|
||||
\Phake::when(Arsse::$db)->subscriptionList->thenReturn(new Result([
|
||||
['id' => 1, 'feed' => 12, 'url' => "http://example.com/ook", 'title' => "Ook", 'source' => "http://example.com/", 'icon_id' => 47, 'icon_url' => "http://example.com/icon", 'folder' => 2112, 'top_folder' => 5, 'pinned' => 0, 'err_count' => 1, 'err_msg' => "Oopsie", 'order_type' => 0, 'keep_rule' => "this|that", 'block_rule' => "both", 'added' => "2020-12-21 21:12:00", 'updated' => "2021-01-05 13:51:32", 'edited' => "2021-01-01 00:00:00", 'modified' => "2020-11-30 04:08:52", 'next_fetch' => "2021-01-20 00:00:00", 'etag' => "OOKEEK", 'scrape' => 0, 'unread' => 42],
|
||||
['id' => 55, 'feed' => 12, 'url' => "http://j%20k:super%20secret@example.com/eek", 'title' => "Eek", 'source' => "http://example.com/", 'icon_id' => null, 'icon_url' => null, 'folder' => null, 'top_folder' => null, 'pinned' => 0, 'err_count' => 0, 'err_msg' => null, 'order_type' => 0, 'keep_rule' => null, 'block_rule' => null, 'added' => "2020-12-21 21:12:00", 'updated' => "2021-01-05 13:51:32", 'edited' => null, 'modified' => "2020-11-30 04:08:52", 'next_fetch' => null, 'etag' => null, 'scrape' => 1, 'unread' => 0],
|
||||
]));
|
||||
$exp = new Response([
|
||||
[
|
||||
'id' => 1,
|
||||
'user_id' => 42,
|
||||
'feed_url' => "http://example.com/ook",
|
||||
'site_url' => "http://example.com/",
|
||||
'title' => "Ook",
|
||||
'checked_at' => "2021-01-05T13:51:32.000000Z",
|
||||
'next_check_at' => "2021-01-20T00:00:00.000000Z",
|
||||
'etag_header' => "OOKEEK",
|
||||
'last_modified_header' => "Fri, 01 Jan 2021 00:00:00 GMT",
|
||||
'parsing_error_message' => "Oopsie",
|
||||
'parsing_error_count' => 1,
|
||||
'scraper_rules' => "",
|
||||
'rewrite_rules' => "",
|
||||
'crawler' => false,
|
||||
'blocklist_rules' => "both",
|
||||
'keeplist_rules' => "this|that",
|
||||
'user_agent' => "",
|
||||
'username' => "",
|
||||
'password' => "",
|
||||
'disabled' => false,
|
||||
'ignore_http_cache' => false,
|
||||
'fetch_via_proxy' => false,
|
||||
'category' => [
|
||||
'id' => 6,
|
||||
'title' => "Cat Ook",
|
||||
'user_id' => 42
|
||||
],
|
||||
'icon' => [
|
||||
'feed_id' => 1,
|
||||
'icon_id' => 47
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 55,
|
||||
'user_id' => 42,
|
||||
'feed_url' => "http://example.com/eek",
|
||||
'site_url' => "http://example.com/",
|
||||
'title' => "Eek",
|
||||
'checked_at' => "2021-01-05T13:51:32.000000Z",
|
||||
'next_check_at' => "0001-01-01T00:00:00.000000Z",
|
||||
'etag_header' => "",
|
||||
'last_modified_header' => "",
|
||||
'parsing_error_message' => "",
|
||||
'parsing_error_count' => 0,
|
||||
'scraper_rules' => "",
|
||||
'rewrite_rules' => "",
|
||||
'crawler' => true,
|
||||
'blocklist_rules' => "",
|
||||
'keeplist_rules' => "",
|
||||
'user_agent' => "",
|
||||
'username' => "j k",
|
||||
'password' => "super secret",
|
||||
'disabled' => false,
|
||||
'ignore_http_cache' => false,
|
||||
'fetch_via_proxy' => false,
|
||||
'category' => [
|
||||
'id' => 1,
|
||||
'title' => "All",
|
||||
'user_id' => 42
|
||||
],
|
||||
'icon' => null,
|
||||
],
|
||||
]);
|
||||
])));
|
||||
\Phake::when(Arsse::$db)->subscriptionList->thenReturn(new Result($this->v($this->feeds)));
|
||||
$exp = new Response($this->feedsOut);
|
||||
$this->assertMessage($exp, $this->req("GET", "/feeds"));
|
||||
}
|
||||
|
||||
public function testListFeedsOfACategory(): void {
|
||||
\Phake::when(Arsse::$db)->folderList->thenReturn(new Result($this->v([
|
||||
['id' => 5, 'name' => "Cat Ook"],
|
||||
])));
|
||||
\Phake::when(Arsse::$db)->subscriptionList->thenReturn(new Result($this->v($this->feeds)));
|
||||
$exp = new Response($this->feedsOut);
|
||||
$this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds"));
|
||||
\Phake::verify(Arsse::$db)->subscriptionList(Arsse::$user->id, 2111, true);
|
||||
}
|
||||
|
||||
public function testListFeedsOfTheRootCategory(): void {
|
||||
\Phake::when(Arsse::$db)->folderList->thenReturn(new Result($this->v([
|
||||
['id' => 5, 'name' => "Cat Ook"],
|
||||
])));
|
||||
\Phake::when(Arsse::$db)->subscriptionList->thenReturn(new Result($this->v($this->feeds)));
|
||||
$exp = new Response($this->feedsOut);
|
||||
$this->assertMessage($exp, $this->req("GET", "/categories/1/feeds"));
|
||||
\Phake::verify(Arsse::$db)->subscriptionList(Arsse::$user->id, 0, false);
|
||||
}
|
||||
|
||||
public function testListFeedsOfAMissingCategory(): void {
|
||||
\Phake::when(Arsse::$db)->subscriptionList->thenThrow(new ExceptionInput("idMissing"));
|
||||
$exp = new EmptyResponse(404);
|
||||
$this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds"));
|
||||
\Phake::verify(Arsse::$db)->subscriptionList(Arsse::$user->id, 2111, true);
|
||||
}
|
||||
|
||||
/** @dataProvider provideFeedCreations */
|
||||
public function testCreateAFeed(array $in, $out1, $out2, $out3, ResponseInterface $exp): void {
|
||||
if ($out1 instanceof \Exception) {
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
/** @license MIT
|
||||
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||
* See LICENSE and AUTHORS files for details */
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Test;
|
||||
|
||||
use JKingWeb\Arsse\AbstractException;
|
||||
|
||||
class FeedException extends \JKingWeb\Arsse\Feed\Exception {
|
||||
public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {
|
||||
AbstractException::__construct($msgID, $vars, $e);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue