Remove V1 item API

Signed-off-by: Sean Molenaar <sean@seanmolenaar.eu>
This commit is contained in:
Sean Molenaar 2021-01-02 17:57:17 +01:00 committed by Sean Molenaar
parent ceba810603
commit b4fa772bc5
50 changed files with 4574 additions and 1980 deletions

View File

@ -5,6 +5,8 @@ The format is almost based on [Keep a Changelog](https://keepachangelog.com/en/1
## [Unreleased]
### Changed
- Remove outdated item DB code.
- Stop returning all feeds after marking folder as read.
### Fixed

View File

@ -31,9 +31,6 @@ use OCP\AppFramework\App;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCA\News\Db\MapperFactory;
use OCA\News\Db\ItemMapper;
use OCA\News\Fetcher\FeedFetcher;
use OCA\News\Fetcher\Fetcher;
use OCP\User\Events\BeforeUserDeletedEvent;
@ -90,11 +87,6 @@ class Application extends App implements IBootstrap
$context->registerParameter('exploreDir', __DIR__ . '/../Explore/feeds');
$context->registerParameter('configFile', 'config.ini');
// factories
$context->registerService(ItemMapper::class, function (ContainerInterface $c): ItemMapper {
return $c->get(MapperFactory::class)->build();
});
$context->registerService(HTMLPurifier::class, function (ContainerInterface $c): HTMLPurifier {
$directory = $c->get(ITempManager::class)->getTempBaseDir() . '/news/cache/purifier';

View File

@ -2,7 +2,7 @@
namespace OCA\News\Command\Config;
use OCA\News\Service\Exceptions\ServiceException;
use OCA\News\Service\Exceptions\ServiceValidationException;
use OCA\News\Service\FolderServiceV2;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@ -50,7 +50,7 @@ class FolderDelete extends Command
$id = $input->getArgument('folder-id');
if ($id === null) {
throw new ServiceException('Can not remove root folder!');
throw new ServiceValidationException('Can not remove root folder!');
}
$this->folderService->delete($user, intval($id));

View File

@ -77,7 +77,7 @@ class ApiController extends BaseApiController
*
* @return array
*/
public function index()
public function index(): array
{
return [
'apiLevels' => ['v1-2']

View File

@ -19,12 +19,12 @@ use Exception;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\ItemServiceV2;
use OCP\AppFramework\Http\JSONResponse;
use \OCP\IRequest;
use \OCP\IUserSession;
use \OCP\AppFramework\Http;
use \OCA\News\Service\ItemService;
use Psr\Log\LoggerInterface;
class FeedApiController extends ApiController
@ -32,10 +32,9 @@ class FeedApiController extends ApiController
use JSONHttpErrorTrait, ApiPayloadTrait;
/**
* TODO: Remove
* @var ItemService
* @var ItemServiceV2
*/
private $oldItemService;
private $itemService;
/**
* @var FeedServiceV2
@ -51,12 +50,12 @@ class FeedApiController extends ApiController
IRequest $request,
?IUserSession $userSession,
FeedServiceV2 $feedService,
ItemService $oldItemService,
ItemServiceV2 $itemService,
LoggerInterface $logger
) {
parent::__construct($request, $userSession);
$this->feedService = $feedService;
$this->oldItemService = $oldItemService;
$this->itemService = $itemService;
$this->logger = $logger;
}
@ -70,12 +69,12 @@ class FeedApiController extends ApiController
{
$result = [
'starredCount' => $this->oldItemService->starredCount($this->getUserId()),
'starredCount' => count($this->itemService->starred($this->getUserId())),
'feeds' => $this->serialize($this->feedService->findAllForUser($this->getUserId()))
];
try {
$result['newestItemId'] = $this->oldItemService->getNewestItemId($this->getUserId());
$result['newestItemId'] = $this->itemService->newest($this->getUserId())->getId();
} catch (ServiceNotFoundException $ex) {
// in case there are no items, ignore
}
@ -96,9 +95,7 @@ class FeedApiController extends ApiController
*/
public function create(string $url, ?int $folderId = null)
{
if ($folderId === 0) {
$folderId = null;
}
$folderId = $folderId === 0 ? null : $folderId;
try {
$this->feedService->purgeDeleted($this->getUserId(), time() - 600);
@ -109,7 +106,7 @@ class FeedApiController extends ApiController
$this->feedService->fetch($feed);
try {
$result['newestItemId'] = $this->oldItemService->getNewestItemId($this->getUserId());
$result['newestItemId'] = $this->itemService->newest($this->getUserId())->getId();
} catch (ServiceNotFoundException $ex) {
// in case there are no items, ignore
}
@ -154,7 +151,7 @@ class FeedApiController extends ApiController
*/
public function read(int $feedId, int $newestItemId): void
{
$this->oldItemService->readFeed($feedId, $newestItemId, $this->getUserId());
$this->itemService->read($this->getUserId(), $feedId, $newestItemId);
}
@ -170,9 +167,7 @@ class FeedApiController extends ApiController
*/
public function move(int $feedId, ?int $folderId)
{
if ($folderId === 0) {
$folderId = null;
}
$folderId = $folderId === 0 ? null : $folderId;
try {
$feed = $this->feedService->find($this->getUserId(), $feedId);

View File

@ -18,12 +18,12 @@ use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ImportService;
use OCA\News\Service\ItemServiceV2;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IConfig;
use OCP\AppFramework\Http;
use OCA\News\Service\ItemService;
use OCA\News\Db\FeedType;
use OCP\IUserSession;
@ -35,7 +35,9 @@ class FeedController extends Controller
* @var FeedServiceV2
*/
private $feedService;
//TODO: Remove
/**
* @var ItemServiceV2
*/
private $itemService;
/**
* @var FolderServiceV2
@ -54,7 +56,7 @@ class FeedController extends Controller
IRequest $request,
FolderServiceV2 $folderService,
FeedServiceV2 $feedService,
ItemService $itemService,
ItemServiceV2 $itemService,
ImportService $importService,
IConfig $settings,
?IUserSession $userSession
@ -79,11 +81,11 @@ class FeedController extends Controller
// item id which will be used for marking feeds read
$params = [
'feeds' => $this->feedService->findAllForUser($this->getUserId()),
'starred' => $this->itemService->starredCount($this->getUserId())
'starred' => count($this->itemService->starred($this->getUserId()))
];
try {
$id = $this->itemService->getNewestItemId($this->getUserId());
$id = $this->itemService->newest($this->getUserId())->getId();
// An exception occurs if there is a newest item. If there is none,
// simply ignore it and do not add the newestItemId
@ -183,7 +185,7 @@ class FeedController extends Controller
$this->feedService->fetch($feed);
try {
$id = $this->itemService->getNewestItemId($this->getUserId());
$id = $this->itemService->newest($this->getUserId())->getId();
// 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;
@ -261,7 +263,7 @@ class FeedController extends Controller
$feed = $this->importService->importArticles($this->getUserId(), $json);
$params = [
'starred' => $this->itemService->starredCount($this->getUserId())
'starred' => count($this->itemService->starred($this->getUserId()))
];
if ($feed) {
@ -281,7 +283,7 @@ class FeedController extends Controller
*/
public function read(int $feedId, int $highestItemId): array
{
$this->itemService->readFeed($feedId, $highestItemId, $this->getUserId());
$this->feedService->read($this->getUserId(), $feedId, $highestItemId);
return [
'feeds' => [

View File

@ -20,7 +20,6 @@ use \OCP\IRequest;
use \OCP\IUserSession;
use \OCP\AppFramework\Http;
use \OCA\News\Service\ItemService;
use \OCA\News\Service\FolderServiceV2;
use \OCA\News\Service\Exceptions\ServiceNotFoundException;
use \OCA\News\Service\Exceptions\ServiceConflictException;
@ -30,20 +29,19 @@ class FolderApiController extends ApiController
{
use JSONHttpErrorTrait, ApiPayloadTrait;
/**
* @var FolderServiceV2
*/
private $folderService;
//TODO: Remove
private $itemService;
public function __construct(
IRequest $request,
?IUserSession $userSession,
FolderServiceV2 $folderService,
ItemService $itemService
FolderServiceV2 $folderService
) {
parent::__construct($request, $userSession);
$this->folderService = $folderService;
$this->itemService = $itemService;
}
@ -52,7 +50,7 @@ class FolderApiController extends ApiController
* @NoCSRFRequired
* @CORS
*/
public function index()
public function index(): array
{
$folders = $this->folderService->findAllForUser($this->getUserId());
return ['folders' => $this->serialize($folders)];
@ -142,14 +140,13 @@ class FolderApiController extends ApiController
* @NoCSRFRequired
* @CORS
*
* @param int|null $folderId
* @param int $newestItemId
* @param int|null $folderId ID of the folder
* @param int $maxItemId The newest read item
*/
public function read(?int $folderId, int $newestItemId): void
public function read(?int $folderId, int $maxItemId): void
{
if ($folderId === 0) {
$folderId = null;
}
$this->itemService->readFolder($folderId, $newestItemId, $this->getUserId());
$folderId = $folderId === 0 ? null : $folderId;
$this->folderService->read($this->getUserId(), $folderId, $maxItemId);
}
}

View File

@ -14,13 +14,11 @@
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\ItemService;
use \OCA\News\Service\Exceptions\ServiceNotFoundException;
use \OCA\News\Service\Exceptions\ServiceConflictException;
use OCP\IUserSession;
@ -33,24 +31,14 @@ class FolderController extends Controller
* @var FolderServiceV2
*/
private $folderService;
/**
* @var FeedServiceV2
*/
private $feedService;
//TODO: Remove
private $itemService;
public function __construct(
IRequest $request,
FolderServiceV2 $folderService,
FeedServiceV2 $feedService,
ItemService $itemService,
?IUserSession $userSession
) {
parent::__construct($request, $userSession);
$this->folderService = $folderService;
$this->feedService = $feedService;
$this->itemService = $itemService;
}
@ -134,12 +122,12 @@ class FolderController extends Controller
/**
* @NoAdminRequired
*
* @param string $folderName
* @param int|null $folderId
* @param int|null $folderId The ID of the folder
* @param string $folderName The new name of the folder
*
* @return array|JSONResponse
*/
public function rename(string $folderName, ?int $folderId)
public function rename(?int $folderId, string $folderName)
{
if (empty($folderId)) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
@ -159,21 +147,18 @@ class FolderController extends Controller
* @NoAdminRequired
*
* @param int|null $folderId
* @param int $highestItemId
* @param int $maxItemId
*
* @return array
* @return void
*
* @throws ServiceConflictException
* @throws ServiceNotFoundException
*/
public function read(?int $folderId, int $highestItemId): array
public function read(?int $folderId, int $maxItemId): void
{
$folderId = $folderId === 0 ? null : $folderId;
$this->itemService->readFolder(
$folderId,
$highestItemId,
$this->getUserId()
);
$feeds = $this->feedService->findAllForUser($this->getUserId());
return ['feeds' => $this->serialize($feeds)];
$this->folderService->read($this->getUserId(), $folderId, $maxItemId);
}

View File

@ -15,7 +15,9 @@
namespace OCA\News\Controller;
use OCA\News\Service\ItemService;
use OCA\News\Db\FeedType;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceValidationException;
use OCA\News\Service\ItemServiceV2;
use OCP\AppFramework\Http\JSONResponse;
use \OCP\IRequest;
@ -24,22 +26,27 @@ use \OCP\AppFramework\Http;
use \OCA\News\Service\Exceptions\ServiceNotFoundException;
/**
* Class ItemApiController
*
* @package OCA\News\Controller
*/
class ItemApiController extends ApiController
{
use JSONHttpErrorTrait, ApiPayloadTrait;
private $oldItemService;
/**
* @var ItemServiceV2
*/
private $itemService;
public function __construct(
IRequest $request,
?IUserSession $userSession,
ItemService $oldItemService,
ItemServiceV2 $itemService
) {
parent::__construct($request, $userSession);
$this->oldItemService = $oldItemService;
$this->itemService = $itemService;
}
@ -64,16 +71,38 @@ class ItemApiController extends ApiController
int $batchSize = -1,
int $offset = 0,
bool $oldestFirst = false
) {
$items = $this->oldItemService->findAllItems(
$id,
$type,
$batchSize,
$offset,
$getRead,
$oldestFirst,
$this->getUserId()
);
): array {
switch ($type) {
case FeedType::FEED:
$items = $this->itemService->findAllInFeedWithFilters(
$this->getUserId(),
$id,
$batchSize,
$offset,
!$getRead,
$oldestFirst
);
break;
case FeedType::FOLDER:
$items = $this->itemService->findAllInFolderWithFilters(
$this->getUserId(),
$id,
$batchSize,
$offset,
!$getRead,
$oldestFirst
);
break;
default:
$items = $this->itemService->findAllWithFilters(
$this->getUserId(),
$type,
$batchSize,
$offset,
$oldestFirst
);
break;
}
return ['items' => $this->serialize($items)];
}
@ -88,8 +117,10 @@ class ItemApiController extends ApiController
* @param int $id
* @param int $lastModified
* @return array|JSONResponse
*
* @throws ServiceValidationException
*/
public function updated(int $type = 3, int $id = 0, int $lastModified = 0)
public function updated(int $type = 3, int $id = 0, int $lastModified = 0): array
{
// needs to be turned into a millisecond timestamp to work properly
if (strlen((string) $lastModified) <= 10) {
@ -97,27 +128,33 @@ class ItemApiController extends ApiController
} else {
$paddedLastModified = $lastModified;
}
$items = $this->oldItemService->findAllNew(
$id,
$type,
(int) $paddedLastModified,
true,
$this->getUserId()
);
switch ($type) {
case FeedType::FEED:
$items = $this->itemService->findAllInFeedAfter($this->getUserId(), $id, $paddedLastModified, false);
break;
case FeedType::FOLDER:
$items = $this->itemService->findAllInFolderAfter($this->getUserId(), $id, $paddedLastModified, false);
break;
default:
$items = $this->itemService->findAllAfter($this->getUserId(), $type, $paddedLastModified);
break;
}
return ['items' => $this->serialize($items)];
}
/**
* @return JSONResponse|array
* @param int $itemId
* @param bool $isRead
*
* @psalm-return JSONResponse|array<empty, empty>
* @return array|JSONResponse
* @throws ServiceConflictException
*/
private function setRead(bool $isRead, int $itemId)
private function setRead(int $itemId, bool $isRead)
{
try {
$this->oldItemService->read($itemId, $isRead, $this->getUserId());
$this->itemService->read($this->getUserId(), $itemId, $isRead);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
@ -134,10 +171,11 @@ class ItemApiController extends ApiController
* @param int $itemId
*
* @return array|JSONResponse
* @throws ServiceConflictException
*/
public function read(int $itemId)
{
return $this->setRead(true, $itemId);
return $this->setRead($itemId, true);
}
@ -149,27 +187,25 @@ class ItemApiController extends ApiController
* @param int $itemId
*
* @return array|JSONResponse
* @throws ServiceConflictException
*/
public function unread(int $itemId)
{
return $this->setRead(false, $itemId);
return $this->setRead($itemId, false);
}
/**
* @return JSONResponse|array
* @param int $feedId
* @param string $guidHash
* @param bool $isStarred
*
* @psalm-return JSONResponse|array<empty, empty>
* @return array|JSONResponse
* @throws ServiceConflictException
*/
private function setStarred(bool $isStarred, int $feedId, string $guidHash)
private function setStarred(int $feedId, string $guidHash, bool $isStarred)
{
try {
$this->oldItemService->star(
$feedId,
$guidHash,
$isStarred,
$this->getUserId()
);
$this->itemService->starByGuid($this->getUserId(), $feedId, $guidHash, $isStarred);
} catch (ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
@ -187,10 +223,11 @@ class ItemApiController extends ApiController
* @param string $guidHash
*
* @return array|JSONResponse
* @throws ServiceConflictException
*/
public function star(int $feedId, string $guidHash)
{
return $this->setStarred(true, $feedId, $guidHash);
return $this->setStarred($feedId, $guidHash, true);
}
@ -203,10 +240,11 @@ class ItemApiController extends ApiController
* @param string $guidHash
*
* @return array|JSONResponse
* @throws ServiceConflictException
*/
public function unstar(int $feedId, string $guidHash)
{
return $this->setStarred(false, $feedId, $guidHash);
return $this->setStarred($feedId, $guidHash, false);
}
@ -223,15 +261,20 @@ class ItemApiController extends ApiController
*/
public function readAll(int $newestItemId): void
{
$this->oldItemService->readAll($newestItemId, $this->getUserId());
$this->itemService->readAll($this->getUserId(), $newestItemId);
}
private function setMultipleRead(bool $isRead, array $items): void
/**
* @param array $items
* @param bool $isRead
*
* @throws ServiceConflictException
*/
private function setMultipleRead(array $items, bool $isRead): void
{
foreach ($items as $id) {
try {
$this->oldItemService->read($id, $isRead, $this->getUserId());
$this->itemService->read($this->getUserId(), $id, $isRead);
} catch (ServiceNotFoundException $ex) {
continue;
}
@ -249,10 +292,12 @@ class ItemApiController extends ApiController
* @param int[] $items item ids
*
* @return void
*
* @throws ServiceConflictException
*/
public function readMultiple(array $items): void
{
$this->setMultipleRead(true, $items);
$this->setMultipleRead($items, true);
}
@ -266,30 +311,32 @@ class ItemApiController extends ApiController
* @param int[] $items item ids
*
* @return void
*
* @throws ServiceConflictException
*/
public function unreadMultiple(array $items): void
{
$this->setMultipleRead(false, $items);
$this->setMultipleRead($items, false);
}
/**
* @param bool $isStarred
* @param array $items
* @param bool $isStarred
*
* @return void
*/
private function setMultipleStarred(bool $isStarred, array $items): void
private function setMultipleStarred(array $items, bool $isStarred): void
{
foreach ($items as $item) {
try {
$this->oldItemService->star(
$this->itemService->starByGuid(
$this->getUserId(),
$item['feedId'],
$item['guidHash'],
$isStarred,
$this->getUserId()
$isStarred
);
} catch (ServiceNotFoundException $ex) {
} catch (ServiceNotFoundException | ServiceConflictException $ex) {
continue;
}
}
@ -309,7 +356,7 @@ class ItemApiController extends ApiController
*/
public function starMultiple(array $items): void
{
$this->setMultipleStarred(true, $items);
$this->setMultipleStarred($items, true);
}
@ -326,6 +373,6 @@ class ItemApiController extends ApiController
*/
public function unstarMultiple(array $items): void
{
$this->setMultipleStarred(false, $items);
$this->setMultipleStarred($items, false);
}
}

View File

@ -13,20 +13,31 @@
namespace OCA\News\Controller;
use OCA\News\Db\FeedType;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\FeedServiceV2;
use OCP\AppFramework\Http\JSONResponse;
use \OCP\IRequest;
use \OCP\IConfig;
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\ItemServiceV2;
use OCP\IUserSession;
/**
* Class ItemController
*
* @package OCA\News\Controller
*/
class ItemController extends Controller
{
use JSONHttpErrorTrait;
/**
* @var ItemServiceV2
*/
private $itemService;
/**
* @var FeedServiceV2
@ -40,7 +51,7 @@ class ItemController extends Controller
public function __construct(
IRequest $request,
FeedServiceV2 $feedService,
ItemService $itemService,
ItemServiceV2 $itemService,
IConfig $settings,
?IUserSession $userSession
) {
@ -71,7 +82,7 @@ class ItemController extends Controller
?bool $showAll = null,
?bool $oldestFirst = null,
string $search = ''
) {
): array {
// in case this is called directly and not from the website use the
// internal state
@ -104,15 +115,14 @@ class ItemController extends Controller
$type
);
$params = [];
$return = [];
// split search parameter on url space
$search = trim(urldecode($search));
$search = preg_replace('/\s+/', ' ', $search); // remove multiple ws
if ($search === '') {
$search = [];
} else {
$search = explode(' ', $search);
$search_string = trim(urldecode($search));
$search_string = preg_replace('/\s+/', ' ', $search_string); // remove multiple ws
$search_items = [];
if ($search !== '') {
$search_items = explode(' ', $search_string);
}
try {
@ -120,30 +130,54 @@ class ItemController extends Controller
// we need to pass the newest feeds to not let the unread count get
// out of sync
if ($offset === 0) {
$params['newestItemId'] =
$this->itemService->getNewestItemId($this->getUserId());
$params['feeds'] = $this->feedService->findAllForUser($this->getUserId());
$params['starred'] =
$this->itemService->starredCount($this->getUserId());
$return['newestItemId'] = $this->itemService->newest($this->getUserId())->getId();
$return['feeds'] = $this->feedService->findAllForUser($this->getUserId());
$return['starred'] = count($this->itemService->starred($this->getUserId()));
}
$params['items'] = $this->itemService->findAllItems(
$id,
$type,
$limit,
$offset,
$showAll,
$oldestFirst,
$this->getUserId(),
$search
);
switch ($type) {
case FeedType::FEED:
$items = $this->itemService->findAllInFeedWithFilters(
$this->getUserId(),
$id,
$limit,
$offset,
!$showAll,
$oldestFirst,
$search_items
);
break;
case FeedType::FOLDER:
$items = $this->itemService->findAllInFolderWithFilters(
$this->getUserId(),
$id,
$limit,
$offset,
!$showAll,
$oldestFirst,
$search_items
);
break;
default:
$items = $this->itemService->findAllWithFilters(
$this->getUserId(),
$type,
$limit,
$offset,
$oldestFirst,
$search_items
);
break;
}
$return['items'] = $items;
// this gets thrown if there are no items
// in that case just return an empty array
} catch (ServiceException $ex) {
//NO-OP
}
return $params;
return $return;
}
@ -155,7 +189,7 @@ class ItemController extends Controller
* @param int $lastModified
* @return array
*/
public function newItems($type, $id, $lastModified = 0)
public function newItems(int $type, int $id, $lastModified = 0): array
{
$showAll = $this->settings->getUserValue(
$this->getUserId(),
@ -163,28 +197,47 @@ class ItemController extends Controller
'showAll'
) === '1';
$params = [];
$return = [];
try {
$params['newestItemId'] =
$this->itemService->getNewestItemId($this->getUserId());
$params['feeds'] = $this->feedService->findAllForUser($this->getUserId());
$params['starred'] =
$this->itemService->starredCount($this->getUserId());
$params['items'] = $this->itemService->findAllNew(
$id,
$type,
$lastModified,
$showAll,
$this->getUserId()
);
switch ($type) {
case FeedType::FEED:
$items = $this->itemService->findAllInFeedAfter(
$this->getUserId(),
$id,
$lastModified,
!$showAll
);
break;
case FeedType::FOLDER:
$items = $this->itemService->findAllInFolderAfter(
$this->getUserId(),
$id,
$lastModified,
!$showAll
);
break;
default:
$items = $this->itemService->findAllAfter(
$this->getUserId(),
$type,
$lastModified
);
break;
}
$return['newestItemId'] = $this->itemService->newest($this->getUserId())->getId();
$return['feeds'] = $this->feedService->findAllForUser($this->getUserId());
$return['starred'] = count($this->itemService->starred($this->getUserId()));
$return['items'] = $items;
// this gets thrown if there are no items
// in that case just return an empty array
} catch (ServiceException $ex) {
//NO-OP
}
return $params;
return $return;
}
@ -194,16 +247,17 @@ class ItemController extends Controller
* @param int $feedId
* @param string $guidHash
* @param bool $isStarred
* @return array|\OCP\AppFramework\Http\JSONResponse
*
* @return array|JSONResponse
*/
public function star($feedId, $guidHash, $isStarred)
public function star(int $feedId, string $guidHash, bool $isStarred)
{
try {
$this->itemService->star(
$this->itemService->starByGuid(
$this->getUserId(),
$feedId,
$guidHash,
$isStarred,
$this->getUserId()
$isStarred
);
} catch (ServiceException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
@ -218,12 +272,13 @@ class ItemController extends Controller
*
* @param int $itemId
* @param bool $isRead
* @return array|\OCP\AppFramework\Http\JSONResponse
*
* @return array|JSONResponse
*/
public function read($itemId, $isRead = true)
public function read(int $itemId, $isRead = true)
{
try {
$this->itemService->read($itemId, $isRead, $this->getUserId());
$this->itemService->read($this->getUserId(), $itemId, $isRead);
} catch (ServiceException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
@ -236,11 +291,12 @@ class ItemController extends Controller
* @NoAdminRequired
*
* @param int $highestItemId
*
* @return array
*/
public function readAll($highestItemId)
public function readAll(int $highestItemId): array
{
$this->itemService->readAll($highestItemId, $this->getUserId());
$this->itemService->readAll($this->getUserId(), $highestItemId);
return ['feeds' => $this->feedService->findAllForUser($this->getUserId())];
}
@ -252,12 +308,12 @@ class ItemController extends Controller
*
* @return void
*/
public function readMultiple($itemIds): void
public function readMultiple(array $itemIds): void
{
foreach ($itemIds as $id) {
try {
$this->itemService->read($id, true, $this->getUserId());
} catch (ServiceNotFoundException $ex) {
$this->itemService->read($this->getUserId(), $id, true);
} catch (ServiceNotFoundException | ServiceConflictException $ex) {
continue;
}
}

View File

@ -14,6 +14,7 @@
namespace OCA\News\Controller;
use OCA\News\AppInfo\Application;
use OCA\News\Explore\Exceptions\RecommendedSiteNotFoundException;
use OCP\IRequest;
use OCP\IConfig;
use OCP\IL10N;
@ -24,7 +25,6 @@ use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCA\News\Service\StatusService;
use OCA\News\Explore\RecommendedSites;
use OCA\News\Explore\RecommendedSiteNotFoundException;
use OCA\News\Db\FeedType;
use OCP\IUserSession;

View File

@ -153,4 +153,28 @@ class FeedMapperV2 extends NewsMapperV2
return $this->findEntities($builder);
}
/**
* @param string $userId
* @param int $id
* @param int|null $maxItemID
*/
public function read(string $userId, int $id, ?int $maxItemID = null): void
{
$builder = $this->db->getQueryBuilder();
$builder->update(ItemMapperV2::TABLE_NAME, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->setValue('unread', 0)
->andWhere('feeds.user_id = :userId')
->andWhere('feeds.id = :feedId')
->setParameter('userId', $userId)
->setParameter('feedId', $id);
if ($maxItemID !== null) {
$builder->andWhere('items.id =< :maxItemId')
->setParameter('maxItemId', $maxItemID);
}
$this->db->executeUpdate($builder->getSQL());
}
}

View File

@ -13,6 +13,11 @@
namespace OCA\News\Db;
/**
* Enum FeedType
*
* @package OCA\News\Db
*/
class FeedType
{
const FEED = 0;

View File

@ -95,4 +95,30 @@ class FolderMapperV2 extends NewsMapperV2
return $this->findEntity($builder);
}
/**
* @param string $userId
* @param int $id
* @param int|null $maxItemID
*
* @return void
*/
public function read(string $userId, int $id, ?int $maxItemID = null): void
{
$builder = $this->db->getQueryBuilder();
$builder->update(ItemMapperV2::TABLE_NAME, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->setValue('unread', 0)
->andWhere('feeds.user_id = :userId')
->andWhere('feeds.folder_id = :folderId')
->setParameter('userId', $userId)
->setParameter('folderId', $id);
if ($maxItemID !== null) {
$builder->andWhere('items.id =< :maxItemId')
->setParameter('maxItemId', $maxItemID);
}
$this->db->executeUpdate($builder->getSQL());
}
}

View File

@ -1,586 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @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\Entity;
use OCP\AppFramework\Db\Mapper;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* Class LegacyItemMapper
*
* @package OCA\News\Db
* @deprecated use ItemMapper
*/
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, static::TABLE_NAME, Item::class);
$this->time = $time;
}
private function makeSelectQuery(
string $prependTo = '',
bool $oldestFirst = false,
bool $distinctFingerprint = false
): string {
if ($oldestFirst) {
$ordering = 'ASC';
} else {
$ordering = 'DESC';
}
return 'SELECT `items`.* FROM `*PREFIX*news_items` `items` ' .
'JOIN `*PREFIX*news_feeds` `feeds` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
'AND `feeds`.`deleted_at` = 0 ' .
'AND `feeds`.`user_id` = ? ' .
$prependTo .
'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
'ON `folders`.`id` = `feeds`.`folder_id` ' .
'WHERE `feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0 ' .
'ORDER BY `items`.`id` ' . $ordering;
}
/**
* check if type is feed or all items should be shown
*
* @param bool $showAll
* @param int|null $type
* @return string
*/
private function buildStatusQueryPart($showAll, $type = null)
{
$sql = '';
if (isset($type) && $type === FeedType::STARRED) {
$sql = 'AND `items`.`starred` = ';
$sql .= $this->db->quote(true, IQueryBuilder::PARAM_BOOL) . ' ';
} elseif (!$showAll || $type === FeedType::UNREAD) {
$sql .= 'AND `items`.`unread` = ';
$sql .= $this->db->quote(true, IQueryBuilder::PARAM_BOOL) . ' ';
}
return $sql;
}
private function buildSearchQueryPart(array $search = []): string
{
return str_repeat('AND `items`.`search_index` LIKE ? ', count($search));
}
/**
* wrap and escape search parameters in a like statement
*
* @param string[] $search an array of strings that should be searched
* @return array with like parameters
*/
private function buildLikeParameters($search = [])
{
return array_map(
function ($param) {
$param = addcslashes($param, '\\_%');
return '%' . mb_strtolower($param, 'UTF-8') . '%';
},
$search
);
}
/**
* @param int $id
* @param string $userId
* @return \OCA\News\Db\Item|Entity
*/
public function find(string $userId, int $id)
{
$sql = $this->makeSelectQuery('AND `items`.`id` = ? ');
return $this->findEntity($sql, [$userId, $id]);
}
public function starredCount(string $userId): int
{
$sql = 'SELECT COUNT(*) AS size FROM `*PREFIX*news_items` `items` ' .
'JOIN `*PREFIX*news_feeds` `feeds` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
'AND `feeds`.`deleted_at` = 0 ' .
'AND `feeds`.`user_id` = ? ' .
'AND `items`.`starred` = ? ' .
'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
'ON `folders`.`id` = `feeds`.`folder_id` ' .
'WHERE `feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0';
$params = [$userId, true];
$result = $this->execute($sql, $params)->fetch();
return (int)$result['size'];
}
public function readAll(int $highestItemId, string $time, string $userId): void
{
$sql = 'UPDATE `*PREFIX*news_items` ' .
'SET unread = ? ' .
', `last_modified` = ? ' .
'WHERE `feed_id` IN (' .
'SELECT `id` FROM `*PREFIX*news_feeds` ' .
'WHERE `user_id` = ? ' .
') ' .
'AND `id` <= ?';
$params = [false, $time, $userId, $highestItemId];
$this->execute($sql, $params);
}
public function readFolder(?int $folderId, int $highestItemId, string $time, string $userId): void
{
$folderWhere = is_null($folderId) ? 'IS' : '=';
$sql = 'UPDATE `*PREFIX*news_items` ' .
'SET unread = ? ' .
', `last_modified` = ? ' .
'WHERE `feed_id` IN (' .
'SELECT `id` FROM `*PREFIX*news_feeds` ' .
"WHERE `folder_id` ${folderWhere} ? " .
'AND `user_id` = ? ' .
') ' .
'AND `id` <= ?';
$params = [false, $time, $folderId, $userId,
$highestItemId];
$this->execute($sql, $params);
}
public function readFeed(int $feedId, int $highestItemId, string $time, string $userId): void
{
$sql = 'UPDATE `*PREFIX*news_items` ' .
'SET unread = ? ' .
', `last_modified` = ? ' .
'WHERE `feed_id` = ? ' .
'AND `id` <= ? ' .
'AND EXISTS (' .
'SELECT * FROM `*PREFIX*news_feeds` ' .
'WHERE `user_id` = ? ' .
'AND `id` = ? ) ';
$params = [false, $time, $feedId, $highestItemId,
$userId, $feedId];
$this->execute($sql, $params);
}
private function getOperator(bool $oldestFirst): string
{
if ($oldestFirst) {
return '>';
} else {
return '<';
}
}
public function findAllNew(int $updatedSince, int $type, bool $showAll, string $userId): array
{
$sql = $this->buildStatusQueryPart($showAll, $type);
$sql .= 'AND `items`.`last_modified` >= ? ';
$sql = $this->makeSelectQuery($sql);
$params = [$userId, $updatedSince];
return $this->findEntities($sql, $params);
}
public function findAllNewFolder(?int $id, int $updatedSince, bool $showAll, string $userId): array
{
$sql = $this->buildStatusQueryPart($showAll);
$folderWhere = is_null($id) ? 'IS' : '=';
$sql .= "AND `feeds`.`folder_id` ${folderWhere} ? " .
'AND `items`.`last_modified` >= ? ';
$sql = $this->makeSelectQuery($sql);
$params = [$userId, $id, $updatedSince];
return $this->findEntities($sql, $params);
}
public function findAllNewFeed(?int $id, int $updatedSince, bool $showAll, string $userId): array
{
$sql = $this->buildStatusQueryPart($showAll);
$sql .= 'AND `items`.`feed_id` = ? ' .
'AND `items`.`last_modified` >= ? ';
$sql = $this->makeSelectQuery($sql);
$params = [$userId, $id, $updatedSince];
return $this->findEntities($sql, $params);
}
/**
* @param (int|mixed|null)[] $params
*/
private function findEntitiesIgnoringNegativeLimit(string $sql, array $params, int $limit): array
{
// ignore limit if negative to offer a way to return all feeds
if ($limit >= 0) {
return $this->findEntities($sql, $params, $limit);
} else {
return $this->findEntities($sql, $params);
}
}
public function findAllFeed(
?int $id,
int $limit,
int $offset,
bool $showAll,
bool $oldestFirst,
string $userId,
array $search = []
): array {
$params = [$userId];
$params = array_merge($params, $this->buildLikeParameters($search));
$params[] = $id;
$sql = $this->buildStatusQueryPart($showAll);
$sql .= $this->buildSearchQueryPart($search);
$sql .= 'AND `items`.`feed_id` = ? ';
if ($offset !== 0) {
$sql .= 'AND `items`.`id` ' .
$this->getOperator($oldestFirst) . ' ? ';
$params[] = $offset;
}
$sql = $this->makeSelectQuery($sql, $oldestFirst);
return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
}
public function findAllFolder(
?int $id,
int $limit,
int $offset,
bool $showAll,
bool $oldestFirst,
string $userId,
array $search = []
): array {
$params = [$userId];
$params = array_merge($params, $this->buildLikeParameters($search));
$params[] = $id;
$sql = $this->buildStatusQueryPart($showAll);
$sql .= $this->buildSearchQueryPart($search);
$folderWhere = is_null($id) ? 'IS' : '=';
$sql .= "AND `feeds`.`folder_id` ${folderWhere} ? ";
if ($offset !== 0) {
$sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? ';
$params[] = $offset;
}
$sql = $this->makeSelectQuery($sql, $oldestFirst);
return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
}
/**
* @param string[] $search
*/
public function findAllItems(
int $limit,
int $offset,
int $type,
bool $showAll,
bool $oldestFirst,
string $userId,
array $search = []
): array {
$params = [$userId];
$params = array_merge($params, $this->buildLikeParameters($search));
$sql = $this->buildStatusQueryPart($showAll, $type);
$sql .= $this->buildSearchQueryPart($search);
if ($offset !== 0) {
$sql .= 'AND `items`.`id` ' .
$this->getOperator($oldestFirst) . ' ? ';
$params[] = $offset;
}
$sql = $this->makeSelectQuery($sql, $oldestFirst);
return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
}
public function findAllUnreadOrStarred(string $userId): array
{
$params = [$userId, true, true];
$sql = 'AND (`items`.`unread` = ? OR `items`.`starred` = ?) ';
$sql = $this->makeSelectQuery($sql);
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(
'AND `items`.`guid_hash` = ? ' .
'AND `feeds`.`id` = ? '
);
return $this->findEntity($sql, [$userId, $guidHash, $feedId]);
}
/**
* Delete all items for feeds that have over $threshold unread and not
* starred items
*
* @param int $threshold the number of items that should be deleted
*
* @return void
*/
public function deleteReadOlderThanThreshold($threshold)
{
$params = [false, false, $threshold];
$sql = 'SELECT (COUNT(*) - `feeds`.`articles_per_update`) AS `size`, ' .
'`feeds`.`id` AS `feed_id`, `feeds`.`articles_per_update` ' .
'FROM `*PREFIX*news_items` `items` ' .
'JOIN `*PREFIX*news_feeds` `feeds` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
'AND `items`.`unread` = ? ' .
'AND `items`.`starred` = ? ' .
'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' .
'HAVING COUNT(*) > ?';
$result = $this->execute($sql, $params);
while ($row = $result->fetch()) {
$size = (int)$row['size'];
$limit = $size - $threshold;
$feed_id = $row['feed_id'];
if ($limit > 0) {
$params = [false, false, $feed_id, $limit];
$sql = 'SELECT `id` FROM `*PREFIX*news_items` ' .
'WHERE `unread` = ? ' .
'AND `starred` = ? ' .
'AND `feed_id` = ? ' .
'ORDER BY `id` ASC ' .
'LIMIT 1 ' .
'OFFSET ? ';
}
$limit_result = $this->execute($sql, $params);
if ($limit_row = $limit_result->fetch()) {
$limit_id = (int)$limit_row['id'];
$params = [false, false, $feed_id, $limit_id];
$sql = 'DELETE FROM `*PREFIX*news_items` ' .
'WHERE `unread` = ? ' .
'AND `starred` = ? ' .
'AND `feed_id` = ? ' .
'AND `id` < ? ';
$this->execute($sql, $params);
}
}
}
public function getNewestItemId(string $userId): int
{
$sql = 'SELECT MAX(`items`.`id`) AS `max_id` ' .
'FROM `*PREFIX*news_items` `items` ' .
'JOIN `*PREFIX*news_feeds` `feeds` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
'AND `feeds`.`user_id` = ?';
$params = [$userId];
$result = $this->findOneQuery($sql, $params);
return (int)$result['max_id'];
}
/**
* Returns a list of ids and userid of all items
*
* @param int|null $limit
* @param int|null $offset
*
* @return array|false
*/
public function findAllIds(?int $limit = null, ?int $offset = null)
{
$sql = 'SELECT `id` FROM `*PREFIX*news_items`';
return $this->execute($sql, [], $limit, $offset)->fetchAll();
}
/**
* Update search indices of all items
*
* @return void
*/
public function updateSearchIndices(): void
{
// update indices in steps to prevent memory issues on larger systems
$step = 1000; // update 1000 items at a time
$itemCount = 1;
$offset = 0;
// stop condition if there are no previously fetched items
while ($itemCount > 0) {
$items = $this->findAllIds($step, $offset);
$itemCount = count($items);
$this->updateSearchIndex($items);
$offset += $step;
}
}
private function updateSearchIndex(array $items = []): void
{
foreach ($items as $row) {
$sql = 'SELECT * FROM `*PREFIX*news_items` WHERE `id` = ?';
$params = [$row['id']];
$item = $this->findEntity($sql, $params);
$item->generateSearchIndex();
$this->update($item);
}
}
/**
* @return void
*/
public function readItem(int $itemId, bool $isRead, string $lastModified, string $userId)
{
$item = $this->find($userId, $itemId);
// reading an item should set all of the same items as read, whereas
// marking an item as unread should only mark the selected instance
// as unread
if ($isRead) {
$sql = 'UPDATE `*PREFIX*news_items`
SET `unread` = ?,
`last_modified` = ?
WHERE `fingerprint` = ?
AND `feed_id` IN (
SELECT `f`.`id` FROM `*PREFIX*news_feeds` AS `f`
WHERE `f`.`user_id` = ?
)';
$params = [false, $lastModified, $item->getFingerprint(), $userId];
$this->execute($sql, $params);
} else {
$item->setLastModified($lastModified);
$item->setUnread(true);
$this->update($item);
}
}
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();
}
/**
* Performs a SELECT query with all arguments appended to the WHERE clause
* The SELECT will be performed on the current table and takes 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 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);
}
}

View File

@ -12,6 +12,7 @@
namespace OCA\News\Db;
use OCA\News\Service\Exceptions\ServiceValidationException;
use Doctrine\DBAL\FetchMode;
use OCA\News\Utility\Time;
use OCP\AppFramework\Db\DoesNotExistException;
@ -55,12 +56,11 @@ class ItemMapperV2 extends NewsMapperV2
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->where('feeds.user_id = :user_id')
->andWhere('deleted_at = 0')
->andWhere('feeds.deleted_at = 0')
->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR);
foreach ($params as $key => $value) {
$builder->andWhere("${key} = :${key}")
->setParameter($key, $value);
$builder->andWhere("${key} = " . $builder->createNamedParameter($value));
}
return $this->findEntities($builder);
@ -74,13 +74,17 @@ class ItemMapperV2 extends NewsMapperV2
public function findAll(): array
{
$builder = $this->db->getQueryBuilder();
$builder->addSelect('*')
$builder->select('*')
->from($this->tableName)
->andWhere('deleted_at = 0');
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->andWhere('feeds.deleted_at = 0');
return $this->findEntities($builder);
}
/**
* @inheritDoc
*/
public function findFromUser(string $userId, int $id): Entity
{
$builder = $this->db->getQueryBuilder();
@ -89,9 +93,9 @@ class ItemMapperV2 extends NewsMapperV2
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->where('feeds.user_id = :user_id')
->andWhere('items.id = :item_id')
->andWhere('deleted_at = 0')
->andWhere('feeds.deleted_at = 0')
->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR)
->setParameter('item_id', $id, IQueryBuilder::PARAM_STR);
->setParameter('item_id', $id, IQueryBuilder::PARAM_INT);
return $this->findEntity($builder);
}
@ -102,15 +106,15 @@ class ItemMapperV2 extends NewsMapperV2
* @param int $feedId ID of the feed
* @param string $guidHash hash to find with
*
* @return Item
* @return Item|Entity
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function findByGuidHash(int $feedId, string $guidHash): Item
public function findByGuidHash(int $feedId, string $guidHash): Entity
{
$builder = $this->db->getQueryBuilder();
$builder->addSelect('*')
$builder->select('*')
->from($this->tableName)
->andWhere('feed_id = :feed_id')
->andWhere('guid_hash = :guid_hash')
@ -120,6 +124,34 @@ class ItemMapperV2 extends NewsMapperV2
return $this->findEntity($builder);
}
/**
* Find a user item by a GUID hash.
*
* @param string $userId
* @param int $feedId ID of the feed
* @param string $guidHash hash to find with
*
* @return Item|Entity
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function findForUserByGuidHash(string $userId, int $feedId, string $guidHash): Item
{
$builder = $this->db->getQueryBuilder();
$builder->select('items.*')
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->andWhere('feeds.user_id = :user_id')
->andWhere('feeds.id = :feed_id')
->andWhere('items.guid_hash = :guid_hash')
->setParameter('user_id', $userId, IQueryBuilder::PARAM_STR)
->setParameter('feed_id', $feedId, IQueryBuilder::PARAM_INT)
->setParameter('guid_hash', $guidHash, IQueryBuilder::PARAM_STR);
return $this->findEntity($builder);
}
/**
* @param int $feedId
*
@ -128,10 +160,10 @@ class ItemMapperV2 extends NewsMapperV2
public function findAllForFeed(int $feedId): array
{
$builder = $this->db->getQueryBuilder();
$builder->addSelect('*')
$builder->select('*')
->from($this->tableName)
->andWhere('feed_id = :feed_id')
->setParameter('feed_id', $feedId, IQueryBuilder::PARAM_INT);
->where('feed_id = :feed_identifier')
->setParameter('feed_identifier', $feedId, IQueryBuilder::PARAM_INT);
return $this->findEntities($builder);
}
@ -214,4 +246,312 @@ class ItemMapperV2 extends NewsMapperV2
{
//NO-OP
}
/**
* @param string $userId
* @param int $maxItemId
*
* @TODO: Update this for NC 21
*/
public function readAll(string $userId, int $maxItemId): void
{
$builder = $this->db->getQueryBuilder();
$builder->update($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->setValue('unread', 0)
->andWhere('items.id =< :maxItemId')
->andWhere('feeds.user_id = :userId')
->setParameter('maxItemId', $maxItemId)
->setParameter('userId', $userId);
$this->db->executeUpdate($builder->getSQL());
}
/**
* @param string $userId
*
* @return Entity|Item
*
* @throws DoesNotExistException The item is not found
* @throws MultipleObjectsReturnedException Multiple items found
*/
public function newest(string $userId): 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 = :userId')
->setParameter('userId', $userId)
->orderBy('items.updated_date', 'DESC')
->addOrderBy('items.id', 'DESC')
->setMaxResults(1);
return $this->findEntity($builder);
}
/**
* @param string $userId
* @param int $feedId
* @param int $updatedSince
* @param bool $hideRead
*
* @return Item[]
*/
public function findAllInFeedAfter(
string $userId,
int $feedId,
int $updatedSince,
bool $hideRead
): array {
$builder = $this->db->getQueryBuilder();
$builder->select('items.*')
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->andWhere('items.updated_date >= :updatedSince')
->andWhere('feeds.user_id = :userId')
->andWhere('feeds.id = :feedId')
->setParameters([
'updatedSince' => $updatedSince,
'feedId' => $feedId,
'userId'=> $userId,
])
->orderBy('items.updated_date', 'DESC')
->addOrderBy('items.id', 'DESC');
if ($hideRead === true) {
$builder->andWhere('items.unread = 1');
}
return $this->findEntities($builder);
}
/**
* @param string $userId
* @param int|null $folderId
* @param int $updatedSince
* @param bool $hideRead
*
* @return Item[]
*/
public function findAllInFolderAfter(
string $userId,
?int $folderId,
int $updatedSince,
bool $hideRead
): array {
$builder = $this->db->getQueryBuilder();
$builder->select('items.*')
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->innerJoin('feeds', FolderMapperV2::TABLE_NAME, 'folders', 'feeds.folder_id = folders.id')
->andWhere('items.updated_date >= :updatedSince')
->andWhere('feeds.user_id = :userId')
->andWhere('folders.id = :folderId')
->setParameters(['updatedSince' => $updatedSince, 'folderId' => $folderId, 'userId' => $userId])
->orderBy('items.updated_date', 'DESC')
->addOrderBy('items.id', 'DESC');
if ($hideRead === true) {
$builder->andWhere('items.unread = 1');
}
return $this->findEntities($builder);
}
/**
* @param string $userId
* @param int $updatedSince
* @param int $feedType
*
* @return Item[]|Entity[]
* @throws ServiceValidationException
*/
public function findAllAfter(string $userId, int $feedType, int $updatedSince): array
{
$builder = $this->db->getQueryBuilder();
$builder->select('items.*')
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->andWhere('items.updated_date >= :updatedSince')
->andWhere('feeds.user_id = :userId')
->setParameters(['updatedSince' => $updatedSince, 'userId' => $userId])
->orderBy('items.updated_date', 'DESC')
->addOrderBy('items.id', 'DESC');
switch ($feedType) {
case FeedType::STARRED:
$builder->andWhere('items.starred = 1');
break;
case FeedType::UNREAD:
$builder->andWhere('items.unread = 1');
break;
default:
throw new ServiceValidationException('Unexpected Feed type in call');
}
return $this->findEntities($builder);
}
/**
* @param string $userId
* @param int $feedId
* @param int $limit
* @param int $offset
* @param bool $hideRead
* @param bool $oldestFirst
* @param array $search
*
* @return Item[]
*/
public function findAllFeed(
string $userId,
int $feedId,
int $limit,
int $offset,
bool $hideRead,
bool $oldestFirst,
array $search
): array {
$builder = $this->db->getQueryBuilder();
$builder->select('items.*')
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->andWhere('feeds.user_id = :userId')
->andWhere('items.feed_id = :feedId')
->setParameter('userId', $userId)
->setParameter('feedId', $feedId)
->setMaxResults($limit)
->setFirstResult($offset)
->orderBy('items.updated_date', ($oldestFirst ? 'ASC' : 'DESC'))
->addOrderBy('items.id', ($oldestFirst ? 'ASC' : 'DESC'));
if ($search !== []) {
foreach ($search as $key => $term) {
$term = $this->db->escapeLikeParameter($term);
$builder->andWhere("items.search_index LIKE :term${key}")
->setParameter("term${key}", "%$term%");
}
}
if ($hideRead === true) {
$builder->andWhere('items.unread = 1');
}
return $this->findEntities($builder);
}
/**
* @param string $userId
* @param int|null $folderId
* @param int $limit
* @param int $offset
* @param bool $hideRead
* @param bool $oldestFirst
* @param array $search
*
* @return Item[]
*/
public function findAllFolder(
string $userId,
?int $folderId,
int $limit,
int $offset,
bool $hideRead,
bool $oldestFirst,
array $search
): array {
$builder = $this->db->getQueryBuilder();
if ($folderId === null) {
$folderWhere = $builder->expr()->isNull('feeds.folder_id');
} else {
$folderWhere = $builder->expr()->eq('feeds.folder_id', $folderId);
}
$builder->select('items.*')
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->andWhere('feeds.user_id = :userId')
->andWhere($folderWhere)
->setParameter('userId', $userId)
->setMaxResults($limit)
->setFirstResult($offset)
->orderBy('items.updated_date', ($oldestFirst ? 'ASC' : 'DESC'))
->addOrderBy('items.id', ($oldestFirst ? 'ASC' : 'DESC'));
if ($search !== []) {
foreach ($search as $key => $term) {
$term = $this->db->escapeLikeParameter($term);
$builder->andWhere("items.search_index LIKE :term${key}")
->setParameter("term${key}", "%$term%");
}
}
if ($hideRead === true) {
$builder->andWhere('items.unread = 1');
}
return $this->findEntities($builder);
}
/**
* @param string $userId
* @param int $type
* @param int $limit
* @param int $offset
* @param bool $oldestFirst
* @param array $search
*
* @return Item[]
* @throws ServiceValidationException
*/
public function findAllItems(
string $userId,
int $type,
int $limit,
int $offset,
bool $oldestFirst,
array $search
): array {
$builder = $this->db->getQueryBuilder();
$builder->select('items.*')
->from($this->tableName, 'items')
->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id')
->andWhere('feeds.user_id = :userId')
->setParameter('userId', $userId)
->setMaxResults($limit)
->setFirstResult($offset)
->orderBy('items.updated_date', ($oldestFirst ? 'ASC' : 'DESC'))
->addOrderBy('items.id', ($oldestFirst ? 'ASC' : 'DESC'));
if ($search !== []) {
foreach ($search as $key => $term) {
$term = $this->db->escapeLikeParameter($term);
$builder->andWhere("items.search_index LIKE :term${key}")
->setParameter("term${key}", "%$term%");
}
}
switch ($type) {
case FeedType::STARRED:
$builder->andWhere('items.starred = 1');
break;
case FeedType::UNREAD:
$builder->andWhere('items.unread = 1');
break;
default:
throw new ServiceValidationException('Unexpected Feed type in call');
}
return $this->findEntities($builder);
}
}

View File

@ -1,54 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Db;
use OCA\News\Utility\Time;
use OCP\IDBConnection;
use OCA\News\Db\Mysql\ItemMapper as MysqlItemMapper;
use OCA\News\DependencyInjection\IFactory;
/**
* Class LegacyMapperFactory
*
* @package OCA\News\Db
* @deprecated not needed in modern system
*/
class MapperFactory implements IFactory
{
private $dbType;
private $db;
/**
* @var Time
*/
private $time;
public function __construct(IDBConnection $db, $databaseType, Time $time)
{
$this->dbType = $databaseType;
$this->db = $db;
$this->time = $time;
}
public function build()
{
switch ($this->dbType) {
case 'mysql':
return new MysqlItemMapper($this->db, $this->time);
default:
return new ItemMapper($this->db, $this->time);
}
}
}

View File

@ -1,98 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Db\Mysql;
use OCA\News\Utility\Time;
use OCP\IDBConnection;
/**
* Class LegacyItemMapper
*
* @package OCA\News\Db\Mysql
* @deprecated use normal ItemMapper
*/
class ItemMapper extends \OCA\News\Db\ItemMapper
{
public function __construct(IDBConnection $db, Time $time)
{
parent::__construct($db, $time);
}
/**
* Delete all items for feeds that have over $threshold unread and not
* starred items
*
* @param int $threshold the number of items that should be deleted
*
* @return void
*/
public function deleteReadOlderThanThreshold($threshold)
{
$sql = 'SELECT (COUNT(*) - `feeds`.`articles_per_update`) AS `size`, ' .
'`feeds`.`id` AS `feed_id`, `feeds`.`articles_per_update` ' .
'FROM `*PREFIX*news_items` `items` ' .
'JOIN `*PREFIX*news_feeds` `feeds` ' .
'ON `feeds`.`id` = `items`.`feed_id` ' .
'AND `items`.`unread` = ? ' .
'AND `items`.`starred` = ? ' .
'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' .
'HAVING COUNT(*) > ?';
$params = [false, false, $threshold];
$result = $this->execute($sql, $params);
while ($row = $result->fetch()) {
$size = (int) $row['size'];
$limit = $size - $threshold;
if ($limit > 0) {
$params = [false, false, $row['feed_id'], $limit];
$sql = 'DELETE FROM `*PREFIX*news_items` ' .
'WHERE `unread` = ? ' .
'AND `starred` = ? ' .
'AND `feed_id` = ? ' .
'ORDER BY `id` ASC ' .
'LIMIT ?';
$this->execute($sql, $params);
}
}
}
/**
* @return void
*/
public function readItem($itemId, $isRead, $lastModified, $userId)
{
$item = $this->find($itemId, $userId);
if ($isRead) {
$sql = 'UPDATE `*PREFIX*news_items` `items`
JOIN `*PREFIX*news_feeds` `feeds`
ON `feeds`.`id` = `items`.`feed_id`
SET `items`.`unread` = ?,
`items`.`last_modified` = ?
WHERE `items`.`fingerprint` = ?
AND `feeds`.`user_id` = ?';
$params = [false, $lastModified, $item->getFingerprint(), $userId];
$this->execute($sql, $params);
} else {
$item->setLastModified($lastModified);
$item->setUnread(true);
$this->update($item);
}
}
}

View File

@ -1,25 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\DependencyInjection;
interface IFactory
{
/**
* Method that constructs the object
*
* @return mixed the constructed object
*/
public function build();
}

View File

@ -11,7 +11,7 @@
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Explore;
namespace OCA\News\Explore\Exceptions;
use Exception;

View File

@ -13,6 +13,8 @@
namespace OCA\News\Explore;
use OCA\News\Explore\Exceptions\RecommendedSiteNotFoundException;
class RecommendedSites
{

View File

@ -1,28 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Fetcher;
class FetcherException extends \Exception
{
/**
* Constructor
*
* @param string $msg the error message
*/
public function __construct(string $msg)
{
parent::__construct($msg);
}
}

View File

@ -13,16 +13,22 @@
namespace OCA\News\Service\Exceptions;
use Exception;
use OCP\AppFramework\Db\IMapperException;
/**
* Class ServiceConflictException
*
* @package OCA\News\Service\Exceptions
*/
class ServiceConflictException extends ServiceException
{
/**
* Constructor
*
* @param string $msg the error message
* @inheritDoc
*/
public function __construct(string $msg)
public static function from(IMapperException $exception): ServiceException
{
parent::__construct($msg);
return new self($exception->getMessage(), $exception->getCode(), $exception);
}
}

View File

@ -13,16 +13,35 @@
namespace OCA\News\Service\Exceptions;
class ServiceException extends \Exception
use Exception;
use OCP\AppFramework\Db\IMapperException;
/**
* Class ServiceException
*
* @package OCA\News\Service\Exceptions
*/
abstract class ServiceException extends Exception
{
/**
* Constructor
*
* @param string $msg the error message
* @param int $code
* @param Exception|null $previous
*/
public function __construct(string $msg)
final public function __construct(string $msg, int $code = 0, ?Exception $previous = null)
{
parent::__construct($msg);
parent::__construct($msg, $code, $previous);
}
/**
* Create exception from Mapper exception.
*
* @param IMapperException|Exception $exception Existing exception
*
* @return static
*/
abstract public static function from(IMapperException $exception): ServiceException;
}

View File

@ -13,16 +13,21 @@
namespace OCA\News\Service\Exceptions;
use Exception;
use OCP\AppFramework\Db\IMapperException;
/**
* Class ServiceNotFoundException
*
* @package OCA\News\Service\Exceptions
*/
class ServiceNotFoundException extends ServiceException
{
/**
* Constructor
*
* @param string $msg the error message
* @inheritDoc
*/
public function __construct(string $msg)
public static function from(IMapperException $exception): ServiceException
{
parent::__construct($msg);
return new self($exception->getMessage(), $exception->getCode(), $exception);
}
}

View File

@ -13,16 +13,22 @@
namespace OCA\News\Service\Exceptions;
use Exception;
use OCP\AppFramework\Db\IMapperException;
/**
* Class ServiceValidationException
*
* @package OCA\News\Service\Exceptions
*/
class ServiceValidationException extends ServiceException
{
/**
* Constructor
*
* @param string $msg the error message
* @inheritDoc
*/
public function __construct(string $msg)
public static function from(IMapperException $exception): ServiceException
{
parent::__construct($msg);
return new self($exception->getMessage(), $exception->getCode(), $exception);
}
}

View File

@ -18,6 +18,7 @@ use FeedIo\Reader\ReadErrorException;
use HTMLPurifier;
use OCA\News\Db\FeedMapperV2;
use OCA\News\Db\Folder;
use OCA\News\Fetcher\FeedFetcher;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
@ -119,7 +120,7 @@ class FeedServiceV2 extends Service
$feeds = $this->mapper->findAllFromUser($userId);
foreach ($feeds as &$feed) {
$items = $this->itemService->findAllForFeed($feed->getId());
$items = $this->itemService->findAllInFeed($userId, $feed->getId());
$feed->items = $items;
}
return $feeds;
@ -341,4 +342,21 @@ class FeedServiceV2 extends Service
$this->fetch($feed);
}
}
/**
* Mark a feed as read
*
* @param string $userId Feed owner
* @param int $id Feed ID
* @param int|null $maxItemID Highest item ID to mark as read
*
* @throws ServiceConflictException
* @throws ServiceNotFoundException
*/
public function read(string $userId, int $id, ?int $maxItemID = null): void
{
$feed = $this->find($userId, $id);
$this->mapper->read($userId, $feed->getId(), $maxItemID);
}
}

View File

@ -178,4 +178,21 @@ class FolderServiceV2 extends Service
$folder->setOpened($open);
return $this->mapper->update($folder);
}
/**
* Mark a folder as read
*
* @param string $userId Folder owner
* @param int $id Folder ID
* @param int|null $maxItemID Highest item ID to mark as read
*
* @throws ServiceConflictException
* @throws ServiceNotFoundException
*/
public function read(string $userId, int $id, ?int $maxItemID = null): void
{
$folder = $this->find($userId, $id);
$this->mapper->read($userId, $folder->getId(), $maxItemID);
}
}

View File

@ -1,352 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Service;
use OCA\News\AppInfo\Application;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCP\IConfig;
use OCP\AppFramework\Db\DoesNotExistException;
use OCA\News\Db\ItemMapper;
use OCA\News\Db\FeedType;
use OCA\News\Utility\Time;
use Psr\Log\LoggerInterface;
/**
* Class LegacyItemService
*
* @package OCA\News\Service
* @deprecated use ItemServiceV2
*/
class ItemService extends Service
{
/**
* @var IConfig
*/
private $config;
/**
* @var Time
*/
private $timeFactory;
/**
* @var ItemMapper
*/
private $oldItemMapper;
public function __construct(
ItemMapperV2 $itemMapper,
ItemMapper $oldItemMapper,
Time $timeFactory,
IConfig $config,
LoggerInterface $logger
) {
parent::__construct($itemMapper, $logger);
$this->config = $config;
$this->timeFactory = $timeFactory;
$this->oldItemMapper = $oldItemMapper;
}
/**
* Returns all new items
*
* @param int|null $id the id of the feed, 0 for starred or all items
* @param int $type the type of the feed
* @param int $updatedSince a timestamp with the last modification date
* returns only items with a >= modified
* timestamp
* @param boolean $showAll if unread items should also be returned
* @param string $userId the name of the user
*
* @return array of items
*/
public function findAllNew(?int $id, $type, int $updatedSince, bool $showAll, string $userId)
{
switch ($type) {
case FeedType::FEED:
return $this->oldItemMapper->findAllNewFeed(
$id,
$updatedSince,
$showAll,
$userId
);
case FeedType::FOLDER:
return $this->oldItemMapper->findAllNewFolder(
$id,
$updatedSince,
$showAll,
$userId
);
default:
return $this->oldItemMapper->findAllNew(
$updatedSince,
$type,
$showAll,
$userId
);
}
}
/**
* Returns all items
*
* @param int|null $id the id of the feed, 0 for starred or all items
* @param int $type the type of the feed
* @param int $limit how many items should be returned
* @param int $offset the offset
* @param boolean $showAll if unread items should also be returned
* @param boolean $oldestFirst if it should be ordered by oldest first
* @param string $userId the name of the user
* @param string[] $search an array of keywords that the result should
* contain in either the author, title, link
* or body
*
* @return array of items
*/
public function findAllItems(
?int $id,
$type,
$limit,
$offset,
$showAll,
$oldestFirst,
$userId,
$search = []
) {
switch ($type) {
case FeedType::FEED:
return $this->oldItemMapper->findAllFeed(
$id,
$limit,
$offset,
$showAll,
$oldestFirst,
$userId,
$search
);
case FeedType::FOLDER:
return $this->oldItemMapper->findAllFolder(
$id,
$limit,
$offset,
$showAll,
$oldestFirst,
$userId,
$search
);
default:
return $this->oldItemMapper->findAllItems(
$limit,
$offset,
$type,
$showAll,
$oldestFirst,
$userId,
$search
);
}
}
public function findAllForUser(string $userId, array $params = []): array
{
return $this->mapper->findAllFromUser($userId, $params);
}
/**
* Star or unstar an item
*
* @param int $feedId the id of the item's feed that should be starred
* @param string $guidHash the guidHash of the item that should be starred
* @param boolean $isStarred if true the item will be marked as starred,
* if false unstar
* @param string $userId the name of the user for security reasons
*
* @throws ServiceNotFoundException if the item does not exist
*
* @return void
*/
public function star($feedId, $guidHash, $isStarred, $userId): void
{
try {
$item = $this->mapper->findByGuidHash($feedId, $guidHash);
$item->setStarred($isStarred);
$this->mapper->update($item);
} catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
}
}
/**
* Read or unread an item
*
* @param int $itemId the id of the item that should be read
* @param boolean $isRead if true the item will be marked as read,
* if false unread
* @param string $userId the name of the user for security reasons
*
* @throws ServiceNotFoundException if the item does not exist
*
* @return void
*/
public function read($itemId, $isRead, $userId): void
{
try {
$lastModified = $this->timeFactory->getMicroTime();
$this->oldItemMapper->readItem($itemId, $isRead, $lastModified, $userId);
} catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
}
}
/**
* Set all items read
*
* @param int $highestItemId all items below that are marked read. This is
* used to prevent marking items as read that
* the users hasn't seen yet
* @param string $userId the name of the user
*
* @return void
*/
public function readAll($highestItemId, $userId): void
{
$time = $this->timeFactory->getMicroTime();
$this->oldItemMapper->readAll($highestItemId, $time, $userId);
}
/**
* Set a folder read
*
* @param int|null $folderId the id of the folder that should be marked read
* @param int $highestItemId all items below that are marked read. This is
* used to prevent marking items as read that
* the users hasn't seen yet
* @param string $userId the name of the user
*
* @return void
*/
public function readFolder(?int $folderId, $highestItemId, $userId): void
{
$time = $this->timeFactory->getMicroTime();
$this->oldItemMapper->readFolder(
$folderId,
$highestItemId,
$time,
$userId
);
}
/**
* Set a feed read
*
* @param int $feedId the id of the feed that should be marked read
* @param int $highestItemId all items below that are marked read. This is
* used to prevent marking items as read that
* the users hasn't seen yet
* @param string $userId the name of the user
*
* @return void
*/
public function readFeed($feedId, $highestItemId, $userId): void
{
$time = $this->timeFactory->getMicroTime();
$this->oldItemMapper->readFeed($feedId, $highestItemId, $time, $userId);
}
/**
* This method deletes all unread feeds that are not starred and over the
* count of $this->autoPurgeCount starting by the oldest. This is to clean
* up the database so that old entries don't spam your db. As criteria for
* old, the id is taken
*
* @return void
*/
public function autoPurgeOld(): void
{
$count = $this->config->getAppValue(
Application::NAME,
'autoPurgeCount',
Application::DEFAULT_SETTINGS['autoPurgeCount']
);
if ($count >= 0) {
$this->oldItemMapper->deleteReadOlderThanThreshold($count);
}
}
/**
* Returns the newest item id, use this for marking feeds read
*
* @param string $userId the name of the user
* @throws ServiceNotFoundException if there is no newest item
* @return int
*/
public function getNewestItemId($userId)
{
try {
return $this->oldItemMapper->getNewestItemId($userId);
} catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
}
}
/**
* Returns the starred count
*
* @param string $userId the name of the user
* @return int the count
*/
public function starredCount($userId)
{
return $this->oldItemMapper->starredCount($userId);
}
/**
* @param string $userId from which user the items should be taken
* @return array of items which are starred or unread
*/
public function getUnreadOrStarred($userId): array
{
return $this->oldItemMapper->findAllUnreadOrStarred($userId);
}
/**
* Regenerates the search index for all items
*
* @return void
*/
public function generateSearchIndices(): void
{
$this->oldItemMapper->updateSearchIndices();
}
public function findAll(): array
{
return $this->mapper->findAll();
}
}

View File

@ -13,10 +13,16 @@
namespace OCA\News\Service;
use OCA\News\AppInfo\Application;
use OCA\News\Db\Feed;
use OCA\News\Db\FeedType;
use OCA\News\Db\Item;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\Exceptions\ServiceValidationException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
@ -37,7 +43,7 @@ class ItemServiceV2 extends Service
* ItemService constructor.
*
* @param ItemMapperV2 $mapper
* @param IConfig $config
* @param IConfig $config
* @param LoggerInterface $logger
*/
public function __construct(
@ -53,7 +59,7 @@ class ItemServiceV2 extends Service
* Finds all items of a user
*
* @param string $userId The ID/name of the user
* @param array $params Filter parameters
* @param array $params Filter parameters
*
*
* @return Item[]
@ -83,14 +89,15 @@ class ItemServiceV2 extends Service
public function insertOrUpdate(Item $item): Entity
{
try {
$db_item = $this->mapper->findByGuidHash($item->getFeedId(), $item->getGuidHash());
$db_item = $this->findByGuidHash($item->getFeedId(), $item->getGuidHash());
// Transfer user modifications
$item->setUnread($db_item->isUnread())
->setStarred($db_item->isStarred())
->setId($db_item->getId());
$item->generateSearchIndex();
$item->generateSearchIndex();//generates fingerprint
// We don't want to update the database record if there is no
// change in the fetched item
if ($db_item->getFingerprint() === $item->getFingerprint()) {
@ -104,13 +111,36 @@ class ItemServiceV2 extends Service
}
/**
* @param int $feedId
* Return all starred items
*
* @return array
* @param string $userId
*
* @return Item[]
*/
public function findAllForFeed(int $feedId): array
public function starred(string $userId): array
{
return $this->mapper->findAllForFeed($feedId);
return $this->findAllForUser($userId, ['starred' => 1]);
}
/**
* Mark an item as read
*
* @param string $userId Item owner
* @param int $id Item ID
* @param bool $read
*
* @return Item
* @throws ServiceNotFoundException
* @throws ServiceConflictException
*/
public function read(string $userId, int $id, bool $read): Entity
{
/** @var Item $item */
$item = $this->find($userId, $id);
$item->setUnread(!$read);
return $this->mapper->update($item);
}
/**
@ -133,13 +163,232 @@ class ItemServiceV2 extends Service
return $this->mapper->deleteOverThreshold($threshold, $removeUnread);
}
/**
* Mark an item as starred
*
* @param string $userId Item owner
* @param int $id Item ID
* @param bool $starred
*
* @return Item
* @throws ServiceNotFoundException|ServiceConflictException
*/
public function star(string $userId, int $id, bool $starred): Entity
{
/** @var Item $item */
$item = $this->find($userId, $id);
$item->setStarred($starred);
return $this->mapper->update($item);
}
/**
* Mark an item as starred by GUID hash
*
* @param string $userId Item owner
* @param int $feedId Item ID
* @param string $guidHash
* @param bool $starred
*
* @return Item
* @throws ServiceConflictException
* @throws ServiceNotFoundException
*/
public function starByGuid(string $userId, int $feedId, string $guidHash, bool $starred): Entity
{
try {
$item = $this->mapper->findForUserByGuidHash($userId, $feedId, $guidHash);
} catch (DoesNotExistException $ex) {
throw ServiceNotFoundException::from($ex);
} catch (MultipleObjectsReturnedException $ex) {
throw ServiceConflictException::from($ex);
}
$item->setStarred($starred);
return $this->mapper->update($item);
}
/**
* Mark all items as read
*
* @param string $userId Item owner
* @param int $maxItemId
*
* @return void
*/
public function readAll(string $userId, int $maxItemId): void
{
$this->mapper->readAll($userId, $maxItemId);
}
/**
* @param string $userId
*
* @return Item
*/
public function newest(string $userId): Entity
{
try {
return $this->mapper->newest($userId);
} catch (DoesNotExistException $e) {
throw ServiceNotFoundException::from($e);
} catch (MultipleObjectsReturnedException $e) {
throw ServiceConflictException::from($e);
}
}
/**
* @param int $feedId
* @param string $guidHash
*
* @return Item|Entity
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function findForGuidHash(int $feedId, string $guidHash)
public function findByGuidHash(int $feedId, string $guidHash): Entity
{
return $this->mapper->findByGuidHash($feedId, $guidHash);
}
/**
* Convenience method to find all items in a feed.
*
* @param string $userId
* @param int $feedId
*
* @return array
*/
public function findAllInFeed(string $userId, int $feedId): array
{
return $this->findAllInFeedAfter($userId, $feedId, PHP_INT_MIN, false);
}
/**
* Returns all new items in a feed
* @param string $userId the name of the user
* @param int $feedId the id of the feed
* @param int $updatedSince a timestamp with the minimal modification date
* @param boolean $hideRead if unread items should also be returned
*
* @return array of items
*/
public function findAllInFeedAfter(string $userId, int $feedId, int $updatedSince, bool $hideRead): array
{
return $this->mapper->findAllInFeedAfter($userId, $feedId, $updatedSince, $hideRead);
}
/**
* Returns all new items in a folder
* @param string $userId the name of the user
* @param int|null $folderId the id of the folder
* @param int $updatedSince a timestamp with the minimal modification date
* @param boolean $hideRead if unread items should also be returned
*
* @return array of items
*/
public function findAllInFolderAfter(string $userId, ?int $folderId, int $updatedSince, bool $hideRead): array
{
return $this->mapper->findAllInFolderAfter($userId, $folderId, $updatedSince, $hideRead);
}
/**
* Returns all new items of a type
*
* @param string $userId the name of the user
* @param int $feedType the type of feed items to fetch. (starred || unread)
* @param int $updatedSince a timestamp with the minimal modification date
*
* @return array of items
*
* @throws ServiceValidationException
*/
public function findAllAfter(string $userId, int $feedType, int $updatedSince): array
{
if (!in_array($feedType, [FeedType::STARRED, FeedType::UNREAD])) {
throw new ServiceValidationException('Trying to find in unknown type');
}
return $this->mapper->findAllAfter($userId, $feedType, $updatedSince);
}
/**
* Returns all items
*
* @param int $feedId the id of the feed
* @param int $limit how many items should be returned
* @param int $offset the offset
* @param boolean $hideRead if unread items should also be returned
* @param boolean $oldestFirst if it should be ordered by oldest first
* @param string $userId the name of the user
* @param string[] $search an array of keywords that the result should
* contain in either the author, title, link
* or body
*
* @return array of items
*/
public function findAllInFeedWithFilters(
string $userId,
int $feedId,
int $limit,
int $offset,
bool $hideRead,
bool $oldestFirst,
array $search = []
): array {
return $this->mapper->findAllFeed($userId, $feedId, $limit, $offset, $hideRead, $oldestFirst, $search);
}
/**
* Returns all items
*
* @param int|null $folderId the id of the folder
* @param int $limit how many items should be returned
* @param int $offset the offset
* @param boolean $hideRead if unread items should also be returned
* @param boolean $oldestFirst if it should be ordered by oldest first
* @param string $userId the name of the user
* @param string[] $search an array of keywords that the result should
* contain in either the author, title, link
* or body
*
* @return array of items
*/
public function findAllInFolderWithFilters(
string $userId,
?int $folderId,
int $limit,
int $offset,
bool $hideRead,
bool $oldestFirst,
array $search = []
): array {
return $this->mapper->findAllFolder($userId, $folderId, $limit, $offset, $hideRead, $oldestFirst, $search);
}
/**
* Returns all items
*
* @param int $type the type of the feed
* @param int $limit how many items should be returned
* @param int $offset the offset
* @param boolean $oldestFirst if it should be ordered by oldest first
* @param string $userId the name of the user
* @param string[] $search an array of keywords that the result should
* contain in either the author, title, link
* or body
*
* @return array of items
*/
public function findAllWithFilters(
string $userId,
int $type,
int $limit,
int $offset,
bool $oldestFirst,
array $search = []
): array {
return $this->mapper->findAllItems($userId, $type, $limit, $offset, $oldestFirst, $search);
}
}

View File

@ -14,6 +14,7 @@
namespace OCA\News\Service;
use OCA\News\Db\NewsMapperV2;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
@ -65,14 +66,13 @@ abstract class Service
*/
abstract public function findAll(): array;
/**
* Delete an entity
*
* @param int $id the id of the entity
* @param string $userId the name of the user for security reasons
*
* @throws ServiceNotFoundException if the entity does not exist, or there
* @throws ServiceNotFoundException|ServiceConflictException if the entity does not exist, or there
* are more than one of it
*/
public function delete(string $userId, int $id): Entity
@ -102,7 +102,7 @@ abstract class Service
* @param string $userId the name of the user for security reasons
* @param Entity $entity the entity
*
* @throws ServiceNotFoundException if the entity does not exist, or there
* @throws ServiceNotFoundException|ServiceConflictException if the entity does not exist, or there
* are more than one of it
*/
public function update(string $userId, Entity $entity): Entity
@ -119,7 +119,7 @@ abstract class Service
* @param string $userId the name of the user for security reasons
*
* @return Entity the entity
* @throws ServiceNotFoundException if the entity does not exist, or there
* @throws ServiceNotFoundException|ServiceConflictException if the entity does not exist, or there
* are more than one of it
*/
public function find(string $userId, int $id): Entity
@ -127,9 +127,9 @@ abstract class Service
try {
return $this->mapper->findFromUser($userId, $id);
} catch (DoesNotExistException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
throw ServiceNotFoundException::from($ex);
} catch (MultipleObjectsReturnedException $ex) {
throw new ServiceNotFoundException($ex->getMessage());
throw ServiceConflictException::from($ex);
}
}

View File

@ -6,6 +6,7 @@
<directory>./tests/Unit</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./lib/</directory>
@ -13,7 +14,10 @@
<exclude>
<file>./lib/AppInfo/Application.php</file>
<file>./lib/Controller/JSONHttpErrorTrait.php</file>
<file>./lib/**Exception.php</file>
<directory suffix=".php">./lib/*/Exceptions</directory>
<directory suffix=".php">./lib/Migration</directory>
<file>./lib/Db/FeedType.php</file>
<file>./lib/Db/IAPI.php</file>
</exclude>
<report>
<clover outputFile="./build/coverage.xml"/>

View File

@ -82,7 +82,7 @@ class FolderDeleteTest extends TestCase
*/
public function testInValid()
{
$this->expectException('OCA\News\Service\Exceptions\ServiceException');
$this->expectException('OCA\News\Service\Exceptions\ServiceValidationException');
$this->expectExceptionMessage('Can not remove root folder!');
$this->consoleInput->expects($this->exactly(2))

View File

@ -18,7 +18,7 @@ namespace OCA\News\Tests\Unit\Controller;
use Exception;
use OCA\News\Controller\FeedApiController;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\ItemService;
use OCA\News\Service\ItemServiceV2;
use \OCP\AppFramework\Http;
use \OCA\News\Service\Exceptions\ServiceNotFoundException;
@ -40,7 +40,7 @@ class FeedApiControllerTest extends TestCase
private $feedService;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemService
* @var \PHPUnit\Framework\MockObject\MockObject|ItemServiceV2
*/
private $itemService;
@ -80,7 +80,7 @@ class FeedApiControllerTest extends TestCase
$this->feedService = $this->getMockBuilder(FeedServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->itemService = $this->getMockBuilder(ItemService::class)
$this->itemService = $this->getMockBuilder(ItemServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->class = new FeedApiController(
@ -96,18 +96,17 @@ class FeedApiControllerTest extends TestCase
public function testIndex()
{
$feeds = [new Feed()];
$starredCount = 3;
$newestItemId = 2;
$feed = Feed::fromParams(['id' => 5]);
$feeds = [$feed];
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with($this->equalTo($this->userID))
->will($this->returnValue($starredCount));
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with($this->equalTo($this->userID))
->will($this->returnValue($newestItemId));
->will($this->returnValue($feeds[0]));
$this->feedService->expects($this->once())
->method('findAllForUser')
->with($this->equalTo($this->userID))
@ -118,8 +117,8 @@ class FeedApiControllerTest extends TestCase
$this->assertEquals(
[
'feeds' => [$feeds[0]->toAPI()],
'starredCount' => $starredCount,
'newestItemId' => $newestItemId
'starredCount' => 3,
'newestItemId' => 5
],
$response
);
@ -129,14 +128,13 @@ class FeedApiControllerTest extends TestCase
public function testIndexNoNewestItemId()
{
$feeds = [new Feed()];
$starredCount = 3;
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with($this->equalTo($this->userID))
->will($this->returnValue($starredCount));
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with($this->equalTo($this->userID))
->will($this->throwException(new ServiceNotFoundException('')));
$this->feedService->expects($this->once())
@ -149,7 +147,7 @@ class FeedApiControllerTest extends TestCase
$this->assertEquals(
[
'feeds' => [$feeds[0]->toAPI()],
'starredCount' => $starredCount,
'starredCount' => 3,
],
$response
);
@ -203,8 +201,8 @@ class FeedApiControllerTest extends TestCase
->method('fetch')
->with($feeds[0]);
$this->itemService->expects($this->once())
->method('getNewestItemId')
->will($this->returnValue(3));
->method('newest')
->will($this->returnValue(Feed::fromParams(['id' => 3])));
$response = $this->class->create('url', 3);
@ -234,7 +232,7 @@ class FeedApiControllerTest extends TestCase
->method('fetch')
->with($feeds[0]);
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->will($this->throwException(new ServiceNotFoundException('')));
$response = $this->class->create('ho', 3);
@ -287,12 +285,8 @@ class FeedApiControllerTest extends TestCase
public function testRead()
{
$this->itemService->expects($this->once())
->method('readFeed')
->with(
$this->equalTo(3),
$this->equalTo(30),
$this->equalTo($this->userID)
);
->method('read')
->with($this->userID,3,30);
$this->class->read(3, 30);
}

View File

@ -18,7 +18,7 @@ use OCA\News\Db\Folder;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\FolderServiceV2;
use OCA\News\Service\ImportService;
use OCA\News\Service\ItemService;
use OCA\News\Service\ItemServiceV2;
use OCP\AppFramework\Http;
use OCA\News\Db\Feed;
@ -55,8 +55,7 @@ class FeedControllerTest extends TestCase
*/
private $importService;
/**
* TODO: Remove
* @var MockObject|ItemService
* @var MockObject|ItemServiceV2
*/
private $itemService;
@ -87,7 +86,7 @@ class FeedControllerTest extends TestCase
->disableOriginalConstructor()
->getMock();
$this->itemService = $this
->getMockBuilder(ItemService::class)
->getMockBuilder(ItemServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->feedService = $this
@ -138,20 +137,20 @@ class FeedControllerTest extends TestCase
'feeds' => [
['a feed'],
],
'starred' => 13
'starred' => 4
];
$this->feedService->expects($this->once())
->method('findAllForUser')
->with($this->uid)
->will($this->returnValue($result['feeds']));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with($this->uid)
->will($this->throwException(new ServiceNotFoundException('')));
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with($this->uid)
->will($this->returnValue($result['starred']));
->will($this->returnValue([1, 2, 3, 4]));
$response = $this->class->index();
@ -165,7 +164,7 @@ class FeedControllerTest extends TestCase
'feeds' => [
['a feed'],
],
'starred' => 13,
'starred' => 2,
'newestItemId' => 5
];
$this->feedService->expects($this->once())
@ -173,13 +172,13 @@ class FeedControllerTest extends TestCase
->with($this->uid)
->will($this->returnValue($result['feeds']));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with($this->uid)
->will($this->returnValue($result['newestItemId']));
->will($this->returnValue(Feed::fromParams(['id' => 5])));
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with($this->uid)
->will($this->returnValue($result['starred']));
->will($this->returnValue([1, 2]));
$response = $this->class->index();
@ -224,6 +223,30 @@ class FeedControllerTest extends TestCase
}
public function testActiveFeed()
{
$id = 3;
$type = FeedType::FEED;
$result = [
'activeFeed' => [
'id' => $id,
'type' => $type
]
];
$this->feedService->expects($this->once())
->method('find')
->with($this->uid, $id)
->will($this->returnValue(new Feed()));
$this->activeInitMocks($id, $type);
$response = $this->class->active();
$this->assertEquals($result, $response);
}
public function testActiveFeedDoesNotExist()
{
$id = 3;
@ -313,8 +336,8 @@ class FeedControllerTest extends TestCase
];
$this->itemService->expects($this->once())
->method('getNewestItemId')
->will($this->returnValue($result['newestItemId']));
->method('newest')
->will($this->returnValue(Feed::fromParams(['id' => 3])));
$this->feedService->expects($this->once())
->method('purgeDeleted')
->with($this->uid, false);
@ -341,8 +364,8 @@ class FeedControllerTest extends TestCase
];
$this->itemService->expects($this->once())
->method('getNewestItemId')
->will($this->returnValue($result['newestItemId']));
->method('newest')
->will($this->returnValue(Feed::fromParams(['id' => 3])));
$this->feedService->expects($this->once())
->method('purgeDeleted')
->with($this->uid, false);
@ -370,7 +393,7 @@ class FeedControllerTest extends TestCase
->with($this->uid, false);
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->will($this->throwException(new ServiceNotFoundException('')));
$this->feedService->expects($this->once())
@ -522,9 +545,9 @@ class FeedControllerTest extends TestCase
->will($this->returnValue($feed));
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with($this->uid)
->will($this->returnValue(3));
->will($this->returnValue([1, 2, 3]));
$response = $this->class->import(['json']);
@ -540,9 +563,9 @@ class FeedControllerTest extends TestCase
->will($this->returnValue(null));
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with($this->uid)
->will($this->returnValue(3));
->will($this->returnValue([1, 2, 3]));
$response = $this->class->import(['json']);
@ -561,9 +584,9 @@ class FeedControllerTest extends TestCase
]
];
$this->itemService->expects($this->once())
->method('readFeed')
->with(4, 5, $this->uid);
$this->feedService->expects($this->once())
->method('read')
->with($this->uid, 4, 5);
$response = $this->class->read(4, 5);
$this->assertEquals($expected, $response);

View File

@ -37,7 +37,7 @@ class FolderApiControllerTest extends TestCase
private $folderService;
private $itemService;
private $folderAPI;
private $class;
private $user;
private $msg;
@ -65,7 +65,7 @@ class FolderApiControllerTest extends TestCase
$this->itemService = $this->getMockBuilder(ItemService::class)
->disableOriginalConstructor()
->getMock();
$this->folderAPI = new FolderApiController(
$this->class = new FolderApiController(
$request,
$userSession,
$this->folderService,
@ -84,7 +84,7 @@ class FolderApiControllerTest extends TestCase
->with($this->equalTo($this->user->getUID()))
->will($this->returnValue($folders));
$response = $this->folderAPI->index();
$response = $this->class->index();
$this->assertEquals(
[
@ -108,7 +108,7 @@ class FolderApiControllerTest extends TestCase
->with($this->user->getUID(), $folderName)
->will($this->returnValue($folder));
$response = $this->folderAPI->create($folderName);
$response = $this->class->create($folderName);
$this->assertEquals(
[
@ -129,7 +129,7 @@ class FolderApiControllerTest extends TestCase
->method('create')
->will($this->throwException(new ServiceConflictException($msg)));
$response = $this->folderAPI->create('hi');
$response = $this->class->create('hi');
$data = $response->getData();
$this->assertEquals($msg, $data['message']);
@ -148,7 +148,7 @@ class FolderApiControllerTest extends TestCase
->method('create')
->will($this->throwException(new ServiceValidationException($msg)));
$response = $this->folderAPI->create('hi');
$response = $this->class->create('hi');
$data = $response->getData();
$this->assertEquals($msg, $data['message']);
@ -164,7 +164,7 @@ class FolderApiControllerTest extends TestCase
->method('delete')
->with($this->user->getUID(), 23);
$this->folderAPI->delete(23);
$this->class->delete(23);
}
@ -180,7 +180,7 @@ class FolderApiControllerTest extends TestCase
)
);
$response = $this->folderAPI->delete($folderId);
$response = $this->class->delete($folderId);
$data = $response->getData();
$this->assertEquals($this->msg, $data['message']);
@ -197,7 +197,7 @@ class FolderApiControllerTest extends TestCase
->method('rename')
->with($this->user->getUID(), $folderId, $folderName);
$this->folderAPI->update($folderId, $folderName);
$this->class->update($folderId, $folderName);
}
public function testUpdateDoesNotExist()
@ -213,7 +213,7 @@ class FolderApiControllerTest extends TestCase
)
);
$response = $this->folderAPI->update($folderId, $folderName);
$response = $this->class->update($folderId, $folderName);
$data = $response->getData();
$this->assertEquals($this->msg, $data['message']);
@ -234,7 +234,7 @@ class FolderApiControllerTest extends TestCase
)
);
$response = $this->folderAPI->update($folderId, $folderName);
$response = $this->class->update($folderId, $folderName);
$data = $response->getData();
$this->assertEquals($this->msg, $data['message']);
@ -255,7 +255,7 @@ class FolderApiControllerTest extends TestCase
)
);
$response = $this->folderAPI->update($folderId, $folderName);
$response = $this->class->update($folderId, $folderName);
$data = $response->getData();
$this->assertEquals($this->msg, $data['message']);
@ -267,15 +267,25 @@ class FolderApiControllerTest extends TestCase
public function testRead()
{
$this->itemService->expects($this->once())
->method('readFolder')
->with(
$this->equalTo(3),
$this->equalTo(30),
$this->equalTo($this->user->getUID())
);
$this->folderService->expects($this->once())
->method('read')
->with($this->user->getUID(), 3, 30);
$this->class->read(3, 30);
}
public function testUpdateRoot()
{
$response = $this->class->update(null, '');
$this->assertSame(400, $response->getStatus());
}
public function testDeleteRoot()
{
$response = $this->class->delete(null);
$this->assertSame(400, $response->getStatus());
$this->folderAPI->read(3, 30);
}

View File

@ -84,8 +84,6 @@ class FolderControllerTest extends TestCase
$this->class = new FolderController(
$request,
$this->folderService,
$this->feedService,
$this->itemService,
$this->userSession
);
$this->msg = 'ron';
@ -162,6 +160,16 @@ class FolderControllerTest extends TestCase
$this->class->delete(5);
}
public function testDeleteRoot()
{
$this->folderService->expects($this->never())
->method('markDelete')
->with('jack', 5, true);
$response = $this->class->delete(null);
$this->assertEquals(400, $response->getStatus());
}
public function testDeleteDoesNotExist()
{
$this->folderService->expects($this->once())
@ -176,6 +184,20 @@ class FolderControllerTest extends TestCase
$this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND);
}
public function testDeleteConflict()
{
$this->folderService->expects($this->once())
->method('markDelete')
->will($this->throwException(new ServiceConflictException($this->msg)));
$response = $this->class->delete(5);
$params = json_decode($response->render(), true);
$this->assertEquals($this->msg, $params['message']);
$this->assertEquals($response->getStatus(), Http::STATUS_CONFLICT);
}
public function testRename()
{
$folder = new Folder();
@ -186,11 +208,21 @@ class FolderControllerTest extends TestCase
->with('jack', 4, 'tech')
->will($this->returnValue($folder));
$response = $this->class->rename('tech', 4);
$response = $this->class->rename(4, 'tech');
$this->assertEquals($result, $response);
}
public function testRenameRoot()
{
$this->folderService->expects($this->never())
->method('rename');
$response = $this->class->rename(null, 'tech');
$this->assertEquals(400, $response->getStatus());
}
public function testRenameDoesNotExist()
{
$msg = 'except';
@ -199,7 +231,7 @@ class FolderControllerTest extends TestCase
->method('rename')
->will($this->throwException($ex));
$response = $this->class->rename('tech', 5);
$response = $this->class->rename(5, 'tech');
$params = json_decode($response->render(), true);
$this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND);
@ -215,7 +247,7 @@ class FolderControllerTest extends TestCase
->method('rename')
->will($this->throwException($ex));
$response = $this->class->rename('tech', 1);
$response = $this->class->rename(1, 'tech');
$params = json_decode($response->render(), true);
$this->assertEquals($response->getStatus(), Http::STATUS_CONFLICT);
@ -226,19 +258,11 @@ class FolderControllerTest extends TestCase
public function testRead()
{
$feed = new Feed();
$expected = ['feeds' => [$feed->toAPI()]];
$this->folderService->expects($this->once())
->method('read')
->with('jack', 4, 5);
$this->itemService->expects($this->once())
->method('readFolder')
->with(4, 5, 'jack');
$this->feedService->expects($this->once())
->method('findAllForUser')
->with('jack')
->will($this->returnValue([$feed]));
$response = $this->class->read(4, 5);
$this->assertEquals($expected, $response);
$this->class->read(4, 5);
}
@ -267,4 +291,20 @@ class FolderControllerTest extends TestCase
$this->assertEquals($this->msg, $params['message']);
}
public function testRestoreConflict()
{
$this->folderService->expects($this->once())
->method('markDelete')
->with('jack', 5, false)
->will($this->throwException(new ServiceConflictException($this->msg)));
$response = $this->class->restore(5);
$params = json_decode($response->render(), true);
$this->assertEquals(Http::STATUS_CONFLICT, $response->getStatus());
$this->assertEquals($this->msg, $params['message']);
}
}

View File

@ -16,7 +16,6 @@
namespace OCA\News\Tests\Unit\Controller;
use OCA\News\Controller\ItemApiController;
use OCA\News\Service\ItemService;
use OCA\News\Service\ItemServiceV2;
use \OCP\AppFramework\Http;
@ -31,18 +30,28 @@ use PHPUnit\Framework\TestCase;
class ItemApiControllerTest extends TestCase
{
/**
* @var ItemServiceV2|\PHPUnit\Framework\MockObject\MockObject
*/
private $itemService;
private $oldItemService;
private $class;
/**
* @var IUserSession|\PHPUnit\Framework\MockObject\MockObject
*/
private $userSession;
/**
* @var IUser|\PHPUnit\Framework\MockObject\MockObject
*/
private $user;
/**
* @var IRequest|\PHPUnit\Framework\MockObject\MockObject
*/
private $request;
private $msg;
private $uid = 'tom';
private $class;
protected function setUp(): void
{
$this->user = 'tom';
$this->appName = 'news';
$this->request = $this->getMockBuilder(IRequest::class)
->disableOriginalConstructor()
@ -58,24 +67,20 @@ class ItemApiControllerTest extends TestCase
->will($this->returnValue($this->user));
$this->user->expects($this->any())
->method('getUID')
->will($this->returnValue('123'));
$this->oldItemService = $this->getMockBuilder(ItemService::class)
->disableOriginalConstructor()
->getMock();
->will($this->returnValue($this->uid));
$this->itemService = $this->getMockBuilder(ItemServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->class = new ItemApiController(
$this->request,
$this->userSession,
$this->oldItemService,
$this->itemService
);
$this->msg = 'hi';
}
public function testIndex()
public function testIndexForFeed()
{
$item = new Item();
$item->setId(5);
@ -83,26 +88,52 @@ class ItemApiControllerTest extends TestCase
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->oldItemService->expects($this->once())
->method('findAllItems')
->with(
$this->equalTo(2),
$this->equalTo(1),
$this->equalTo(30),
$this->equalTo(20),
$this->equalTo(true),
$this->equalTo(true),
$this->equalTo($this->user->getUID())
)
$this->itemService->expects($this->once())
->method('findAllInFeedWithFilters')
->with($this->uid, 2, 30, 20, false, true)
->will($this->returnValue([$item]));
$response = $this->class->index(0, 2, true, 30, 20, true);
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
public function testIndexForFolder()
{
$item = new Item();
$item->setId(5);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->itemService->expects($this->once())
->method('findAllInFolderWithFilters')
->with($this->uid, 2, 30, 20, false, true)
->will($this->returnValue([$item]));
$response = $this->class->index(1, 2, true, 30, 20, true);
$this->assertEquals(
[
'items' => [$item->toApi()]
], $response
);
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
public function testIndexForItems()
{
$item = new Item();
$item->setId(5);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->itemService->expects($this->once())
->method('findAllWithFilters')
->with($this->uid, 3, 30, 20, true)
->will($this->returnValue([$item]));
$response = $this->class->index(3, 2, true, 30, 20, true);
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
@ -114,30 +145,18 @@ class ItemApiControllerTest extends TestCase
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->oldItemService->expects($this->once())
->method('findAllItems')
->with(
$this->equalTo(2),
$this->equalTo(1),
$this->equalTo(-1),
$this->equalTo(0),
$this->equalTo(false),
$this->equalTo(false),
$this->equalTo($this->user->getUID())
)
$this->itemService->expects($this->once())
->method('findAllInFolderWithFilters')
->with($this->uid, 2, -1, 0, true, false)
->will($this->returnValue([$item]));
$response = $this->class->index(1, 2, false);
$this->assertEquals(
[
'items' => [$item->toApi()]
], $response
);
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
public function testUpdated()
public function testUpdatedFeed()
{
$item = new Item();
$item->setId(5);
@ -145,36 +164,78 @@ class ItemApiControllerTest extends TestCase
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->oldItemService->expects($this->once())
->method('findAllNew')
->with(
$this->equalTo(2),
$this->equalTo(1),
$this->equalTo(30000000),
$this->equalTo(true),
$this->equalTo($this->user->getUID())
)
$this->itemService->expects($this->once())
->method('findAllInFeedAfter')
->with($this->uid, 2, 30000000, false)
->will($this->returnValue([$item]));
$response = $this->class->updated(0, 2, 30);
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
public function testUpdatedFolder()
{
$item = new Item();
$item->setId(5);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->itemService->expects($this->once())
->method('findAllInFolderAfter')
->with($this->uid, 2, 30000000, false)
->will($this->returnValue([$item]));
$response = $this->class->updated(1, 2, 30);
$this->assertEquals(
[
'items' => [$item->toApi()]
], $response
);
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
public function testUpdatedItems()
{
$item = new Item();
$item->setId(5);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->itemService->expects($this->once())
->method('findAllAfter')
->with($this->uid, 3, 30000000)
->will($this->returnValue([$item]));
$response = $this->class->updated(3, 2, 30);
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
public function testUpdatedFeedFullTimestamp()
{
$item = new Item();
$item->setId(5);
$item->setGuid('guid');
$item->setGuidHash('guidhash');
$item->setFeedId(123);
$this->itemService->expects($this->once())
->method('findAllInFeedAfter')
->with($this->uid, 2, 1609598359000000, false)
->will($this->returnValue([$item]));
$response = $this->class->updated(0, 2, '1609598359000000');
$this->assertEquals(['items' => [$item->toApi()]], $response);
}
public function testRead()
{
$this->oldItemService->expects($this->once())
$this->itemService->expects($this->once())
->method('read')
->with(
$this->equalTo(2),
$this->equalTo(true),
$this->equalTo($this->user->getUID())
);
->with($this->user->getUID(), 2, true);
$this->class->read(2);
}
@ -182,7 +243,7 @@ class ItemApiControllerTest extends TestCase
public function testReadDoesNotExist()
{
$this->oldItemService->expects($this->once())
$this->itemService->expects($this->once())
->method('read')
->will(
$this->throwException(
@ -200,12 +261,12 @@ class ItemApiControllerTest extends TestCase
public function testUnread()
{
$this->oldItemService->expects($this->once())
$this->itemService->expects($this->once())
->method('read')
->with(
$this->equalTo($this->user->getUID()),
$this->equalTo(2),
$this->equalTo(false),
$this->equalTo($this->user->getUID())
$this->equalTo(false)
);
$this->class->unread(2);
@ -214,7 +275,7 @@ class ItemApiControllerTest extends TestCase
public function testUnreadDoesNotExist()
{
$this->oldItemService->expects($this->once())
$this->itemService->expects($this->once())
->method('read')
->will(
$this->throwException(
@ -232,14 +293,9 @@ class ItemApiControllerTest extends TestCase
public function testStar()
{
$this->oldItemService->expects($this->once())
->method('star')
->with(
$this->equalTo(2),
$this->equalTo('hash'),
$this->equalTo(true),
$this->equalTo($this->user->getUID())
);
$this->itemService->expects($this->once())
->method('starByGuid')
->with('tom', 2, 'hash', true);
$this->class->star(2, 'hash');
}
@ -247,13 +303,9 @@ class ItemApiControllerTest extends TestCase
public function testStarDoesNotExist()
{
$this->oldItemService->expects($this->once())
->method('star')
->will(
$this->throwException(
new ServiceNotFoundException($this->msg)
)
);
$this->itemService->expects($this->once())
->method('starByGuid')
->will($this->throwException(new ServiceNotFoundException($this->msg)));
$response = $this->class->star(2, 'test');
@ -265,14 +317,9 @@ class ItemApiControllerTest extends TestCase
public function testUnstar()
{
$this->oldItemService->expects($this->once())
->method('star')
->with(
$this->equalTo(2),
$this->equalTo('hash'),
$this->equalTo(false),
$this->equalTo($this->user->getUID())
);
$this->itemService->expects($this->once())
->method('starByGuid')
->with($this->uid, 2, 'hash', false);
$this->class->unstar(2, 'hash');
}
@ -280,8 +327,8 @@ class ItemApiControllerTest extends TestCase
public function testUnstarDoesNotExist()
{
$this->oldItemService->expects($this->once())
->method('star')
$this->itemService->expects($this->once())
->method('starByGuid')
->will(
$this->throwException(
new ServiceNotFoundException($this->msg)
@ -298,12 +345,9 @@ class ItemApiControllerTest extends TestCase
public function testReadAll()
{
$this->oldItemService->expects($this->once())
$this->itemService->expects($this->once())
->method('readAll')
->with(
$this->equalTo(30),
$this->equalTo($this->user->getUID())
);
->with($this->user->getUID(), 30);
$this->class->readAll(30);
}
@ -312,11 +356,11 @@ class ItemApiControllerTest extends TestCase
public function testReadMultiple()
{
$this->oldItemService->expects($this->exactly(2))
$this->itemService->expects($this->exactly(2))
->method('read')
->withConsecutive(
[2, true, $this->user->getUID()],
[4, true, $this->user->getUID()]
[$this->user->getUID(), 2, true],
[$this->user->getUID(), 4, true]
);
$this->class->readMultiple([2, 4]);
}
@ -324,24 +368,24 @@ class ItemApiControllerTest extends TestCase
public function testReadMultipleDoesntCareAboutException()
{
$this->oldItemService->expects($this->exactly(2))
$this->itemService->expects($this->exactly(2))
->method('read')
->withConsecutive(
[2, true, $this->user->getUID()],
[4, true, $this->user->getUID()]
[$this->user->getUID(), 2, true],
[$this->user->getUID(), 4, true]
)
->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), null);
->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), new Item());
$this->class->readMultiple([2, 4]);
}
public function testUnreadMultiple()
{
$this->oldItemService->expects($this->exactly(2))
$this->itemService->expects($this->exactly(2))
->method('read')
->withConsecutive(
[2, false, $this->user->getUID()],
[4, false, $this->user->getUID()]
[$this->user->getUID(), 2, false],
[$this->user->getUID(), 4, false]
);
$this->class->unreadMultiple([2, 4]);
}
@ -360,11 +404,11 @@ class ItemApiControllerTest extends TestCase
]
];
$this->oldItemService->expects($this->exactly(2))
->method('star')
$this->itemService->expects($this->exactly(2))
->method('starByGuid')
->withConsecutive(
[2, 'a', true, $this->user->getUID()],
[4, 'b', true, $this->user->getUID()]
[$this->user->getUID(), 2, 'a', true],
[$this->user->getUID(), 4, 'b', true]
);
$this->class->starMultiple($ids);
}
@ -383,13 +427,13 @@ class ItemApiControllerTest extends TestCase
]
];
$this->oldItemService->expects($this->exactly(2))
->method('star')
$this->itemService->expects($this->exactly(2))
->method('starByGuid')
->withConsecutive(
[2, 'a', true, $this->user->getUID()],
[4, 'b', true, $this->user->getUID()]
[$this->user->getUID(), 2, 'a', true],
[$this->user->getUID(), 4, 'b', true]
)
->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), null);
->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('')), new Item());
$this->class->starMultiple($ids);
}
@ -408,11 +452,11 @@ class ItemApiControllerTest extends TestCase
]
];
$this->oldItemService->expects($this->exactly(2))
->method('star')
$this->itemService->expects($this->exactly(2))
->method('starByGuid')
->withConsecutive(
[2, 'a', false, $this->user->getUID()],
[4, 'b', false, $this->user->getUID()]
[$this->user->getUID(), 2, 'a', false],
[$this->user->getUID(), 4, 'b', false]
);
$this->class->unstarMultiple($ids);

View File

@ -15,7 +15,7 @@ namespace OCA\News\Tests\Unit\Controller;
use OCA\News\Controller\ItemController;
use OCA\News\Service\FeedServiceV2;
use OCA\News\Service\ItemService;
use OCA\News\Service\ItemServiceV2;
use \OCP\AppFramework\Http;
use \OCA\News\Db\Item;
@ -39,7 +39,7 @@ class ItemControllerTest extends TestCase
*/
private $settings;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemService
* @var \PHPUnit\Framework\MockObject\MockObject|ItemServiceV2
*/
private $itemService;
/**
@ -72,7 +72,7 @@ class ItemControllerTest extends TestCase
->disableOriginalConstructor()
->getMock();
$this->itemService =
$this->getMockBuilder(ItemService::class)
$this->getMockBuilder(ItemServiceV2::class)
->disableOriginalConstructor()
->getMock();
$this->feedService =
@ -106,7 +106,7 @@ class ItemControllerTest extends TestCase
{
$this->itemService->expects($this->once())
->method('read')
->with(4, true, 'user');
->with('user', 4, true);
$this->controller->read(4, true);
}
@ -133,8 +133,8 @@ class ItemControllerTest extends TestCase
$this->itemService->expects($this->exactly(2))
->method('read')
->withConsecutive(
[2, true, 'user'],
[4, true, 'user']
['user', 2, true],
['user', 4, true]
);
$this->controller->readMultiple([2, 4]);
@ -147,10 +147,10 @@ class ItemControllerTest extends TestCase
$this->itemService->expects($this->exactly(2))
->method('read')
->withConsecutive(
[2, true, 'user'],
[4, true, 'user']
['user', 2, true],
['user', 4, true]
)
->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('yo')), null);
->willReturnOnConsecutiveCalls($this->throwException(new ServiceNotFoundException('yo')), new Item());
$this->controller->readMultiple([2, 4]);
}
@ -158,8 +158,8 @@ class ItemControllerTest extends TestCase
public function testStar()
{
$this->itemService->expects($this->once())
->method('star')
->with(4, 'test', true, 'user');
->method('starByGuid')
->with('user', 4, 'test', true);
$this->controller->star(4, 'test', true);
}
@ -170,7 +170,7 @@ class ItemControllerTest extends TestCase
$msg = 'ho';
$this->itemService->expects($this->once())
->method('star')
->method('starByGuid')
->will($this->throwException(new ServiceNotFoundException($msg)));
$response = $this->controller->star(4, 'test', false);
@ -189,7 +189,7 @@ class ItemControllerTest extends TestCase
$this->itemService->expects($this->once())
->method('readAll')
->with(5, 'user');
->with('user', 5);
$this->feedService->expects($this->once())
->method('findAllForUser')
->with('user')
@ -199,8 +199,14 @@ class ItemControllerTest extends TestCase
$this->assertEquals($expected, $response);
}
private function itemsApiExpects($id, $type, $oldestFirst = '1')
/**
* Setup expectations for settings
*
* @param $id
* @param $type
* @param string $oldestFirst
*/
private function itemsApiExpects($id, $type, $oldestFirst = '1'): void
{
$this->settings->expects($this->exactly(2))
->method('getUserValue')
@ -218,14 +224,14 @@ class ItemControllerTest extends TestCase
}
public function testIndex()
public function testIndexForFeed()
{
$feeds = [new Feed()];
$result = [
'items' => [new Item()],
'feeds' => $feeds,
'newestItemId' => $this->newestItemId,
'starred' => 3111
'starred' => 3
];
$this->itemsApiExpects(2, FeedType::FEED, '0');
@ -236,18 +242,18 @@ class ItemControllerTest extends TestCase
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with('user')
->will($this->returnValue($this->newestItemId));
->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId])));
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with('user')
->will($this->returnValue(3111));
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('findAllItems')
->with(2, FeedType::FEED, 3, 0, true, false, 'user', [])
->method('findAllInFeedWithFilters')
->with('user', 2, 3, 0, false, false, [])
->will($this->returnValue($result['items']));
$response = $this->controller->index(FeedType::FEED, 2, 3);
@ -255,14 +261,88 @@ class ItemControllerTest extends TestCase
}
public function testIndexSearch()
public function testIndexForFolder()
{
$feeds = [new Feed()];
$result = [
'items' => [new Item()],
'feeds' => $feeds,
'newestItemId' => $this->newestItemId,
'starred' => 3111
'starred' => 3
];
$this->itemsApiExpects(2, FeedType::FOLDER, '0');
$this->feedService->expects($this->once())
->method('findAllForUser')
->with('user')
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('newest')
->with('user')
->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId])));
$this->itemService->expects($this->once())
->method('starred')
->with('user')
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('findAllInFolderWithFilters')
->with('user', 2, 3, 0, false, false, [])
->will($this->returnValue($result['items']));
$response = $this->controller->index(FeedType::FOLDER, 2, 3);
$this->assertEquals($result, $response);
}
public function testIndexForOther()
{
$feeds = [new Feed()];
$result = [
'items' => [new Item()],
'feeds' => $feeds,
'newestItemId' => $this->newestItemId,
'starred' => 3
];
$this->itemsApiExpects(2, FeedType::STARRED, '0');
$this->feedService->expects($this->once())
->method('findAllForUser')
->with('user')
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('newest')
->with('user')
->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId])));
$this->itemService->expects($this->once())
->method('starred')
->with('user')
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('findAllWithFilters')
->with('user', 2, 3, 0, false, [])
->will($this->returnValue($result['items']));
$response = $this->controller->index(FeedType::STARRED, 2, 3);
$this->assertEquals($result, $response);
}
public function testIndexSearchFeed()
{
$feeds = [new Feed()];
$result = [
'items' => [new Item()],
'feeds' => $feeds,
'newestItemId' => $this->newestItemId,
'starred' => 3
];
$this->itemsApiExpects(2, FeedType::FEED, '0');
@ -273,18 +353,18 @@ class ItemControllerTest extends TestCase
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with('user')
->will($this->returnValue($this->newestItemId));
->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId])));
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with('user')
->will($this->returnValue(3111));
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('findAllItems')
->with(2, FeedType::FEED, 3, 0, true, false, 'user', ['test', 'search'])
->method('findAllInFeedWithFilters')
->with('user', 2, 3, 0, false, false, ['test', 'search'])
->will($this->returnValue($result['items']));
$response = $this->controller->index(FeedType::FEED, 2, 3, 0, null, null, 'test%20%20search%20');
@ -299,8 +379,8 @@ class ItemControllerTest extends TestCase
$this->itemsApiExpects(2, FeedType::FEED);
$this->itemService->expects($this->once())
->method('findAllItems')
->with(2, FeedType::FEED, 3, 10, true, true, 'user')
->method('findAllInFeedWithFilters')
->with('user', 2, 3, 10, false, true)
->will($this->returnValue($result['items']));
$this->feedService->expects($this->never())
@ -316,7 +396,7 @@ class ItemControllerTest extends TestCase
$this->itemsApiExpects(2, FeedType::FEED);
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with('user')
->will($this->throwException(new ServiceNotFoundException('')));
@ -325,14 +405,14 @@ class ItemControllerTest extends TestCase
}
public function testNewItems()
public function testNewItemsFeed()
{
$feeds = [new Feed()];
$result = [
'items' => [new Item()],
'feeds' => $feeds,
'newestItemId' => $this->newestItemId,
'starred' => 3111
'starred' => 3
];
$this->settings->expects($this->once())
@ -346,18 +426,18 @@ class ItemControllerTest extends TestCase
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with('user')
->will($this->returnValue($this->newestItemId));
->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId])));
$this->itemService->expects($this->once())
->method('starredCount')
->method('starred')
->with('user')
->will($this->returnValue(3111));
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('findAllNew')
->with(2, FeedType::FEED, 3, true, 'user')
->method('findAllInFeedAfter')
->with('user', 2, 3, false)
->will($this->returnValue($result['items']));
$response = $this->controller->newItems(FeedType::FEED, 2, 3);
@ -365,6 +445,86 @@ class ItemControllerTest extends TestCase
}
public function testNewItemsFolder()
{
$feeds = [new Feed()];
$result = [
'items' => [new Item()],
'feeds' => $feeds,
'newestItemId' => $this->newestItemId,
'starred' => 3
];
$this->settings->expects($this->once())
->method('getUserValue')
->with('user', $this->appName, 'showAll')
->will($this->returnValue('1'));
$this->feedService->expects($this->once())
->method('findAllForUser')
->with('user')
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('newest')
->with('user')
->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId])));
$this->itemService->expects($this->once())
->method('starred')
->with('user')
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('findAllInFolderAfter')
->with('user', 2, 3, false)
->will($this->returnValue($result['items']));
$response = $this->controller->newItems(FeedType::FOLDER, 2, 3);
$this->assertEquals($result, $response);
}
public function testNewItemsOther()
{
$feeds = [new Feed()];
$result = [
'items' => [new Item()],
'feeds' => $feeds,
'newestItemId' => $this->newestItemId,
'starred' => 3
];
$this->settings->expects($this->once())
->method('getUserValue')
->with('user', $this->appName, 'showAll')
->will($this->returnValue('1'));
$this->feedService->expects($this->once())
->method('findAllForUser')
->with('user')
->will($this->returnValue($feeds));
$this->itemService->expects($this->once())
->method('newest')
->with('user')
->will($this->returnValue(Item::fromParams(['id' => $this->newestItemId])));
$this->itemService->expects($this->once())
->method('starred')
->with('user')
->will($this->returnValue([1, 2, 3]));
$this->itemService->expects($this->once())
->method('findAllAfter')
->with('user', 6, 3)
->will($this->returnValue($result['items']));
$response = $this->controller->newItems(FeedType::UNREAD, 2, 3);
$this->assertEquals($result, $response);
}
public function testGetNewItemsNoNewestItemsId()
{
$this->settings->expects($this->once())
@ -373,7 +533,7 @@ class ItemControllerTest extends TestCase
->will($this->returnValue('1'));
$this->itemService->expects($this->once())
->method('getNewestItemId')
->method('newest')
->with('user')
->will($this->throwException(new ServiceNotFoundException('')));

View File

@ -16,6 +16,7 @@ namespace OCA\News\Tests\Unit\Controller;
use OC\L10N\L10N;
use OCA\News\Controller\PageController;
use \OCA\News\Db\FeedType;
use OCA\News\Explore\Exceptions\RecommendedSiteNotFoundException;
use OCA\News\Explore\RecommendedSites;
use OCA\News\Service\StatusService;
use OCP\IConfig;
@ -26,7 +27,6 @@ use OCP\IUser;
use OCP\IUserSession;
use PHPUnit\Framework\TestCase;
class PageControllerTest extends TestCase
{
@ -278,4 +278,24 @@ class PageControllerTest extends TestCase
}
public function testExploreError()
{
$this->settings->expects($this->exactly(2))
->method('setUserValue')
->withConsecutive(
['becka', 'news', 'lastViewedFeedId', 0],
['becka', 'news', 'lastViewedFeedType', FeedType::EXPLORE]
);
$this->recommended->expects($this->once())
->method('forLanguage')
->with('nl')
->will($this->throwException(new RecommendedSiteNotFoundException('error')));
$out = $this->controller->explore('nl');
$this->assertEquals(404, $out->getStatus());
}
}

View File

@ -69,7 +69,7 @@ class FeedMapperTest extends MapperTestUtility
->getMock();
$func = $this->getMockBuilder(IQueryFunction::class)
->getMock();
->getMock();
$funcbuilder->expects($this->once())
->method('count')
@ -451,4 +451,94 @@ class FeedMapperTest extends MapperTestUtility
$result = $this->class->findAllFromFolder(null);
$this->assertEquals($this->feeds, $result);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::read
*/
public function testRead()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('update')
->with('news_items', 'items')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('innerJoin')
->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('setValue')
->with('unread', 0)
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('andWhere')
->withConsecutive(['feeds.user_id = :userId'], ['feeds.id = :feedId'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('setParameter')
->withConsecutive(['userId', 'admin'], ['feedId', 1])
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('getSQL')
->will($this->returnValue('QUERY'));
$this->db->expects($this->exactly(1))
->method('executeUpdate')
->with('QUERY');
$this->class->read('admin', 1);
}
/**
* @covers \OCA\News\Db\FeedMapperV2::read
*/
public function testReadWithMaxId()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('update')
->with('news_items', 'items')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('innerJoin')
->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('setValue')
->with('unread', 0)
->will($this->returnSelf());
$this->builder->expects($this->exactly(3))
->method('andWhere')
->withConsecutive(['feeds.user_id = :userId'], ['feeds.id = :feedId'], ['items.id =< :maxItemId'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(3))
->method('setParameter')
->withConsecutive(['userId', 'admin'], ['feedId', 1], ['maxItemId', 4])
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('getSQL')
->will($this->returnValue('QUERY'));
$this->db->expects($this->exactly(1))
->method('executeUpdate')
->with('QUERY');
$this->class->read('admin', 1, 4);
}
}

View File

@ -279,4 +279,94 @@ class FolderMapperTest extends MapperTestUtility
$result = $this->class->findAll();
$this->assertEquals($this->folders, $result);
}
/**
* @covers \OCA\News\Db\FolderMapperV2::read
*/
public function testRead()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('update')
->with('news_items', 'items')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('innerJoin')
->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('setValue')
->with('unread', 0)
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('andWhere')
->withConsecutive(['feeds.user_id = :userId'], ['feeds.folder_id = :folderId'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(2))
->method('setParameter')
->withConsecutive(['userId', 'admin'], ['folderId', 1])
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('getSQL')
->will($this->returnValue('QUERY'));
$this->db->expects($this->exactly(1))
->method('executeUpdate')
->with('QUERY');
$this->class->read('admin', 1);
}
/**
* @covers \OCA\News\Db\FolderMapperV2::read
*/
public function testReadWithMaxId()
{
$this->db->expects($this->once())
->method('getQueryBuilder')
->willReturn($this->builder);
$this->builder->expects($this->once())
->method('update')
->with('news_items', 'items')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('innerJoin')
->with('items', 'news_feeds', 'feeds', 'items.feed_id = feeds.id')
->will($this->returnSelf());
$this->builder->expects($this->once())
->method('setValue')
->with('unread', 0)
->will($this->returnSelf());
$this->builder->expects($this->exactly(3))
->method('andWhere')
->withConsecutive(['feeds.user_id = :userId'], ['feeds.folder_id = :folderId'], ['items.id =< :maxItemId'])
->will($this->returnSelf());
$this->builder->expects($this->exactly(3))
->method('setParameter')
->withConsecutive(['userId', 'admin'], ['folderId', 1], ['maxItemId', 4])
->will($this->returnSelf());
$this->builder->expects($this->exactly(1))
->method('getSQL')
->will($this->returnValue('QUERY'));
$this->db->expects($this->exactly(1))
->method('executeUpdate')
->with('QUERY');
$this->class->read('admin', 1, 4);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +0,0 @@
<?php
/**
* Nextcloud - News
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Alessandro Cosentino <cosenal@gmail.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
*/
namespace OCA\News\Tests\Unit\Db;
use OCA\News\Db\ItemMapper;
use OCA\News\Db\MapperFactory;
use OCA\News\Utility\Time;
use PHPUnit\Framework\TestCase;
use OCP\IDBConnection;
use OCA\News\Db\Mysql\ItemMapper as MysqlMapper;
class MapperFactoryTest extends TestCase
{
/**
* @var \PHPUnit\Framework\MockObject\MockObject|IDBConnection
*/
private $db;
public function setUp(): void
{
$this->db = $this->getMockBuilder(IDBConnection::class)
->disableOriginalConstructor()
->getMock();
}
public function testGetItemMapperSqlite()
{
$factory = new MapperFactory($this->db, 'sqlite', new Time());
$this->assertTrue($factory->build() instanceof ItemMapper);
}
public function testGetItemMapperPostgres()
{
$factory = new MapperFactory($this->db, 'pgsql', new Time());
$this->assertTrue($factory->build() instanceof ItemMapper);
}
public function testGetItemMapperMysql()
{
$factory = new MapperFactory($this->db, 'mysql', new Time());
$this->assertTrue($factory->build() instanceof MysqlMapper);
}
}

View File

@ -24,6 +24,7 @@
namespace OCA\News\Tests\Unit\Db;
use Doctrine\DBAL\Driver\Statement;
use OC\DB\QueryBuilder\QueryBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@ -74,6 +75,7 @@ abstract class MapperTestUtility extends TestCase
->getMock();
$this->builder = $this->getMockBuilder(IQueryBuilder::class)
->disableOriginalConstructor()
->getMock();
$this->cursor = $this->getMockBuilder(Statement::class)

View File

@ -815,8 +815,8 @@ class FeedServiceTest extends TestCase
->will($this->returnValue([$feed1, $feed2]));
$this->itemService->expects($this->exactly(2))
->method('findAllForFeed')
->withConsecutive([1], [2])
->method('findAllInFeed')
->withConsecutive(['jack', 1], ['jack', 2])
->willReturn(['a']);
$feeds = $this->class->findAllForUserRecursive($this->uid);
@ -824,4 +824,21 @@ class FeedServiceTest extends TestCase
$this->assertEquals(['a'], $feeds[1]->items);
}
public function testRead()
{
$feed1 = new Feed();
$feed1->setId(1);
$this->mapper->expects($this->once())
->method('findFromUser')
->with($this->uid, 1)
->will($this->returnValue($feed1));
$this->mapper->expects($this->exactly(1))
->method('read')
->withConsecutive(['jack', 1, null]);
$this->class->read($this->uid, 1);
}
}

View File

@ -262,4 +262,21 @@ class FolderServiceTest extends TestCase
}
public function testRead()
{
$folder = new Folder();
$folder->setId(1);
$this->mapper->expects($this->once())
->method('findFromUser')
->with('jack', 1)
->will($this->returnValue($folder));
$this->mapper->expects($this->exactly(1))
->method('read')
->withConsecutive(['jack', 1, null]);
$this->class->read('jack', 1);
}
}

View File

@ -13,84 +13,63 @@
namespace OCA\News\Tests\Unit\Service;
use OC\Log;
use OCA\News\Db\ItemMapper;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\ItemService;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceValidationException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Utility\PsrLogger;
use OCA\News\Utility\Time;
use OCA\News\Service\ItemServiceV2;
use \OCP\AppFramework\Db\DoesNotExistException;
use \OCA\News\Db\Item;
use \OCA\News\Db\FeedType;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
/**
* Class ItemServiceTest
*
* @package OCA\News\Tests\Unit\Service
*/
class ItemServiceTest extends TestCase
{
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemMapper
*/
private $oldItemMapper;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|ItemMapperV2
* @var MockObject|ItemMapperV2
*/
private $mapper;
/**
* @var ItemService
* @var ItemServiceV2
*/
private $itemService;
private $class;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|IConfig
* @var MockObject|IConfig
*/
private $config;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|LoggerInterface
* @var MockObject|LoggerInterface
*/
private $logger;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|Time
*/
private $timeFactory;
/**
* @var int
*/
private $newestItemId;
/**
* @var string
*/
private $time;
private $user;
protected function setUp(): void
{
$this->time = '222';
$this->timeFactory = $this->getMockBuilder(Time::class)
->disableOriginalConstructor()
->getMock();
$this->timeFactory->expects($this->any())
->method('getTime')
->will($this->returnValue($this->time));
$this->timeFactory->expects($this->any())
->method('getMicroTime')
->will($this->returnValue($this->time));
$this->mapper = $this->getMockBuilder(ItemMapperV2::class)
->disableOriginalConstructor()
->getMock();
$this->oldItemMapper = $this->getMockBuilder(ItemMapper::class)
->disableOriginalConstructor()
->getMock();
$this->config = $this->getMockBuilder(IConfig::class)
->disableOriginalConstructor()
->getMock();
@ -99,10 +78,8 @@ class ItemServiceTest extends TestCase
->disableOriginalConstructor()
->getMock();
$this->itemService = new ItemService(
$this->class = new ItemServiceV2(
$this->mapper,
$this->oldItemMapper,
$this->timeFactory,
$this->config,
$this->logger
);
@ -115,164 +92,126 @@ class ItemServiceTest extends TestCase
$this->newestItemId = 4;
}
public function testFindAllNewFeed()
{
$type = FeedType::FEED;
$this->oldItemMapper->expects($this->once())
->method('findAllNewFeed')
->with(
$this->equalTo(3),
$this->equalTo(20333),
$this->equalTo(true),
$this->equalTo('jack')
)
$this->mapper->expects($this->once())
->method('findAllInFeedAfter')
->with('jack', 2, 20333, true)
->will($this->returnValue([]));
$result = $this->itemService->findAllNew(3, $type, 20333, true, 'jack');
$result = $this->class->findAllInFeedAfter($this->user, 2, 20333, true);
$this->assertEquals([], $result);
}
public function testFindAllNewFolder()
{
$type = FeedType::FOLDER;
$this->oldItemMapper->expects($this->once())
->method('findAllNewFolder')
->with(
$this->equalTo(3),
$this->equalTo(20333),
$this->equalTo(true),
$this->equalTo('jack')
)
->will($this->returnValue(['val']));
$this->mapper->expects($this->once())
->method('findAllInFolderAfter')
->with('jack', 2, 20333, true)
->will($this->returnValue([]));
$result = $this->itemService->findAllNew(3, $type, 20333, true, 'jack');
$this->assertEquals(['val'], $result);
$result = $this->class->findAllInFolderAfter($this->user, 2, 20333, true);
$this->assertEquals([], $result);
}
public function testFindAllNew()
public function testFindAllNewItem()
{
$type = FeedType::STARRED;
$this->oldItemMapper->expects($this->once())
->method('findAllNew')
->with(
$this->equalTo(20333),
$this->equalTo($type),
$this->equalTo(true),
$this->equalTo('jack')
)
->will($this->returnValue(['val']));
$this->mapper->expects($this->once())
->method('findAllAfter')
->with('jack', 2, 20333)
->will($this->returnValue([]));
$result = $this->itemService->findAllNew(
3, $type, 20333, true,
'jack'
);
$this->assertEquals(['val'], $result);
$result = $this->class->findAllAfter($this->user, 2, 20333);
$this->assertEquals([], $result);
}
public function testFindAllNewItemWrongType()
{
$this->expectException(ServiceValidationException::class);
$this->expectExceptionMessage('Trying to find in unknown type');
$this->mapper->expects($this->never())
->method('findAllAfter');
$result = $this->class->findAllAfter($this->user, 3, 20333);
$this->assertEquals([], $result);
}
public function testFindAllFeed()
{
$type = FeedType::FEED;
$this->oldItemMapper->expects($this->once())
$this->mapper->expects($this->once())
->method('findAllFeed')
->with(
$this->equalTo(3),
$this->equalTo(20),
$this->equalTo(5),
$this->equalTo(true),
$this->equalTo(false),
$this->equalTo('jack'),
$this->equalTo([])
)
->with('jack', 3, 20, 5, true, false, [])
->will($this->returnValue(['val']));
$result = $this->itemService->findAllItems(
3, $type, 20, 5,
true, false, 'jack'
$result = $this->class->findAllInFeedWithFilters(
'jack',
3,
20,
5,
true,
false
);
$this->assertEquals(['val'], $result);
}
public function testFindAllFolder()
{
$type = FeedType::FOLDER;
$this->oldItemMapper->expects($this->once())
$this->mapper->expects($this->once())
->method('findAllFolder')
->with(
$this->equalTo(3),
$this->equalTo(20),
$this->equalTo(5),
$this->equalTo(true),
$this->equalTo(true),
$this->equalTo('jack'),
$this->equalTo([])
)
->with('jack', 3, 20, 5, true, true, [])
->will($this->returnValue(['val']));
$result = $this->itemService->findAllItems(
3, $type, 20, 5,
true, true, 'jack'
$result = $this->class->findAllInFolderWithFilters(
'jack',
3,
20,
5,
true,
true,
[]
);
$this->assertEquals(['val'], $result);
}
public function testFindAll()
public function testFindAllItems()
{
$type = FeedType::STARRED;
$this->oldItemMapper->expects($this->once())
$this->mapper->expects($this->once())
->method('findAllItems')
->with(
$this->equalTo(20),
$this->equalTo(5),
$this->equalTo($type),
$this->equalTo(true),
$this->equalTo(true),
$this->equalTo('jack'),
$this->equalTo([])
)
->with('jack', $type, 20, 5, true, [])
->will($this->returnValue(['val']));
$result = $this->itemService->findAllItems(
3, $type, 20, 5,
true, true, 'jack'
);
$result = $this->class->findAllWithFilters('jack', $type, 20, 5, true);
$this->assertEquals(['val'], $result);
}
public function testFindAllSearch()
{
$type = FeedType::STARRED;
$search = ['test'];
$this->oldItemMapper->expects($this->once())
$this->mapper->expects($this->once())
->method('findAllItems')
->with(
$this->equalTo(20),
$this->equalTo(5),
$this->equalTo($type),
$this->equalTo(true),
$this->equalTo(true),
$this->equalTo('jack'),
$this->equalTo($search)
)
->with('jack', $type, 20, 5, true, $search)
->will($this->returnValue(['val']));
$result = $this->itemService->findAllItems(
3, $type, 20, 5,
true, true, 'jack', $search
);
$result = $this->class->findAllWithFilters('jack', $type, 20, 5, true, $search);
$this->assertEquals(['val'], $result);
}
public function testFindAll()
{
$this->mapper->expects($this->once())
->method('findAll')
->will($this->returnValue(['val']));
$result = $this->class->findAll();
$this->assertEquals(['val'], $result);
}
public function testStar()
public function testStarByGuid()
{
$itemId = 3;
$feedId = 5;
@ -287,20 +226,19 @@ class ItemServiceTest extends TestCase
$expectedItem->setId($itemId);
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with($feedId, $guidHash)
->method('findForUserByGuidHash')
->with('jack', $feedId, $guidHash)
->will($this->returnValue($item));
$this->mapper->expects($this->once())
->method('update')
->with($this->equalTo($expectedItem));
$this->itemService->star($feedId, $guidHash, true, 'jack');
$this->class->starByGuid('jack', $feedId, $guidHash, true);
$this->assertTrue($item->isStarred());
}
public function testUnstar()
{
$itemId = 3;
@ -317,162 +255,111 @@ class ItemServiceTest extends TestCase
$expectedItem->setId($itemId);
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with($feedId, $guidHash)
->method('findForUserByGuidHash')
->with('jack', $feedId, $guidHash)
->will($this->returnValue($item));
$this->mapper->expects($this->once())
->method('update')
->with($this->equalTo($expectedItem));
$this->itemService->star($feedId, $guidHash, false, 'jack');
$this->class->starByGuid('jack', $feedId, $guidHash, false);
$this->assertFalse($item->isStarred());
}
public function testRead()
{
$itemId = 3;
$item = new Item();
$item->setId($itemId);
$item->setUnread(true);
$item = $this->getMockBuilder(Item::class)
->getMock();
$expectedItem = new Item();
$expectedItem->setUnread(false);
$expectedItem->setId($itemId);
$expectedItem->setLastModified($this->time);
$item->expects($this->once())
->method('setUnread')
->with(false);
$this->oldItemMapper->expects($this->once())
->method('readItem')
->with(
$this->equalTo($itemId),
$this->equalTo(true),
$this->equalTo($this->time),
$this->equalTo('jack')
)
$this->mapper->expects($this->once())
->method('findFromUser')
->with('jack', 3)
->will($this->returnValue($item));
$this->itemService->read($itemId, true, 'jack');
$this->mapper->expects($this->once())
->method('update')
->with($item)
->will($this->returnValue($item));
$this->class->read('jack', 3, true);
}
public function testReadDoesNotExist()
public function testStar()
{
$item = $this->getMockBuilder(Item::class)
->getMock();
$this->expectException(ServiceNotFoundException::class);
$this->oldItemMapper->expects($this->once())
->method('readItem')
->will($this->throwException(new DoesNotExistException('')));
$item->expects($this->once())
->method('setStarred')
->with(true);
$this->itemService->read(1, true, 'jack');
$this->mapper->expects($this->once())
->method('findFromUser')
->with('jack', 3)
->will($this->returnValue($item));
$this->mapper->expects($this->once())
->method('update')
->with($item)
->will($this->returnValue($item));
$this->class->star('jack', 3, true);
}
public function testStarDoesNotExist()
public function testStarByGuidDoesNotExist()
{
$this->expectException(ServiceNotFoundException::class);
$this->mapper->expects($this->once())
->method('findByGuidHash')
->method('findForUserByGuidHash')
->will($this->throwException(new DoesNotExistException('')));
$this->itemService->star(1, 'hash', true, 'jack');
$this->class->starByGuid('jack', 1, 'hash', true);
}
public function testStarByGuidDuplicate()
{
$this->expectException(ServiceConflictException::class);
$this->mapper->expects($this->once())
->method('findForUserByGuidHash')
->will($this->throwException(new MultipleObjectsReturnedException('')));
$this->class->starByGuid('jack', 1, 'hash', true);
}
public function testReadAll()
{
$highestItemId = 6;
$this->oldItemMapper->expects($this->once())
$this->mapper->expects($this->once())
->method('readAll')
->with(
$this->equalTo($highestItemId),
$this->equalTo($this->time),
$this->equalTo('jack')
);
->with('jack', $highestItemId);
$this->itemService->readAll($highestItemId, 'jack');
$this->class->readAll('jack', $highestItemId);
}
public function testReadFolder()
{
$folderId = 3;
$highestItemId = 6;
$this->oldItemMapper->expects($this->once())
->method('readFolder')
->with(
$this->equalTo($folderId),
$this->equalTo($highestItemId),
$this->equalTo($this->time),
$this->equalTo('jack')
);
$this->itemService->readFolder($folderId, $highestItemId, 'jack');
}
public function testReadFeed()
{
$feedId = 3;
$highestItemId = 6;
$this->oldItemMapper->expects($this->once())
->method('readFeed')
->with(
$this->equalTo($feedId),
$this->equalTo($highestItemId),
$this->equalTo($this->time),
$this->equalTo('jack')
);
$this->itemService->readFeed($feedId, $highestItemId, 'jack');
}
public function testAutoPurgeOldWillPurgeOld()
{
$this->config->expects($this->once())
->method('getAppValue')
->with('news', 'autoPurgeCount')
->will($this->returnValue(2));
$this->oldItemMapper->expects($this->once())
->method('deleteReadOlderThanThreshold')
->with($this->equalTo(2));
$this->itemService->autoPurgeOld();
}
public function testAutoPurgeOldWontPurgeOld()
{
$this->config->expects($this->once())
->method('getAppValue')
->with('news', 'autoPurgeCount')
->will($this->returnValue(-1));
$this->oldItemMapper->expects($this->never())
->method('deleteReadOlderThanThreshold');
$this->itemService->autoPurgeOld();
}
public function testGetNewestItemId()
{
$this->oldItemMapper->expects($this->once())
->method('getNewestItemId')
$this->mapper->expects($this->once())
->method('newest')
->with($this->equalTo('jack'))
->will($this->returnValue(12));
->will($this->returnValue(Item::fromParams(['id' => 12])));
$result = $this->itemService->getNewestItemId('jack');
$this->assertEquals(12, $result);
$result = $this->class->newest('jack');
$this->assertEquals(12, $result->getId());
}
public function testGetNewestItemIdDoesNotExist()
{
$this->oldItemMapper->expects($this->once())
->method('getNewestItemId')
$this->mapper->expects($this->once())
->method('newest')
->with($this->equalTo('jack'))
->will(
$this->throwException(
@ -481,37 +368,266 @@ class ItemServiceTest extends TestCase
);
$this->expectException(ServiceNotFoundException::class);
$this->itemService->getNewestItemId('jack');
$this->class->newest('jack');
}
public function testGetNewestItemDuplicate()
{
$this->mapper->expects($this->once())
->method('newest')
->with($this->equalTo('jack'))
->will(
$this->throwException(
new MultipleObjectsReturnedException('There are no items')
)
);
$this->expectException(ServiceConflictException::class);
$this->class->newest('jack');
}
public function testStarredCount()
{
$star = 18;
$this->mapper->expects($this->once())
->method('findAllFromUser')
->with('jack', ['starred' => 1])
->will($this->returnValue([new Item(), new Item()]));
$this->oldItemMapper->expects($this->once())
->method('starredCount')
->with($this->equalTo('jack'))
->will($this->returnValue($star));
$result = $this->class->starred('jack');
$result = $this->itemService->starredCount('jack');
$this->assertEquals($star, $result);
$this->assertEquals(2, count($result));
}
public function testGetUnreadOrStarred()
public function testInsertOrUpdateInserts()
{
$this->oldItemMapper->expects($this->once())
->method('findAllUnreadOrStarred')
->with($this->equalTo('jack'))
->will($this->returnValue([]));
$item = $this->getMockBuilder(Item::class)
->getMock();
$result = $this->itemService->getUnreadOrStarred('jack');
$item->expects($this->once())
->method('getFeedId')
->will($this->returnValue(1));
$this->assertEquals([], $result);
$item->expects($this->once())
->method('getGuidHash')
->will($this->returnValue('hash'));
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with(1, 'hash')
->will($this->throwException(new DoesNotExistException('exception')));
$this->mapper->expects($this->once())
->method('insert')
->with($item)
->will($this->returnValue($item));
$result = $this->class->insertOrUpdate($item);
$this->assertEquals($item, $result);
}
public function testInsertOrUpdateUpdates()
{
$item = $this->getMockBuilder(Item::class)
->getMock();
$db_item = $this->getMockBuilder(Item::class)
->getMock();
$item->expects($this->once())
->method('getFeedId')
->will($this->returnValue(1));
$item->expects($this->once())
->method('getGuidHash')
->will($this->returnValue('hash'));
$item->expects($this->once())
->method('setUnread')
->with(true)
->will($this->returnSelf());
$db_item->expects($this->once())
->method('isUnread')
->will($this->returnValue(true));
$item->expects($this->once())
->method('setStarred')
->with(true)
->will($this->returnSelf());
$db_item->expects($this->once())
->method('isStarred')
->will($this->returnValue(true));
$item->expects($this->once())
->method('generateSearchIndex')
->will($this->returnSelf());
$item->expects($this->once())
->method('getFingerprint')
->will($this->returnValue('fingerA'));
$db_item->expects($this->once())
->method('getFingerprint')
->will($this->returnValue('fingerB'));
$item->expects($this->never())
->method('resetUpdatedFields');
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with(1, 'hash')
->will($this->returnValue($db_item));
$this->mapper->expects($this->once())
->method('update')
->with($item)
->will($this->returnValue($item));
$result = $this->class->insertOrUpdate($item);
$this->assertEquals($item, $result);
}
public function testInsertOrUpdateSkipsSame()
{
$item = $this->getMockBuilder(Item::class)
->getMock();
$db_item = $this->getMockBuilder(Item::class)
->getMock();
$item->expects($this->once())
->method('getFeedId')
->will($this->returnValue(1));
$item->expects($this->once())
->method('getGuidHash')
->will($this->returnValue('hash'));
$item->expects($this->once())
->method('setUnread')
->with(true)
->will($this->returnSelf());
$db_item->expects($this->once())
->method('isUnread')
->will($this->returnValue(true));
$item->expects($this->once())
->method('setStarred')
->with(true)
->will($this->returnSelf());
$db_item->expects($this->once())
->method('isStarred')
->will($this->returnValue(true));
$item->expects($this->once())
->method('generateSearchIndex')
->will($this->returnSelf());
$item->expects($this->once())
->method('getFingerprint')
->will($this->returnValue('fingerA'));
$db_item->expects($this->once())
->method('getFingerprint')
->will($this->returnValue('fingerA'));
$item->expects($this->once())
->method('resetUpdatedFields');
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with(1, 'hash')
->will($this->returnValue($db_item));
$this->mapper->expects($this->once())
->method('update')
->with($item)
->will($this->returnValue($item));
$result = $this->class->insertOrUpdate($item);
$this->assertEquals($item, $result);
}
public function testFindByGuidHash()
{
$item = $this->getMockBuilder(Item::class)
->getMock();
$this->mapper->expects($this->once())
->method('findByGuidHash')
->with(1, 'a')
->will($this->returnValue($item));
$result = $this->class->findByGuidHash(1, 'a');
$this->assertEquals($item, $result);
}
public function testFindAllInFeed()
{
$items = [new Item(), new Item()];
$this->mapper->expects($this->once())
->method('findAllInFeedAfter')
->with('jack', 1, PHP_INT_MIN, false)
->will($this->returnValue($items));
$result = $this->class->findAllInFeed('jack', 1);
$this->assertEquals($items, $result);
}
public function testPurgeOverThreshold()
{
$this->mapper->expects($this->once())
->method('deleteOverThreshold')
->with(1, true)
->will($this->returnValue(1));
$result = $this->class->purgeOverThreshold(1, true);
$this->assertEquals(1, $result);
}
public function testPurgeOverThresholdWithNegative()
{
$this->mapper->expects($this->never())
->method('deleteOverThreshold');
$result = $this->class->purgeOverThreshold(-1, true);
$this->assertEquals(null, $result);
}
public function testPurgeOverThresholdNull()
{
$this->config->expects($this->once())
->method('getAppValue')
->with('news', 'autoPurgeCount', 200)
->will($this->returnValue(200));
$this->mapper->expects($this->once())
->method('deleteOverThreshold')
->with(200);
$this->class->purgeOverThreshold();
}
public function testPurgeOverThresholdSet()
{
$this->config->expects($this->never())
->method('getAppValue')
->with('news', 'autoPurgeCount', 200);
$this->mapper->expects($this->once())
->method('deleteOverThreshold')
->with(5);
$this->class->purgeOverThreshold(5);
}
}

View File

@ -14,8 +14,8 @@
namespace OCA\News\Tests\Unit\Service;
use OCA\News\Db\Feed;
use OCA\News\Db\ItemMapper;
use OCA\News\Db\ItemMapperV2;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\Service;
use \OCP\AppFramework\Db\DoesNotExistException;
@ -112,7 +112,7 @@ class ServiceTest extends TestCase
->method('findFromUser')
->will($this->throwException($ex));
$this->expectException(ServiceNotFoundException::class);
$this->expectException(ServiceConflictException::class);
$this->class->find('', 1);
}

View File

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.4.1@9fd7a7d885b3a216cff8dec9d8c21a132f275224">
<file src="lib/AppInfo/Application.php">
<MissingDependency occurrences="5">
<MissingDependency occurrences="4">
<code>$fs</code>
<code>$fs</code>
<code>$fs</code>
<code>BeforeUserDeletedEvent</code>
<code>IRootFolder</code>
</MissingDependency>
</file>
@ -21,18 +20,30 @@
</InvalidReturnType>
</file>
<file src="lib/Hooks/UserDeleteHook.php">
<MissingDependency occurrences="2">
<code>BeforeUserDeletedEvent</code>
<code>UserDeleteHook</code>
</MissingDependency>
<MoreSpecificImplementedParamType occurrences="1">
<code>$event</code>
</MoreSpecificImplementedParamType>
</file>
<file src="lib/Service/Exceptions/ServiceConflictException.php">
<MoreSpecificImplementedParamType occurrences="1">
<code>$exception</code>
</MoreSpecificImplementedParamType>
</file>
<file src="lib/Service/Exceptions/ServiceNotFoundException.php">
<MoreSpecificImplementedParamType occurrences="1">
<code>$exception</code>
</MoreSpecificImplementedParamType>
</file>
<file src="lib/Service/Exceptions/ServiceValidationException.php">
<MoreSpecificImplementedParamType occurrences="1">
<code>$exception</code>
</MoreSpecificImplementedParamType>
</file>
<file src="lib/Service/FeedServiceV2.php">
<UndefinedMethod occurrences="2">
<UndefinedMethod occurrences="3">
<code>findAllFromFolder</code>
<code>findByURL</code>
<code>read</code>
</UndefinedMethod>
</file>
<file src="lib/Service/FolderServiceV2.php">
@ -43,18 +54,23 @@
<code>$this-&gt;timeFactory</code>
<code>TimeFactory</code>
</UndefinedDocblockClass>
</file>
<file src="lib/Service/ItemService.php">
<UndefinedMethod occurrences="1">
<code>findByGuidHash</code>
<code>read</code>
</UndefinedMethod>
</file>
<file src="lib/Service/ItemServiceV2.php">
<UndefinedMethod occurrences="4">
<UndefinedMethod occurrences="11">
<code>deleteOverThreshold</code>
<code>findAllForFeed</code>
<code>findByGuidHash</code>
<code>findAllAfter</code>
<code>findAllFeed</code>
<code>findAllFolder</code>
<code>findAllInFeedAfter</code>
<code>findAllInFolderAfter</code>
<code>findAllItems</code>
<code>findByGuidHash</code>
<code>findForUserByGuidHash</code>
<code>newest</code>
<code>readAll</code>
</UndefinedMethod>
</file>
</files>