diff --git a/lib/Command/BoardImport.php b/lib/Command/BoardImport.php index 2c825ae9e..0674b128a 100644 --- a/lib/Command/BoardImport.php +++ b/lib/Command/BoardImport.php @@ -45,6 +45,7 @@ class BoardImport extends Command { */ protected function configure() { $allowedSystems = $this->boardImportCommandService->getAllowedImportSystems(); + $names = array_column($allowedSystems, 'name'); $this ->setName('deck:import') ->setDescription('Import data') @@ -52,8 +53,8 @@ class BoardImport extends Command { 'system', null, InputOption::VALUE_REQUIRED, - 'Source system for import. Available options: ' . implode(', ', $allowedSystems) . '.', - 'trello' + 'Source system for import. Available options: ' . implode(', ', $names) . '.', + null ) ->addOption( 'config', @@ -65,7 +66,7 @@ class BoardImport extends Command { ->addOption( 'data', null, - InputOption::VALUE_REQUIRED, + InputOption::VALUE_OPTIONAL, 'Data file to import.', 'data.json' ) diff --git a/lib/Service/ABoardImportService.php b/lib/Service/ABoardImportService.php index 6b942410e..746febbc9 100644 --- a/lib/Service/ABoardImportService.php +++ b/lib/Service/ABoardImportService.php @@ -33,8 +33,11 @@ use OCP\AppFramework\Db\Entity; use OCP\Comments\IComment; abstract class ABoardImportService { + /** @var string */ + public static $name = ''; /** @var BoardImportService */ private $boardImportService; + protected $needValidateData = true; /** @var Stack[] */ protected $stacks = []; /** @var Label[] */ @@ -123,4 +126,8 @@ abstract class ABoardImportService { public function getImportService(): BoardImportService { return $this->boardImportService; } + + public function needValidateData(): bool { + return $this->needValidateData; + } } diff --git a/lib/Service/BoardImportCommandService.php b/lib/Service/BoardImportCommandService.php index 1a3300cc1..a9ca01a74 100644 --- a/lib/Service/BoardImportCommandService.php +++ b/lib/Service/BoardImportCommandService.php @@ -126,19 +126,24 @@ class BoardImportCommandService extends BoardImportService { } catch (\Throwable $th) { } $helper = $this->getCommand()->getHelper('question'); + $allowedSystems = $this->getAllowedImportSystems(); + $names = array_column($allowedSystems, 'name'); $question = new ChoiceQuestion( 'Please inform a source system', - $this->getAllowedImportSystems(), + $names, 0 ); $question->setErrorMessage('System %s is invalid.'); - $system = $helper->ask($this->getInput(), $this->getOutput(), $question); - $this->getInput()->setOption('system', $system); - $this->setSystem($system); + $selectedName = $helper->ask($this->getInput(), $this->getOutput(), $question); + $className = $allowedSystems[array_flip($names)[$selectedName]]['internalName']; + $this->setSystem($className); return; } protected function validateData(): void { + if (!$this->getImportSystem()->needValidateData()) { + return; + } $data = $this->getInput()->getOption('data'); if (is_string($data)) { $data = json_decode(file_get_contents($data)); diff --git a/lib/Service/BoardImportService.php b/lib/Service/BoardImportService.php index 61e126177..3a35a57aa 100644 --- a/lib/Service/BoardImportService.php +++ b/lib/Service/BoardImportService.php @@ -67,7 +67,7 @@ class BoardImportService { private $system = ''; /** @var null|ABoardImportService */ private $systemInstance; - /** @var string[] */ + /** @var array */ private $allowedSystems = []; /** * Data object created from config JSON @@ -142,7 +142,9 @@ class BoardImportService { } public function validateSystem(): void { - if (!in_array($this->getSystem(), $this->getAllowedImportSystems())) { + $allowedSystems = $this->getAllowedImportSystems(); + $allowedSystems = array_column($allowedSystems, 'internalName'); + if (!in_array($this->getSystem(), $allowedSystems)) { throw new NotFoundException('Invalid system'); } } @@ -173,9 +175,23 @@ class BoardImportService { } return true; }); - $allowedSystems = array_map(function ($name) { - preg_match('/\/BoardImport(?\w+)Service\.php$/', $name, $matches); - return lcfirst($matches['system']); + $allowedSystems = array_map(function ($filename) { + preg_match('/\/(?BoardImport(?\w+)Service)\.php$/', $filename, $matches); + $className = 'OCA\Deck\Service\\'.$matches['class']; + if (!class_exists($className)) { + /** @psalm-suppress UnresolvableInclude */ + require_once $name; + } + /** @psalm-suppress InvalidPropertyFetch */ + $name = $className::$name; + if (empty($name)) { + $name = lcfirst($matches['system']); + } + return [ + 'name' => $name, + 'class' => $className, + 'internalName' => lcfirst($matches['system']) + ]; }, $allowedSystems); $this->allowedSystems = array_values($allowedSystems); } diff --git a/lib/Service/BoardImportTrelloApiService.php b/lib/Service/BoardImportTrelloApiService.php new file mode 100644 index 000000000..5a8c54117 --- /dev/null +++ b/lib/Service/BoardImportTrelloApiService.php @@ -0,0 +1,99 @@ + + * + * @author Vitor Mattos + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Deck\Service; + +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\RequestException; +use OCP\AppFramework\Http; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IUserManager; + +class BoardImportTrelloApiService extends BoardImportTrelloJsonService { + /** @var string */ + public static $name = 'Trello API'; + protected $needValidateData = false; + /** @var IClient */ + private $httpClient; + /** @var ILogger */ + protected $logger; + /** @var string */ + private $baseApiUrl = 'https://api.trello.com/1'; + + + public function __construct( + IUserManager $userManager, + IL10N $l10n, + ILogger $logger, + IClientService $httpClientService + ) { + parent::__construct($userManager, $l10n); + $this->logger = $logger; + $this->httpClient = $httpClientService->newClient(); + } + + public function bootstrap(): void { + $this->getBoards(); + parent::bootstrap(); + } + + private function getBoards() { + $boards = $this->doRequest('/members/me/boards'); + } + + private function doRequest($path, $queryString = []) { + try { + $target = $this->baseApiUrl . $path; + $result = $this->httpClient + ->get($target, $this->getQueryString($queryString)) + ->getBody(); + $data = json_decode($result); + } catch (ClientException $e) { + $status = $e->getCode(); + if ($status === Http::STATUS_FORBIDDEN) { + $this->logger->info($target . ' refused.', ['app' => 'deck']); + } else { + $this->logger->info($target . ' responded with a ' . $status . ' containing: ' . $e->getMessage(), ['app' => 'deck']); + } + } catch (RequestException $e) { + $this->logger->logException($e, [ + 'message' => 'Could not connect to ' . $target, + 'level' => ILogger::INFO, + 'app' => 'deck', + ]); + } catch (\Throwable $e) { + $this->logger->logException($e, ['app' => 'deck']); + } + return $data; + } + + private function getQueryString($params = []): array { + $apiSettings = $this->getImportService()->getConfig('api'); + $params['key'] = $apiSettings->key; + $params['value'] = $apiSettings->token; + return $params; + } +} diff --git a/lib/Service/BoardImportTrelloService.php b/lib/Service/BoardImportTrelloJsonService.php similarity index 98% rename from lib/Service/BoardImportTrelloService.php rename to lib/Service/BoardImportTrelloJsonService.php index 3068ad66f..9bd7f6ff4 100644 --- a/lib/Service/BoardImportTrelloService.php +++ b/lib/Service/BoardImportTrelloJsonService.php @@ -35,7 +35,9 @@ use OCP\IL10N; use OCP\IUser; use OCP\IUserManager; -class BoardImportTrelloService extends ABoardImportService { +class BoardImportTrelloJsonService extends ABoardImportService { + /** @var string */ + public static $name = 'Trello JSON'; /** @var IUserManager */ private $userManager; /** @var IL10N */ diff --git a/lib/Service/fixtures/config-trelloApi-schema.json b/lib/Service/fixtures/config-trelloApi-schema.json new file mode 100644 index 000000000..baef76ce5 --- /dev/null +++ b/lib/Service/fixtures/config-trelloApi-schema.json @@ -0,0 +1,44 @@ +{ + "type": "object", + "properties": { + "api": { + "type": "object", + "properties": { + "key": { + "type": "string", + "pattern": "^\\w{32}$" + }, + "token": { + "type": "string", + "pattern": "^\\w{1,}$" + } + } + }, + "boards": { + "type": "array", + "items": { + "type": "string", + "pattern": "^\\w{1,}$" + } + }, + "uidRelation": { + "type": "object", + "comment": "Relationship between Trello and Nextcloud usernames", + "example": { + "johndoe": "admin" + } + }, + "owner": { + "type": "string", + "required": true, + "comment": "Nextcloud owner username" + }, + "color": { + "type": "string", + "required": true, + "pattern": "^[0-9a-fA-F]{6}$", + "comment": "Default color for the board. If you don't inform, the default color will be used.", + "default": "0800fd" + } + } +} \ No newline at end of file diff --git a/lib/Service/fixtures/config-trello-schema.json b/lib/Service/fixtures/config-trelloJson-schema.json similarity index 100% rename from lib/Service/fixtures/config-trello-schema.json rename to lib/Service/fixtures/config-trelloJson-schema.json diff --git a/tests/data/config-trello.json b/tests/data/config-trelloJson.json similarity index 100% rename from tests/data/config-trello.json rename to tests/data/config-trelloJson.json diff --git a/tests/data/data-trello.json b/tests/data/data-trelloJson.json similarity index 100% rename from tests/data/data-trello.json rename to tests/data/data-trelloJson.json diff --git a/tests/unit/Command/BoardImportTest.php b/tests/unit/Command/BoardImportTest.php index e2cdbbfbc..8c0c23119 100644 --- a/tests/unit/Command/BoardImportTest.php +++ b/tests/unit/Command/BoardImportTest.php @@ -58,7 +58,7 @@ class BoardImportTest extends \Test\TestCase { ['config'] ) ->will($this->returnValueMap([ - ['system', 'trello'], + ['system', 'trelloJson'], ['config', null] ])); diff --git a/tests/unit/Service/BoardImportServiceTest.php b/tests/unit/Service/BoardImportServiceTest.php index e0baab7c3..49f2afcd9 100644 --- a/tests/unit/Service/BoardImportServiceTest.php +++ b/tests/unit/Service/BoardImportServiceTest.php @@ -59,8 +59,8 @@ class BoardImportServiceTest extends \Test\TestCase { private $assignmentMapper; /** @var ICommentsManager|MockObject */ private $commentsManager; - /** @var BoardImportTrelloService|MockObject */ - private $importTrelloService; + /** @var BoardImportTrelloJsonService|MockObject */ + private $importTrelloJsonService; /** @var BoardImportService|MockObject */ private $boardImportService; public function setUp(): void { @@ -85,16 +85,16 @@ class BoardImportServiceTest extends \Test\TestCase { $this->commentsManager ); - $this->boardImportService->setSystem('trello'); + $this->boardImportService->setSystem('trelloJson'); - $data = json_decode(file_get_contents(__DIR__ . '/../../data/data-trello.json')); + $data = json_decode(file_get_contents(__DIR__ . '/../../data/data-trelloJson.json')); $this->boardImportService->setData($data); - $configInstance = json_decode(file_get_contents(__DIR__ . '/../../data/config-trello.json')); + $configInstance = json_decode(file_get_contents(__DIR__ . '/../../data/config-trelloJson.json')); $this->boardImportService->setConfigInstance($configInstance); - $this->importTrelloService = $this->createMock(BoardImportTrelloService::class); - $this->boardImportService->setImportSystem($this->importTrelloService); + $this->importTrelloJsonService = $this->createMock(BoardImportTrelloJsonService::class); + $this->boardImportService->setImportSystem($this->importTrelloJsonService); $owner = $this->createMock(IUser::class); $owner @@ -122,35 +122,35 @@ class BoardImportServiceTest extends \Test\TestCase { ->expects($this->once()) ->method('insert'); - $this->importTrelloService + $this->importTrelloJsonService ->method('getAclList') ->willReturn([new Acl()]); $this->aclMapper ->expects($this->once()) ->method('insert'); - $this->importTrelloService + $this->importTrelloJsonService ->method('getLabels') ->willReturn([new Label()]); $this->labelMapper ->expects($this->once()) ->method('insert'); - $this->importTrelloService + $this->importTrelloJsonService ->method('getStacks') ->willReturn([new Stack()]); $this->stackMapper ->expects($this->once()) ->method('insert'); - $this->importTrelloService + $this->importTrelloJsonService ->method('getCards') ->willReturn([new Card()]); $this->cardMapper ->expects($this->any()) ->method('insert'); - $this->importTrelloService + $this->importTrelloJsonService ->method('getComments') ->willReturn([ 'fakecardid' => [new Comment()] @@ -159,7 +159,7 @@ class BoardImportServiceTest extends \Test\TestCase { ->expects($this->once()) ->method('save'); - $this->importTrelloService + $this->importTrelloJsonService ->method('getCardAssignments') ->willReturn([ 'fakecardid' => [new Assignment()] diff --git a/tests/unit/Service/BoardImportTrelloServiceTest.php b/tests/unit/Service/BoardImportTrelloServiceTest.php index 734347008..99c8f8c2d 100644 --- a/tests/unit/Service/BoardImportTrelloServiceTest.php +++ b/tests/unit/Service/BoardImportTrelloServiceTest.php @@ -26,8 +26,8 @@ use OCP\IL10N; use OCP\IUser; use OCP\IUserManager; -class BoardImportTrelloServiceTest extends \Test\TestCase { - /** @var BoardImportTrelloService */ +class BoardImportTrelloJsonServiceTest extends \Test\TestCase { + /** @var BoardImportTrelloJsonService */ private $service; /** @var IUserManager */ private $userManager; @@ -36,7 +36,7 @@ class BoardImportTrelloServiceTest extends \Test\TestCase { public function setUp(): void { $this->userManager = $this->createMock(IUserManager::class); $this->l10n = $this->createMock(IL10N::class); - $this->service = new BoardImportTrelloService( + $this->service = new BoardImportTrelloJsonService( $this->userManager, $this->l10n ); @@ -62,7 +62,7 @@ class BoardImportTrelloServiceTest extends \Test\TestCase { ->willReturn(json_decode('{"members": [{"username": "othre_trello_user"}]}')); $this->service->setImportService($importService); $actual = $this->service->validateUsers(); - $this->assertInstanceOf(BoardImportTrelloService::class, $actual); + $this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual); } public function testValidateUsersWithNotStringNextcloud() { @@ -78,7 +78,7 @@ class BoardImportTrelloServiceTest extends \Test\TestCase { ->willReturn(json_decode('{"members": [{"username": "trello_user"}]}')); $this->service->setImportService($importService); $actual = $this->service->validateUsers(); - $this->assertInstanceOf(BoardImportTrelloService::class, $actual); + $this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual); } public function testValidateUsersWithNotFoundUser() { @@ -92,7 +92,7 @@ class BoardImportTrelloServiceTest extends \Test\TestCase { ->willReturn(json_decode('{"members": [{"username": "trello_user"}]}')); $this->service->setImportService($importService); $actual = $this->service->validateUsers(); - $this->assertInstanceOf(BoardImportTrelloService::class, $actual); + $this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual); } public function testValidateUsersWithValidUsers() { @@ -123,10 +123,10 @@ class BoardImportTrelloServiceTest extends \Test\TestCase { public function testGetBoardWithSuccess() { $importService = \OC::$server->get(BoardImportService::class); - $data = json_decode(file_get_contents(__DIR__ . '/../../data/data-trello.json')); + $data = json_decode(file_get_contents(__DIR__ . '/../../data/data-trelloJson.json')); $importService->setData($data); - $configInstance = json_decode(file_get_contents(__DIR__ . '/../../data/config-trello.json')); + $configInstance = json_decode(file_get_contents(__DIR__ . '/../../data/config-trelloJson.json')); $importService->setConfigInstance($configInstance); $owner = $this->createMock(IUser::class); diff --git a/tests/unit/controller/BoardImportApiControllerTest.php b/tests/unit/controller/BoardImportApiControllerTest.php index 5389b46f1..ef16cab26 100644 --- a/tests/unit/controller/BoardImportApiControllerTest.php +++ b/tests/unit/controller/BoardImportApiControllerTest.php @@ -50,16 +50,23 @@ class BoardImportApiControllerTest extends \Test\TestCase { } public function testGetAllowedSystems() { + $allowedSystems = [ + [ + 'name' => '', + 'class' => '', + 'internalName' => 'trelloJson' + ] + ]; $this->boardImportService ->method('getAllowedImportSystems') - ->willReturn(['trello']); + ->willReturn($allowedSystems); $actual = $this->controller->getAllowedSystems(); - $expected = new DataResponse(['trello'], HTTP::STATUS_OK); + $expected = new DataResponse($allowedSystems, HTTP::STATUS_OK); $this->assertEquals($expected, $actual); } public function testImport() { - $system = 'trello'; + $system = 'trelloJson'; $config = [ 'owner' => 'test' ];