diff --git a/AUTHORS.md b/AUTHORS.md index 7ac9408e14..4e305b9edf 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -175,6 +175,7 @@ * [markusj](mailto:markusj@users.noreply.github.com) * [mnassabain](mailto:34754819+mnassabain@users.noreply.github.com) * [mormegil](mailto:mormegil@centrum.cz) +* [nextcloud486153](mailto:78801830+nextcloud486153@users.noreply.github.com) * [nexus-uw](mailto:you@example.com) * [repat](mailto:repat@repat.de) * [ritchiewilson](mailto:rawilson52@gmail.com) diff --git a/lib/Db/FeedMapperV2.php b/lib/Db/FeedMapperV2.php index bab170f792..9c90b6f4e5 100644 --- a/lib/Db/FeedMapperV2.php +++ b/lib/Db/FeedMapperV2.php @@ -29,6 +29,7 @@ class FeedMapperV2 extends NewsMapperV2 { const TABLE_NAME = 'news_feeds'; + const USER_TABLE_NAME = 'news_user_feeds'; /** * FeedMapper constructor. diff --git a/lib/Db/ItemMapperV2.php b/lib/Db/ItemMapperV2.php index 8a92354d80..8a337e0c14 100644 --- a/lib/Db/ItemMapperV2.php +++ b/lib/Db/ItemMapperV2.php @@ -31,6 +31,7 @@ class ItemMapperV2 extends NewsMapperV2 { const TABLE_NAME = 'news_items'; + const USER_TABLE_NAME = 'news_user_items'; /** * ItemMapper constructor. @@ -56,9 +57,8 @@ public function findAllFromUser(string $userId, array $params = []): array $builder = $this->db->getQueryBuilder(); $builder->select('items.*') ->from($this->tableName, 'items') - ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') - ->where('feeds.user_id = :user_id') - ->andWhere('feeds.deleted_at = 0') + ->innerJoin('items', self::USER_TABLE_NAME, 'users', 'items.id = users.item_id') + ->where('users.user_id = :user_id') ->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR); foreach ($params as $key => $value) { @@ -92,10 +92,10 @@ public function findFromUser(string $userId, int $id): Entity $builder = $this->db->getQueryBuilder(); $builder->select('items.*') ->from($this->tableName, 'items') - ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') - ->where('feeds.user_id = :user_id') + ->innerJoin('items', self::USER_TABLE_NAME, 'users', 'items.id = users.item_id') + ->where('users.user_id = :user_id') ->andWhere('items.id = :item_id') - ->andWhere('feeds.deleted_at = 0') + ->andWhere('users.deleted_at = 0') ->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR) ->setParameter('item_id', $id, IQueryBuilder::PARAM_INT); @@ -444,9 +444,10 @@ public function findAllFeed( ): array { $builder = $this->db->getQueryBuilder(); - $builder->select('items.*') + $builder->select('items.*', 'users.unread', 'users.starred', 'users.shared_by') ->from($this->tableName, 'items') ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->innerJoin('items', self::USER_TABLE_NAME, 'users', 'items.id = users.item_id') ->andWhere('feeds.deleted_at = 0') ->andWhere('feeds.user_id = :userId') ->andWhere('items.feed_id = :feedId') @@ -501,9 +502,10 @@ public function findAllFolder( $folderWhere = $builder->expr()->eq('feeds.folder_id', new Literal($folderId), IQueryBuilder::PARAM_INT); } - $builder->select('items.*') + $builder->select('items.*', 'users.unread', 'users.starred', 'users.shared_by') ->from($this->tableName, 'items') ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->innerJoin('items', self::USER_TABLE_NAME, 'users', 'items.id = users.item_id') ->andWhere('feeds.user_id = :userId') ->andWhere('feeds.deleted_at = 0') ->andWhere($folderWhere) @@ -550,11 +552,10 @@ public function findAllItems( ): array { $builder = $this->db->getQueryBuilder(); - $builder->select('items.*') + $builder->select('items.*', 'users.unread', 'users.starred', 'users.shared_by') ->from($this->tableName, 'items') - ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') - ->andWhere('feeds.user_id = :userId') - ->andWhere('feeds.deleted_at = 0') + ->innerJoin('items', self::USER_TABLE_NAME, 'users', 'items.id = users.item_id') + ->andWhere('users.user_id = :userId') ->setParameter('userId', $userId) ->addOrderBy('items.id', ($oldestFirst ? 'ASC' : 'DESC')); diff --git a/lib/Migration/Version160100Date20210821130702.php b/lib/Migration/Version160100Date20210821130702.php new file mode 100644 index 0000000000..6ed600672e --- /dev/null +++ b/lib/Migration/Version160100Date20210821130702.php @@ -0,0 +1,141 @@ +connection = $connection; + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if (!$schema->hasTable('news_user_items')) { + $table = $schema->createTable('news_user_items'); + $table->addColumn('item_id', 'bigint', [ + 'notnull' => true, + 'length' => 8, + 'unsigned' => true, + ]); + $table->addColumn('user_id', 'string', [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('unread', 'boolean', [ + 'notnull' => true, + 'default' => false, + ]); + $table->addColumn('starred', 'boolean', [ + 'notnull' => true, + 'default' => false, + ]); + $table->addColumn('last_modified', 'bigint', [ + 'notnull' => false, + 'length' => 8, + 'default' => 0, + 'unsigned' => true, + ]); + $table->addColumn('shared_by', 'string', [ + 'notnull' => false, + 'length' => 64 + ]); + $table->setPrimaryKey(['item_id', 'user_id']); + } + + if (!$schema->hasTable('news_user_feeds')) { + $table = $schema->createTable('news_user_feeds'); + $table->addColumn('feed_id', 'bigint', [ + 'notnull' => true, + 'length' => 8, + 'unsigned' => true, + ]); + $table->addColumn('user_id', 'string', [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('folder_id', 'bigint', [ + 'notnull' => false, + 'length' => 8, + ]); + $table->addColumn('deleted_at', 'bigint', [ + 'notnull' => false, + 'length' => 8, + 'default' => 0, + 'unsigned' => true, + ]); + $table->addColumn('added', 'bigint', [ + 'notnull' => false, + 'length' => 8, + 'default' => 0, + 'unsigned' => true, + ]); + $table->addColumn('title', 'text', [ + 'notnull' => true, + ]); + $table->addColumn('last_modified', 'bigint', [ + 'notnull' => false, + 'length' => 8, + 'default' => 0, + 'unsigned' => true, + ]); + $table->setPrimaryKey(['feed_id', 'user_id']); + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + $qb = $this->connection->getQueryBuilder(); + $user_item_table = $qb->getTableName('news_user_items'); + $user_feed_table = $qb->getTableName('news_user_feeds'); + $item_table = $qb->getTableName('news_items'); + $feed_table = $qb->getTableName('news_feeds'); + + $items_query = "REPLACE INTO $user_item_table SELECT id AS 'item_id', ? AS 'user_id',`unread`,`starred`,`last_modified`,`shared_by` FROM $item_table where feed_id = ?;"; + + $feeds = $this->connection->executeQuery("SELECT `id`,`user_id` FROM $feed_table;")->fetchAll(); + foreach ($feeds as $feed) { + $this->connection->executeUpdate($items_query, [$feed['user_id'], $feed['id']]); + } + + $this->connection->executeUpdate("REPLACE INTO $user_feed_table SELECT id AS 'feed_id',user_id,folder_id,deleted_at,added,title,last_modified FROM $feed_table;"); + } +} diff --git a/tests/Unit/Db/ItemMapperPaginatedTest.php b/tests/Unit/Db/ItemMapperPaginatedTest.php index 9d44e0756e..f25f19b306 100644 --- a/tests/Unit/Db/ItemMapperPaginatedTest.php +++ b/tests/Unit/Db/ItemMapperPaginatedTest.php @@ -64,15 +64,13 @@ public function testFindAllItemsInvalid() $this->builder->expects($this->exactly(1)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive(['items', 'news_user_items', 'users', 'items.id = users.item_id']) ->will($this->returnSelf()); - $this->builder->expects($this->exactly(3)) + $this->builder->expects($this->exactly(2)) ->method('andWhere') ->withConsecutive( - ['feeds.user_id = :userId'], - ['feeds.deleted_at = 0'] - ) + ['users.user_id = :userId']) ->will($this->returnSelf()); $this->builder->expects($this->exactly(2)) @@ -129,14 +127,13 @@ public function testFindAllItemsFullInverted() $this->builder->expects($this->exactly(1)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive(['items', 'news_user_items', 'users', 'items.id = users.item_id']) ->will($this->returnSelf()); - $this->builder->expects($this->exactly(3)) + $this->builder->expects($this->exactly(2)) ->method('andWhere') ->withConsecutive( - ['feeds.user_id = :userId'], - ['feeds.deleted_at = 0'], + ['users.user_id = :userId'], ['items.id > :offset'] ) ->will($this->returnSelf()); @@ -196,14 +193,13 @@ public function testFindAllItemsUnread() $this->builder->expects($this->exactly(1)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive(['items', 'news_user_items', 'users', 'items.id = users.item_id']) ->will($this->returnSelf()); - $this->builder->expects($this->exactly(4)) + $this->builder->expects($this->exactly(3)) ->method('andWhere') ->withConsecutive( - ['feeds.user_id = :userId'], - ['feeds.deleted_at = 0'], + ['users.user_id = :userId'], ['items.id < :offset'], ['items.unread = :unread'] ) @@ -267,14 +263,13 @@ public function testFindAllItemsUnreadNoLimit() $this->builder->expects($this->exactly(1)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive(['items', 'news_user_items', 'users', 'items.id = users.item_id']) ->will($this->returnSelf()); - $this->builder->expects($this->exactly(4)) + $this->builder->expects($this->exactly(3)) ->method('andWhere') ->withConsecutive( - ['feeds.user_id = :userId'], - ['feeds.deleted_at = 0'], + ['users.user_id = :userId'], ['items.id < :offset'], ['items.unread = :unread'] ) @@ -336,14 +331,13 @@ public function testFindAllItemsStarred() $this->builder->expects($this->exactly(1)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive(['items', 'news_user_items', 'users', 'items.id = users.item_id']) ->will($this->returnSelf()); - $this->builder->expects($this->exactly(4)) + $this->builder->expects($this->exactly(3)) ->method('andWhere') ->withConsecutive( - ['feeds.user_id = :userId'], - ['feeds.deleted_at = 0'], + ['users.user_id = :userId'], ['items.id < :offset'], ['items.starred = :starred'] ) @@ -411,14 +405,13 @@ public function testFindAllItemsStarredSearch() $this->builder->expects($this->exactly(1)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive(['items', 'news_user_items', 'users', 'items.id = users.item_id']) ->will($this->returnSelf()); - $this->builder->expects($this->exactly(6)) + $this->builder->expects($this->exactly(5)) ->method('andWhere') ->withConsecutive( - ['feeds.user_id = :userId'], - ['feeds.deleted_at = 0'], + ['users.user_id = :userId'], ['items.id < :offset'], ['items.search_index LIKE :term0'], ['items.search_index LIKE :term1'], @@ -490,9 +483,12 @@ public function testFindAllFeed() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(4)) @@ -563,9 +559,12 @@ public function testFindAllFeedNoLimit() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(4)) @@ -634,9 +633,12 @@ public function testFindAllFeedInverted() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(4)) @@ -705,9 +707,12 @@ public function testFindAllFeedHideRead() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(5)) @@ -782,9 +787,12 @@ public function testFindAllFeedSearch() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(6)) @@ -875,9 +883,12 @@ public function testFindAllFolderIdNull() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(4)) @@ -960,9 +971,12 @@ public function testFindAllFolderIdNullNoLimit() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(4)) @@ -1043,9 +1057,12 @@ public function testFindAllFolderHideRead() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(5)) @@ -1129,9 +1146,12 @@ public function testFindAllFolderHideReadInvertOrder() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(5)) @@ -1218,9 +1238,12 @@ public function testFindAllFolderSearchId() ->with('news_items', 'items') ->will($this->returnSelf()); - $this->builder->expects($this->exactly(1)) + $this->builder->expects($this->exactly(2)) ->method('innerJoin') - ->withConsecutive(['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id']) + ->withConsecutive( + ['items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id'], + ['items', 'news_user_items', 'users', 'items.id = users.item_id'] + ) ->will($this->returnSelf()); $this->builder->expects($this->exactly(6)) diff --git a/tests/Unit/Db/ItemMapperTest.php b/tests/Unit/Db/ItemMapperTest.php index 7fc45018e2..5a9241a5eb 100644 --- a/tests/Unit/Db/ItemMapperTest.php +++ b/tests/Unit/Db/ItemMapperTest.php @@ -88,17 +88,15 @@ public function testFindAllFromUser() $this->builder->expects($this->once()) ->method('innerJoin') - ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->withConsecutive( + ['items', 'news_user_items', 'users', 'items.id = users.item_id'], + ['a', 'a', 'a', 'a'] + ) ->will($this->returnSelf()); $this->builder->expects($this->once()) ->method('where') - ->with('feeds.user_id = :user_id') - ->will($this->returnSelf()); - - $this->builder->expects($this->once()) - ->method('andWhere') - ->with('feeds.deleted_at = 0') + ->with('users.user_id = :user_id') ->will($this->returnSelf()); $this->builder->expects($this->exactly(1)) @@ -149,17 +147,12 @@ public function testFindAllFromUserWithParams() $this->builder->expects($this->once()) ->method('innerJoin') - ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->with('items', 'news_user_items', 'users', 'items.id = users.item_id') ->will($this->returnSelf()); $this->builder->expects($this->once()) ->method('where') - ->with('feeds.user_id = :user_id') - ->will($this->returnSelf()); - - $this->builder->expects($this->exactly(2)) - ->method('andWhere') - ->withConsecutive(['feeds.deleted_at = 0'], ['key = :val']) + ->with('users.user_id = :user_id') ->will($this->returnSelf()); $this->builder->expects($this->exactly(1)) @@ -291,17 +284,17 @@ public function testFindFromUser() $this->builder->expects($this->once()) ->method('innerJoin') - ->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id') + ->with('items', 'news_user_items', 'users', 'items.id = users.item_id') ->will($this->returnSelf()); $this->builder->expects($this->once()) ->method('where') - ->with('feeds.user_id = :user_id') + ->with('users.user_id = :user_id') ->will($this->returnSelf()); $this->builder->expects($this->exactly(2)) ->method('andWhere') - ->withConsecutive(['items.id = :item_id'], ['feeds.deleted_at = 0']) + ->withConsecutive(['items.id = :item_id'], ['users.deleted_at = 0']) ->will($this->returnSelf()); $this->builder->expects($this->exactly(2))