From 05377d023ef4d43818a4b42039c8143cb0f907e4 Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Sat, 26 Dec 2020 13:09:41 +0100 Subject: Remove PHPunit integration tests Signed-off-by: Sean Molenaar --- CHANGELOG.md | 5 +- lib/Command/Updater/UpdateFeed.php | 2 +- lib/Controller/EntityApiSerializer.php | 76 -- lib/Controller/FeedApiController.php | 26 +- lib/Controller/FeedController.php | 110 +- lib/Controller/FolderController.php | 8 +- lib/Controller/ItemController.php | 10 +- lib/Db/Feed.php | 10 + lib/Db/FeedMapper.php | 202 ---- lib/Db/FeedMapperV2.php | 16 +- lib/Db/ItemMapper.php | 121 ++- lib/Db/ItemMapperV2.php | 5 + lib/Db/NewsMapper.php | 158 --- lib/Fetcher/IFeedFetcher.php | 4 +- lib/Hooks/UserDeleteHook.php | 8 +- lib/Service/FeedService.php | 521 --------- lib/Service/FeedServiceV2.php | 122 +-- lib/Service/FolderServiceV2.php | 59 +- lib/Service/ImportService.php | 125 +++ lib/Service/ItemService.php | 71 +- lib/Service/ItemServiceV2.php | 25 +- lib/Service/OpmlService.php | 7 - lib/Service/Service.php | 48 +- lib/Service/StatusService.php | 5 +- phpunit.xml | 1 + tests/Unit/Command/UpdateFeedTest.php | 6 +- tests/Unit/Controller/EntityApiSerializerTest.php | 144 --- tests/Unit/Controller/FeedApiControllerTest.php | 128 ++- tests/Unit/Controller/FeedControllerTest.php | 265 ++++- tests/Unit/Controller/FolderControllerTest.php | 7 +- tests/Unit/Controller/ItemControllerTest.php | 16 +- tests/Unit/Db/FeedMapperTest.php | 451 ++++++++ tests/Unit/Db/NewsMapperTest.php | 270 +++++ tests/Unit/Service/FeedServiceTest.php | 1162 ++++++++------------- tests/Unit/Service/FolderServiceTest.php | 39 - tests/Unit/Service/ImportServiceTest.php | 232 ++++ tests/Unit/Service/ItemServiceTest.php | 72 +- tests/Unit/Service/OPMLServiceTest.php | 143 +++ tests/Unit/Service/ServiceTest.php | 66 +- tests/Unit/Service/StatusServiceTest.php | 23 +- 40 files changed, 2382 insertions(+), 2387 deletions(-) delete mode 100644 lib/Controller/EntityApiSerializer.php delete mode 100644 lib/Db/FeedMapper.php delete mode 100644 lib/Db/NewsMapper.php delete mode 100644 lib/Service/FeedService.php create mode 100644 lib/Service/ImportService.php delete mode 100644 tests/Unit/Controller/EntityApiSerializerTest.php create mode 100644 tests/Unit/Db/FeedMapperTest.php create mode 100644 tests/Unit/Db/NewsMapperTest.php create mode 100644 tests/Unit/Service/ImportServiceTest.php create mode 100644 tests/Unit/Service/OPMLServiceTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6874d02f6..a875cec74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog All notable changes to this project will be documented in this file. - The format is almost based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), older entries don't fully match. ## [Unreleased] +### Changed +- Remove outdated feed DB code +- add background & hover for entries + ## [15.1.1] - 2020-12-27 ### Changed diff --git a/lib/Command/Updater/UpdateFeed.php b/lib/Command/Updater/UpdateFeed.php index 93fa2a8d3..4ffb8f57a 100644 --- a/lib/Command/Updater/UpdateFeed.php +++ b/lib/Command/Updater/UpdateFeed.php @@ -51,7 +51,7 @@ class UpdateFeed extends Command $feedId = $input->getArgument('feed-id'); $userId = $input->getArgument('user-id'); try { - $feed = $this->feedService->findForUser($userId, $feedId); + $feed = $this->feedService->find($userId, $feedId); $updated_feed = $this->feedService->fetch($feed); } catch (\Exception $e) { $output->writeln( diff --git a/lib/Controller/EntityApiSerializer.php b/lib/Controller/EntityApiSerializer.php deleted file mode 100644 index daa0f20e5..000000000 --- a/lib/Controller/EntityApiSerializer.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Controller; - -use \OCA\News\Db\IAPI; - -/** - * Class EntityApiSerializer - * - * @package OCA\News\Controller - * @deprecated use ApiPayloadTrait - */ -class EntityApiSerializer -{ - - private $level; - - public function __construct($level) - { - $this->level = $level; - } - - - /** - * Call toAPI() method on all entities. Works on - * - * @param mixed $data : - * * Entity - * * Entity[] - * * array('level' => Entity[]) - * * Response - * @return array|mixed - */ - public function serialize($data) - { - - if ($data instanceof IAPI) { - return [$this->level => [$data->toAPI()]]; - } - - if (is_array($data) && array_key_exists($this->level, $data)) { - $data[$this->level] = $this->convert($data[$this->level]); - } elseif (is_array($data)) { - $data = [$this->level => $this->convert($data)]; - } - - return $data; - } - - - private function convert(array $entities) - { - $converted = []; - - foreach ($entities as $entity) { - if ($entity instanceof IAPI) { - $converted[] = $entity->toAPI(); - - // break if it contains anything else than entities - } else { - return $entities; - } - } - - return $converted; - } -} diff --git a/lib/Controller/FeedApiController.php b/lib/Controller/FeedApiController.php index eb6edcad2..af552f411 100644 --- a/lib/Controller/FeedApiController.php +++ b/lib/Controller/FeedApiController.php @@ -25,10 +25,8 @@ use \OCP\IRequest; use \OCP\IUserSession; use \OCP\AppFramework\Http; -use \OCA\News\Service\FeedService; use \OCA\News\Service\ItemService; use Psr\Log\LoggerInterface; -use function GuzzleHttp\Psr7\uri_for; class FeedApiController extends ApiController { @@ -45,12 +43,6 @@ class FeedApiController extends ApiController */ private $feedService; - /** - * TODO: Remove - * @var FeedService - */ - private $oldFeedService; - /** * @var LoggerInterface */ @@ -64,14 +56,12 @@ class FeedApiController extends ApiController public function __construct( IRequest $request, ?IUserSession $userSession, - FeedService $oldFeedService, FeedServiceV2 $feedService, ItemService $oldItemService, LoggerInterface $logger ) { parent::__construct($request, $userSession); $this->feedService = $feedService; - $this->oldFeedService = $oldFeedService; $this->oldItemService = $oldItemService; $this->logger = $logger; } @@ -189,11 +179,9 @@ class FeedApiController extends ApiController } try { - $this->oldFeedService->patch( - $feedId, - $this->getUserId(), - ['folderId' => $folderId] - ); + $feed = $this->feedService->find($this->getUserId(), $feedId); + $feed->setFolderId($folderId); + $this->feedService->update($this->getUserId(), $feed); } catch (ServiceNotFoundException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } @@ -215,11 +203,9 @@ class FeedApiController extends ApiController public function rename(int $feedId, string $feedTitle) { try { - $this->oldFeedService->patch( - $feedId, - $this->getUserId(), - ['title' => $feedTitle] - ); + $feed = $this->feedService->find($this->getUserId(), $feedId); + $feed->setTitle($feedTitle); + $this->feedService->update($this->getUserId(), $feed); } catch (ServiceNotFoundException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } diff --git a/lib/Controller/FeedController.php b/lib/Controller/FeedController.php index 5abcd3393..ba39fdf7a 100644 --- a/lib/Controller/FeedController.php +++ b/lib/Controller/FeedController.php @@ -15,14 +15,15 @@ namespace OCA\News\Controller; use OCA\News\Service\Exceptions\ServiceConflictException; use OCA\News\Service\Exceptions\ServiceNotFoundException; +use OCA\News\Service\FeedServiceV2; use OCA\News\Service\FolderServiceV2; +use OCA\News\Service\ImportService; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; use OCP\IConfig; use OCP\AppFramework\Http; use OCA\News\Service\ItemService; -use OCA\News\Service\FeedService; use OCA\News\Db\FeedType; use OCP\IUserSession; @@ -30,7 +31,9 @@ class FeedController extends Controller { use JSONHttpErrorTrait; - //TODO: Remove + /** + * @var FeedServiceV2 + */ private $feedService; //TODO: Remove private $itemService; @@ -38,6 +41,10 @@ class FeedController extends Controller * @var FolderServiceV2 */ private $folderService; + /** + * @var ImportService + */ + private $importService; /** * @var IConfig */ @@ -46,8 +53,9 @@ class FeedController extends Controller public function __construct( IRequest $request, FolderServiceV2 $folderService, - FeedService $feedService, + FeedServiceV2 $feedService, ItemService $itemService, + ImportService $importService, IConfig $settings, ?IUserSession $userSession ) { @@ -55,6 +63,7 @@ class FeedController extends Controller $this->folderService = $folderService; $this->feedService = $feedService; $this->itemService = $itemService; + $this->importService = $importService; $this->settings = $settings; } @@ -74,12 +83,13 @@ class FeedController extends Controller ]; try { - $params['newestItemId'] = - $this->itemService->getNewestItemId($this->getUserId()); + $id = $this->itemService->getNewestItemId($this->getUserId()); // An exception occurs if there is a newest item. If there is none, // simply ignore it and do not add the newestItemId + $params['newestItemId'] = $id; } catch (ServiceNotFoundException $ex) { + //NO-OP } return $params; @@ -102,24 +112,22 @@ class FeedController extends Controller 'lastViewedFeedType' ); - // cast from null to int is 0 - if ($feedType !== null) { - $feedType = (int) $feedType; - } - // check if feed or folder exists try { - if ($feedType === FeedType::FOLDER) { - if ($feedId === 0) { - $feedId = null; - } - $this->folderService->find($this->getUserId(), $feedId); - } elseif ($feedType === FeedType::FEED) { - $this->feedService->find($this->getUserId(), $feedId); - - // if its the first launch, those values will be null - } elseif ($feedType === null) { - throw new ServiceNotFoundException(''); + if ($feedType === null) { + throw new ServiceNotFoundException('First launch'); + } + + $feedType = intval($feedType); + switch ($feedType) { + case FeedType::FOLDER: + $this->folderService->find($this->getUserId(), $feedId); + break; + case FeedType::FEED: + $this->feedService->find($this->getUserId(), $feedId); + break; + default: + break; } } catch (ServiceNotFoundException $ex) { $feedId = 0; @@ -162,9 +170,10 @@ class FeedController extends Controller $this->feedService->purgeDeleted($this->getUserId(), false); $feed = $this->feedService->create( + $this->getUserId(), $url, $parentFolderId, - $this->getUserId(), + false, $title, $user, $password @@ -172,12 +181,12 @@ class FeedController extends Controller $params = ['feeds' => [$feed]]; try { - $params['newestItemId'] = - $this->itemService->getNewestItemId($this->getUserId()); - + $id = $this->itemService->getNewestItemId($this->getUserId()); // An exception occurs if there is a newest item. If there is none, // simply ignore it and do not add the newestItemId + $params['newestItemId'] = $id; } catch (ServiceNotFoundException $ex) { + //NO-OP } return $params; @@ -199,7 +208,9 @@ class FeedController extends Controller public function delete(int $feedId) { try { - $this->feedService->markDeleted($feedId, $this->getUserId()); + $feed = $this->feedService->find($this->getUserId(), $feedId); + $feed->setDeletedAt(time()); + $this->feedService->update($this->getUserId(), $feed); } catch (ServiceNotFoundException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } @@ -218,7 +229,8 @@ class FeedController extends Controller public function update(int $feedId) { try { - $feed = $this->feedService->update($this->getUserId(), $feedId); + $old_feed = $this->feedService->find($this->getUserId(), $feedId); + $feed = $this->feedService->fetch($old_feed); return [ 'feeds' => [ @@ -244,7 +256,7 @@ class FeedController extends Controller */ public function import(array $json): array { - $feed = $this->feedService->importArticles($json, $this->getUserId()); + $feed = $this->importService->importArticles($this->getUserId(), $json); $params = [ 'starred' => $this->itemService->starredCount($this->getUserId()) @@ -290,7 +302,9 @@ class FeedController extends Controller public function restore(int $feedId) { try { - $this->feedService->unmarkDeleted($feedId, $this->getUserId()); + $feed = $this->feedService->find($this->getUserId(), $feedId); + $feed->setDeletedAt(null); + $this->feedService->update($this->getUserId(), $feed); } catch (ServiceNotFoundException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } @@ -320,24 +334,32 @@ class FeedController extends Controller ?int $folderId = null, ?string $title = null ) { - $attributes = [ - 'pinned' => $pinned, - 'fullTextEnabled' => $fullTextEnabled, - 'updateMode' => $updateMode, - 'ordering' => $ordering, - 'title' => $title, - 'folderId' => $folderId === 0 ? null : $folderId - ]; + try { + $feed = $this->feedService->find($this->getUserId(), $feedId); + } catch (ServiceNotFoundException $ex) { + return $this->error($ex, Http::STATUS_NOT_FOUND); + } - $diff = array_filter( - $attributes, - function ($value) { - return $value !== null; - } - ); + $fId = $folderId === 0 ? null : $folderId; + $feed->setFolderId($fId); + if ($pinned !== null) { + $feed->setPinned($pinned); + } + if ($fullTextEnabled !== null) { + $feed->setFullTextEnabled($fullTextEnabled); + } + if ($updateMode !== null) { + $feed->setUpdateMode($updateMode); + } + if ($ordering !== null) { + $feed->setOrdering($ordering); + } + if ($title !== null) { + $feed->setTitle($title); + } try { - $this->feedService->patch($feedId, $this->getUserId(), $diff); + $this->feedService->update($this->getUserId(), $feed); } catch (ServiceNotFoundException $ex) { return $this->error($ex, Http::STATUS_NOT_FOUND); } diff --git a/lib/Controller/FolderController.php b/lib/Controller/FolderController.php index b33f46c54..acf3f0109 100644 --- a/lib/Controller/FolderController.php +++ b/lib/Controller/FolderController.php @@ -14,12 +14,12 @@ namespace OCA\News\Controller; use OCA\News\Service\Exceptions\ServiceException; +use OCA\News\Service\FeedServiceV2; use OCP\AppFramework\Http\JSONResponse; use \OCP\IRequest; use \OCP\AppFramework\Http; use \OCA\News\Service\FolderServiceV2; -use \OCA\News\Service\FeedService; use \OCA\News\Service\ItemService; use \OCA\News\Service\Exceptions\ServiceNotFoundException; use \OCA\News\Service\Exceptions\ServiceConflictException; @@ -33,7 +33,9 @@ class FolderController extends Controller * @var FolderServiceV2 */ private $folderService; - //TODO: Remove + /** + * @var FeedServiceV2 + */ private $feedService; //TODO: Remove private $itemService; @@ -41,7 +43,7 @@ class FolderController extends Controller public function __construct( IRequest $request, FolderServiceV2 $folderService, - FeedService $feedService, + FeedServiceV2 $feedService, ItemService $itemService, ?IUserSession $userSession ) { diff --git a/lib/Controller/ItemController.php b/lib/Controller/ItemController.php index 7506891d4..8783a5286 100644 --- a/lib/Controller/ItemController.php +++ b/lib/Controller/ItemController.php @@ -13,6 +13,7 @@ namespace OCA\News\Controller; +use OCA\News\Service\FeedServiceV2; use \OCP\IRequest; use \OCP\IConfig; use \OCP\AppFramework\Http; @@ -20,7 +21,6 @@ use \OCP\AppFramework\Http; use \OCA\News\Service\Exceptions\ServiceException; use \OCA\News\Service\Exceptions\ServiceNotFoundException; use \OCA\News\Service\ItemService; -use \OCA\News\Service\FeedService; use OCP\IUserSession; class ItemController extends Controller @@ -28,12 +28,18 @@ class ItemController extends Controller use JSONHttpErrorTrait; private $itemService; + /** + * @var FeedServiceV2 + */ private $feedService; + /** + * @var IConfig + */ private $settings; public function __construct( IRequest $request, - FeedService $feedService, + FeedServiceV2 $feedService, ItemService $itemService, IConfig $settings, ?IUserSession $userSession diff --git a/lib/Db/Feed.php b/lib/Db/Feed.php index 473912acc..1d5721d3b 100644 --- a/lib/Db/Feed.php +++ b/lib/Db/Feed.php @@ -25,6 +25,16 @@ class Feed extends Entity implements IAPI, \JsonSerializable { use EntityJSONSerializer; + /** + * Silently import new items + */ + const UPDATE_MODE_SILENT = 0; + + /** + * Mark new items as unread. + */ + const UPDATE_MODE_NORMAL = 1; + /** @var string */ protected $userId = ''; /** @var string */ diff --git a/lib/Db/FeedMapper.php b/lib/Db/FeedMapper.php deleted file mode 100644 index cf12c4dfa..000000000 --- a/lib/Db/FeedMapper.php +++ /dev/null @@ -1,202 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Db; - -use OCA\News\Utility\Time; -use OCP\AppFramework\Db\DoesNotExistException; -use OCP\AppFramework\Db\MultipleObjectsReturnedException; -use OCP\IDBConnection; -use OCP\AppFramework\Db\Entity; - -/** - * Class LegacyFeedMapper - * - * @package OCA\News\Db - * @deprecated use FeedMapper - */ -class FeedMapper extends NewsMapper -{ - const TABLE_NAME = 'news_feeds'; - - public function __construct(IDBConnection $db, Time $time) - { - parent::__construct($db, $time, Feed::class); - } - - - public function find(string $userId, int $id) - { - $sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'JOIN ( ' . - 'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - // WARNING: this is a desperate attempt at making this query - // work because prepared statements dont work. This is a - // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. - // think twice when changing this - 'AND `items`.`unread` = ? ' . - 'WHERE `feeds`.`id` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id` ' . - ') `item_numbers` ' . - 'ON `item_numbers`.`id` = `feeds`.`id` '; - $params = [true, $id, $userId]; - - return $this->findEntity($sql, $params); - } - - - public function findAllFromUser(string $userId): array - { - $sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'JOIN ( ' . - 'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' . - 'ON `feeds`.`folder_id` = `folders`.`id` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - // WARNING: this is a desperate attempt at making this query - // work because prepared statements dont work. This is a - // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. - // think twice when changing this - 'AND `items`.`unread` = ? ' . - 'WHERE `feeds`.`user_id` = ? ' . - 'AND (`feeds`.`folder_id` IS NULL ' . - 'OR `folders`.`deleted_at` = 0 ' . - ') ' . - 'AND `feeds`.`deleted_at` = 0 ' . - 'GROUP BY `feeds`.`id` ' . - ') `item_numbers` ' . - 'ON `item_numbers`.`id` = `feeds`.`id` '; - $params = [true, $userId]; - - return $this->findEntities($sql, $params); - } - - - public function findAll(): array - { - $sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'JOIN ( ' . - 'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' . - 'ON `feeds`.`folder_id` = `folders`.`id` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - // WARNING: this is a desperate attempt at making this query - // work because prepared statements dont work. This is a - // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. - // think twice when changing this - 'AND `items`.`unread` = ? ' . - 'WHERE (`feeds`.`folder_id` IS NULL ' . - 'OR `folders`.`deleted_at` = 0 ' . - ') ' . - 'AND `feeds`.`deleted_at` = 0 ' . - 'GROUP BY `feeds`.`id` ' . - ') `item_numbers` ' . - 'ON `item_numbers`.`id` = `feeds`.`id` '; - - return $this->findEntities($sql, [true]); - } - - - public function findByUrlHash($hash, $userId) - { - $sql = 'SELECT `feeds`.*, `item_numbers`.`unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'JOIN ( ' . - 'SELECT `feeds`.`id`, COUNT(`items`.`id`) AS `unread_count` ' . - 'FROM `*PREFIX*news_feeds` `feeds` ' . - 'LEFT JOIN `*PREFIX*news_items` `items` ' . - 'ON `feeds`.`id` = `items`.`feed_id` ' . - // WARNING: this is a desperate attempt at making this query - // work because prepared statements dont work. This is a - // POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT. - // think twice when changing this - 'AND `items`.`unread` = ? ' . - 'WHERE `feeds`.`url_hash` = ? ' . - 'AND `feeds`.`user_id` = ? ' . - 'GROUP BY `feeds`.`id` ' . - ') `item_numbers` ' . - 'ON `item_numbers`.`id` = `feeds`.`id` '; - $params = [true, $hash, $userId]; - - return $this->findEntity($sql, $params); - } - - - public function delete(Entity $entity): Entity - { - // someone please slap me for doing this manually :P - // we needz CASCADE + FKs please - $sql = 'DELETE FROM `*PREFIX*news_items` WHERE `feed_id` = ?'; - $params = [$entity->getId()]; - $this->execute($sql, $params); - - return parent::delete($entity); - } - - - /** - * @param int $deleteOlderThan if given gets all entries with a delete date - * older than that timestamp - * @param string $userId if given returns only entries from the given user - * @return array with the database rows - */ - public function getToDelete($deleteOlderThan = null, $userId = null) - { - $sql = 'SELECT * FROM `*PREFIX*news_feeds` ' . - 'WHERE `deleted_at` > 0 '; - $params = []; - - // sometimes we want to delete all entries - if ($deleteOlderThan !== null) { - $sql .= 'AND `deleted_at` < ? '; - $params[] = $deleteOlderThan; - } - - // we need to sometimes only delete feeds of a user - if ($userId !== null) { - $sql .= 'AND `user_id` = ?'; - $params[] = $userId; - } - - return $this->findEntities($sql, $params); - } - - - /** - * Deletes all feeds of a user, delete items first since the user_id - * is not defined in there - * - * @param string $userId the name of the user - */ - public function deleteUser($userId) - { - $sql = 'DELETE FROM `*PREFIX*news_feeds` WHERE `user_id` = ?'; - $this->execute($sql, [$userId]); - } - - public function findFromUser(string $userId, int $id): Entity - { - return $this->find($userId, $id); - } -} diff --git a/lib/Db/FeedMapperV2.php b/lib/Db/FeedMapperV2.php index 0ecb5ba4f..85a1bd3f3 100644 --- a/lib/Db/FeedMapperV2.php +++ b/lib/Db/FeedMapperV2.php @@ -52,7 +52,7 @@ class FeedMapperV2 extends NewsMapperV2 { $builder = $this->db->getQueryBuilder(); $builder->select('feeds.*', $builder->func()->count('items.id', 'unreadCount')) - ->from($this->tableName, 'feeds') + ->from(static::TABLE_NAME, 'feeds') ->leftJoin( 'feeds', ItemMapperV2::TABLE_NAME, @@ -82,8 +82,8 @@ class FeedMapperV2 extends NewsMapperV2 public function findFromUser(string $userId, int $id): Entity { $builder = $this->db->getQueryBuilder(); - $builder->addSelect('*') - ->from($this->tableName) + $builder->select('*') + ->from(static::TABLE_NAME) ->where('user_id = :user_id') ->andWhere('id = :id') ->setParameter(':user_id', $userId) @@ -101,7 +101,7 @@ class FeedMapperV2 extends NewsMapperV2 { $builder = $this->db->getQueryBuilder(); $builder->select('*') - ->from($this->tableName) + ->from(static::TABLE_NAME) ->where('deleted_at = 0'); return $this->findEntities($builder); @@ -121,8 +121,8 @@ class FeedMapperV2 extends NewsMapperV2 public function findByURL(string $userId, string $url): Entity { $builder = $this->db->getQueryBuilder(); - $builder->addSelect('*') - ->from($this->tableName) + $builder->select('*') + ->from(static::TABLE_NAME) ->where('user_id = :user_id') ->andWhere('url = :url') ->setParameter(':user_id', $userId) @@ -141,8 +141,8 @@ class FeedMapperV2 extends NewsMapperV2 public function findAllFromFolder(?int $id): array { $builder = $this->db->getQueryBuilder(); - $builder->addSelect('*') - ->from($this->tableName); + $builder->select('*') + ->from(static::TABLE_NAME); if (is_null($id)) { $builder->where('folder_id IS NULL'); diff --git a/lib/Db/ItemMapper.php b/lib/Db/ItemMapper.php index 65c6e0b15..71e31dbc4 100644 --- a/lib/Db/ItemMapper.php +++ b/lib/Db/ItemMapper.php @@ -17,6 +17,7 @@ use Exception; use OCA\News\Utility\Time; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Db\Mapper; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -27,13 +28,25 @@ use OCP\IDBConnection; * @package OCA\News\Db * @deprecated use ItemMapper */ -class ItemMapper extends NewsMapper +class ItemMapper extends Mapper { const TABLE_NAME = 'news_items'; + /** + * @var Time + */ + private $time; + + /** + * NewsMapper constructor. + * + * @param IDBConnection $db Database connection + * @param Time $time Time class + */ public function __construct(IDBConnection $db, Time $time) { - parent::__construct($db, $time, Item::class); + parent::__construct($db, static::TABLE_NAME, Item::class); + $this->time = $time; } private function makeSelectQuery( @@ -107,7 +120,7 @@ class ItemMapper extends NewsMapper /** * @param int $id * @param string $userId - * @return \OCA\News\Db\Item + * @return \OCA\News\Db\Item|Entity */ public function find(string $userId, int $id) { @@ -332,7 +345,15 @@ class ItemMapper extends NewsMapper return $this->findEntities($sql, $params); } - + /** + * @param $guidHash + * @param $feedId + * @param $userId + * + * @return Entity|Item + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException + */ public function findByGuidHash($guidHash, $feedId, $userId) { $sql = $this->makeSelectQuery( @@ -411,23 +432,6 @@ class ItemMapper extends NewsMapper } - /** - * Deletes all items of a user - * - * @param string $userId the name of the user - */ - public function deleteUser($userId) - { - $sql = 'DELETE FROM `*PREFIX*news_items` ' . - 'WHERE `feed_id` IN (' . - 'SELECT `feeds`.`id` FROM `*PREFIX*news_feeds` `feeds` ' . - 'WHERE `feeds`.`user_id` = ?' . - ')'; - - $this->execute($sql, [$userId]); - } - - /** * Returns a list of ids and userid of all items */ @@ -492,29 +496,74 @@ class ItemMapper extends NewsMapper } } - /** - * NO-OP - * - * @param string $userId - * - * @return array - */ - public function findAllFromUser(string $userId): array + public function update(Entity $entity): Entity { - return []; + $entity->setLastModified($this->time->getMicroTime()); + return parent::update($entity); } - public function findFromUser(string $userId, int $id): Entity + public function insert(Entity $entity): Entity { - return $this->find($id, $userId); + $entity->setLastModified($this->time->getMicroTime()); + return parent::insert($entity); } /** - * NO-OP - * @return array + * Remove deleted items. + * + * @return void + */ + public function purgeDeleted(): void + { + $builder = $this->db->getQueryBuilder(); + $builder->delete($this->tableName) + ->where('deleted_at != 0') + ->execute(); + } + /** + * Performs a SELECT query with all arguments appened to the WHERE clause + * The SELECT will be performed on the current table and take the entity + * that is related for transforming the properties into column names + * + * Important: This method does not filter marked as deleted rows! + * + * @param array $search an assoc array from property to filter value + * @param int|null $limit Output limit + * @param int|null $offset Output offset + * + * @depreacted Legacy function + * + * @return Entity[] */ - public function findAll(): array + public function where(array $search = [], ?int $limit = null, ?int $offset = null) { - return []; + $entity = new $this->entityClass(); + + // turn keys into sql query filter, e.g. feedId -> feed_id = :feedId + $filter = array_map( + function ($property) use ($entity) { + // check if the property actually exists on the entity to prevent + // accidental Sql injection + if (!property_exists($entity, $property)) { + $msg = 'Property ' . $property . ' does not exist on ' + . $this->entityClass; + throw new \BadFunctionCallException($msg); + } + + $column = $entity->propertyToColumn($property); + return $column . ' = :' . $property; + }, + array_keys($search) + ); + + $andStatement = implode(' AND ', $filter); + + $sql = 'SELECT * FROM `' . $this->getTableName() . '`'; + + if (count($search) > 0) { + $sql .= 'WHERE ' . $andStatement; + } + + return $this->findEntities($sql, $search, $limit, $offset); } } diff --git a/lib/Db/ItemMapperV2.php b/lib/Db/ItemMapperV2.php index 71a6ba043..10ebe056b 100644 --- a/lib/Db/ItemMapperV2.php +++ b/lib/Db/ItemMapperV2.php @@ -120,6 +120,11 @@ class ItemMapperV2 extends NewsMapperV2 return $this->findEntity($builder); } + /** + * @param int $feedId + * + * @return array + */ public function findAllForFeed(int $feedId): array { $builder = $this->db->getQueryBuilder(); diff --git a/lib/Db/NewsMapper.php b/lib/Db/NewsMapper.php deleted file mode 100644 index b03c42c91..000000000 --- a/lib/Db/NewsMapper.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Db; - -use OCA\News\Utility\Time; -use OCP\AppFramework\Db\DoesNotExistException; -use OCP\AppFramework\Db\MultipleObjectsReturnedException; -use OCP\AppFramework\Db\QBMapper; -use OCP\IDBConnection; -use OCP\AppFramework\Db\Mapper; -use OCP\AppFramework\Db\Entity; - -/** - * Class NewsMapper - * - * @package OCA\News\Db - */ -abstract class NewsMapper extends Mapper -{ - const TABLE_NAME = ''; - - /** - * @var Time - */ - private $time; - - /** - * NewsMapper constructor. - * - * @param IDBConnection $db Database connection - * @param Time $time Time class - * @param string $entity Entity class - */ - public function __construct( - IDBConnection $db, - Time $time, - string $entity - ) { - parent::__construct($db, static::TABLE_NAME, $entity); - $this->time = $time; - } - - public function update(Entity $entity): Entity - { - $entity->setLastModified($this->time->getMicroTime()); - return parent::update($entity); - } - - public function insert(Entity $entity): Entity - { - $entity->setLastModified($this->time->getMicroTime()); - return parent::insert($entity); - } - - /** - * Remove deleted items. - * - * @return void - */ - public function purgeDeleted(): void - { - $builder = $this->db->getQueryBuilder(); - $builder->delete($this->tableName) - ->where('deleted_at != 0') - ->execute(); - } - - abstract public function find(string $userId, int $id); - - /** - * Find all items. - * - * @return Entity[] - */ - abstract public function findAll(): array; - - /** - * Find all items for a user. - * - * @param string $userId ID of the user - * - * @return Entity[] - */ - abstract public function findAllFromUser(string $userId): array; - - /** - * Find item for a user. - * - * @param string $userId ID of the user - * @param int $id ID of the item - * - * @return Feed - * - * @throws DoesNotExistException The item is not found - * @throws MultipleObjectsReturnedException Multiple items found - */ - abstract public function findFromUser(string $userId, int $id): Entity; - - - - /** - * Performs a SELECT query with all arguments appened to the WHERE clause - * The SELECT will be performed on the current table and take the entity - * that is related for transforming the properties into column names - * - * Important: This method does not filter marked as deleted rows! - * - * @param array $search an assoc array from property to filter value - * @param int|null $limit Output limit - * @param int|null $offset Output offset - * - * @depreacted Legacy function - * - * @return array - */ - public function where(array $search = [], ?int $limit = null, ?int $offset = null) - { - $entity = new $this->entityClass(); - - // turn keys into sql query filter, e.g. feedId -> feed_id = :feedId - $filter = array_map( - function ($property) use ($entity) { - // check if the property actually exists on the entity to prevent - // accidental Sql injection - if (!property_exists($entity, $property)) { - $msg = 'Property ' . $property . ' does not exist on ' - . $this->entityClass; - throw new \BadFunctionCallException($msg); - } - - $column = $entity->propertyToColumn($property); - return $column . ' = :' . $property; - }, - array_keys($search) - ); - - $andStatement = implode(' AND ', $filter); - - $sql = 'SELECT * FROM `' . $this->getTableName() . '`'; - - if (count($search) > 0) { - $sql .= 'WHERE ' . $andStatement; - } - - return $this->findEntities($sql, $search, $limit, $offset); - } -} diff --git a/lib/Fetcher/IFeedFetcher.php b/lib/Fetcher/IFeedFetcher.php index aaba9de67..bfa737805 100644 --- a/lib/Fetcher/IFeedFetcher.php +++ b/lib/Fetcher/IFeedFetcher.php @@ -14,6 +14,8 @@ namespace OCA\News\Fetcher; use FeedIo\Reader\ReadErrorException; +use OCA\News\Db\Feed; +use OCA\News\Db\Item; interface IFeedFetcher { @@ -29,7 +31,7 @@ interface IFeedFetcher * @param string|null $user if given, basic auth is set for this feed * @param string|null $password if given, basic auth is set for this feed. Ignored if user is empty * - * @return array an array containing the new feed and its items, first + * @return an array containing the new feed and its items, first * element being the Feed and second element being an array of Items * * @throws ReadErrorException if the Feed-IO fetcher encounters a problem diff --git a/lib/Hooks/UserDeleteHook.php b/lib/Hooks/UserDeleteHook.php index 128e6fa79..d406e5741 100644 --- a/lib/Hooks/UserDeleteHook.php +++ b/lib/Hooks/UserDeleteHook.php @@ -14,9 +14,9 @@ namespace OCA\News\Hooks; use OCA\News\AppInfo\Application; -use OCA\News\Service\ItemService; -use OCA\News\Service\FeedService; +use OCA\News\Service\FeedServiceV2; use OCA\News\Service\FolderServiceV2; +use OCA\News\Service\ItemServiceV2; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\User\Events\BeforeUserDeletedEvent; @@ -37,8 +37,8 @@ class UserDeleteHook implements IEventListener $container = $app->getContainer(); // order is important! - $container->get(ItemService::class)->deleteUser($userId); - $container->get(FeedService::class)->deleteUser($userId); + $container->get(ItemServiceV2::class)->deleteUser($userId); + $container->get(FeedServiceV2::class)->deleteUser($userId); $container->get(FolderServiceV2::class)->deleteUser($userId); } } diff --git a/lib/Service/FeedService.php b/lib/Service/FeedService.php deleted file mode 100644 index c671a035c..000000000 --- a/lib/Service/FeedService.php +++ /dev/null @@ -1,521 +0,0 @@ - - * @author Bernhard Posselt - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - -namespace OCA\News\Service; - -use FeedIo\Reader\ReadErrorException; -use HTMLPurifier; - -use OCA\News\AppInfo\Application; -use OCP\IConfig; -use OCA\News\Service\Exceptions\ServiceConflictException; -use OCA\News\Service\Exceptions\ServiceNotFoundException; -use OCP\AppFramework\Db\Entity; -use OCP\IL10N; -use OCP\AppFramework\Db\DoesNotExistException; - -use OCA\News\Db\Feed; -use OCA\News\Db\Item; -use OCA\News\Db\FeedMapper; -use OCA\News\Db\ItemMapper; -use OCA\News\Fetcher\Fetcher; -use OCA\News\Utility\Time; -use Psr\Log\LoggerInterface; - -/** - * Class LegacyFeedService - * - * @package OCA\News\Service - * @deprecated use FeedServiceV2 - */ -class FeedService extends Service -{ - - private $feedFetcher; - private $itemMapper; - private $feedMapper; - private $l10n; - private $timeFactory; - private $autoPurgeMinimumInterval; - private $purifier; - private $loggerParams; - - public function __construct( - FeedMapper $legacyFeedMapper, - Fetcher $feedFetcher, - ItemMapper $legacyItemMapper, - LoggerInterface $logger, - IL10N $l10n, - Time $timeFactory, - IConfig $config, - HTMLPurifier $purifier - ) { - parent::__construct($legacyFeedMapper, $logger); - $this->feedFetcher = $feedFetcher; - $this->feedMapper = $legacyFeedMapper; - $this->itemMapper = $legacyItemMapper; - $this->logger = $logger; - $this->l10n = $l10n; - $this->timeFactory = $timeFactory; - $this->autoPurgeMinimumInterval = $config->getAppValue( - Application::NAME, - 'autoPurgeMinimumInterval', - Application::DEFAULT_SETTINGS['autoPurgeMinimumInterval'] - ); - $this->purifier = $purifier; - $this->loggerParams = ['app' => Application::NAME]; - } - - /** - * Finds all feeds of a user - * - * @param string $userId the name of the user - * - * @return Feed[] - */ - public function findAllForUser($userId, array $params = []): array - { - return $this->feedMapper->findAllFromUser($userId); - } - - - /** - * Finds all feeds from all users - * - * @return array of feeds - */ - public function findAllFromAllUsers() - { - return $this->findAll(); - } - - - /** - * Creates a new feed - * - * @param string $feedUrl the url to the feed - * @param int|null $folderId the folder where it should be put into, null for root - * folder - * @param string $userId for which user the feed should be created - * @param string|null $title if given, this is used for the opml feed title - * @param string|null $user if given, basic auth is set for this feed - * @param string|null $password if given, basic auth is set for this - * feed. Ignored if user is null or an empty string - * - * @return Feed the newly created feed - * @throws ServiceConflictException if the feed exists already - * @throws ServiceNotFoundException if the url points to an invalid feed - */ - public function create( - string $feedUrl, - ?int $folderId, - string $userId, - string $title = null, - string $user = null, - string $password = null - ) { - // first try if the feed exists already - try { - /** - * @var Feed $feed - * @var Item[] $items - */ - list($feed, $items) = $this->feedFetcher->fetch($feedUrl, true, null, false, $user, $password); - // try again if feed exists depending on the reported link - if ($feed === null) { - throw new ServiceNotFoundException($this->l10n->t('Can not add feed: Unable to parse feed')); - } - try { - $hash = $feed->getUrlHash(); - $this->feedMapper->findByUrlHash($hash, $userId); - throw new ServiceConflictException( - $this->l10n->t('Can not add feed: Exists already') - ); - } catch (DoesNotExistException $ex) { - // If no matching feed was found everything was ok - } - - // insert feed - $itemCount = count($items); - $feed->setBasicAuthUser($user); - $feed->setBasicAuthPassword($password); - $feed->setFolderId($folderId); - $feed->setUserId($userId); - $feed->setArticlesPerUpdate($itemCount); - - if (!empty($title)) { - $feed->setTitle($title); - } - - $feed = $this->feedMapper->insert($feed); - - // insert items in reverse order because the first one is usually - // the newest item - $unreadCount = 0; - foreach (array_reverse($items) as $item) { - $item->setFeedId($feed->getId()); - - // check if item exists (guidhash is the same) - // and ignore it if it does - try { - $this->itemMapper->findByGuidHash( - $item->getGuidHash(), - $item->getFeedId(), - $userId - ); - continue; - } catch (DoesNotExistException $ex) { - $unreadCount += 1; - $item->setBody($this->purifier->purify($item->getBody())); - $this->itemMapper->insert($item); - } - } - - // set unread count - $feed->setUnreadCount($unreadCount); - - return $feed; - } catch (ReadErrorException $ex) { - $this->logger->debug($ex->getMessage(), $this->loggerParams); - throw new ServiceNotFoundException($ex->getMessage()); - } - } - - - /** - * Runs all the feed updates - */ - public function updateAll() - { - // TODO: this method is not covered by any tests - $feeds = $this->feedMapper->findAll(); - foreach ($feeds as $feed) { - try { - $this->update($feed->getId(), $feed->getUserId()); - } catch (\Exception $ex) { - // something is really wrong here, log it - $this->logger->error( - 'Unexpected error when updating feed ' . $ex->getMessage(), - $this->loggerParams - ); - } - } - } - - - /** - * Updates a single feed - * - * @param string $userId the id of the user - * @param int $feedId the id of the feed that should be updated - * @param bool $forceUpdate update even if the article exists already - * - * @throws ServiceNotFoundException if the feed does not exist - * @return Feed the updated feed entity - */ - public function update(string $userId, int $feedId, $forceUpdate = false) - { - /** @var Feed $existingFeed */ - $existingFeed = $this->find($userId, $feedId); - - if ($existingFeed->getPreventUpdate() === true) { - return $existingFeed; - } - - // for backwards compability it can be that the location is not set - // yet, if so use the url - $location = $existingFeed->getLocation(); - if (!$location) { - $location = $existingFeed->getUrl(); - } - - try { - list($fetchedFeed, $items) = $this->feedFetcher->fetch( - $location, - false, - $existingFeed->getHttpLastModified(), - $existingFeed->getFullTextEnabled(), - $existingFeed->getBasicAuthUser(), - $existingFeed->getBasicAuthPassword() - ); - - // if there is no feed it means that no update took place - if (!$fetchedFeed) { - return $existingFeed; - } - - // update number of articles on every feed update - $itemCount = count($items); - - // this is needed to adjust to updates that add more items - // than when the feed was created. You can't update the count - // if it's lower because it may be due to the caching headers - // that were sent as the request and it might cause unwanted - // deletion and reappearing of feeds - if ($itemCount > $existingFeed->getArticlesPerUpdate()) { - $existingFeed->setArticlesPerUpdate($itemCount); - } - - $existingFeed->setHttpLastModified( - $fetchedFeed->getHttpLastModified() - ); - $existingFeed->setHttpEtag($fetchedFeed->getHttpEtag()); - $existingFeed->setLocation($fetchedFeed->getLocation()); - - // insert items in reverse order because the first one is - // usually the newest item - for ($i = $itemCount - 1; $i >= 0; $i--) { - $item = $items[$i]; - $item->setFeedId($existingFeed->getId()); - - try { - $dbItem = $this->itemMapper->findByGuidHash( - $item->getGuidHash(), - $feedId, - $userId - ); - - // in case of update - if ($forceUpdate - || $item->getUpdatedDate() > $dbItem->getUpdatedDate() - ) { - $dbItem->setTitle($item->getTitle()); - $dbItem->setUrl($item->getUrl()); - $dbItem->setAuthor($item->getAuthor()); - $dbItem->setSearchIndex($item->getSearchIndex()); - $dbItem->setRtl($item->getRtl()); - $dbItem->setLastModified($item->getLastModified()); - $dbItem->setPubDate($item->getPubDate()); - $dbItem->setUpdatedDate($item->getUpdatedDate()); - $dbItem->setEnclosureMime($item->getEnclosureMime()); - $dbItem->setEnclosureLink($item->getEnclosureLink()); - $dbItem->setBody( - $this->purifier->purify($item->getBody()) - ); - - // update modes: 0 nothing, 1 set unread - if ($existingFeed->getUpdateMode() === 1) { - $dbItem->setUnread(true); - } - - $this->itemMapper->update($dbItem); - } - } catch (DoesNotExistException $ex) { - $item->setBody( - $this->purifier->purify($item->getBody()) - ); - $this->itemMapper->insert($item); - } - } - - // mark feed as successfully updated - $existingFeed->setUpdateErrorCount(0); - $existingFeed->setLastUpdateError(''); - } catch (ReadErrorException $ex) { - $existingFeed->setUpdateErrorCount( - $existingFeed->getUpdateErrorCount() + 1 - ); - $existingFeed->setLastUpdateError($ex->getMessage()); - } - - $this->feedMapper->update($existingFeed); - - return $this->find($userId, $feedId); - } - - /** - * Import articles - * - * @param array $json the array with json - * @param string $userId the username - * - * @return Feed if one had to be created for nonexistent feeds - */ - public function importArticles($json, $userId) - { - $url = 'http://nextcloud/nofeed'; - $urlHash = md5($url); - - // build assoc array for fast access - $feeds = $this->findAllForUser($userId); - $feedsDict = []; - foreach ($feeds as $feed) { - $feedsDict[$feed->getLink()] = $feed; - } - - $createdFeed = false; - - // loop over all items and get the corresponding feed - // if the feed does not exist, create a separate feed for them - foreach ($json as $entry) { - $item = Item::fromImport($entry); - $feedLink = $entry['feedLink']; // this is not set on the item yet - - if (array_key_exists($feedLink, $feedsDict)) { - $feed = $feedsDict[$feedLink]; - $item->setFeedId($feed->getId()); - } elseif (array_key_exists($url, $feedsDict)) { - $feed = $feedsDict[$url]; - $item->setFeedId($feed->getId()); - } else { - $createdFeed = true; - $feed = new Feed(); - $feed->setUserId($userId); - $feed->setLink($url); - $feed->setUrl($url); - $feed->setTitle($this->l10n->t('Articles without feed')); - $feed->setAdded($this->timeFactory->getTime()); - $feed->setFolderId(null); - $feed->setPreventUpdate(true); - /** @var Feed $feed */ - $feed = $this->feedMapper->insert($feed); - - $item->setFeedId($feed->getId()); - $feedsDict[$feed->getLink()] = $feed; - } - - try { - // if item exists, copy the status - $existingItem = $this->itemMapper->findByGuidHash( - $item->getGuidHash(), - $feed->getId(), - $userId - ); - $existingItem->setStatus($item->getStatus()); - $this->itemMapper->update($existingItem); - } catch (DoesNotExistException $ex) { - $item->setBody($this->purifier->purify($item->getBody())); - $item->generateSearchIndex(); - $this->itemMapper->insert($item); - } - } - - if ($createdFeed) { - return $this->feedMapper->findByUrlHash($urlHash, $userId); - } - - return null; - } - - - /** - * Use this to mark a feed as deleted. That way it can be un-deleted - * - * @param int $feedId the id of the feed that should be deleted - * @param string $userId the name of the user for security reasons - * - * @throws ServiceNotFoundException when feed does not exist - */ - public function markDeleted(int $feedId, string $userId) - { - $feed = $this->find($userId, $feedId); - $feed->setDeletedAt($this->timeFactory->getTime()); - $this->feedMapper->update($feed); - } - - - /** - * Use this to undo a feed deletion - * - * @param int $feedId the id of the feed that should be restored - * @param string $userId the name of the user for security reasons - * - * @throws ServiceNotFoundException when feed does not exist - */ - public function unmarkDeleted(int $feedId, string $userId) - { - $feed = $this->find($userId, $feedId); - $feed->setDeletedAt(0); - $this->feedMapper->update($feed); - } - - - /** - * Deletes all deleted feeds - * - * @param string $userId if given it purges only feeds of that user - * @param boolean $useInterval defaults to true, if true it only purges - * entries in a given interval to give the user a chance to undo the - * deletion - */ - public function purgeDeleted($userId = null, $useInterval = true) - { - $deleteOlderThan = null; - - if ($useInterval) { - $now = $this->timeFactory->getTime(); - $deleteOlderThan = $now - $this->autoPurgeMinimumInterval; - } - - $toDelete = $this->feedMapper->getToDelete($deleteOlderThan, $userId); - - foreach ($toDelete as $feed) { - $this->feedMapper->delete($feed); - } - } - - - /** - * Deletes all feeds of a user, delete items first since the user_id - * is not defined in there - * - * @param string $userId the name of the user - */ - public function deleteUser($userId) - { - $this->feedMapper->deleteUser($userId); - } - - /** - * @param int $feedId ID of the feed. - * @param string $userId ID of the user. - * @param array $diff An array containing the fields to update, e.g.: - * - * [ - * 'ordering' => 1, - * 'fullTextEnabled' => true, - * 'pinned' => true, - * 'updateMode' => 0, - * 'title' => 'title' - * ] - * - * - * @throws ServiceNotFoundException if feed does not exist - * @return Feed The patched feed - */ - public function patch(int $feedId, string $userId, array $diff = []) - { - $feed = $this->find($userId, $feedId); - - foreach ($diff as $attribute => $value) { - $method = 'set' . ucfirst($attribute); - $feed->$method($value); - } - - // special feed updates - if (array_key_exists('fullTextEnabled', $diff)) { - // disable caching for the next update - $feed->setHttpEtag(''); - $feed->setHttpLastModified(0); - $this->feedMapper->update($feed); - return $this->update($userId, $feedId, true); - } - - return $this->feedMapper->update($feed); - } - - public function findAll(): array - { - return $this->feedMapper->findAll(); - } -} diff --git a/lib/Service/FeedServiceV2.php b/lib/Service/FeedServiceV2.php index 16402d0da..88f18a3bf 100644 --- a/lib/Service/FeedServiceV2.php +++ b/lib/Service/FeedServiceV2.php @@ -29,7 +29,6 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCA\News\Db\Feed; use OCA\News\Db\Item; -use OCA\News\Db\FeedMapper; use OCA\News\Db\ItemMapper; use OCA\News\Fetcher\Fetcher; use OCA\News\Config\Config; @@ -104,22 +103,6 @@ class FeedServiceV2 extends Service return $this->mapper->findAllFromUser($userId, $params); } - /** - * Finds a feed of a user - * - * @param string $userId the name of the user - * @param string $id the id of the feed - * - * @return Feed - * - * @throws DoesNotExistException - * @throws MultipleObjectsReturnedException - */ - public function findForUser(string $userId, string $id): Feed - { - return $this->mapper->findFromUser($userId, $id); - } - /** * @param int|null $id * @@ -139,6 +122,7 @@ class FeedServiceV2 extends Service */ public function findAllForUserRecursive(string $userId): array { + /** @var Feed[] $feeds */ $feeds = $this->mapper->findAllFromUser($userId); foreach ($feeds as &$feed) { @@ -167,12 +151,24 @@ class FeedServiceV2 extends Service * @return bool */ public function existsForUser(string $userID, string $url): bool + { + return $this->findByURL($userID, $url) !== null; + } + + /** + * Check if a feed exists for a user + * + * @param string $userID the name of the user + * @param string $url the feed URL + * + * @return Entity|Feed|null + */ + public function findByURL(string $userID, string $url): ?Entity { try { - $this->mapper->findByURL($userID, $url); - return true; + return $this->mapper->findByURL($userID, $url); } catch (DoesNotExistException $e) { - return false; + return null; } } @@ -274,58 +270,59 @@ class FeedServiceV2 extends Service $feed->getBasicAuthUser(), $feed->getBasicAuthPassword() ); + } catch (ReadErrorException $ex) { + $feed->setUpdateErrorCount($feed->getUpdateErrorCount() + 1); + $feed->setLastUpdateError($ex->getMessage()); - // if there is no feed it means that no update took place - if (!$fetchedFeed) { - return $feed; - } + return $this->mapper->update($feed); + } - // update number of articles on every feed update - $itemCount = count($items); + // if there is no feed it means that no update took place + if (!$fetchedFeed) { + return $feed; + } - // this is needed to adjust to updates that add more items - // than when the feed was created. You can't update the count - // if it's lower because it may be due to the caching headers - // that were sent as the request and it might cause unwanted - // deletion and reappearing of feeds - if ($itemCount > $feed->getArticlesPerUpdate()) { - $feed->setArticlesPerUpdate($itemCount); - } + // update number of articles on every feed update + $itemCount = count($items); - $feed->setHttpLastModified($fetchedFeed->getHttpLastModified()) - ->setHttpEtag($fetchedFeed->getHttpEtag()) - ->setLocation($fetchedFeed->getLocation()); + // this is needed to adjust to updates that add more items + // than when the feed was created. You can't update the count + // if it's lower because it may be due to the caching headers + // that were sent as the request and it might cause unwanted + // deletion and reappearing of feeds + if ($itemCount > $feed->getArticlesPerUpdate()) { + $feed->setArticlesPerUpdate($itemCount); + } - // insert items in reverse order because the first