1
0
mirror of https://github.com/chylex/Nextcloud-News.git synced 2025-07-29 23:59:03 +02:00

Add migration with foreign keys

Closes 

Signed-off-by: Sean Molenaar <sean@seanmolenaar.eu>
This commit is contained in:
Sean Molenaar 2020-10-04 20:45:33 +02:00 committed by Benjamin Brahmer
parent 361cfa55b7
commit 10e8c28fea
33 changed files with 313 additions and 357 deletions

View File

@ -140,4 +140,4 @@ jobs:
run: make integration-test
- name: Feed tests
working-directory: ../server/apps/news
run: make feed-test
run: make feed-test

View File

@ -5,8 +5,8 @@
* [Robin Appelman](mailto:icewind@owncloud.com)
* [Gregor Tätzner](mailto:gregor@freenet.de)
* [Morris Jobke](mailto:hey@morrisjobke.de)
* [Sean Molenaar](mailto:SMillerDev@users.noreply.github.com)
* [Sean Molenaar](mailto:sean@seanmolenaar.eu)
* [Sean Molenaar](mailto:SMillerDev@users.noreply.github.com)
* [Jan-Christoph Borchardt](mailto:hey@jancborchardt.net)
* [Daniel Schaal](mailto:daniel@schaal.email)
* [Davide Saurino](mailto:davide.saurino@alcacoop.it)

View File

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
- Stop overloading DB ids
- Unittest commands and utilities
- Upload codecoverage to codecov.io
- Use foreign keys in db
## 15.0.6

View File

@ -2,6 +2,8 @@
declare(strict_types=1);
namespace OCA\News\Command\Config;
use OCA\News\Service\Exceptions\ServiceConflictException;
use OCA\News\Service\Exceptions\ServiceNotFoundException;
use OCA\News\Service\FeedServiceV2;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@ -56,13 +58,31 @@ class FeedAdd extends Command
{
$user = $input->getArgument('user-id');
$url = $input->getArgument('feed');
$folder = (int) $input->getOption('folder') ?? 0;
$folder = $input->getOption('folder');
$title = $input->getOption('title');
$full_text = (bool) $input->getOption('full-text');
$username = $input->getOption('username');
$password = $input->getOption('password');
$feed = $this->feedService->create($user, $url, $folder, $full_text, $title, $username, $password);
if ($folder !== null) {
$folder = intval($folder);
}
try {
$feed = $this->feedService->create(
$user,
$url,
$folder,
$full_text,
$title,
$username,
$password
);
} catch (ServiceNotFoundException|ServiceConflictException $e) {
$output->write($e->getMessage());
return 1;
}
$this->feedService->fetch($feed);
$output->writeln(json_encode($feed->toAPI(), JSON_PRETTY_PRINT));

View File

@ -47,9 +47,13 @@ class FolderAdd extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$user = $input->getArgument('user-id');
$name = $input->getArgument('name');
$parent = (int) $input->getOption('parent') ?? 0;
$user = $input->getArgument('user-id');
$name = $input->getArgument('name');
$parent = $input->getOption('parent');
if ($parent !== null) {
$parent = intval($parent);
}
$this->folderService->create($user, $name, $parent);

View File

@ -50,11 +50,11 @@ class FolderDelete extends Command
$user = $input->getArgument('user-id');
$id = $input->getArgument('folder-id');
if ($id === '0') {
if ($id === null) {
throw new ServiceException('Can not remove root folder!');
}
$this->folderService->delete($user, $id);
$this->folderService->delete($user, intval($id));
return 0;
}

View File

@ -27,6 +27,7 @@ use \OCP\AppFramework\Http;
use \OCA\News\Service\FeedService;
use \OCA\News\Service\ItemService;
use Psr\Log\LoggerInterface;
use function GuzzleHttp\Psr7\uri_for;
class FeedApiController extends ApiController
{
@ -99,13 +100,17 @@ class FeedApiController extends ApiController
* @NoCSRFRequired
* @CORS
*
* @param string $url
* @param int $folderId
* @param string $url
* @param int|null $folderId
*
* @return array|mixed|JSONResponse
*/
public function create(string $url, int $folderId = 0)
public function create(string $url, ?int $folderId = null)
{
if ($folderId === 0) {
$folderId = null;
}
try {
$this->feedService->purgeDeleted($this->getUserId(), false);
@ -169,13 +174,17 @@ class FeedApiController extends ApiController
* @NoCSRFRequired
* @CORS
*
* @param int $feedId
* @param int $folderId
* @param int $feedId
* @param int|null $folderId
*
* @return array|JSONResponse
*/
public function move(int $feedId, int $folderId)
public function move(int $feedId, ?int $folderId)
{
if ($folderId === 0) {
$folderId = null;
}
try {
$this->feedService->patch(
$feedId,

View File

@ -105,6 +105,9 @@ class FeedController extends Controller
// check if feed or folder exists
try {
if ($feedType === FeedType::FOLDER) {
if ($feedId === 0) {
$feedId = null;
}
$this->folderService->find($this->userId, $feedId);
} elseif ($feedType === FeedType::FEED) {
$this->feedService->find($this->userId, $feedId);
@ -131,20 +134,23 @@ class FeedController extends Controller
* @NoAdminRequired
*
* @param string $url
* @param int $parentFolderId
* @param string $title
* @param string $user
* @param string $password
* @param int|null $parentFolderId
* @param string|null $title
* @param string|null $user
* @param string|null $password
*
* @return array|JSONResponse
*/
public function create(
string $url,
int $parentFolderId,
?int $parentFolderId,
?string $title = null,
?string $user = null,
?string $password = null
) {
if ($parentFolderId === 0) {
$parentFolderId = null;
}
try {
// we need to purge deleted feeds if a feed is created to
// prevent already exists exceptions
@ -290,13 +296,13 @@ class FeedController extends Controller
/**
* @NoAdminRequired
*
* @param int $feedId
* @param bool $pinned
* @param bool $fullTextEnabled
* @param int $updateMode
* @param int $ordering
* @param int $folderId
* @param string $title
* @param int $feedId
* @param bool $pinned
* @param bool $fullTextEnabled
* @param int|null $updateMode
* @param int|null $ordering
* @param int|null $folderId
* @param string|null $title
*
* @return array|JSONResponse
*/
@ -315,7 +321,7 @@ class FeedController extends Controller
'updateMode' => $updateMode,
'ordering' => $ordering,
'title' => $title,
'folderId' => $folderId
'folderId' => $folderId === 0 ? null : $folderId
];
$diff = array_filter(

View File

@ -90,12 +90,16 @@ class FolderApiController extends ApiController
* @NoCSRFRequired
* @CORS
*
* @param int $folderId
* @param int|null $folderId
*
* @return array|JSONResponse
*/
public function delete(int $folderId)
public function delete(?int $folderId)
{
if (empty($folderId)) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$this->folderService->delete($folderId, $this->getUserId());
} catch (ServiceNotFoundException $ex) {
@ -111,13 +115,17 @@ class FolderApiController extends ApiController
* @NoCSRFRequired
* @CORS
*
* @param int $folderId
* @param string $name
* @param int|null $folderId
* @param string $name
*
* @return array|JSONResponse
*/
public function update(int $folderId, string $name)
public function update(?int $folderId, string $name)
{
if (empty($folderId)) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$this->folderService->rename($folderId, $name, $this->getUserId());
} catch (ServiceValidationException $ex) {
@ -137,11 +145,14 @@ class FolderApiController extends ApiController
* @NoCSRFRequired
* @CORS
*
* @param int $folderId
* @param int $newestItemId
* @param int|null $folderId
* @param int $newestItemId
*/
public function read(int $folderId, int $newestItemId): void
public function read(?int $folderId, int $newestItemId): void
{
if ($folderId === 0) {
$folderId = null;
}
$this->itemService->readFolder($folderId, $newestItemId, $this->getUserId());
}
}

View File

@ -64,13 +64,15 @@ class FolderController extends Controller
/**
* @NoAdminRequired
*
* @param int $folderId
* @param bool $open
* @param int|null $folderId
* @param bool $open
*
* @return array|JSONResponse
*/
public function open(int $folderId, bool $open)
public function open(?int $folderId, bool $open)
{
$folderId = $folderId === 0 ? null : $folderId;
try {
$this->folderService->open($folderId, $open, $this->userId);
} catch (ServiceException $ex) {
@ -108,12 +110,15 @@ class FolderController extends Controller
/**
* @NoAdminRequired
*
* @param int $folderId
* @param int|null $folderId
*
* @return array|JSONResponse
*/
public function delete(int $folderId)
public function delete(?int $folderId)
{
if (empty($folderId)) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$this->folderService->markDeleted($folderId, $this->userId);
} catch (ServiceNotFoundException $ex) {
@ -127,13 +132,16 @@ class FolderController extends Controller
/**
* @NoAdminRequired
*
* @param string $folderName
* @param int $folderId
* @param string $folderName
* @param int|null $folderId
*
* @return array|JSONResponse
*/
public function rename(string $folderName, int $folderId)
public function rename(string $folderName, ?int $folderId)
{
if (empty($folderId)) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$folder = $this->folderService->rename(
$folderId,
@ -154,12 +162,15 @@ class FolderController extends Controller
/**
* @NoAdminRequired
*
* @param int $folderId
* @param int $highestItemId
* @param int|null $folderId
* @param int $highestItemId
*
* @return array
*/
public function read(int $folderId, int $highestItemId): array
public function read(?int $folderId, int $highestItemId): array
{
$folderId = $folderId === 0 ? null : $folderId;
$this->itemService->readFolder(
$folderId,
$highestItemId,
@ -173,12 +184,14 @@ class FolderController extends Controller
/**
* @NoAdminRequired
*
* @param int $folderId
* @param int|null $folderId
*
* @return array|JSONResponse
*/
public function restore(int $folderId)
public function restore(?int $folderId)
{
$folderId = $folderId === 0 ? null : $folderId;
try {
$this->folderService->unmarkDeleted($folderId, $this->userId);
} catch (ServiceNotFoundException $ex) {

View File

@ -1,74 +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>
* @author David Guillot <david@guillot.me>
* @copyright 2012 Alessandro Cosentino
* @copyright 2012-2014 Bernhard Posselt
* @copyright 2018 David Guillot
*/
namespace OCA\News\Controller;
use \OCP\IRequest;
use \OCP\IUserSession;
use \OCP\IURLGenerator;
use \OCP\Files\IRootFolder;
use \OCP\AppFramework\Http;
class UserApiController extends ApiController
{
private $rootFolder;
public function __construct(
string $appName,
IRequest $request,
IUserSession $userSession,
IRootFolder $rootFolder
) {
parent::__construct($appName, $request, $userSession);
$this->rootFolder = $rootFolder;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @CORS
*/
public function index(): array
{
$user = $this->getUser();
// find the avatar
$jpgAvatar = '/' . $user->getUID() . '/avatar.jpg';
$pngAvatar = '/' . $user->getUID() . '/avatar.png';
$avatar = null;
if ($this->rootFolder->nodeExists($jpgAvatar)) {
$file = $this->rootFolder->get($jpgAvatar);
$avatar = [
'data' => base64_encode($file->getContent()),
'mime' => 'image/jpeg'
];
} elseif ($this->rootFolder->nodeExists($pngAvatar)) {
$file = $this->rootFolder->get($pngAvatar);
$avatar = [
'data' => base64_encode($file->getContent()),
'mime' => 'image/png'
];
}
return [
'userId' => $user->getUID(),
'displayName' => $user->getDisplayName(),
'lastLoginTimestamp' => $user->getLastLogin(),
'avatar' => $avatar
];
}
}

View File

@ -37,7 +37,7 @@ class Feed extends Entity implements IAPI, \JsonSerializable
protected $faviconLink = null;
/** @var int|null */
protected $added = 0;
/** @var int */
/** @var int|null */
protected $folderId;
/** @var int */
protected $unreadCount;
@ -152,9 +152,9 @@ class Feed extends Entity implements IAPI, \JsonSerializable
}
/**
* @return int
* @return int|null
*/
public function getFolderId(): int
public function getFolderId(): ?int
{
return $this->folderId;
}
@ -416,9 +416,11 @@ class Feed extends Entity implements IAPI, \JsonSerializable
}
/**
* @param int $folderId
* @param int|null $folderId
*
* @return Feed
*/
public function setFolderId(int $folderId): Feed
public function setFolderId(?int $folderId): Feed
{
if ($this->folderId !== $folderId) {
$this->folderId = $folderId;

View File

@ -77,7 +77,7 @@ class FeedMapper extends NewsMapper
// think twice when changing this
'AND `items`.`unread` = ? ' .
'WHERE `feeds`.`user_id` = ? ' .
'AND (`feeds`.`folder_id` = 0 ' .
'AND (`feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0 ' .
') ' .
'AND `feeds`.`deleted_at` = 0 ' .
@ -106,7 +106,7 @@ class FeedMapper extends NewsMapper
// POSSIBLE SQL INJECTION RISK WHEN MODIFIED WITHOUT THOUGHT.
// think twice when changing this
'AND `items`.`unread` = ? ' .
'WHERE (`feeds`.`folder_id` = 0 ' .
'WHERE (`feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0 ' .
') ' .
'AND `feeds`.`deleted_at` = 0 ' .

View File

@ -43,6 +43,7 @@ class FeedMapperV2 extends NewsMapperV2
* Find all feeds for a user.
*
* @param string $userId The user identifier
* @param array $params Filter parameters
*
* @return Entity[]
*/
@ -62,7 +63,7 @@ class FeedMapperV2 extends NewsMapperV2
* Find all feeds for a user.
*
* @param string $userId The user identifier
* @param int $id The feed identifier
* @param int $id The feed identifier
*
* @return Entity
*
@ -124,17 +125,22 @@ class FeedMapperV2 extends NewsMapperV2
/**
* Find all feeds in a folder
*
* @param int $id ID of the folder
* @param int|null $id ID of the folder
*
* @return Feed[]
*/
public function findAllFromFolder(int $id): array
public function findAllFromFolder(?int $id): array
{
$builder = $this->db->getQueryBuilder();
$builder->addSelect('*')
->from($this->tableName)
->where('folder_id = :folder_id')
->setParameter(':folder_id', $id);
->from($this->tableName);
if (is_null($id)) {
$builder->where('folder_id IS NULL');
} else {
$builder->where('folder_id = :folder_id')
->setParameter(':folder_id', $id);
}
return $this->findEntities($builder);
}

View File

@ -141,7 +141,7 @@ class Folder extends Entity implements IAPI, \JsonSerializable
return $this;
}
public function setParentId(int $parentId = 0): self
public function setParentId(?int $parentId = null): self
{
if ($this->parentId !== $parentId) {
$this->parentId = $parentId;

View File

@ -41,6 +41,7 @@ class FolderMapperV2 extends NewsMapperV2
* Find all feeds for a user.
*
* @param string $userId The user identifier
* @param array $params Filter parameters
*
* @return Entity[]
*/

View File

@ -55,7 +55,7 @@ class ItemMapper extends NewsMapper
$prependTo .
'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
'ON `folders`.`id` = `feeds`.`folder_id` ' .
'WHERE `feeds`.`folder_id` = 0 ' .
'WHERE `feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0 ' .
'ORDER BY `items`.`id` ' . $ordering;
}
@ -125,7 +125,7 @@ class ItemMapper extends NewsMapper
'AND `items`.`starred` = ? ' .
'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
'ON `folders`.`id` = `feeds`.`folder_id` ' .
'WHERE `feeds`.`folder_id` = 0 ' .
'WHERE `feeds`.`folder_id` IS NULL ' .
'OR `folders`.`deleted_at` = 0';
$params = [$userId, true];
@ -151,14 +151,15 @@ class ItemMapper extends NewsMapper
}
public function readFolder($folderId, $highestItemId, $time, $userId)
public function readFolder(?int $folderId, $highestItemId, $time, $userId)
{
$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` = ? ' .
"WHERE `folder_id` ${folderWhere} ? " .
'AND `user_id` = ? ' .
') ' .
'AND `id` <= ?';
@ -207,11 +208,12 @@ class ItemMapper extends NewsMapper
}
public function findAllNewFolder($id, $updatedSince, $showAll, $userId)
public function findAllNewFolder(?int $id, $updatedSince, $showAll, $userId)
{
$sql = $this->buildStatusQueryPart($showAll);
$sql .= 'AND `feeds`.`folder_id` = ? ' .
$folderWhere = is_null($id) ? 'IS' : '=';
$sql .= "AND `feeds`.`folder_id` ${$folderWhere} ? " .
'AND `items`.`last_modified` >= ? ';
$sql = $this->makeSelectQuery($sql);
$params = [$userId, $id, $updatedSince];
@ -270,7 +272,7 @@ class ItemMapper extends NewsMapper
public function findAllFolder(
$id,
?int $id,
$limit,
$offset,
$showAll,
@ -285,10 +287,10 @@ class ItemMapper extends NewsMapper
$sql = $this->buildStatusQueryPart($showAll);
$sql .= $this->buildSearchQueryPart($search);
$sql .= 'AND `feeds`.`folder_id` = ? ';
$folderWhere = is_null($id) ? 'IS' : '=';
$sql .= "AND `feeds`.`folder_id` ${folderWhere} ? ";
if ($offset !== 0) {
$sql .= 'AND `items`.`id` ' .
$this->getOperator($oldestFirst) . ' ? ';
$sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? ';
$params[] = $offset;
}
$sql = $this->makeSelectQuery($sql, $oldestFirst, $search);

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace OCA\News\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version150005Date20201009192341 extends SimpleMigrationStep {
protected $connection;
public function __construct(IDBConnection $connection)
{
$this->connection = $connection;
}
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
$qb = $this->connection->getQueryBuilder();
$qb->update('news_feeds')
->set('folder_id', $qb->createPositionalParameter(null, IQueryBuilder::PARAM_NULL))
->where('folder_id = 0')
->execute();
$feed_name = $this->connection->getQueryBuilder()->getTableName('news_feeds');
$folder_name = $this->connection->getQueryBuilder()->getTableName('news_folders');
$items_query = "DELETE FROM ${feed_name} WHERE ${feed_name}.`folder_id` NOT IN (SELECT DISTINCT id FROM ${folder_name}) AND ${feed_name}.`folder_id` IS NOT NULL";
$this->connection->executeQuery($items_query);
$item_name = $this->connection->getQueryBuilder()->getTableName('news_items');
$feed_name = $this->connection->getQueryBuilder()->getTableName('news_feeds');
$items_query = "DELETE FROM ${item_name} WHERE ${item_name}.`feed_id` NOT IN (SELECT DISTINCT id FROM ${feed_name})";
$this->connection->executeQuery($items_query);
}
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('news_items') &&
!$schema->getTable('news_items')->hasForeignKey('feed')) {
$schema->getTable('news_items')
->addForeignKeyConstraint(
$schema->getTable('news_feeds')->getName(),
['feed_id'],
['id'],
['onDelete' => 'CASCADE'],
'feed'
);
}
if ($schema->hasTable('news_feeds') &&
!$schema->getTable('news_feeds')->hasForeignKey('folder')) {
$schema->getTable('news_feeds')
->addForeignKeyConstraint(
$schema->getTable('news_folders')->getName(),
['folder_id'],
['id'],
['onDelete' => 'CASCADE'],
'folder'
);
}
return $schema;
}
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
}
}

View File

@ -103,21 +103,27 @@ class FeedService extends Service
/**
* Creates a new feed
*
* @param string $feedUrl the url to the feed
* @param int $folderId the folder where it should be put into, 0 for root
* @param string $feedUrl the url to the feed
* @param int|null $folderId the folder where it should be put into, null for root
* folder
* @param string $userId for which user the feed should be created
* @param string $title if given, this is used for the opml feed title
* @param string $user if given, basic auth is set for this feed
* @param string $password if given, basic auth is set for this
* @param string $userId for which user the feed should be created
* @param string|null $title if given, this is used for the opml feed title
* @param string|null $user if given, basic auth is set for this feed
* @param string|null $password if given, basic auth is set for this
* feed. Ignored if user is null or an empty string
*
* @return Feed the newly created feed
* @throws ServiceConflictException if the feed exists already
* @throws ServiceNotFoundException if the url points to an invalid feed
* @return Feed the newly created feed
*/
public function create($feedUrl, $folderId, $userId, $title = null, $user = null, $password = null)
{
public function create(
string $feedUrl,
?int $folderId,
string $userId,
string $title = null,
string $user = null,
string $password = null
) {
// first try if the feed exists already
try {
/**
@ -369,7 +375,7 @@ class FeedService extends Service
$feed->setUrl($url);
$feed->setTitle($this->l10n->t('Articles without feed'));
$feed->setAdded($this->timeFactory->getTime());
$feed->setFolderId(0);
$feed->setFolderId(null);
$feed->setPreventUpdate(true);
/** @var Feed $feed */
$feed = $this->feedMapper->insert($feed);

View File

@ -121,11 +121,11 @@ class FeedServiceV2 extends Service
}
/**
* @param int $id
* @param int|null $id
*
* @return Feed[]
*/
public function findAllFromFolder(int $id): array
public function findAllFromFolder(?int $id): array
{
return $this->mapper->findAllFromFolder($id);
}
@ -182,7 +182,7 @@ class FeedServiceV2 extends Service
*
* @param string $userId Feed owner
* @param string $feedUrl Feed URL
* @param int $folderId Target folder, defaults to root
* @param int|null $folderId Target folder, defaults to root
* @param bool $full_text Scrape the feed for full text
* @param string|null $title The feed title
* @param string|null $user Basic auth username, if set
@ -196,7 +196,7 @@ class FeedServiceV2 extends Service
public function create(
string $userId,
string $feedUrl,
int $folderId = 0,
?int $folderId = null,
bool $full_text = false,
?string $title = null,
?string $user = null,

View File

@ -101,24 +101,28 @@ class FolderService extends Service
* @throws ServiceValidationException if the folder has invalid parameters
* @throws ServiceConflictException if name exists already
*/
public function create(string $folderName, string $userId, int $parentId = 0)
public function create(string $folderName, string $userId, ?int $parentId = null)
{
$this->validateFolder($folderName, $userId);
$folder = new Folder();
$folder->setName($folderName);
$folder->setUserId($userId);
$folder->setParentId($parentId);
$folder->setOpened(true);
$folder->setName($folderName)
->setUserId($userId)
->setParentId($parentId)
->setOpened(true);
return $this->folderMapper->insert($folder);
}
/**
* @throws ServiceException if the folder does not exist
* @param int|null $folderId
* @param bool $opened
* @param string $userId
*
* @throws ServiceNotFoundException
*/
public function open(int $folderId, bool $opened, string $userId)
public function open(?int $folderId, bool $opened, string $userId)
{
$folder = $this->find($userId, $folderId);
$folder->setOpened($opened);

View File

@ -80,7 +80,7 @@ class FolderServiceV2 extends Service
return $this->mapper->findAll();
}
public function create(string $userId, string $name, int $parent = 0): Entity
public function create(string $userId, string $name, ?int $parent = null): Entity
{
$folder = new Folder();
$folder->setUserId($userId)

View File

@ -54,16 +54,17 @@ class ItemService extends Service
/**
* Returns all new items
*
* @param int $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
* @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
* @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($id, $type, $updatedSince, $showAll, $userId)
public function findAllNew(?int $id, $type, $updatedSince, $showAll, $userId)
{
switch ($type) {
case FeedType::FEED:
@ -94,20 +95,21 @@ class ItemService extends Service
/**
* Returns all items
*
* @param int $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
* @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(
$id,
?int $id,
$type,
$limit,
$offset,
@ -225,13 +227,13 @@ class ItemService extends Service
/**
* Set a folder read
*
* @param int $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
* @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
*/
public function readFolder($folderId, $highestItemId, $userId)
public function readFolder(?int $folderId, $highestItemId, $userId)
{
$time = $this->timeFactory->getMicroTime();
$this->itemMapper->readFolder(

View File

@ -69,7 +69,7 @@ class OPMLExporter
// feeds without folders
foreach ($feeds as $feed) {
if ($feed->getFolderId() === 0) {
if ($feed->getFolderId() === null) {
$feedOutline = $this->createFeedOutline($feed, $document);
$body->appendChild($feedOutline);
}

View File

@ -57,7 +57,7 @@ foreach (Plugin::getScripts() as $appName => $fileName) {
<?php print_unescaped($this->inc('part.navigation.unreadfeed')) ?>
<?php print_unescaped($this->inc('part.navigation.starredfeed')) ?>
<?php print_unescaped($this->inc(
'part.navigation.feed', ['folderId' => '0']
'part.navigation.feed', ['folderId' => null]
)) ?>
<?php print_unescaped($this->inc('part.navigation.folder')) ?>
<?php print_unescaped($this->inc('part.navigation.explore')) ?>

View File

@ -30,7 +30,7 @@ class FeedFixture extends Feed
'title' => 'title',
'faviconLink' => 'http://feed.com/favicon.ico',
'added' => 3000,
'folderId' => 0,
'folderId' => null,
'link' => 'http://feed.com/rss',
'preventUpdate' => false,
'deletedAt' => 0,

View File

@ -19,11 +19,11 @@ class FolderFixture extends Folder
{
use Fixture;
public function __construct(array $defaults=[])
public function __construct(array $defaults=[])
{
$defaults = array_merge(
[
'parentId' => 0,
'parentId' => null,
'name' => 'folder',
'userId' => 'test',
'opened' => true,

View File

@ -141,7 +141,7 @@ abstract class IntegrationTest extends \Test\TestCase
}
}
protected function loadFeedFixtures(array $feedFixtures = [], $folderId = 0)
protected function loadFeedFixtures(array $feedFixtures = [], $folderId = null)
{
foreach ($feedFixtures as $feedFixture) {
$feed = new FeedFixture($feedFixture);

View File

@ -69,7 +69,7 @@ class FeedAddTest extends TestCase
$this->consoleInput->expects($this->exactly(5))
->method('getOption')
->will($this->returnValueMap([
['folder', '0'],
['folder', null],
['title', 'title'],
['username', 'user'],
['password', 'pass'],
@ -80,7 +80,7 @@ class FeedAddTest extends TestCase
$this->service->expects($this->exactly(1))
->method('create')
->with('admin', 'http://feed', 0, true, 'title', 'user', 'pass')
->with('admin', 'http://feed', null, true, 'title', 'user', 'pass')
->willReturn($feed);
$this->service->expects($this->exactly(1))

View File

@ -88,7 +88,7 @@ class FolderDeleteTest extends TestCase
$this->consoleInput->expects($this->exactly(2))
->method('getArgument')
->will($this->returnValueMap([
['folder-id', '0'],
['folder-id', null],
['user-id', 'admin'],
]));

View File

@ -1,155 +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\Controller;
use OCA\News\Controller\UserApiController;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
use PHPUnit\Framework\TestCase;
class UserApiControllerTest extends TestCase
{
private $request;
private $appName;
private $rootFolder;
private $userSession;
private $controller;
private $user;
private $file;
protected function setUp(): void
{
$this->appName = 'news';
$this->request = $this->getMockBuilder(IRequest::class)
->disableOriginalConstructor()
->getMock();
$this->rootFolder = $this->getMockBuilder(IRootFolder::class)
->disableOriginalConstructor()
->getMock();
$this->file = $this->getMockBuilder(File::class)
->disableOriginalConstructor()
->getMock();
$this->userSession = $this->getMockBuilder(IUserSession::class)
->disableOriginalConstructor()
->getMock();
$this->user = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor()
->getMock();
$this->controller = new UserApiController(
$this->appName, $this->request, $this->userSession,
$this->rootFolder
);
}
private function expectUser($uid, $displayName, $lastLogin)
{
$this->userSession->expects($this->any())
->method('getUser')
->will($this->returnValue($this->user));
$this->user->expects($this->any())
->method('getUID')
->will($this->returnValue($uid));
$this->user->expects($this->any())
->method('getLastLogin')
->will($this->returnValue($lastLogin));
$this->user->expects($this->any())
->method('getDisplayName')
->will($this->returnValue($displayName));
}
private function expectImg($isJpg, $isPng, $user, $exists, $data)
{
$jpg = '/' . $user . '/' . 'avatar.jpg';
$png = '/' . $user . '/' . 'avatar.png';
$this->rootFolder->expects($this->any())
->method('nodeExists')
->will(
$this->returnValueMap(
[
[$jpg, $isJpg],
[$png, $isPng]
]
)
);
$this->rootFolder->expects($this->any())
->method('get')
->will($this->returnValue($this->file));
$this->file->expects($this->any())
->method('getContent')
->will($this->returnValue($data));
}
public function testGetJpeg()
{
$this->expectUser('john', 'John', 123);
$this->expectImg(true, false, 'john', true, 'hi');
$result = $this->controller->index();
$expected = [
'userId' => 'john',
'displayName' => 'John',
'lastLoginTimestamp' => 123,
'avatar' => [
'data' => base64_encode('hi'),
'mime' => 'image/jpeg'
]
];
$this->assertEquals($expected, $result);
}
public function testGetPng()
{
$this->expectUser('john', 'John', 123);
$this->expectImg(false, true, 'john', false, 'hi');
$result = $this->controller->index();
$expected = [
'userId' => 'john',
'displayName' => 'John',
'lastLoginTimestamp' => 123,
'avatar' => [
'data' => base64_encode('hi'),
'mime' => 'image/png'
]
];
$this->assertEquals($expected, $result);
}
public function testNoAvatar()
{
$this->expectUser('john', 'John', 123);
$this->expectImg(false, false, 'john', false, 'hi');
$result = $this->controller->index();
$expected = [
'userId' => 'john',
'displayName' => 'John',
'lastLoginTimestamp' => 123,
'avatar' => null
];
$this->assertEquals($expected, $result);
}
}

View File

@ -913,7 +913,7 @@ class FeedServiceTest extends TestCase
$insertFeed->setTitle('Articles without feed');
$insertFeed->setAdded($this->time);
$insertFeed->setPreventUpdate(true);
$insertFeed->setFolderId(0);
$insertFeed->setFolderId(null);
$this->l10n->expects($this->once())
->method('t')

View File

@ -37,7 +37,7 @@ class OPMLExporterTest extends TestCase
$this->exporter = new OPMLExporter();
$this->folder1 = new Folder();
$this->folder1->setId(3);
$this->folder1->setParentId(0);
$this->folder1->setParentId(null);
$this->folder1->setName('Örgendwas');
$this->folder2 = new Folder();
$this->folder2->setId(1);
@ -46,7 +46,7 @@ class OPMLExporterTest extends TestCase
$this->feed1 = new Feed();
$this->feed1->setUrl('http://url1');
$this->feed1->setTitle('tötel');
$this->feed1->setFolderId(0);
$this->feed1->setFolderId(null);
$this->feed2 = new Feed();
$this->feed2->setUrl('http://url');
$this->feed2->setTitle('ttel df');