diff --git a/appinfo/info.xml b/appinfo/info.xml
index cf8d707e2..0bf8183e6 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -44,6 +44,7 @@
OCA\Deck\Command\UserExport
+ OCA\Deck\Command\BoardImport
diff --git a/composer.json b/composer.json
index 7bf463588..88d40fc46 100644
--- a/composer.json
+++ b/composer.json
@@ -9,7 +9,8 @@
}
],
"require": {
- "cogpowered/finediff": "0.3.*"
+ "cogpowered/finediff": "0.3.*",
+ "justinrainbow/json-schema": "^5.2"
},
"require-dev": {
"roave/security-advisories": "dev-master",
diff --git a/composer.lock b/composer.lock
index 08f9378c0..64256a599 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1f6d91406db4e7e16e31113951986e13",
+ "content-hash": "cf4fb1b424f5f0c36ecc1391b10de59c",
"packages": [
{
"name": "cogpowered/finediff",
@@ -60,6 +60,76 @@
"source": "https://github.com/cogpowered/FineDiff/tree/master"
},
"time": "2014-05-19T10:25:02+00:00"
+ },
+ {
+ "name": "justinrainbow/json-schema",
+ "version": "5.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/justinrainbow/json-schema.git",
+ "reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ab6744b7296ded80f8cc4f9509abbff393399aa",
+ "reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
+ "json-schema/json-schema-test-suite": "1.2.0",
+ "phpunit/phpunit": "^4.8.35"
+ },
+ "bin": [
+ "bin/validate-json"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "JsonSchema\\": "src/JsonSchema/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bruno Prieto Reis",
+ "email": "bruno.p.reis@gmail.com"
+ },
+ {
+ "name": "Justin Rainbow",
+ "email": "justin.rainbow@gmail.com"
+ },
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ },
+ {
+ "name": "Robert Schönthal",
+ "email": "seroscho@googlemail.com"
+ }
+ ],
+ "description": "A library to validate a json schema.",
+ "homepage": "https://github.com/justinrainbow/json-schema",
+ "keywords": [
+ "json",
+ "schema"
+ ],
+ "support": {
+ "issues": "https://github.com/justinrainbow/json-schema/issues",
+ "source": "https://github.com/justinrainbow/json-schema/tree/5.2.11"
+ },
+ "time": "2021-07-22T09:24:00+00:00"
}
],
"packages-dev": [
@@ -3677,16 +3747,16 @@
},
{
"name": "symfony/console",
- "version": "v5.4.1",
+ "version": "v5.4.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4"
+ "reference": "a2c6b7ced2eb7799a35375fb9022519282b5405e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4",
- "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4",
+ "url": "https://api.github.com/repos/symfony/console/zipball/a2c6b7ced2eb7799a35375fb9022519282b5405e",
+ "reference": "a2c6b7ced2eb7799a35375fb9022519282b5405e",
"shasum": ""
},
"require": {
@@ -3756,7 +3826,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v5.4.1"
+ "source": "https://github.com/symfony/console/tree/v5.4.2"
},
"funding": [
{
@@ -3772,7 +3842,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-09T11:22:43+00:00"
+ "time": "2021-12-20T16:11:12+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -4070,16 +4140,16 @@
},
{
"name": "symfony/finder",
- "version": "v5.4.0",
+ "version": "v5.4.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590"
+ "reference": "e77046c252be48c48a40816187ed527703c8f76c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/d2f29dac98e96a98be467627bd49c2efb1bc2590",
- "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/e77046c252be48c48a40816187ed527703c8f76c",
+ "reference": "e77046c252be48c48a40816187ed527703c8f76c",
"shasum": ""
},
"require": {
@@ -4113,7 +4183,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v5.4.0"
+ "source": "https://github.com/symfony/finder/tree/v5.4.2"
},
"funding": [
{
@@ -4129,7 +4199,7 @@
"type": "tidelift"
}
],
- "time": "2021-11-28T15:25:38+00:00"
+ "time": "2021-12-15T11:06:13+00:00"
},
{
"name": "symfony/options-resolver",
@@ -4767,16 +4837,16 @@
},
{
"name": "symfony/process",
- "version": "v5.4.0",
+ "version": "v5.4.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "5be20b3830f726e019162b26223110c8f47cf274"
+ "reference": "2b3ba8722c4aaf3e88011be5e7f48710088fb5e4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/5be20b3830f726e019162b26223110c8f47cf274",
- "reference": "5be20b3830f726e019162b26223110c8f47cf274",
+ "url": "https://api.github.com/repos/symfony/process/zipball/2b3ba8722c4aaf3e88011be5e7f48710088fb5e4",
+ "reference": "2b3ba8722c4aaf3e88011be5e7f48710088fb5e4",
"shasum": ""
},
"require": {
@@ -4809,7 +4879,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v5.4.0"
+ "source": "https://github.com/symfony/process/tree/v5.4.2"
},
"funding": [
{
@@ -4825,7 +4895,7 @@
"type": "tidelift"
}
],
- "time": "2021-11-28T15:25:38+00:00"
+ "time": "2021-12-27T21:01:00+00:00"
},
{
"name": "symfony/service-contracts",
@@ -4974,16 +5044,16 @@
},
{
"name": "symfony/string",
- "version": "v5.4.0",
+ "version": "v5.4.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "9ffaaba53c61ba75a3c7a3a779051d1e9ec4fd2d"
+ "reference": "e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/9ffaaba53c61ba75a3c7a3a779051d1e9ec4fd2d",
- "reference": "9ffaaba53c61ba75a3c7a3a779051d1e9ec4fd2d",
+ "url": "https://api.github.com/repos/symfony/string/zipball/e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d",
+ "reference": "e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d",
"shasum": ""
},
"require": {
@@ -5040,7 +5110,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v5.4.0"
+ "source": "https://github.com/symfony/string/tree/v5.4.2"
},
"funding": [
{
@@ -5056,7 +5126,7 @@
"type": "tidelift"
}
],
- "time": "2021-11-24T10:02:00+00:00"
+ "time": "2021-12-16T21:52:00+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/lib/Command/BoardImport.php b/lib/Command/BoardImport.php
index 296c08bf8..0870c52a1 100644
--- a/lib/Command/BoardImport.php
+++ b/lib/Command/BoardImport.php
@@ -1,8 +1,8 @@
+ * @copyright Copyright (c) 2021 Vitor Mattos
*
- * @author Julius Härtl
+ * @author Vitor Mattos
*
* @license GNU AGPL version 3 or any later version
*
@@ -23,80 +23,37 @@
namespace OCA\Deck\Command;
-use JsonSchema\Validator;
-use OCA\Deck\Db\AssignmentMapper;
-use OCA\Deck\Db\Board;
-use OCA\Deck\Db\BoardMapper;
-use OCA\Deck\Db\Card;
-use OCA\Deck\Db\CardMapper;
-use OCA\Deck\Db\Stack;
-use OCA\Deck\Db\StackMapper;
-use OCA\Deck\Service\BoardService;
-use OCA\Deck\Service\LabelService;
-use OCA\Deck\Service\PermissionService;
-use OCA\Deck\Service\StackService;
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\AppFramework\Db\MultipleObjectsReturnedException;
-use OCP\IGroupManager;
-use OCP\IUserManager;
-use OCP\IUserSession;
+use OCA\Deck\Command\Helper\ImportInterface;
+use OCA\Deck\Command\Helper\TrelloHelper;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
-use Symfony\Component\Console\Question\Question;
class BoardImport extends Command {
- /** @var BoardService */
- private $boardService;
- // protected $cardMapper;
- /** @var LabelService */
- private $labelService;
- /** @var StackMapper */
- private $stackMapper;
- /** @var CardMapper */
- private $cardMapper;
- /** @var IUserManager */
- private $userManager;
- // /** @var IGroupManager */
- // private $groupManager;
- // private $assignedUsersMapper;
+ /** @var string */
+ private $system;
private $allowedSystems = ['trello'];
- /** @var Board */
- private $board;
+ /** @var TrelloHelper */
+ private $trelloHelper;
+ /**
+ * Data object created from settings JSON
+ *
+ * @var \StdClass
+ */
+ public $settings;
public function __construct(
- // BoardMapper $boardMapper,
- BoardService $boardService,
- LabelService $labelService,
- StackMapper $stackMapper,
- CardMapper $cardMapper,
- // IUserSession $userSession,
- // StackMapper $stackMapper,
- // CardMapper $cardMapper,
- // AssignmentMapper $assignedUsersMapper,
- IUserManager $userManager
- // IGroupManager $groupManager
+ TrelloHelper $trelloHelper
) {
parent::__construct();
-
- // $this->cardMapper = $cardMapper;
- $this->boardService = $boardService;
- $this->labelService = $labelService;
- $this->stackMapper = $stackMapper;
- $this->cardMapper = $cardMapper;
-
- // $this->userSession = $userSession;
- // $this->stackMapper = $stackMapper;
- // $this->assignedUsersMapper = $assignedUsersMapper;
- // $this->boardMapper = $boardMapper;
-
- $this->userManager = $userManager;
- // $this->groupManager = $groupManager;
+ $this->trelloHelper = $trelloHelper;
}
+ /**
+ * @return void
+ */
protected function configure() {
$this
->setName('deck:import')
@@ -127,73 +84,35 @@ class BoardImport extends Command {
/**
* @inheritDoc
+ *
+ * @return void
*/
- protected function interact(InputInterface $input, OutputInterface $output)
- {
+ protected function interact(InputInterface $input, OutputInterface $output) {
$this->validateSystem($input, $output);
- $this->validateData($input, $output);
- $this->validateSettings($input, $output);
- $this->validateUsers();
- $this->validateOwner();
+ $this->getSystem()
+ ->validate($input, $output);
}
- public function validateData(InputInterface $input, OutputInterface $output) {
- $filename = $input->getOption('data');
- if (!is_file($filename)) {
- $helper = $this->getHelper('question');
- $question = new Question(
- 'Please inform a valid data json file: ',
- 'data.json'
- );
- $question->setValidator(function ($answer) {
- if (!is_file($answer)) {
- throw new \RuntimeException(
- 'Data file not found'
- );
- }
- return $answer;
- });
- $data = $helper->ask($input, $output, $question);
- $input->setOption('data', $data);
- }
- $this->data = json_decode(file_get_contents($filename));
- if (!$this->data) {
- $output->writeln('Is not a json file: ' . $filename . '');
- $this->validateData($input, $output);
- }
- if (!$this->data) {
- $this->data = json_decode(file_get_contents($filename));
- }
+ private function setSystem(string $system): void {
+ $this->system = $system;
}
- private function validateOwner() {
- $this->settings->owner = $this->userManager->get($this->settings->owner);
- if (!$this->settings->owner) {
- throw new \LogicException('Owner "' . $this->settings->owner . '" not found on Nextcloud. Check setting json.');
- }
- }
-
- private function validateUsers() {
- if (empty($this->settings->uidRelation)) {
- return;
- }
- foreach ($this->settings->uidRelation as $trelloUid => $nextcloudUid) {
- $user = array_filter($this->data->members, fn($u) => $u->username === $trelloUid);
- if (!$user) {
- throw new \LogicException('Trello user ' . $trelloUid . ' not found in property "members" of json data');
- }
- if (!is_string($nextcloudUid)) {
- throw new \LogicException('User on setting uidRelation must be a string');
- }
- $this->settings->uidRelation->$trelloUid = $this->userManager->get($nextcloudUid);
- if (!$this->settings->uidRelation->$trelloUid) {
- throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
- }
- }
+ /**
+ * @return ImportInterface
+ */
+ private function getSystem() {
+ $helper = $this->{$this->system . 'Helper'};
+ $helper->setCommand($this);
+ return $helper;
}
+ /**
+ * @return void
+ */
private function validateSystem(InputInterface $input, OutputInterface $output) {
- if (in_array($input->getOption('system'), $this->allowedSystems)) {
+ $system = $input->getOption('system');
+ if (in_array($system, $this->allowedSystems)) {
+ $this->setSystem($system);
return;
}
$helper = $this->getHelper('question');
@@ -205,220 +124,19 @@ class BoardImport extends Command {
$question->setErrorMessage('System %s is invalid.');
$system = $helper->ask($input, $output, $question);
$input->setOption('system', $system);
- }
-
- private function validateSettings(InputInterface $input, OutputInterface $output) {
- if (!is_file($input->getOption('setting'))) {
- $helper = $this->getHelper('question');
- $question = new Question(
- 'Please inform a valid setting json file: ',
- 'config.json'
- );
- $question->setValidator(function ($answer) {
- if (!is_file($answer)) {
- throw new \RuntimeException(
- 'Setting file not found'
- );
- }
- return $answer;
- });
- $setting = $helper->ask($input, $output, $question);
- $input->setOption('setting', $setting);
- }
-
- $this->settings = json_decode(file_get_contents($input->getOption('setting')));
- $validator = new Validator();
- $validator->validate(
- $this->settings,
- (object)['$ref' => 'file://' . realpath(__DIR__ . '/fixtures/setting-schema.json')]
- );
- if (!$validator->isValid()) {
- $output->writeln('Invalid setting file');
- $output->writeln(array_map(fn($v) => $v['message'], $validator->getErrors()));
- $output->writeln('Valid schema:');
- $output->writeln(print_r(file_get_contents(__DIR__ . '/fixtures/setting-schema.json'), true));
- $input->setOption('setting', null);
- $this->validateSettings($input, $output);
- }
+ $this->setSystem($system);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
- * @return void
- * @throws DoesNotExistException
- * @throws MultipleObjectsReturnedException
- * @throws \ReflectionException
+ *
+ * @return int
*/
- protected function execute(InputInterface $input, OutputInterface $output) {
- // $this->boardService->setUserId($this->settings->owner->getUID());
- $this->setUserId($this->settings->owner->getUID());
- // $this->userSession->setUser($this->settings->owner);
- $this->importBoard();
- $this->importLabels();
- $this->importStacks();
- $this->importCards();
- // $boards = $this->boardService->findAll();
-
- // $data = [];
- // foreach ($boards as $board) {
- // $fullBoard = $this->boardMapper->find($board->getId(), true, true);
- // $data[$board->getId()] = (array)$fullBoard->jsonSerialize();
- // $stacks = $this->stackMapper->findAll($board->getId());
- // foreach ($stacks as $stack) {
- // $data[$board->getId()]['stacks'][] = (array)$stack->jsonSerialize();
- // $cards = $this->cardMapper->findAllByStack($stack->getId());
- // foreach ($cards as $card) {
- // $fullCard = $this->cardMapper->find($card->getId());
- // $assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
- // $fullCard->setAssignedUsers($assignedUsers);
- // $data[$board->getId()]['stacks'][$stack->getId()]['cards'][] = (array)$fullCard->jsonSerialize();
- // }
- // }
- // }
- // $output->writeln(json_encode($data, JSON_PRETTY_PRINT));
- return self::SUCCESS;
- }
-
- private function checklistItem($item) {
- if (($item->state == 'incomplete')) {
- $string_start = '- [ ]';
- } else {
- $string_start = '- [x]';
- }
- $check_item_string = $string_start . ' ' . $item->name . "\n";
- return $check_item_string;
- }
-
- function formulateChecklistText($checklist) {
- $checklist_string = "\n\n## {$checklist->name}\n";
- foreach ($checklist->checkItems as $item) {
- $checklist_item_string = $this->checklistItem($item);
- $checklist_string = $checklist_string . "\n" . $checklist_item_string;
- }
- return $checklist_string;
- }
-
- private function importCards() {
- # Save checklist content into a dictionary (_should_ work even if a card has multiple checklists
- foreach ($this->data->checklists as $checklist) {
- $checklists[$checklist->idCard][$checklist->id] = $this->formulateChecklistText($checklist);
- }
- $this->data->checklists = $checklists;
-
- foreach ($this->data->cards as $trelloCard) {
- # Check whether a card is archived, if true, skipping to the next card
- if ($trelloCard->closed) {
- continue;
- }
- if ((count($trelloCard->idChecklists) !== 0)) {
- foreach ($this->data->checklists[$trelloCard->id] as $checklist) {
- $trelloCard->desc .= "\n" . $checklist;
- }
- }
-
- $card = new Card();
- $card->setTitle($trelloCard->name);
- $card->setStackId($this->stacks[$trelloCard->idList]);
- $card->setType('plain');
- $card->setOrder($trelloCard->idShort);
- $card->setOwner($this->settings->owner->getUID());
- $card->setDescription($trelloCard->desc);
- if ($trelloCard->due) {
- $duedate = \DateTime::createFromFormat('Y-m-d\TH:i:s.000\Z', $trelloCard->due)
- ->format('Y-m-d H:i:s');
- $card->setDuedate($duedate);
- }
- $card = $this->cardMapper->insert($card);
-
- $this->associateCardToLabels($card->getId(), $trelloCard);
- }
- }
-
- public function associateCardToLabels($cardId, $card) {
- foreach ($card->labels as $label) {
- $this->cardMapper->assignLabel(
- $cardId,
- $this->labels[$label->id]->getId()
- );
- }
- }
-
- private function importStacks() {
- $this->stacks = [];
- foreach ($this->data->lists as $order => $list) {
- if ($list->closed) {
- continue;
- }
- $stack = new Stack();
- $stack->setTitle($list->name);
- $stack->setBoardId($this->board->getId());
- $stack->setOrder($order + 1);
- $stack = $this->stackMapper->insert($stack);
- $this->stacks[$list->id] = $stack;
- }
- }
-
- private function translateColor($color) {
- switch ($color) {
- case 'red':
- return 'ff0000';
- case 'yellow':
- return 'ffff00';
- case 'orange':
- return 'ff6600';
- case 'green':
- return '00ff00';
- case 'purple':
- return '9900ff';
- case 'blue':
- return '0000ff';
- case 'sky':
- return '00ccff';
- case 'lime':
- return '00ff99';
- case 'pink':
- return 'ff66cc';
- case 'black':
- return '000000';
- default:
- return 'ffffff';
- }
- }
-
- private function importBoard() {
- $this->board = $this->boardService->create(
- $this->data->name,
- $this->settings->owner->getUID(),
- $this->settings->color
- );
- // $this->boardService->find($this->board->getId());
- }
-
- public function importLabels() {
- $this->labels = [];
- foreach ($this->data->labels as $label) {
- if (empty($label->name)) {
- $labelTitle = 'Unnamed ' . $label->color . ' label';
- } else {
- $labelTitle = $label->name;
- }
- $newLabel = $this->labelService->create(
- $labelTitle,
- $this->translateColor($label->color),
- $this->board->getId()
- );
- $this->labels[$label->id] = $newLabel;
- }
- }
-
- private function setUserId() {
- $propertyPermissionService = new \ReflectionProperty($this->labelService, 'permissionService');
- $propertyPermissionService->setAccessible(true);
- $permissionService = $propertyPermissionService->getValue($this->labelService);
-
- $propertyUserId = new \ReflectionProperty($permissionService, 'userId');
- $propertyUserId->setAccessible(true);
- $propertyUserId->setValue($permissionService, $this->settings->owner->getUID());
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $this->getSystem()
+ ->import($input, $output);
+ $output->writeln('Done!');
+ return 0;
}
}
diff --git a/lib/Command/Helper/ImportAbstract.php b/lib/Command/Helper/ImportAbstract.php
new file mode 100644
index 000000000..de271f2c8
--- /dev/null
+++ b/lib/Command/Helper/ImportAbstract.php
@@ -0,0 +1,86 @@
+command = $command;
+ }
+
+ /**
+ * @return Command
+ */
+ public function getCommand() {
+ return $this->command;
+ }
+
+ /**
+ * Get a setting
+ *
+ * @param string $setting Setting name
+ * @return mixed
+ */
+ public function getSetting($setting) {
+ return $this->settings->$setting;
+ }
+
+ /**
+ * Define a setting
+ *
+ * @param string $settingName
+ * @param mixed $value
+ * @return void
+ */
+ public function setSetting($settingName, $value) {
+ $this->settings->$settingName = $value;
+ }
+
+ protected function validateSettings(InputInterface $input, OutputInterface $output): void {
+ $settingFile = $input->getOption('setting');
+ if (!is_file($settingFile)) {
+ $helper = $this->getCommand()->getHelper('question');
+ $question = new Question(
+ 'Please inform a valid setting json file: ',
+ 'config.json'
+ );
+ $question->setValidator(function ($answer) {
+ if (!is_file($answer)) {
+ throw new \RuntimeException(
+ 'Setting file not found'
+ );
+ }
+ return $answer;
+ });
+ $settingFile = $helper->ask($input, $output, $question);
+ $input->setOption('setting', $settingFile);
+ }
+
+ $this->settings = json_decode(file_get_contents($settingFile));
+ $validator = new Validator();
+ $validator->validate(
+ $this->settings,
+ (object)['$ref' => 'file://' . realpath(__DIR__ . '/../fixtures/setting-schema.json')]
+ );
+ if (!$validator->isValid()) {
+ $output->writeln('Invalid setting file');
+ $output->writeln(array_map(function ($v) {
+ return $v['message'];
+ }, $validator->getErrors()));
+ $output->writeln('Valid schema:');
+ $output->writeln(print_r(file_get_contents(__DIR__ . '/fixtures/setting-schema.json'), true));
+ $input->setOption('setting', null);
+ $this->validateSettings($input, $output);
+ }
+ }
+}
diff --git a/lib/Command/Helper/ImportInterface.php b/lib/Command/Helper/ImportInterface.php
new file mode 100644
index 000000000..08f0e6a68
--- /dev/null
+++ b/lib/Command/Helper/ImportInterface.php
@@ -0,0 +1,47 @@
+
+ *
+ * @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\Command\Helper;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+interface ImportInterface {
+ /**
+ * Validate data before run execute method
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return void
+ */
+ public function validate(InputInterface $input, OutputInterface $output): void;
+
+ /**
+ * Run import
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return void
+ */
+ public function import(InputInterface $input, OutputInterface $output): void;
+}
diff --git a/lib/Command/Helper/TrelloHelper.php b/lib/Command/Helper/TrelloHelper.php
new file mode 100644
index 000000000..50e1be346
--- /dev/null
+++ b/lib/Command/Helper/TrelloHelper.php
@@ -0,0 +1,429 @@
+
+ *
+ * @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\Command\Helper;
+
+use OCA\Deck\Db\Acl;
+use OCA\Deck\Db\AclMapper;
+use OCA\Deck\Db\Assignment;
+use OCA\Deck\Db\AssignmentMapper;
+use OCA\Deck\Db\Board;
+use OCA\Deck\Db\Card;
+use OCA\Deck\Db\CardMapper;
+use OCA\Deck\Db\Label;
+use OCA\Deck\Db\Stack;
+use OCA\Deck\Db\StackMapper;
+use OCA\Deck\Service\BoardService;
+use OCA\Deck\Service\LabelService;
+use OCP\IDBConnection;
+use OCP\IUser;
+use OCP\IUserManager;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\Question;
+
+class TrelloHelper extends ImportAbstract implements ImportInterface {
+ /** @var BoardService */
+ private $boardService;
+ /** @var StackMapper */
+ private $stackMapper;
+ /** @var CardMapper */
+ private $cardMapper;
+ /** @var AssignmentMapper */
+ private $assignmentMapper;
+ /** @var AclMapper */
+ private $aclMapper;
+ /** @var IDBConnection */
+ private $connection;
+ /** @var IUserManager */
+ private $userManager;
+ /** @var TrelloActions */
+ private $trelloActions;
+ /** @var Board */
+ private $board;
+ /** @var LabelService */
+ private $labelService;
+ /**
+ * Data object created from JSON of origin system
+ *
+ * @var \StdClass
+ */
+ private $data;
+ /**
+ * Array of stacks
+ *
+ * @var Stack[]
+ */
+ private $stacks = [];
+ /**
+ * Array of labels
+ *
+ * @var Label[]
+ */
+ private $labels = [];
+ /** @var Card[] */
+ private $cards = [];
+ /** @var IUser */
+ private $members = [];
+
+ public function __construct(
+ BoardService $boardService,
+ LabelService $labelService,
+ StackMapper $stackMapper,
+ CardMapper $cardMapper,
+ AssignmentMapper $assignmentMapper,
+ AclMapper $aclMapper,
+ IDBConnection $connection,
+ IUserManager $userManager
+ ) {
+ $this->boardService = $boardService;
+ $this->labelService = $labelService;
+ $this->stackMapper = $stackMapper;
+ $this->cardMapper = $cardMapper;
+ $this->assignmentMapper = $assignmentMapper;
+ $this->aclMapper = $aclMapper;
+ $this->connection = $connection;
+ $this->userManager = $userManager;
+ }
+
+ public function validate(InputInterface $input, OutputInterface $output): void {
+ $this->validateData($input, $output);
+ $this->validateSettings($input, $output);
+ $this->validateUsers();
+ $this->validateOwner();
+ }
+
+ public function import(InputInterface $input, OutputInterface $output): void {
+ $this->setUserId();
+ $output->writeln('Importing board...');
+ $this->importBoard();
+ $output->writeln('Assign users to board...');
+ $this->assignUsersToBoard();
+ $output->writeln('Importing labels...');
+ $this->importLabels();
+ $output->writeln('Importing stacks...');
+ $this->importStacks();
+ $output->writeln('Importing cards...');
+ $this->importCards();
+ }
+
+ private function assignUsersToBoard() {
+ foreach ($this->members as $member) {
+ $acl = new Acl();
+ $acl->setBoardId($this->board->getId());
+ $acl->setType(Acl::PERMISSION_TYPE_USER);
+ $acl->setParticipant($member->getUid());
+ $acl->setPermissionEdit(true);
+ $acl->setPermissionShare($member->getUID() === $this->getSetting('owner')->getUID());
+ $acl->setPermissionManage($member->getUID() === $this->getSetting('owner')->getUID());
+ $this->aclMapper->insert($acl);
+ }
+ }
+
+ private function validateData(InputInterface $input, OutputInterface $output): void {
+ $filename = $input->getOption('data');
+ if (!is_file($filename)) {
+ $helper = $this->getCommand()->getHelper('question');
+ $question = new Question(
+ 'Please inform a valid data json file: ',
+ 'data.json'
+ );
+ $question->setValidator(function ($answer) {
+ if (!is_file($answer)) {
+ throw new \RuntimeException(
+ 'Data file not found'
+ );
+ }
+ return $answer;
+ });
+ $data = $helper->ask($input, $output, $question);
+ $input->setOption('data', $data);
+ }
+ $this->data = json_decode(file_get_contents($filename));
+ if (!$this->data) {
+ $output->writeln('Is not a json file: ' . $filename . '');
+ $this->validateData($input, $output);
+ }
+ }
+
+ private function validateOwner(): void {
+ $owner = $this->userManager->get($this->getSetting('owner'));
+ if (!$owner) {
+ throw new \LogicException('Owner "' . $this->getSetting('owner') . '" not found on Nextcloud. Check setting json.');
+ }
+ $this->setSetting('owner', $owner);
+ }
+
+ /**
+ * @return void
+ */
+ private function validateUsers() {
+ if (empty($this->getSetting('uidRelation'))) {
+ return;
+ }
+ foreach ($this->getSetting('uidRelation') as $trelloUid => $nextcloudUid) {
+ $user = array_filter($this->data->members, function ($u) use ($trelloUid) {
+ return $u->username === $trelloUid;
+ });
+ if (!$user) {
+ throw new \LogicException('Trello user ' . $trelloUid . ' not found in property "members" of json data');
+ }
+ if (!is_string($nextcloudUid)) {
+ throw new \LogicException('User on setting uidRelation must be a string');
+ }
+ $this->getSetting('uidRelation')->$trelloUid = $this->userManager->get($nextcloudUid);
+ if (!$this->getSetting('uidRelation')->$trelloUid) {
+ throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
+ }
+ $user = current($user);
+ $this->members[$user->id] = $this->getSetting('uidRelation')->$trelloUid;
+ }
+ }
+
+ private function checklistItem($item): string {
+ if (($item->state == 'incomplete')) {
+ $string_start = '- [ ]';
+ } else {
+ $string_start = '- [x]';
+ }
+ $check_item_string = $string_start . ' ' . $item->name . "\n";
+ return $check_item_string;
+ }
+
+ private function formulateChecklistText($checklist): string {
+ $checklist_string = "\n\n## {$checklist->name}\n";
+ foreach ($checklist->checkItems as $item) {
+ $checklist_item_string = $this->checklistItem($item);
+ $checklist_string = $checklist_string . "\n" . $checklist_item_string;
+ }
+ return $checklist_string;
+ }
+
+ private function importCards(): void {
+ $checklists = [];
+ foreach ($this->data->checklists as $checklist) {
+ $checklists[$checklist->idCard][$checklist->id] = $this->formulateChecklistText($checklist);
+ }
+ $this->data->checklists = $checklists;
+
+ foreach ($this->data->cards as $trelloCard) {
+ $card = new Card();
+ $lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
+ $card->setLastModified($lastModified->format('Y-m-d H:i:s'));
+ if ($trelloCard->closed) {
+ $card->setDeletedAt($lastModified->format('U'));
+ }
+ if ((count($trelloCard->idChecklists) !== 0)) {
+ foreach ($this->data->checklists[$trelloCard->id] as $checklist) {
+ $trelloCard->desc .= "\n" . $checklist;
+ }
+ }
+ $this->appendAttachmentsToDescription($trelloCard);
+
+ $card->setTitle($trelloCard->name);
+ $card->setStackId($this->stacks[$trelloCard->idList]->getId());
+ $card->setType('plain');
+ $card->setOrder($trelloCard->idShort);
+ $card->setOwner($this->getSetting('owner')->getUID());
+ $card->setDescription($trelloCard->desc);
+ if ($trelloCard->due) {
+ $duedate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->due)
+ ->format('Y-m-d H:i:s');
+ $card->setDuedate($duedate);
+ }
+ $card = $this->cardMapper->insert($card);
+ $this->cards[$trelloCard->id] = $card;
+
+ $this->associateCardToLabels($card, $trelloCard);
+ $this->importComments($card, $trelloCard);
+ $this->assignToMember($card, $trelloCard);
+ }
+ }
+
+ private function appendAttachmentsToDescription($trelloCard) {
+ if (empty($trelloCard->attachments)) {
+ return;
+ }
+ $translations = $this->getSetting('translations');
+ $attachmentsLabel = empty($translations->{'Attachments'}) ? 'Attachments' : $translations->{'Attachments'};
+ $URLLabel = empty($translations->{'URL'}) ? 'URL' : $translations->{'URL'};
+ $nameLabel = empty($translations->{'Name'}) ? 'Name' : $translations->{'Name'};
+ $dateLabel = empty($translations->{'Date'}) ? 'Date' : $translations->{'Date'};
+ $trelloCard->desc .= "\n\n## {$attachmentsLabel}\n";
+ $trelloCard->desc .= "| $URLLabel | $nameLabel | $dateLabel |\n";
+ $trelloCard->desc .= "|---|---|---|\n";
+ foreach ($trelloCard->attachments as $attachment) {
+ $name = $attachment->name === $attachment->url ? null : $attachment->name;
+ $trelloCard->desc .= "| {$attachment->url} | {$name} | {$attachment->date} |\n";
+ }
+ }
+
+ private function assignToMember(Card $card, $trelloCard) {
+ foreach ($trelloCard->idMembers as $idMember) {
+ $assignment = new Assignment();
+ $assignment->setCardId($card->getId());
+ $assignment->setParticipant($this->members[$idMember]->getUID());
+ $assignment->setType(Assignment::TYPE_USER);
+ $assignment = $this->assignmentMapper->insert($assignment);
+ }
+ }
+
+ private function importComments(\OCP\AppFramework\Db\Entity $card, $trelloCard): void {
+ $comments = array_filter(
+ $this->data->actions,
+ function ($a) use ($trelloCard) {
+ return $a->type === 'commentCard' && $a->data->card->id === $trelloCard->id;
+ }
+ );
+ foreach ($comments as $trelloComment) {
+ if (!empty($this->getSetting('uidRelation')->{$trelloComment->memberCreator->username})) {
+ $actor = $this->getSetting('uidRelation')->{$trelloComment->memberCreator->username}->getUID();
+ } else {
+ $actor = $this->getSetting('owner')->getUID();
+ }
+ $message = $this->replaceUsernames($trelloComment->data->text);
+ $qb = $this->connection->getQueryBuilder();
+
+ $values = [
+ 'parent_id' => $qb->createNamedParameter(0),
+ 'topmost_parent_id' => $qb->createNamedParameter(0),
+ 'children_count' => $qb->createNamedParameter(0),
+ 'actor_type' => $qb->createNamedParameter('users'),
+ 'actor_id' => $qb->createNamedParameter($actor),
+ 'message' => $qb->createNamedParameter($message),
+ 'verb' => $qb->createNamedParameter('comment'),
+ 'creation_timestamp' => $qb->createNamedParameter(
+ \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloComment->date)
+ ->format('Y-m-d H:i:s')
+ ),
+ 'latest_child_timestamp' => $qb->createNamedParameter(null),
+ 'object_type' => $qb->createNamedParameter('deckCard'),
+ 'object_id' => $qb->createNamedParameter($card->getId()),
+ ];
+
+ $qb->insert('comments')
+ ->values($values)
+ ->execute();
+ }
+ }
+
+ private function replaceUsernames($text) {
+ foreach ($this->getSetting('uidRelation') as $trello => $nextcloud) {
+ $text = str_replace($trello, $nextcloud->getUID(), $text);
+ }
+ return $text;
+ }
+
+ private function associateCardToLabels(\OCP\AppFramework\Db\Entity $card, $trelloCard): void {
+ foreach ($trelloCard->labels as $label) {
+ $this->cardMapper->assignLabel(
+ $card->getId(),
+ $this->labels[$label->id]->getId()
+ );
+ }
+ }
+
+ private function importStacks(): void {
+ $this->stacks = [];
+ foreach ($this->data->lists as $order => $list) {
+ $stack = new Stack();
+ if ($list->closed) {
+ $stack->setDeletedAt(time());
+ }
+ $stack->setTitle($list->name);
+ $stack->setBoardId($this->board->getId());
+ $stack->setOrder($order + 1);
+ $stack = $this->stackMapper->insert($stack);
+ $this->stacks[$list->id] = $stack;
+ }
+ }
+
+ private function translateColor($color): string {
+ switch ($color) {
+ case 'red':
+ return 'ff0000';
+ case 'yellow':
+ return 'ffff00';
+ case 'orange':
+ return 'ff6600';
+ case 'green':
+ return '00ff00';
+ case 'purple':
+ return '9900ff';
+ case 'blue':
+ return '0000ff';
+ case 'sky':
+ return '00ccff';
+ case 'lime':
+ return '00ff99';
+ case 'pink':
+ return 'ff66cc';
+ case 'black':
+ return '000000';
+ default:
+ return 'ffffff';
+ }
+ }
+
+ private function importBoard(): void {
+ $this->board = $this->boardService->create(
+ $this->data->name,
+ $this->getSetting('owner')->getUID(),
+ $this->getSetting('color')
+ );
+ }
+
+ private function importLabels(): void {
+ $this->labels = [];
+ foreach ($this->data->labels as $label) {
+ if (empty($label->name)) {
+ $labelTitle = 'Unnamed ' . $label->color . ' label';
+ } else {
+ $labelTitle = $label->name;
+ }
+ $newLabel = $this->labelService->create(
+ $labelTitle,
+ $this->translateColor($label->color),
+ $this->board->getId()
+ );
+ $this->labels[$label->id] = $newLabel;
+ }
+ }
+
+ private function setUserId(): void {
+ if (!property_exists($this->labelService, 'permissionService')) {
+ return;
+ }
+ $propertyPermissionService = new \ReflectionProperty($this->labelService, 'permissionService');
+ $propertyPermissionService->setAccessible(true);
+ $permissionService = $propertyPermissionService->getValue($this->labelService);
+
+ if (!property_exists($permissionService, 'userId')) {
+ return;
+ }
+
+ $propertyUserId = new \ReflectionProperty($permissionService, 'userId');
+ $propertyUserId->setAccessible(true);
+ $propertyUserId->setValue($permissionService, $this->getSetting('owner')->getUID());
+ }
+}
diff --git a/lib/Command/UserExport.php b/lib/Command/UserExport.php
index 1d73e8dcf..0b595c773 100644
--- a/lib/Command/UserExport.php
+++ b/lib/Command/UserExport.php
@@ -63,6 +63,9 @@ class UserExport extends Command {
$this->groupManager = $groupManager;
}
+ /**
+ * @return void
+ */
protected function configure() {
$this
->setName('deck:export')
diff --git a/lib/Command/fixtures/setting-schema.json b/lib/Command/fixtures/setting-schema.json
index b21501c2b..f63cb3529 100644
--- a/lib/Command/fixtures/setting-schema.json
+++ b/lib/Command/fixtures/setting-schema.json
@@ -12,6 +12,9 @@
"type": "string",
"required": true,
"pattern": "^[0-9a-fA-F]{6}$"
+ },
+ "translations": {
+ "type": "object"
}
}
}
\ No newline at end of file
diff --git a/tests/unit/Command/BoardImportTest.php b/tests/unit/Command/BoardImportTest.php
new file mode 100644
index 000000000..a6d9c904c
--- /dev/null
+++ b/tests/unit/Command/BoardImportTest.php
@@ -0,0 +1,77 @@
+
+ *
+ * @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\Command;
+
+use OCA\Deck\Command\Helper\TrelloHelper;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class BoardImportTest extends \Test\TestCase {
+ /** @var TrelloHelper */
+ private $trelloHelper;
+ /** @var BoardImport */
+ private $boardImport;
+
+ public function setUp(): void {
+ parent::setUp();
+ $this->trelloHelper = $this->createMock(TrelloHelper::class);
+ $this->boardImport = new BoardImport(
+ $this->trelloHelper
+ );
+ $questionHelper = new QuestionHelper();
+ $this->boardImport->setHelperSet(
+ new HelperSet([
+ $questionHelper
+ ])
+ );
+ }
+
+ public function testExecuteWithSuccess() {
+ $input = $this->createMock(InputInterface::class);
+
+ $input->method('getOption')
+ ->withConsecutive(
+ [$this->equalTo('system')],
+ [$this->equalTo('setting')],
+ [$this->equalTo('data')]
+ )
+ ->will($this->returnValueMap([
+ ['system', 'trello'],
+ ['setting', __DIR__ . '/fixtures/setting-trello.json'],
+ ['data', __DIR__ . '/fixtures/data-trello.json']
+ ]));
+ $output = $this->createMock(OutputInterface::class);
+
+ $output
+ ->expects($this->once())
+ ->method('writeLn')
+ ->with('Done!');
+
+ $this->invokePrivate($this->boardImport, 'interact', [$input, $output]);
+ $actual = $this->invokePrivate($this->boardImport, 'execute', [$input, $output]);
+ $this->assertEquals(0, $actual);
+ }
+}
diff --git a/tests/unit/Command/Helper/TrelloHelperTest.php b/tests/unit/Command/Helper/TrelloHelperTest.php
new file mode 100644
index 000000000..1e4dd6b8d
--- /dev/null
+++ b/tests/unit/Command/Helper/TrelloHelperTest.php
@@ -0,0 +1,134 @@
+
+ *
+ * @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\Command;
+
+use OCA\Deck\Command\Helper\TrelloHelper;
+use OCA\Deck\Db\AclMapper;
+use OCA\Deck\Db\AssignmentMapper;
+use OCA\Deck\Db\CardMapper;
+use OCA\Deck\Db\StackMapper;
+use OCA\Deck\Service\BoardService;
+use OCA\Deck\Service\LabelService;
+use OCP\IDBConnection;
+use OCP\IUserManager;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class TrelloHelperTest extends \Test\TestCase {
+ /** @var BoardService */
+ private $boardService;
+ /** @var LabelService */
+ private $labelService;
+ /** @var StackMapper */
+ private $stackMapper;
+ /** @var CardMapper */
+ private $cardMapper;
+ /** @var IDBConnection */
+ private $connection;
+ /** @var IUserManager */
+ private $userManager;
+ /** @var TrelloHelper */
+ private $trelloHelper;
+ public function setUp(): void {
+ parent::setUp();
+ $this->boardService = $this->createMock(BoardService::class);
+ $this->labelService = $this->createMock(LabelService::class);
+ $this->stackMapper = $this->createMock(StackMapper::class);
+ $this->cardMapper = $this->createMock(CardMapper::class);
+ $this->assignmentMapper = $this->createMock(AssignmentMapper::class);
+ $this->aclMapper = $this->createMock(AclMapper::class);
+ $this->connection = $this->createMock(IDBConnection::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->trelloHelper = new TrelloHelper(
+ $this->boardService,
+ $this->labelService,
+ $this->stackMapper,
+ $this->cardMapper,
+ $this->assignmentMapper,
+ $this->aclMapper,
+ $this->connection,
+ $this->userManager
+ );
+ $questionHelper = new QuestionHelper();
+ $command = new BoardImport($this->trelloHelper);
+ $command->setHelperSet(
+ new HelperSet([
+ $questionHelper
+ ])
+ );
+ $this->trelloHelper->setCommand($command);
+ }
+
+ public function testImportWithSuccess() {
+ $input = $this->createMock(InputInterface::class);
+
+ $input->method('getOption')
+ ->withConsecutive(
+ [$this->equalTo('data')],
+ [$this->equalTo('setting')]
+ )
+ ->will($this->returnValueMap([
+ ['data', __DIR__ . '/../fixtures/data-trello.json'],
+ ['setting', __DIR__ . '/../fixtures/setting-trello.json']
+ ]));
+ $output = $this->createMock(OutputInterface::class);
+
+ $user = $this->createMock(\OCP\IUser::class);
+ $user
+ ->method('getUID')
+ ->willReturn('admin');
+ $this->userManager
+ ->method('get')
+ ->willReturn($user);
+ $this->userManager
+ ->method('get')
+ ->willReturn($user);
+ $board = $this->createMock(\OCA\Deck\Db\Board::class);
+ $this->boardService
+ ->expects($this->once())
+ ->method('create')
+ ->willReturn($board);
+ $label = $this->createMock(\OCA\Deck\Db\Label::class);
+ $this->labelService
+ ->expects($this->once())
+ ->method('create')
+ ->willReturn($label);
+ $stack = $this->createMock(\OCA\Deck\Db\Stack::class);
+ $this->stackMapper
+ ->expects($this->once())
+ ->method('insert')
+ ->willReturn($stack);
+ $card = $this->createMock(\OCA\Deck\Db\Card::class);
+ $this->cardMapper
+ ->expects($this->once())
+ ->method('insert')
+ ->willReturn($card);
+
+ $this->trelloHelper->validate($input, $output);
+ $actual = $this->trelloHelper->import($input, $output);
+ $this->assertNull($actual);
+ }
+}
diff --git a/tests/unit/Command/fixtures/data-trello.json b/tests/unit/Command/fixtures/data-trello.json
new file mode 100644
index 000000000..5d27a8c54
--- /dev/null
+++ b/tests/unit/Command/fixtures/data-trello.json
@@ -0,0 +1,582 @@
+{
+ "id": "fakeboardidhash",
+ "name": "Test Board Name",
+ "desc": "",
+ "descData": null,
+ "closed": false,
+ "dateClosed": null,
+ "idOrganization": null,
+ "shortLink": "qwerty",
+ "powerUps": [],
+ "dateLastActivity": "2021-07-10T17:01:58.633Z",
+ "idTags": [],
+ "datePluginDisable": null,
+ "creationMethod": null,
+ "idBoardSource": null,
+ "idMemberCreator": "fakeidmemberhash",
+ "idEnterprise": null,
+ "pinned": false,
+ "starred": false,
+ "url": "https://trello.com/b/qwerty/fakeboardurl",
+ "prefs": {
+ "permissionLevel": "private",
+ "hideVotes": false,
+ "voting": "disabled",
+ "comments": "members",
+ "invitations": "members",
+ "selfJoin": false,
+ "cardCovers": true,
+ "isTemplate": false,
+ "cardAging": "regular",
+ "calendarFeedEnabled": false,
+ "background": "blue",
+ "backgroundImage": null,
+ "backgroundImageScaled": null,
+ "backgroundTile": false,
+ "backgroundBrightness": "dark",
+ "backgroundColor": "#0079BF",
+ "backgroundBottomColor": "#0079BF",
+ "backgroundTopColor": "#0079BF",
+ "canBePublic": true,
+ "canBeEnterprise": true,
+ "canBeOrg": true,
+ "canBePrivate": true,
+ "canInvite": true
+ },
+ "shortUrl": "https://trello.com/b/qwerty",
+ "premiumFeatures": [],
+ "enterpriseOwned": false,
+ "ixUpdate": "67",
+ "limits": {
+ "attachments": {
+ "perBoard": {
+ "status": "ok",
+ "disableAt": 36000,
+ "warnAt": 32400
+ },
+ "perCard": {
+ "status": "ok",
+ "disableAt": 1000,
+ "warnAt": 900
+ }
+ },
+ "boards": {
+ "totalMembersPerBoard": {
+ "status": "ok",
+ "disableAt": 1600,
+ "warnAt": 1440
+ }
+ },
+ "cards": {
+ "openPerBoard": {
+ "status": "ok",
+ "disableAt": 5000,
+ "warnAt": 4500
+ },
+ "openPerList": {
+ "status": "ok",
+ "disableAt": 5000,
+ "warnAt": 4500
+ },
+ "totalPerBoard": {
+ "status": "ok",
+ "disableAt": 2000000,
+ "warnAt": 1800000
+ },
+ "totalPerList": {
+ "status": "ok",
+ "disableAt": 1000000,
+ "warnAt": 900000
+ }
+ },
+ "checklists": {
+ "perBoard": {
+ "status": "ok",
+ "disableAt": 2000000,
+ "warnAt": 1800000
+ },
+ "perCard": {
+ "status": "ok",
+ "disableAt": 500,
+ "warnAt": 450
+ }
+ },
+ "checkItems": {
+ "perChecklist": {
+ "status": "ok",
+ "disableAt": 200,
+ "warnAt": 180
+ }
+ },
+ "customFields": {
+ "perBoard": {
+ "status": "ok",
+ "disableAt": 50,
+ "warnAt": 45
+ }
+ },
+ "customFieldOptions": {
+ "perField": {
+ "status": "ok",
+ "disableAt": 50,
+ "warnAt": 45
+ }
+ },
+ "labels": {
+ "perBoard": {
+ "status": "ok",
+ "disableAt": 1000,
+ "warnAt": 900
+ }
+ },
+ "lists": {
+ "openPerBoard": {
+ "status": "ok",
+ "disableAt": 500,
+ "warnAt": 450
+ },
+ "totalPerBoard": {
+ "status": "ok",
+ "disableAt": 3000,
+ "warnAt": 2700
+ }
+ },
+ "stickers": {
+ "perCard": {
+ "status": "ok",
+ "disableAt": 70,
+ "warnAt": 63
+ }
+ },
+ "reactions": {
+ "perAction": {
+ "status": "ok",
+ "disableAt": 1000,
+ "warnAt": 900
+ },
+ "uniquePerAction": {
+ "status": "ok",
+ "disableAt": 17,
+ "warnAt": 16
+ }
+ }
+ },
+ "subscribed": false,
+ "templateGallery": null,
+ "dateLastView": "2021-07-10T17:01:58.665Z",
+ "labelNames": {
+ "green": "",
+ "yellow": "",
+ "orange": "",
+ "red": "",
+ "purple": "",
+ "blue": "",
+ "sky": "",
+ "lime": "",
+ "pink": "",
+ "black": ""
+ },
+ "actions": [
+ {
+ "id": "60e9d2869efe2e1141be2798",
+ "idMemberCreator": "fakeidmemberhash",
+ "data": {
+ "idMember": "fakeidmemberhash",
+ "deactivated": false,
+ "card": {
+ "id": "hashcard7",
+ "name": "Name Card 7",
+ "idShort": 7,
+ "shortLink": "fakeshortlinkcard7"
+ },
+ "board": {
+ "id": "fakeboardidhash",
+ "name": "Test Board Name",
+ "shortLink": "qwerty"
+ },
+ "member": {
+ "id": "fakeidmemberhash",
+ "name": "John Doe"
+ }
+ },
+ "type": "removeMemberFromCard",
+ "date": "2021-07-10T17:01:58.636Z",
+ "appCreator": null,
+ "limits": {},
+ "member": {
+ "id": "fakeidmemberhash",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idMemberReferrer": null,
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true
+ },
+ "memberCreator": {
+ "id": "fakeidmemberhash",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idMemberReferrer": null,
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true
+ }
+ },
+ {
+ "id": "60e9d1832ff82d10c0cea6ba",
+ "idMemberCreator": "fakeidmemberhash",
+ "data": {
+ "idMember": "fakeidmemberhash",
+ "card": {
+ "id": "hashcard7",
+ "name": "Name Card 7",
+ "idShort": 7,
+ "shortLink": "fakeshortlinkcard7"
+ },
+ "board": {
+ "id": "fakeboardidhash",
+ "name": "Test Board Name",
+ "shortLink": "qwerty"
+ },
+ "member": {
+ "id": "fakeidmemberhash",
+ "name": "John Doe"
+ }
+ },
+ "type": "addMemberToCard",
+ "date": "2021-07-10T16:57:39.999Z",
+ "appCreator": null,
+ "limits": {},
+ "member": {
+ "id": "fakeidmemberhash",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idMemberReferrer": null,
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true
+ },
+ "memberCreator": {
+ "id": "fakeidmemberhash",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idMemberReferrer": null,
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true
+ }
+ },
+ {
+ "id": "59bbfc4bf36aa0270d6bfd43",
+ "idMemberCreator": "fakeidmemberhash",
+ "data": {
+ "board": {
+ "shortLink": "qwerty",
+ "name": "Test Board Name",
+ "id": "fakeboardidhash"
+ },
+ "list": {
+ "name": "TODO",
+ "id": "hashlisttodo"
+ },
+ "card": {
+ "shortLink": "fakeshortlinkcard7",
+ "idShort": 7,
+ "name": "Name Card 7",
+ "id": "hashcard7"
+ }
+ },
+ "type": "createCard",
+ "date": "2017-09-15T16:14:03.187Z",
+ "appCreator": null,
+ "limits": {},
+ "memberCreator": {
+ "id": "fakeidmemberhash",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idMemberReferrer": null,
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true
+ }
+ },
+ {
+ "id": "59bbfb8e4a6f8ca35be9b82a",
+ "idMemberCreator": "fakeidmemberhash",
+ "data": {
+ "board": {
+ "shortLink": "qwerty",
+ "name": "Test Board Name",
+ "id": "fakeboardidhash"
+ },
+ "list": {
+ "name": "TODO",
+ "id": "hashlisttodo"
+ }
+ },
+ "type": "createList",
+ "date": "2017-09-15T16:10:54.714Z",
+ "appCreator": null,
+ "limits": {},
+ "memberCreator": {
+ "id": "fakeidmemberhash",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idMemberReferrer": null,
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true
+ }
+ },
+ {
+ "id": "59bbfb88973b76e586edec5e",
+ "idMemberCreator": "fakeidmemberhash",
+ "data": {
+ "board": {
+ "shortLink": "qwerty",
+ "name": "Test Board Name",
+ "id": "fakeboardidhash"
+ }
+ },
+ "type": "createBoard",
+ "date": "2017-09-15T16:10:48.069Z",
+ "appCreator": null,
+ "limits": {},
+ "memberCreator": {
+ "id": "fakeidmemberhash",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idMemberReferrer": null,
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true
+ }
+ }
+ ],
+ "cards": [
+ {
+ "id": "hashcard7",
+ "address": null,
+ "checkItemStates": null,
+ "closed": false,
+ "coordinates": null,
+ "creationMethod": null,
+ "dateLastActivity": "2021-07-10T17:01:58.633Z",
+ "desc": "",
+ "descData": null,
+ "dueReminder": null,
+ "idBoard": "fakeboardidhash",
+ "idLabels": [],
+ "idList": "hashlisttodo",
+ "idMembersVoted": [],
+ "idShort": 7,
+ "idAttachmentCover": null,
+ "locationName": null,
+ "manualCoverAttachment": false,
+ "name": "Name Card 7",
+ "pos": 65535,
+ "shortLink": "fakeshortlinkcard7",
+ "isTemplate": false,
+ "cardRole": null,
+ "badges": {
+ "attachmentsByType": {
+ "trello": {
+ "board": 0,
+ "card": 0
+ }
+ },
+ "location": false,
+ "votes": 0,
+ "viewingMemberVoted": false,
+ "subscribed": false,
+ "fogbugz": "",
+ "checkItems": 0,
+ "checkItemsChecked": 0,
+ "checkItemsEarliestDue": null,
+ "comments": 0,
+ "attachments": 0,
+ "description": false,
+ "due": null,
+ "dueComplete": false,
+ "start": null
+ },
+ "dueComplete": false,
+ "due": null,
+ "email": "johndoe+card7@boards.trello.com",
+ "idChecklists": [],
+ "idMembers": [],
+ "labels": [],
+ "limits": {
+ "attachments": {
+ "perCard": {
+ "status": "ok",
+ "disableAt": 1000,
+ "warnAt": 900
+ }
+ },
+ "checklists": {
+ "perCard": {
+ "status": "ok",
+ "disableAt": 500,
+ "warnAt": 450
+ }
+ },
+ "stickers": {
+ "perCard": {
+ "status": "ok",
+ "disableAt": 70,
+ "warnAt": 63
+ }
+ }
+ },
+ "shortUrl": "https://trello.com/c/fakeshortlinkcard7",
+ "start": null,
+ "subscribed": false,
+ "url": "https://trello.com/c/fakeshortlinkcard7/7-name-card-7",
+ "cover": {
+ "idAttachment": null,
+ "color": null,
+ "idUploadedBackground": null,
+ "size": "normal",
+ "brightness": "dark",
+ "idPlugin": null
+ },
+ "attachments": [],
+ "pluginData": [],
+ "customFieldItems": []
+ }
+ ],
+ "labels": [
+ {
+ "id": "59bbfb881314a339999eb855",
+ "idBoard": "fakeboardidhash",
+ "name": "",
+ "color": "yellow"
+ }
+ ],
+ "lists": [
+ {
+ "id": "hashlisttodo",
+ "name": "TODO",
+ "closed": false,
+ "pos": 65535,
+ "softLimit": null,
+ "creationMethod": null,
+ "idBoard": "fakeboardidhash",
+ "limits": {
+ "cards": {
+ "openPerList": {
+ "status": "ok",
+ "disableAt": 5000,
+ "warnAt": 4500
+ },
+ "totalPerList": {
+ "status": "ok",
+ "disableAt": 1000000,
+ "warnAt": 900000
+ }
+ }
+ },
+ "subscribed": false
+ }
+ ],
+ "members": [
+ {
+ "id": "fakeidmemberhash",
+ "bio": "",
+ "bioData": {
+ "emoji": {}
+ },
+ "confirmed": true,
+ "memberType": "normal",
+ "username": "johndoe",
+ "activityBlocked": false,
+ "avatarHash": "fakeavatarhash",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "fullName": "John Doe",
+ "idEnterprise": null,
+ "idEnterprisesDeactivated": [],
+ "idMemberReferrer": null,
+ "idPremOrgsAdmin": [],
+ "initials": "JD",
+ "nonPublic": {
+ "fullName": "John Doe",
+ "initials": "JD",
+ "avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
+ "avatarHash": "fakeavatarhash"
+ },
+ "nonPublicAvailable": true,
+ "products": [],
+ "url": "https://trello.com/johndoe",
+ "status": "disconnected"
+ }
+ ],
+ "checklists": [],
+ "customFields": [],
+ "memberships": [
+ {
+ "id": "59bbfb88973b76e586edec5d",
+ "idMember": "fakeidmemberhash",
+ "memberType": "admin",
+ "unconfirmed": false,
+ "deactivated": false
+ }
+ ],
+ "pluginData": []
+}
\ No newline at end of file
diff --git a/tests/unit/Command/fixtures/setting-trello.json b/tests/unit/Command/fixtures/setting-trello.json
new file mode 100644
index 000000000..544118212
--- /dev/null
+++ b/tests/unit/Command/fixtures/setting-trello.json
@@ -0,0 +1,7 @@
+{
+ "owner": "admin",
+ "color": "0800fd",
+ "uidRelation": {
+ "johndoe": "admin"
+ }
+}
\ No newline at end of file