diff --git a/lib/Database.php b/lib/Database.php index 4da524a..f297675 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -86,13 +86,13 @@ class Database { return $out; } - public function settingGet(string $key) { - return $this->db->prepare("SELECT value from arsse_settings where key is ?", "str")->run($key)->getValue(); - } - public function begin(): Db\Transaction { return $this->db->begin(); } + + public function settingGet(string $key) { + return $this->db->prepare("SELECT value from arsse_settings where key is ?", "str")->run($key)->getValue(); + } public function settingSet(string $key, string $value): bool { $out = !$this->db->prepare("UPDATE arsse_settings set value = ? where key is ?", "str", "str")->run($value, $key)->changes(); @@ -575,7 +575,17 @@ class Database { public function articleStarredCount(string $user, array $context = []): int { if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); - return $this->db->prepare("SELECT count(*) from arsse_marks where owner is ? and starred is 1", "str")->run($user)->getValue(); + return $this->db->prepare( + "WITH RECURSIVE + user(user) as (SELECT ?), + subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner) ". + "SELECT count(*) from arsse_marks + join user on user is owner + join arsse_articles on arsse_marks.article is arsse_articles.id + join subscribed_feeds on arsse_articles.feed is subscribed_feeds.id + where starred is 1", + "str" + )->run($user)->getValue(); } public function editionLatest(string $user, Context $context = null): int { @@ -589,27 +599,12 @@ class Database { $q->setWhere("arsse_feeds.id is ?", "int", $id); } else { $q->setCTE("user(user) as (SELECT ?)", "str", $user); - if($context->folder()) { - // if a folder is specified, make sure it exists - $this->folderValidateId($user, $context->folder); - // if it does exist, add a common table expression to list it and its children so that we select from the entire subtree - $q->setCTE("folders(folder) as (SELECT ? union select id from arsse_folders join folders on parent is folder)", "int", $context->folder); - // add another CTE for the subscriptions within the folder - $q->setCTE( - "feeds(feed) as (SELECT feed from arsse_subscriptions join user on user is owner join folders on arsse_subscription.folder is folders.folder)", - [], // binding types - [], // binding values - "join feeds on arsse_articles.feed is feeds.feed" // join expression - ); - } else { - // if no folder is specified, a single CTE is added - $q->setCTE( - "feeds(feed) as (SELECT feed from arsse_subscriptions join user on user is owner)", - [], // binding types - [], // binding values - "join feeds on arsse_articles.feed is feeds.feed" // join expression - ); - } + $q->setCTE( + "feeds(feed) as (SELECT feed from arsse_subscriptions join user on user is owner)", + [], // binding types + [], // binding values + "join feeds on arsse_articles.feed is feeds.feed" // join expression + ); } return (int) $this->db->prepare($q)->run()->getValue(); } @@ -797,7 +792,7 @@ class Database { arsse_articles.id is ? and arsse_subscriptions.owner is ?", "int", "str" )->run($id, $user)->getRow(); - if(!$out) throw new Db\ExceptionInput("idMissing", ["action" => $this->caller(), "field" => "article", 'id' => $id]); + if(!$out) throw new Db\ExceptionInput("subjectMissing", ["action" => $this->caller(), "field" => "article", 'id' => $id]); return $out; } @@ -815,7 +810,7 @@ class Database { edition is ? and arsse_subscriptions.owner is ?", "int", "str" )->run($id, $user)->getRow(); - if(!$out) throw new Db\ExceptionInput("idMissing", ["action" => $this->caller(), "field" => "edition", 'id' => $id]); + if(!$out) throw new Db\ExceptionInput("subjectMissing", ["action" => $this->caller(), "field" => "edition", 'id' => $id]); return $out; } } \ No newline at end of file diff --git a/tests/lib/Database/SeriesArticle.php b/tests/lib/Database/SeriesArticle.php index 0dead0c..78d4cce 100644 --- a/tests/lib/Database/SeriesArticle.php +++ b/tests/lib/Database/SeriesArticle.php @@ -167,6 +167,7 @@ trait SeriesArticle { ["john.doe@example.net", 20,1,0,'2017-01-01 00:00:00'], ["john.doe@example.net", 3,0,1,'2017-01-01 00:00:00'], ["john.doe@example.net", 4,1,1,'2017-01-01 00:00:00'], + ["john.doe@example.net", 12,0,1,'2017-01-01 00:00:00'], // user is no longer subscribed to this article's feed; the star should not be counted in articleStarredCount ] ], ]; @@ -327,12 +328,28 @@ trait SeriesArticle { $this->compareIds([7,6], (new Context)->reverse(true)->limit(2)->latestEdition(8-1)); } + function testListArticlesOfAMissingFolder() { + $this->assertException("idMissing", "Db", "ExceptionInput"); + Data::$db->articleList($this->user, (new Context)->folder(1)); + } + + function testListArticlesOfAMissingSubscription() { + $this->assertException("idMissing", "Db", "ExceptionInput"); + Data::$db->articleList($this->user, (new Context)->subscription(1)); + } + function testListArticlesCheckingProperties() { $this->user = "john.doe@example.org"; Data::$db->dateFormatDefault("unix"); $this->assertResult($this->matches, Data::$db->articleList($this->user)); } + function testListArticlesWithoutAuthority() { + Phake::when(Data::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Data::$db->articleList($this->user); + } + function testMarkAllArticlesUnread() { Data::$db->articleMark($this->user, ['read'=>false]); $now = $this->dateTransform(time(), "sql"); @@ -502,7 +519,7 @@ trait SeriesArticle { } function testMarkAMissingArticle() { - $this->assertException("idMissing", "Db", "ExceptionInput"); + $this->assertException("subjectMissing", "Db", "ExceptionInput"); Data::$db->articleMark($this->user, ['starred'=>true], (new Context)->article(1)); } @@ -546,7 +563,7 @@ trait SeriesArticle { } function testMarkAMissingEdition() { - $this->assertException("idMissing", "Db", "ExceptionInput"); + $this->assertException("subjectMissing", "Db", "ExceptionInput"); Data::$db->articleMark($this->user, ['starred'=>true], (new Context)->edition(2)); } @@ -593,4 +610,39 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [$this->user,7,0,1,$now]; $this->compareExpectations($state); } + + function testMarkArticlesWithoutAuthority() { + Phake::when(Data::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Data::$db->articleMark($this->user, ['read'=>false]); + } + + function testCountStarredArticles() { + $this->assertSame(2, Data::$db->articleStarredCount("john.doe@example.com")); + $this->assertSame(2, Data::$db->articleStarredCount("john.doe@example.org")); + $this->assertSame(2, Data::$db->articleStarredCount("john.doe@example.net")); + $this->assertSame(0, Data::$db->articleStarredCount("jane.doe@example.com")); + } + + function testCountStarredArticlesWithoutAuthority() { + Phake::when(Data::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Data::$db->articleStarredCount($this->user); + } + + function testFetchLatestEdition() { + $this->assertSame(1001, Data::$db->editionLatest($this->user)); + $this->assertSame(4, Data::$db->editionLatest($this->user, (new Context)->subscription(12))); + } + + function testFetchLatestEditionOfMissingSubscription() { + $this->assertException("idMissing", "Db", "ExceptionInput"); + Data::$db->editionLatest($this->user, (new Context)->subscription(1)); + } + + function testFetchLatestEditionWithoutAuthority() { + Phake::when(Data::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Data::$db->editionLatest($this->user); + } } \ No newline at end of file