From 3b2190ca105afb200f8b7a86c87101f718e7f0c7 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Thu, 28 Jan 2021 14:55:18 -0500 Subject: [PATCH] Include folder names directly in subscription list --- lib/Database.php | 8 ++- lib/REST/Miniflux/V1.php | 75 ++++++++++----------- tests/cases/Database/SeriesSubscription.php | 32 +++++---- tests/cases/REST/Miniflux/TestV1.php | 12 +--- 4 files changed, 59 insertions(+), 68 deletions(-) diff --git a/lib/Database.php b/lib/Database.php index f8053ae9..4c7237ad 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -796,19 +796,21 @@ class Database { "SELECT s.id as id, s.feed as feed, - f.url,source,folder,pinned,err_count,err_msg,order_type,added,keep_rule,block_rule,f.etag,s.scrape, + f.url,source,pinned,err_count,err_msg,order_type,added,keep_rule,block_rule,f.etag,s.scrape, f.updated as updated, f.modified as edited, s.modified as modified, f.next_fetch, i.id as icon_id, i.url as icon_url, - t.top as top_folder, + folder, t.top as top_folder, d.name as folder_name, dt.name as top_folder_name, coalesce(s.title, f.title) as title, coalesce((articles - hidden - marked), articles) as unread FROM arsse_subscriptions as s - left join topmost as t on t.f_id = s.folder join arsse_feeds as f on f.id = s.feed + left join topmost as t on t.f_id = s.folder + left join arsse_folders as d on s.folder = d.id + left join arsse_folders as dt on t.top = dt.id left join arsse_icons as i on i.id = f.icon left join ( select diff --git a/lib/REST/Miniflux/V1.php b/lib/REST/Miniflux/V1.php index efd04695..acecc027 100644 --- a/lib/REST/Miniflux/V1.php +++ b/lib/REST/Miniflux/V1.php @@ -554,21 +554,30 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { return new EmptyResponse(204); } - 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 + /** Returns a useful subset of user metadata + * + * The following keys are included: + * + * - "num": The user's numeric ID, + * - "root": The effective name of the root folder + */ + protected function userMeta(string $user): array { $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']]; + return [ + 'num' => $meta['num'], + 'root' => $meta['root_folder_name'] ?? Arsse::$lang->msg("API.Miniflux.DefaultCategoryName") + ]; } protected function getCategories(): ResponseInterface { + $out = []; // add the root folder as a category - $out = [$this->baseCategory()]; - $num = $out[0]['user_id']; + $meta = $this->userMeta(Arsse::$user->id); + $out[] = ['id' => 1, 'title' => $meta['root'], 'user_id' => $meta['num']]; // 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' => $num]; + $out[] = ['id' => $f['id'] + 1, 'title' => $f['name'], 'user_id' => $meta['num']]; } return new Response($out); } @@ -651,24 +660,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { return new EmptyResponse(204); } - protected function mapFolders(): array { - $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' => $num, - ]; - } - return $folders; - } - - protected function transformFeed(array $sub, array $folders): array { + protected function transformFeed(array $sub, int $uid, string $rootName): array { $url = new Uri($sub['url']); return [ 'id' => (int) $sub['id'], - 'user_id' => $folders[0]['user_id'], + 'user_id' => $uid, 'feed_url' => (string) $url->withUserInfo(""), 'site_url' => (string) $sub['source'], 'title' => (string) $sub['title'], @@ -689,19 +685,21 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { 'disabled' => false, 'ignore_http_cache' => false, 'fetch_via_proxy' => false, - 'category' => $folders[(int) $sub['top_folder']], + 'category' => [ + 'id' => (int) $sub['top_folder'] + 1, + 'title' => $sub['top_folder_name'] ?? $rootName, + 'user_id' => $uid, + ], 'icon' => $sub['icon_id'] ? ['feed_id' => (int) $sub['id'], 'icon_id' => (int) $sub['icon_id']] : null, ]; } protected function getFeeds(): ResponseInterface { - $tr = Arsse::$db->begin(); - // compile the list of folders; the feed list includes folder names - $folders = $this->mapFolders(); - // next compile the list of feeds $out = []; + $tr = Arsse::$db->begin(); + $meta = $this->userMeta(Arsse::$user->id); foreach (Arsse::$db->subscriptionList(Arsse::$user->id) as $r) { - $out[] = $this->transformFeed($r, $folders); + $out[] = $this->transformFeed($r, $meta['num'], $meta['root']); } return new Response($out); } @@ -711,35 +709,30 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { $folder = ((int) $path[1]) - 1; // unless the folder is root, list recursive $recursive = $folder > 0; + $out = []; $tr = Arsse::$db->begin(); - // get the list of subscriptions, or bail\ + // get the list of subscriptions, or bail try { - $subs = Arsse::$db->subscriptionList(Arsse::$user->id, $folder, $recursive)->getAll(); + $meta = $this->userMeta(Arsse::$user->id); + foreach (Arsse::$db->subscriptionList(Arsse::$user->id, $folder, $recursive) as $r) { + $out[] = $this->transformFeed($r, $meta['num'], $meta['root']); + } } catch (ExceptionInput $e) { // the folder does not exist return new ErrorResponse("404", 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 getFeed(array $path): ResponseInterface { $tr = Arsse::$db->begin(); + $meta = $this->userMeta(Arsse::$user->id); try { $sub = Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]); + return new Response($this->transformFeed($sub, $meta['num'], $meta['root'])); } catch (ExceptionInput $e) { return new ErrorResponse("404", 404); } - // compile the list of folders; the feed list includes folder names - $folders = $this->mapFolders(); - return new Response($this->transformFeed($sub, $folders)); } protected function createFeed(array $data): ResponseInterface { diff --git a/tests/cases/Database/SeriesSubscription.php b/tests/cases/Database/SeriesSubscription.php index e4a2b099..f235a916 100644 --- a/tests/cases/Database/SeriesSubscription.php +++ b/tests/cases/Database/SeriesSubscription.php @@ -314,22 +314,26 @@ trait SeriesSubscription { public function testListSubscriptions(): void { $exp = [ [ - 'url' => "http://example.com/feed2", - 'title' => "eek", - 'folder' => null, - 'top_folder' => null, - 'unread' => 4, - 'pinned' => 1, - 'order_type' => 2, + 'url' => "http://example.com/feed2", + 'title' => "eek", + 'folder' => null, + 'top_folder' => null, + 'folder_name' => null, + 'top_folder_name' => null, + 'unread' => 4, + 'pinned' => 1, + 'order_type' => 2, ], [ - 'url' => "http://example.com/feed3", - 'title' => "Ook", - 'folder' => 2, - 'top_folder' => 1, - 'unread' => 2, - 'pinned' => 0, - 'order_type' => 1, + 'url' => "http://example.com/feed3", + 'title' => "Ook", + 'folder' => 2, + 'top_folder' => 1, + 'folder_name' => "Software", + 'top_folder_name' => "Technology", + 'unread' => 2, + 'pinned' => 0, + 'order_type' => 1, ], ]; $this->assertResult($exp, Arsse::$db->subscriptionList($this->user)); diff --git a/tests/cases/REST/Miniflux/TestV1.php b/tests/cases/REST/Miniflux/TestV1.php index d04f45a3..818bffc1 100644 --- a/tests/cases/REST/Miniflux/TestV1.php +++ b/tests/cases/REST/Miniflux/TestV1.php @@ -67,8 +67,8 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { ], ]; 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], + ['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, 'folder_name' => "Cat Eek", 'top_folder_name' => "Cat Ook", '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, 'folder_name' => null, 'top_folder_name' => 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]], @@ -545,18 +545,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { } 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($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")); @@ -564,7 +558,6 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { } 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")); @@ -580,7 +573,6 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { public function testGetAFeed(): void { \Phake::when(Arsse::$db)->subscriptionPropertiesGet->thenReturn($this->v($this->feeds[0]))->thenReturn($this->v($this->feeds[1])); - \Phake::when(Arsse::$db)->folderList->thenReturn(new Result($this->v([['id' => 5, 'name' => "Cat Ook"],]))); $this->assertMessage(new Response($this->feedsOut[0]), $this->req("GET", "/feeds/1")); \Phake::verify(Arsse::$db)->subscriptionPropertiesGet(Arsse::$user->id, 1); $this->assertMessage(new Response($this->feedsOut[1]), $this->req("GET", "/feeds/55"));