mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Implement contexts for non-recursive folders, and any/no label
Adjusted TTRSS handler accordingly
This commit is contained in:
parent
de92fb514b
commit
6c8598d897
5 changed files with 40 additions and 54 deletions
|
@ -845,6 +845,11 @@ class Database {
|
||||||
$q->setCTE("folders(folder)", "SELECT ? union select id from arsse_folders join folders on parent is folder", "int", $context->folder);
|
$q->setCTE("folders(folder)", "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
|
// add another CTE for the subscriptions within the folder
|
||||||
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner join folders on arsse_subscriptions.folder is folders.folder", [], [], "join subscribed_feeds on feed is subscribed_feeds.id");
|
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner join folders on arsse_subscriptions.folder is folders.folder", [], [], "join subscribed_feeds on feed is subscribed_feeds.id");
|
||||||
|
} elseif ($context->folderShallow()) {
|
||||||
|
// if a shallow folder is specified, make sure it exists
|
||||||
|
$this->folderValidateId($user, $context->folderShallow);
|
||||||
|
// if it does exist, add a CTE with only its subscriptions (and not those of its descendents)
|
||||||
|
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner and coalesce(folder,0) is ?", "strict int", $context->folderShallow, "join subscribed_feeds on feed is subscribed_feeds.id");
|
||||||
} else {
|
} else {
|
||||||
// otherwise add a CTE for all the user's subscriptions
|
// otherwise add a CTE for all the user's subscriptions
|
||||||
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner", [], [], "join subscribed_feeds on feed is subscribed_feeds.id");
|
$q->setCTE("subscribed_feeds(id,sub)", "SELECT feed,id from arsse_subscriptions join user on user is owner", [], [], "join subscribed_feeds on feed is subscribed_feeds.id");
|
||||||
|
@ -889,7 +894,11 @@ class Database {
|
||||||
$q->setCTE("requested_articles(id,edition)", "SELECT 'empty','table' where 1 is 0");
|
$q->setCTE("requested_articles(id,edition)", "SELECT 'empty','table' where 1 is 0");
|
||||||
}
|
}
|
||||||
// filter based on label by ID or name
|
// filter based on label by ID or name
|
||||||
if ($context->label() || $context->labelName()) {
|
if ($context->labelled()) {
|
||||||
|
// any label (true) or no label (false)
|
||||||
|
$q->setWhere((!$context->labelled ? "not " : "")."exists(select article from arsse_label_members where assigned is 1 and article is arsse_articles.id and subscription in (select sub from subscribed_feeds))");
|
||||||
|
} elseif ($context->label() || $context->labelName()) {
|
||||||
|
// specific label ID or name
|
||||||
if ($context->label()) {
|
if ($context->label()) {
|
||||||
$id = $this->labelValidateId($user, $context->label, false)['id'];
|
$id = $this->labelValidateId($user, $context->label, false)['id'];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Context {
|
||||||
public $limit = 0;
|
public $limit = 0;
|
||||||
public $offset = 0;
|
public $offset = 0;
|
||||||
public $folder;
|
public $folder;
|
||||||
|
public $folderShallow;
|
||||||
public $subscription;
|
public $subscription;
|
||||||
public $oldestEdition;
|
public $oldestEdition;
|
||||||
public $latestEdition;
|
public $latestEdition;
|
||||||
|
@ -23,6 +24,7 @@ class Context {
|
||||||
public $articles;
|
public $articles;
|
||||||
public $label;
|
public $label;
|
||||||
public $labelName;
|
public $labelName;
|
||||||
|
public $labelled = null;
|
||||||
|
|
||||||
protected $props = [];
|
protected $props = [];
|
||||||
|
|
||||||
|
@ -64,6 +66,10 @@ class Context {
|
||||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function folderShallow(int $spec = null) {
|
||||||
|
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||||
|
}
|
||||||
|
|
||||||
public function subscription(int $spec = null) {
|
public function subscription(int $spec = null) {
|
||||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||||
}
|
}
|
||||||
|
@ -123,4 +129,8 @@ class Context {
|
||||||
public function labelName(string $spec = null) {
|
public function labelName(string $spec = null) {
|
||||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function labelled(bool $spec = null) {
|
||||||
|
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1039,35 +1039,12 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
// not valid
|
// not valid
|
||||||
return $out;
|
return $out;
|
||||||
case self::CAT_UNCATEGORIZED:
|
case self::CAT_UNCATEGORIZED:
|
||||||
// this is a special case
|
// this requires a shallow context since in TTRSS folder zero/null is apart from the tree rather than at the root
|
||||||
try {
|
$c->folderShallow(0);
|
||||||
$tr = Arsse::$db->begin();
|
break;
|
||||||
// filter the subscription list to return only uncategorized, and get their IDs
|
|
||||||
$list = array_column(Arsse::$db->subscriptionList(Arsse::$user->id, null, false)->getAll(), "id");
|
|
||||||
// perform marking for each applicable subscription
|
|
||||||
foreach ($list as $id) {
|
|
||||||
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], (new Context)->subscription($id));
|
|
||||||
}
|
|
||||||
$tr->commit();
|
|
||||||
} catch (ExceptionInput $e) { // @codeCoverageIgnore
|
|
||||||
// ignore errors; none should occur
|
|
||||||
}
|
|
||||||
return $out;
|
|
||||||
case self::CAT_LABELS:
|
case self::CAT_LABELS:
|
||||||
// this is also a special case
|
$c->labelled(true);
|
||||||
try {
|
break;
|
||||||
$tr = Arsse::$db->begin();
|
|
||||||
// list all non-empty labels
|
|
||||||
$list = array_column(Arsse::$db->labelList(Arsse::$user->id, false)->getAll(), "id");
|
|
||||||
// perform marking for each label
|
|
||||||
foreach ($list as $id) {
|
|
||||||
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], (new Context)->label($id));
|
|
||||||
}
|
|
||||||
$tr->commit();
|
|
||||||
} catch (ExceptionInput $e) { // @codeCoverageIgnore
|
|
||||||
// ignore errors; none should occur
|
|
||||||
}
|
|
||||||
return $out;
|
|
||||||
default:
|
default:
|
||||||
// any actual category
|
// any actual category
|
||||||
$c->folder($id);
|
$c->folder($id);
|
||||||
|
|
|
@ -941,13 +941,10 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
||||||
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112],
|
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2112],
|
||||||
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112],
|
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 2112],
|
||||||
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true],
|
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 42, 'is_cat' => true],
|
||||||
];
|
|
||||||
$in3 = [
|
|
||||||
// complex context
|
|
||||||
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0, 'is_cat' => true],
|
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => 0, 'is_cat' => true],
|
||||||
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true],
|
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -2, 'is_cat' => true],
|
||||||
];
|
];
|
||||||
$in4 = [
|
$in3 = [
|
||||||
// this one has a tricky time-based context
|
// this one has a tricky time-based context
|
||||||
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
|
['op' => "catchupFeed", 'sid' => "PriestsOfSyrinx", 'feed_id' => -3],
|
||||||
];
|
];
|
||||||
|
@ -967,25 +964,12 @@ class TestTinyTinyAPI extends Test\AbstractTest {
|
||||||
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->label(1088));
|
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->label(1088));
|
||||||
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->subscription(2112));
|
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->subscription(2112));
|
||||||
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folder(42));
|
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folder(42));
|
||||||
// reset the database mock
|
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->folderShallow(0));
|
||||||
$this->setUp();
|
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->labelled(true));
|
||||||
Phake::when(Arsse::$db)->articleMark->thenReturn(42);
|
|
||||||
Phake::when(Arsse::$db)->subscriptionList->thenReturn(new Result($this->subscriptions));
|
|
||||||
Phake::when(Arsse::$db)->subscriptionList($this->anything(), null, false)->thenReturn(new Result($this->filterSubs(null)));
|
|
||||||
Phake::when(Arsse::$db)->labelList->thenReturn(new Result($this->labels));
|
|
||||||
Phake::when(Arsse::$db)->labelList($this->anything(), false)->thenReturn(new Result($this->usedLabels));
|
|
||||||
// verify the complex contexts
|
|
||||||
for ($a = 0; $a < sizeof($in3); $a++) {
|
|
||||||
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "", json_encode($in3[$a]))), "Test $a failed");
|
|
||||||
}
|
|
||||||
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->subscription(6));
|
|
||||||
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->label(3));
|
|
||||||
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->label(1));
|
|
||||||
Phake::verify(Arsse::$db, Phake::times(3))->articleMark;
|
|
||||||
// verify the time-based mock
|
// verify the time-based mock
|
||||||
$t = Date::sub("PT24H");
|
$t = Date::sub("PT24H");
|
||||||
for ($a = 0; $a < sizeof($in4); $a++) {
|
for ($a = 0; $a < sizeof($in3); $a++) {
|
||||||
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "", json_encode($in4[$a]))), "Test $a failed");
|
$this->assertResponse($exp, $this->h->dispatch(new Request("POST", "", json_encode($in3[$a]))), "Test $a failed");
|
||||||
}
|
}
|
||||||
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->modifiedSince($t));
|
Phake::verify(Arsse::$db)->articleMark($this->anything(), ['read' => true], (new Context)->modifiedSince($t));
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,6 +237,8 @@ trait SeriesArticle {
|
||||||
[2,20,5,1],
|
[2,20,5,1],
|
||||||
[1, 5,3,0],
|
[1, 5,3,0],
|
||||||
[2, 5,3,1],
|
[2, 5,3,1],
|
||||||
|
[4, 7,4,0],
|
||||||
|
[4, 8,4,1],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -362,11 +364,12 @@ trait SeriesArticle {
|
||||||
$this->compareIds($exp, new Context);
|
$this->compareIds($exp, new Context);
|
||||||
$this->compareIds($exp, (new Context)->articles(range(1, Database::LIMIT_ARTICLES * 3)));
|
$this->compareIds($exp, (new Context)->articles(range(1, Database::LIMIT_ARTICLES * 3)));
|
||||||
// get items from a folder tree
|
// get items from a folder tree
|
||||||
$exp = [5,6,7,8];
|
$this->compareIds([5,6,7,8], (new Context)->folder(1));
|
||||||
$this->compareIds($exp, (new Context)->folder(1));
|
|
||||||
// get items from a leaf folder
|
// get items from a leaf folder
|
||||||
$exp = [7,8];
|
$this->compareIds([7,8], (new Context)->folder(6));
|
||||||
$this->compareIds($exp, (new Context)->folder(6));
|
// get items from a non-leaf folder without descending
|
||||||
|
$this->compareIds([1,2,3,4], (new Context)->folderShallow(0));
|
||||||
|
$this->compareIds([5,6], (new Context)->folderShallow(1));
|
||||||
// get items from a single subscription
|
// get items from a single subscription
|
||||||
$exp = [19,20];
|
$exp = [19,20];
|
||||||
$this->compareIds($exp, (new Context)->subscription(5));
|
$this->compareIds($exp, (new Context)->subscription(5));
|
||||||
|
@ -405,6 +408,9 @@ trait SeriesArticle {
|
||||||
// label by name
|
// label by name
|
||||||
$this->compareIds([1,19], (new Context)->labelName("Interesting"));
|
$this->compareIds([1,19], (new Context)->labelName("Interesting"));
|
||||||
$this->compareIds([1,5,20], (new Context)->labelName("Fascinating"));
|
$this->compareIds([1,5,20], (new Context)->labelName("Fascinating"));
|
||||||
|
// any or no label
|
||||||
|
$this->compareIds([1,5,8,19,20], (new Context)->labelled(true));
|
||||||
|
$this->compareIds([2,3,4,6,7], (new Context)->labelled(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testListArticlesOfAMissingFolder() {
|
public function testListArticlesOfAMissingFolder() {
|
||||||
|
|
Loading…
Reference in a new issue