From fbbf7512147863cac755181deb125d1f8ac1c4a1 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Fri, 13 Oct 2017 17:05:06 -0400 Subject: [PATCH] Implement the TTRSS getLabels operation; fixes #89 --- lib/Database.php | 36 +++++++++----- lib/REST/TinyTinyRSS/API.php | 21 +++++++++ tests/REST/TinyTinyRSS/TestTinyTinyAPI.php | 55 ++++++++++++++++++++-- tests/lib/Database/SeriesArticle.php | 15 ++++++ 4 files changed, 111 insertions(+), 16 deletions(-) diff --git a/lib/Database.php b/lib/Database.php index 71e85cb..f8ef9da 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -937,6 +937,17 @@ class Database { return $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues()); } + public function articleCount(string $user, Context $context = null): int { + if (!Arsse::$user->authorize($user, __FUNCTION__)) { + throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + } + $context = $context ?? new Context; + $q = $this->articleQuery($user, $context); + $q->pushCTE("selected_articles"); + $q->setBody("SELECT count(*) from selected_articles"); + return $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getValue(); + } + public function articleMark(string $user, array $data, Context $context = null): bool { if (!Arsse::$user->authorize($user, __FUNCTION__)) { throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); @@ -1000,17 +1011,6 @@ class Database { return (bool) $out; } - public function articleCount(string $user, Context $context = null): int { - if (!Arsse::$user->authorize($user, __FUNCTION__)) { - throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); - } - $context = $context ?? new Context; - $q = $this->articleQuery($user, $context); - $q->pushCTE("selected_articles"); - $q->setBody("SELECT count(*) from selected_articles"); - return $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->getValue(); - } - public function articleStarred(string $user): array { if (!Arsse::$user->authorize($user, __FUNCTION__)) { throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); @@ -1026,6 +1026,20 @@ class Database { )->run($user)->getRow(); } + public function articleLabelsGet(string $user, $id, bool $byName = false): array { + if (!Arsse::$user->authorize($user, __FUNCTION__)) { + throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + } + $id = $this->articleValidateId($user, $id)['article']; + $out = $this->db->prepare("SELECT id,name from arsse_labels where owner is ? and exists(select id from arsse_label_members where article is ? and label is arsse_labels.id and assigned is 1)", "str", "int")->run($user, $id)->getAll(); + if (!$out) { + return $out; + } else { + // flatten the result to return just the label ID or name + return array_column($out, !$byName ? "id" : "name"); + } + } + public function articleCleanup(): bool { $query = $this->db->prepare( "WITH target_feed(id,subs) as (". diff --git a/lib/REST/TinyTinyRSS/API.php b/lib/REST/TinyTinyRSS/API.php index b6f10ff..4c3c97d 100644 --- a/lib/REST/TinyTinyRSS/API.php +++ b/lib/REST/TinyTinyRSS/API.php @@ -553,6 +553,27 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler { return ($id * -1 - self::LABEL_OFFSET); } + public function opGetLabels(array $data): array { + // this function doesn't complain about invalid article IDs + $article = (isset($data['article_id']) && ValueInfo::id($data['article_id'])) ? (int) $data['article_id'] : 0; + try { + $list = $article ? Arsse::$db->articleLabelsGet(Arsse::$user->id, $article) : []; + } catch (ExceptionInput $e) { + $list = []; + } + $out = []; + foreach (Arsse::$db->labelList(Arsse::$user->id) as $l) { + $out[] = [ + 'id' => $this->labelOut($l['id']), + 'caption' => $l['name'], + 'fg_color' => "", + 'bg_color' => "", + 'checked' => in_array($l['id'], $list), + ]; + } + return $out; + } + public function opAddLabel(array $data) { $in = [ 'name' => isset($data['caption']) ? $data['caption'] : "", diff --git a/tests/REST/TinyTinyRSS/TestTinyTinyAPI.php b/tests/REST/TinyTinyRSS/TestTinyTinyAPI.php index 77a98f1..e2ac0e9 100644 --- a/tests/REST/TinyTinyRSS/TestTinyTinyAPI.php +++ b/tests/REST/TinyTinyRSS/TestTinyTinyAPI.php @@ -38,13 +38,13 @@ class TestTinyTinyAPI extends Test\AbstractTest { ['id' => 4, 'folder' => 6, 'top_folder' => 3, 'unread' => 6, 'updated' => "2017-10-09 15:58:34", 'favicon' => 'http://example.com/4.png'], ]; protected $labels = [ - ['id' => 5, 'articles' => 0, 'read' => 0], - ['id' => 3, 'articles' => 100, 'read' => 94], - ['id' => 1, 'articles' => 2, 'read' => 0], + ['id' => 5, 'articles' => 0, 'read' => 0, 'name' => "Interesting"], + ['id' => 3, 'articles' => 100, 'read' => 94, 'name' => "Fascinating"], + ['id' => 1, 'articles' => 2, 'read' => 0, 'name' => "Logical"], ]; protected $usedLabels = [ - ['id' => 3, 'articles' => 100, 'read' => 94], - ['id' => 1, 'articles' => 2, 'read' => 0], + ['id' => 3, 'articles' => 100, 'read' => 94, 'name' => "Fascinating"], + ['id' => 1, 'articles' => 2, 'read' => 0, 'name' => "Logical"], ]; protected function respGood($content = null, $seq = 0): Response { @@ -768,4 +768,49 @@ class TestTinyTinyAPI extends Test\AbstractTest { ]; $this->assertResponse($this->respGood($exp), $this->h->dispatch(new Request("POST", "", json_encode($in)))); } + + public function testRetrieveLabelList() { + $in = [ + ['op' => "getLabels", 'sid' => "PriestsOfSyrinx"], + ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 1], + ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 2], + ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 3], + ['op' => "getLabels", 'sid' => "PriestsOfSyrinx", 'article_id' => 4], + ]; + Phake::when(Arsse::$db)->labelList($this->anything())->thenReturn(new Result($this->labels)); + Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 1)->thenReturn([1,3]); + Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 2)->thenReturn([3]); + Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 3)->thenReturn([]); + Phake::when(Arsse::$db)->articleLabelsGet($this->anything(), 4)->thenThrow(new ExceptionInput("idMissing")); + $exp = [ + [ + ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ], + [ + ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => true], + ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => true], + ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ], + [ + ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => true], + ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ], + [ + ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ], + [ + ['id' => -1025, 'caption' => "Logical", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ['id' => -1027, 'caption' => "Fascinating", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ['id' => -1029, 'caption' => "Interesting", 'fg_color' => "", 'bg_color' => "", 'checked' => false], + ], + ]; + for ($a = 0; $a < sizeof($in); $a++) { + $this->assertResponse($this->respGood($exp[$a]), $this->h->dispatch(new Request("POST", "", json_encode($in[$a]))), "Test $a failed"); + } + } } diff --git a/tests/lib/Database/SeriesArticle.php b/tests/lib/Database/SeriesArticle.php index 0ecccfd..83c4d80 100644 --- a/tests/lib/Database/SeriesArticle.php +++ b/tests/lib/Database/SeriesArticle.php @@ -805,4 +805,19 @@ trait SeriesArticle { $this->assertException("notAuthorized", "User", "ExceptionAuthz"); Arsse::$db->editionLatest($this->user); } + + public function testListTheLabelsOfAnArticle() { + $this->assertEquals([2,1], Arsse::$db->articleLabelsGet("john.doe@example.com", 1)); + $this->assertEquals([2], Arsse::$db->articleLabelsGet("john.doe@example.com", 5)); + $this->assertEquals([], Arsse::$db->articleLabelsGet("john.doe@example.com", 2)); + $this->assertEquals(["Fascinating","Interesting"], Arsse::$db->articleLabelsGet("john.doe@example.com", 1, true)); + $this->assertEquals(["Fascinating"], Arsse::$db->articleLabelsGet("john.doe@example.com", 5, true)); + $this->assertEquals([], Arsse::$db->articleLabelsGet("john.doe@example.com", 2, true)); + } + + public function testListTheLabelsOfAnArticleWithoutAuthority() { + Phake::when(Arsse::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Arsse::$db->articleLabelsGet("john.doe@example.com", 1); + } }