starting to fix psalm issues

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
This commit is contained in:
Julien Veyssier
2024-09-10 17:13:39 +02:00
parent 284c5b6510
commit 55c217f486
17 changed files with 193 additions and 61 deletions

View File

@@ -11,6 +11,8 @@
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm.phar --no-cache",
"psalm:update-baseline": "psalm.phar --threads=1 --update-baseline",
"psalm:update-baseline:force": "psalm.phar --threads=1 --update-baseline --set-baseline=tests/psalm-baseline.xml",
"test:unit": "phpunit --config tests/phpunit.xml",
"openapi": "generate-spec --verbose --allow-missing-docs"
},

View File

@@ -14,6 +14,7 @@ namespace OCA\Cospend\Command;
use OC\Core\Command\Base;
use OCA\Cospend\Db\ProjectMapper;
use OCA\Cospend\Service\CospendService;
use OCA\Cospend\Service\LocalProjectService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -22,8 +23,9 @@ use Symfony\Component\Console\Output\OutputInterface;
class ExportProject extends Base {
public function __construct(
private LocalProjectService $projectService,
private ProjectMapper $projectMapper,
private LocalProjectService $localProjectService,
private CospendService $cospendService,
private ProjectMapper $projectMapper,
) {
parent::__construct();
}
@@ -48,7 +50,9 @@ class ExportProject extends Base {
$name = $input->getArgument('filename');
$dbProject = $this->projectMapper->find($projectId);
if ($dbProject !== null) {
$result = $this->projectService->exportCsvProject($projectId, $dbProject->getUserid(), $name);
$projectInfo = $this->localProjectService->getProjectInfoWithAccessLevel($projectId, $dbProject->getUserid());
$bills = $this->localProjectService->getBills($projectId);
$result = $this->cospendService->exportCsvProject($projectId, $dbProject->getUserid(), $projectInfo, $bills, $name);
if (array_key_exists('path', $result)) {
$output->writeln(
'Project "'.$projectId.'" exported in "'.$result['path'].

View File

@@ -929,7 +929,7 @@ class PublicApiController extends OCSController {
*
* @param string $token Project share token
* @param array<array{order: int, id: int}> $order Array describing the categories ordering
* @return DataResponse<Http::STATUS_OK|Http::STATUS_FORBIDDEN, '', array{}>
* @return DataResponse<Http::STATUS_OK, '', array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array<string, string>, array{}>
*
* 200: Categories order is saved
* 403: Not saved
@@ -944,8 +944,6 @@ class PublicApiController extends OCSController {
try {
$this->localProjectService->saveCategoryOrder($this->projectId, $order);
return new DataResponse('');
} catch (CospendBasicException $e) {
return new DataResponse($e->data, $e->getCode());
} catch (\Throwable $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
@@ -971,8 +969,6 @@ class PublicApiController extends OCSController {
try {
$this->localProjectService->deleteCategory($this->projectId, $categoryId);
return new DataResponse($categoryId);
} catch (CospendBasicException $e) {
return new DataResponse($e->data, $e->getCode());
} catch (\Throwable $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
@@ -984,7 +980,7 @@ class PublicApiController extends OCSController {
* @param string $token
* @param string $name
* @param float $rate
* @return DataResponse<Http::STATUS_OK, int, array{}>
* @return DataResponse<Http::STATUS_OK, int, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array<string, string>, array{}>
*/
#[NoAdminRequired]
#[PublicPage]
@@ -1008,8 +1004,9 @@ class PublicApiController extends OCSController {
* @param int $currencyId
* @param string $name
* @param float $rate
* @return DataResponse<Http::STATUS_OK, CospendCurrency, array{}>|DataResponse<Http::STATUS_FORBIDDEN, array<string, string>, array{}>
* @return DataResponse<Http::STATUS_OK, CospendCurrency, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND|Http::STATUS_BAD_REQUEST, array<string, string>, array{}>
* @throws Exception
* @throws MultipleObjectsReturnedException
*/
#[NoAdminRequired]
#[PublicPage]
@@ -1024,7 +1021,12 @@ class PublicApiController extends OCSController {
);
return new DataResponse($currency);
} catch (CospendBasicException $e) {
return new DataResponse($e->data, $e->getCode());
if ($e->getCode() == Http::STATUS_FORBIDDEN) {
return new DataResponse($e->data, Http::STATUS_FORBIDDEN);
} elseif ($e->getCode() == Http::STATUS_NOT_FOUND) {
return new DataResponse($e->data, Http::STATUS_NOT_FOUND);
}
return new DataResponse($e->data, Http::STATUS_BAD_REQUEST);
}
}
@@ -1046,7 +1048,7 @@ class PublicApiController extends OCSController {
$this->localProjectService->deleteCurrency($this->projectId, $currencyId);
return new DataResponse('');
} catch (CospendBasicException $e) {
return new DataResponse($e->data, $e->getCode());
return new DataResponse($e->data, Http::STATUS_BAD_REQUEST);
} catch (\Throwable $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}

View File

@@ -23,7 +23,7 @@ use OCP\IDBConnection;
* @method Category mapRowToEntity(array $row)
* @method Category findEntity(IQueryBuilder $query)
* @method Category[] findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Share>
* @template-extends QBMapper<Category>
*/
class CategoryMapper extends QBMapper {
public function __construct(IDBConnection $db) {

View File

@@ -23,7 +23,7 @@ use OCP\IDBConnection;
* @method Currency mapRowToEntity(array $row)
* @method Currency findEntity(IQueryBuilder $query)
* @method Currency[] findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Share>
* @template-extends QBMapper<Currency>
*/
class CurrencyMapper extends QBMapper {
public function __construct(IDBConnection $db) {

View File

@@ -23,7 +23,7 @@ use OCP\IDBConnection;
* @method PaymentMode mapRowToEntity(array $row)
* @method PaymentMode findEntity(IQueryBuilder $query)
* @method PaymentMode[] findEntities(IQueryBuilder $query)
* @template-extends QBMapper<Share>
* @template-extends QBMapper<PaymentMode>
*/
class PaymentModeMapper extends QBMapper {
public function __construct(IDBConnection $db) {

View File

@@ -56,6 +56,25 @@ class ProjectMapper extends QBMapper {
return $this->findEntity($qb);
}
/**
* @param string $id
* @return Project|null
*/
public function find(string $id): ?Project {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_STR))
);
try {
return $this->findEntity($qb);
} catch (DoesNotExistException | MultipleObjectsReturnedException |\OCP\DB\Exception $e) {
return null;
}
}
/**
* @param string $name
* @param string $id
@@ -113,25 +132,6 @@ class ProjectMapper extends QBMapper {
return $insertedProject;
}
/**
* @param string $id
* @return Project|null
*/
public function find(string $id): ?Project {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_STR))
);
try {
return $this->findEntity($qb);
} catch (DoesNotExistException | MultipleObjectsReturnedException |\OCP\DB\Exception $e) {
return null;
}
}
/**
* @param string $userId
* @return Project[]

View File

@@ -746,7 +746,9 @@ class CospendService {
$userFolder = $this->root->getUserFolder($uid);
if (!$userFolder->nodeExists($outPath . '/' . $exportName)) {
$this->localProjectService->exportCsvProject($dbProjectId, $uid, $exportName);
$projectInfo = $this->localProjectService->getProjectInfoWithAccessLevel($dbProjectId, $uid);
$bills = $this->localProjectService->getBills($dbProjectId);
$this->exportCsvProject($dbProjectId, $uid, $projectInfo, $bills, $exportName);
}
}
$req->closeCursor();
@@ -911,14 +913,15 @@ class CospendService {
*
* @param string $projectId
* @param string $userId
* @param array $projectInfo
* @param array $bills
* @param string|null $name
* @return array
* @throws InvalidPathException
* @throws LockedException
* @throws NoUserException
* @throws NotFoundException
* @throws NotPermittedException
* @throws \OCP\DB\Exception
* @throws InvalidPathException
* @throws LockedException
*/
public function exportCsvProject(string $projectId, string $userId, array $projectInfo, array $bills, ?string $name = null): array {
// create export directory if needed

View File

@@ -27,6 +27,7 @@ class FederatedProjectService implements IProjectService {
private IClient $client;
public string $userId;
public string $USER_AGENT;
public function __construct(
IClientService $clientService,
@@ -102,7 +103,7 @@ class FederatedProjectService implements IProjectService {
$this->request($projectId, 'api/v1/public/projects/{token}/{password}', [], 'DELETE');
}
public function getProjectInfoWithAccessLevel(string $projectId, string $userId): ?array {
public function getProjectInfoWithAccessLevel(string $projectId, string $userId): array {
$projectInfo = $this->request($projectId, 'api/v1/public/projects/{token}/{password}');
$projectInfo['id'] = $projectId;
$projectInfo['federated'] = true;

View File

@@ -31,9 +31,9 @@ interface IProjectService {
/**
* @param string $projectId
* @param string $userId
* @return array|null
* @return array
*/
public function getProjectInfoWithAccessLevel(string $projectId, string $userId): ?array;
public function getProjectInfoWithAccessLevel(string $projectId, string $userId): array;
/**
* Get project statistics

View File

@@ -318,19 +318,16 @@ class LocalProjectService implements IProjectService {
* Get all project data
*
* @param string $projectId
* @return CospendProjectInfoPlusExtra|null
* @return CospendProjectInfoPlusExtra
* @throws CospendBasicException
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function getProjectInfo(string $projectId): ?array {
public function getProjectInfo(string $projectId): array {
try {
$dbProject = $this->projectMapper->find($projectId);
} catch (Exception | Throwable $e) {
return null;
}
if ($dbProject === null) {
$dbProject = $this->projectMapper->getById($projectId);
} catch (DoesNotExistException) {
throw new CospendBasicException('', Http::STATUS_NOT_FOUND, ['error' => 'project not found']);
}
$dbProjectId = $dbProject->getId();
@@ -374,10 +371,13 @@ class LocalProjectService implements IProjectService {
/**
* @param string $projectId
* @param string $userId
* @return array|null
* @return array
* @throws CospendBasicException
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function getProjectInfoWithAccessLevel(string $projectId, string $userId): ?array {
public function getProjectInfoWithAccessLevel(string $projectId, string $userId): array {
$projectInfo = $this->getProjectInfo($projectId);
$projectInfo['myaccesslevel'] = $this->getUserMaxAccessLevel($userId, $projectId);
return $projectInfo;
@@ -998,11 +998,10 @@ class LocalProjectService implements IProjectService {
$insertedBillId = $createdBill->getId();
// insert bill owers
$qb = $this->db->getQueryBuilder();
foreach ($owerIds as $owerId) {
$billOwer = new BillOwer();
$billOwer->setBillid($insertedBillId);
$billOwer->setMemberid($owerId);
$billOwer->setMemberid((int)$owerId);
$this->billOwerMapper->insert($billOwer);
}
@@ -1464,7 +1463,7 @@ class LocalProjectService implements IProjectService {
* @param bool $active
* @param string|null $color
* @param string|null $userId
* @return array
* @return CospendMember
* @throws CospendBasicException
* @throws \OCP\DB\Exception
*/
@@ -2251,7 +2250,7 @@ class LocalProjectService implements IProjectService {
foreach ($owerIds as $owerId) {
$billOwer = new BillOwer();
$billOwer->setBillid($billId);
$billOwer->setMemberid($owerId);
$billOwer->setMemberid((int)$owerId);
$this->billOwerMapper->insert($billOwer);
}
}
@@ -2709,7 +2708,7 @@ class LocalProjectService implements IProjectService {
* @throws \OCP\DB\Exception
*/
public function createPaymentMode(string $projectId, string $name, ?string $icon, string $color, ?int $order = 0): int {
$pm = new Category();
$pm = new PaymentMode();
$pm->setProjectid($projectId);
$pm->setName($name);
$pm->setOrder(is_null($order) ? 0 : $order);
@@ -2740,12 +2739,12 @@ class LocalProjectService implements IProjectService {
* @param string $projectId
* @param int $pmId
* @return void
* @throws CospendBasicException
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function deletePaymentMode(string $projectId, int $pmId): void {
$pmToDelete = $this->getPaymentMode($projectId, $pmId);
$pmToDelete = $this->paymentModeMapper->getPaymentModeOfProject($projectId, $pmId);
$this->paymentModeMapper->delete($pmToDelete);
// then get rid of this pm in bills
@@ -2841,12 +2840,12 @@ class LocalProjectService implements IProjectService {
* @param string $projectId
* @param int $categoryId
* @return void
* @throws CospendBasicException
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function deleteCategory(string $projectId, int $categoryId): void {
$categoryToDelete = $this->getCategory($projectId, $categoryId);
$categoryToDelete = $this->categoryMapper->getCategoryOfProject($projectId, $categoryId);
$this->categoryMapper->delete($categoryToDelete);
// then get rid of this category in bills

View File

@@ -64,13 +64,14 @@ class UserMigrator implements IMigrator, ISizeEstimationMigrator {
public function export(IUser $user, IExportDestination $exportDestination, OutputInterface $output): void {
$output->writeln('Exporting Cospend projects in ' . self::PROJECTS_PATH . '…');
$userId = $user->getUID();
/** @var Project[] $projects */
$projects = $this->projectMapper->getProjects($userId);
foreach ($projects as $project) {
try {
$exportFilePath = self::PROJECTS_PATH . '/' . $project->getId() . '.csv';
$content = '';
foreach ($this->localProjectService->getJsonProject($project->getId()) as $chunk) {
$projectInfo = $this->localProjectService->getProjectInfoWithAccessLevel($project->getId(), $userId);
$bills = $this->localProjectService->getBills($project->getId());
foreach ($this->cospendService->getJsonProject($projectInfo, $bills) as $chunk) {
$content .= $chunk;
}
$exportDestination->addFileContents($exportFilePath, $content);

View File

@@ -32,6 +32,7 @@
<referencedClass name="Symfony\Component\Console\Input\InputInterface" />
<referencedClass name="Symfony\Component\Console\Output\OutputInterface" />
<referencedClass name="Doctrine\DBAL\Types\Type" />
<referencedClass name="OCA\FederatedFileSharing\AddressHandler" />
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>
@@ -56,5 +57,9 @@
<file name="tests/stubs/oc_hooks_emitter.php" />
<file name="tests/stubs/oc_filesystem.php" />
<file name="tests/stubs/oc_core_command_base.php" />
<file name="tests/stubs/GuzzleHttp_Exception_ClientException.php" />
<file name="tests/stubs/GuzzleHttp_Exception_ConnectException.php" />
<file name="tests/stubs/GuzzleHttp_Exception_ServerException.php" />
<file name="tests/stubs/oca_federation_trustedservers.php" />
</stubs>
</psalm>

View File

@@ -0,0 +1,12 @@
<?php
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace GuzzleHttp\Exception;
class ClientException extends \RuntimeException {
public function getResponse() {
}
}

View File

@@ -0,0 +1,10 @@
<?php
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace GuzzleHttp\Exception;
class ConnectException extends \RuntimeException {
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace GuzzleHttp\Exception;
class ServerException extends \RuntimeException {
public function getResponse() {
}
public function hasResponse(): bool {
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Federation;
class TrustedServers {
/** after a user list was exchanged at least once successfully */
public const STATUS_OK = 1;
/** waiting for shared secret or initial user list exchange */
public const STATUS_PENDING = 2;
/** something went wrong, misconfigured server, software bug,... user interaction needed */
public const STATUS_FAILURE = 3;
/** remote server revoked access */
public const STATUS_ACCESS_REVOKED = 4;
public function __construct(\OCA\Federation\DbHandler $dbHandler, \OCP\Http\Client\IClientService $httpClientService, \Psr\Log\LoggerInterface $logger, \OCP\BackgroundJob\IJobList $jobList, \OCP\Security\ISecureRandom $secureRandom, \OCP\IConfig $config, \OCP\EventDispatcher\IEventDispatcher $dispatcher, \OCP\AppFramework\Utility\ITimeFactory $timeFactory) {
}
/**
* Add server to the list of trusted servers
*/
public function addServer(string $url) : int {
}
/**
* Get shared secret for the given server
*/
public function getSharedSecret(string $url) : string {
}
/**
* Add shared secret for the given server
*/
public function addSharedSecret(string $url, string $sharedSecret) : void {
}
/**
* Remove server from the list of trusted servers
*/
public function removeServer(int $id) : void {
}
/**
* Get all trusted servers
*
* @return list<array{id: int, url: string, url_hash: string, shared_secret: ?string, status: int, sync_token: ?string}>
* @throws Exception
*/
public function getServers() {
}
/**
* Check if given server is a trusted Nextcloud server
*/
public function isTrustedServer(string $url) : bool {
}
/**
* Set server status
*/
public function setServerStatus(string $url, int $status) : void {
}
/**
* Get server status
*/
public function getServerStatus(string $url) : int {
}
/**
* Check if URL point to a ownCloud/Nextcloud server
*/
public function isNextcloudServer(string $url) : bool {
}
/**
* Check if ownCloud/Nextcloud version is >= 9.0
* @throws HintException
*/
protected function checkNextcloudVersion(string $status) : bool {
}
/**
* Check if the URL contain a protocol, if not add https
*/
protected function updateProtocol(string $url) : string {
}
}