Import participants
Big refactor to create route Import participants Signed-off-by: Vitor Mattos <vitor@php.rio>
This commit is contained in:
committed by
Julius Härtl
parent
fd92fc3c4d
commit
c5d10dafb8
53
lib/Service/ABoardImportService.php
Normal file
53
lib/Service/ABoardImportService.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\Stack;
|
||||
|
||||
abstract class ABoardImportService {
|
||||
/** @var BoardImportService */
|
||||
private $boardImportService;
|
||||
|
||||
abstract public function getBoard(): ?Board;
|
||||
|
||||
/**
|
||||
* @return Acl[]
|
||||
*/
|
||||
abstract public function getAclList(): array;
|
||||
|
||||
/**
|
||||
* @return Stack[]
|
||||
*/
|
||||
abstract function getStacks(): array;
|
||||
|
||||
/**
|
||||
* @return Card[]
|
||||
*/
|
||||
abstract function getCards(): array;
|
||||
|
||||
abstract function updateStack(string $id, Stack $stack): self;
|
||||
|
||||
abstract function updateCard(string $id, Card $card): self;
|
||||
|
||||
abstract function assignCardsToLabels(): self;
|
||||
|
||||
abstract function importParticipants(): self;
|
||||
|
||||
abstract function importComments(): self;
|
||||
|
||||
abstract public function importLabels(): self;
|
||||
|
||||
abstract public function validateUsers(): self;
|
||||
|
||||
public function setImportService($service): self {
|
||||
$this->boardImportService = $service;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImportService(): BoardImportService {
|
||||
return $this->boardImportService;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
abstract class AImportService {
|
||||
/**
|
||||
* Data object created from config JSON
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
public $config;
|
||||
|
||||
public function setConfigInstance(\stdClass $config) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a config
|
||||
*
|
||||
* @param string $configName
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function setConfig(string $configName, $value): void {
|
||||
if (!$this->config) {
|
||||
$this->setConfigInstance(new \stdClass);
|
||||
}
|
||||
$this->config->$configName = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a config
|
||||
*
|
||||
* @param string $configName config name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $configName = null) {
|
||||
if (!is_object($this->config)) {
|
||||
return;
|
||||
}
|
||||
if (!$configName) {
|
||||
return $this->config;
|
||||
}
|
||||
if (!property_exists($this->config, $configName)) {
|
||||
return;
|
||||
}
|
||||
return $this->config->$configName;
|
||||
}
|
||||
}
|
||||
180
lib/Service/BoardImportCommandService.php
Normal file
180
lib/Service/BoardImportCommandService.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use JsonSchema\Constraints\Constraint;
|
||||
use JsonSchema\Validator;
|
||||
use OCA\Deck\Command\BoardImport;
|
||||
use OCA\Deck\Service\BoardImportService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
class BoardImportCommandService extends BoardImportService {
|
||||
/** @var Command */
|
||||
private $command;
|
||||
/** @var InputInterface */
|
||||
private $input;
|
||||
/** @var OutputInterface */
|
||||
private $output;
|
||||
/**
|
||||
* Data object created from config JSON
|
||||
*
|
||||
* @var \StdClass
|
||||
*/
|
||||
public $config;
|
||||
|
||||
/**
|
||||
* Define Command instance
|
||||
*
|
||||
* @param Command $command
|
||||
* @return void
|
||||
*/
|
||||
public function setCommand(Command $command): void {
|
||||
$this->command = $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BoardImport
|
||||
*/
|
||||
public function getCommand() {
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function setInput($input): self {
|
||||
$this->input = $input;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInput(): InputInterface {
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
public function setOutput($output): self {
|
||||
$this->output = $output;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOutput(): OutputInterface {
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
public function validate(): self {
|
||||
$this->validateSystem();
|
||||
$this->validateConfig();
|
||||
$this->validateData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function validateConfig(): void {
|
||||
$configFile = $this->getInput()->getOption('config');
|
||||
if (!is_file($configFile)) {
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$question = new Question(
|
||||
'Please inform a valid config json file: ',
|
||||
'config.json'
|
||||
);
|
||||
$question->setValidator(function ($answer) {
|
||||
if (!is_file($answer)) {
|
||||
throw new \RuntimeException(
|
||||
'config file not found'
|
||||
);
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
$configFile = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$this->getInput()->setOption('config', $configFile);
|
||||
}
|
||||
|
||||
$config = json_decode(file_get_contents($configFile));
|
||||
$system = $this->getSystem();
|
||||
$schemaPath = __DIR__ . '/fixtures/config-' . $system . '-schema.json';
|
||||
$validator = new Validator();
|
||||
$validator->validate(
|
||||
$config,
|
||||
(object)['$ref' => 'file://' . realpath($schemaPath)],
|
||||
Constraint::CHECK_MODE_APPLY_DEFAULTS
|
||||
);
|
||||
if (!$validator->isValid()) {
|
||||
$this->getOutput()->writeln('<error>Invalid config file</error>');
|
||||
$this->getOutput()->writeln(array_map(function ($v) {
|
||||
return $v['message'];
|
||||
}, $validator->getErrors()));
|
||||
$this->getOutput()->writeln('Valid schema:');
|
||||
$this->getOutput()->writeln(print_r(file_get_contents($schemaPath), true));
|
||||
$this->getInput()->setOption('config', null);
|
||||
$this->validateConfig($this->getInput(), $this->getOutput());
|
||||
}
|
||||
$this->setConfigInstance($config);
|
||||
$this->validateOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function validateSystem(): self {
|
||||
$system = $this->getInput()->getOption('system');
|
||||
if (in_array($system, $this->getAllowedImportSystems())) {
|
||||
return $this->setSystem($system);
|
||||
}
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$question = new ChoiceQuestion(
|
||||
'Please inform a source system',
|
||||
$this->allowedSystems,
|
||||
0
|
||||
);
|
||||
$question->setErrorMessage('System %s is invalid.');
|
||||
$system = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$this->getInput()->setOption('system', $system);
|
||||
return $this->setSystem($system);
|
||||
}
|
||||
|
||||
private function validateData(): self {
|
||||
$filename = $this->getInput()->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($this->getInput(), $this->getOutput(), $question);
|
||||
$this->getInput()->setOption('data', $data);
|
||||
}
|
||||
$this->setData(json_decode(file_get_contents($filename)));
|
||||
if (!$this->getData()) {
|
||||
$this->getOutput()->writeln('<error>Is not a json file: ' . $filename . '</error>');
|
||||
$this->validateData($this->getInput(), $this->getOutput());
|
||||
}
|
||||
$this->validateUsers();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function import(): void {
|
||||
$this->getOutput()->writeln('Importing board...');
|
||||
$this->importBoard();
|
||||
$this->getOutput()->writeln('Assign users to board...');
|
||||
$this->importAcl();
|
||||
$this->getOutput()->writeln('Importing labels...');
|
||||
$this->importLabels();
|
||||
$this->getOutput()->writeln('Importing stacks...');
|
||||
$this->importStacks();
|
||||
$this->getOutput()->writeln('Importing cards...');
|
||||
$this->importCards();
|
||||
$this->getOutput()->writeln('Assign cards to labels...');
|
||||
$this->assignCardsToLabels();
|
||||
$this->getOutput()->writeln('Iporting comments...');
|
||||
$this->importComments();
|
||||
$this->getOutput()->writeln('Iporting participants...');
|
||||
$this->importParticipants();
|
||||
}
|
||||
}
|
||||
363
lib/Service/BoardImportService.php
Normal file
363
lib/Service/BoardImportService.php
Normal file
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use JsonSchema\Constraints\Constraint;
|
||||
use JsonSchema\Validator;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\Comments\MessageTooLongException;
|
||||
use OCP\Comments\NotFoundException as CommentNotFoundException;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class BoardImportService {
|
||||
/** @var IDBConnection */
|
||||
protected $dbConn;
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
/** @var AclMapper */
|
||||
private $aclMapper;
|
||||
/** @var LabelMapper */
|
||||
private $labelMapper;
|
||||
/** @var StackMapper */
|
||||
private $stackMapper;
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
/** @var ICommentsManager */
|
||||
private $commentsManager;
|
||||
/** @var string */
|
||||
private $system;
|
||||
/** @var ABoardImportService */
|
||||
private $systemInstance;
|
||||
/** @var string[] */
|
||||
private $allowedSystems;
|
||||
/**
|
||||
* Data object created from config JSON
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
public $config;
|
||||
/**
|
||||
* Data object created from JSON of origin system
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
private $data;
|
||||
/** @var Board */
|
||||
private $board;
|
||||
|
||||
public function __construct(
|
||||
IDBConnection $dbConn,
|
||||
IUserManager $userManager,
|
||||
BoardMapper $boardMapper,
|
||||
AclMapper $aclMapper,
|
||||
LabelMapper $labelMapper,
|
||||
StackMapper $stackMapper,
|
||||
CardMapper $cardMapper,
|
||||
ICommentsManager $commentsManager
|
||||
) {
|
||||
$this->dbConn = $dbConn;
|
||||
$this->userManager = $userManager;
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->aclMapper = $aclMapper;
|
||||
$this->labelMapper = $labelMapper;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->commentsManager = $commentsManager;
|
||||
}
|
||||
|
||||
public function import(): void {
|
||||
$this->validate();
|
||||
$schemaPath = __DIR__ . '/fixtures/config-' . $system . '-schema.json';
|
||||
$validator = new Validator();
|
||||
$validator->validate(
|
||||
$config,
|
||||
(object)['$ref' => 'file://' . realpath($schemaPath)],
|
||||
Constraint::CHECK_MODE_APPLY_DEFAULTS
|
||||
);
|
||||
if (!$validator->isValid()) {
|
||||
throw new BadRequestException('invalid config');
|
||||
}
|
||||
|
||||
if (empty($data)) {
|
||||
throw new BadRequestException('data must be provided');
|
||||
}
|
||||
$this->getImportService()->setData($data);
|
||||
$this->getImportService()->import();
|
||||
// return $newBoard;
|
||||
}
|
||||
|
||||
public function validate(): self {
|
||||
if (is_string($system) === false) {
|
||||
throw new BadRequestException('system must be provided');
|
||||
}
|
||||
|
||||
if (!in_array($system, $this->getAllowedImportSystems())) {
|
||||
throw new BadRequestException('not allowed system');
|
||||
}
|
||||
|
||||
if (empty($config)) {
|
||||
throw new BadRequestException('config must be provided');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSystem(string $system): self {
|
||||
$this->system = $system;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSystem() {
|
||||
return $this->system;
|
||||
}
|
||||
|
||||
public function getAllowedImportSystems(): array {
|
||||
if (!$this->allowedSystems) {
|
||||
$allowedSystems = glob(__DIR__ . '/BoardImport*Service.php');
|
||||
$allowedSystems = array_filter($allowedSystems, function($name) {
|
||||
$name = basename($name);
|
||||
switch($name) {
|
||||
case 'ABoardImportService.php':
|
||||
case 'BoardImportService.php':
|
||||
case 'BoardImportCommandService.php':
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
$this->allowedSystems = array_map(function ($name) {
|
||||
preg_match('/\/BoardImport(?<system>\w+)Service\.php$/', $name, $matches);
|
||||
return lcfirst($matches['system']);
|
||||
}, $allowedSystems);
|
||||
}
|
||||
return $this->allowedSystems;
|
||||
}
|
||||
|
||||
public function getImportSystem(): ABoardImportService {
|
||||
$systemClass = 'OCA\\Deck\\Service\\BoardImport' . ucfirst($this->getSystem()) . 'Service';
|
||||
if (!is_object($this->systemInstance)) {
|
||||
$this->systemInstance = \OC::$server->get($systemClass);
|
||||
$this->systemInstance->setImportService($this);
|
||||
}
|
||||
|
||||
return $this->systemInstance;
|
||||
}
|
||||
|
||||
public function importBoard() {
|
||||
$board = $this->getImportSystem()->getBoard();
|
||||
if ($board) {
|
||||
$this->boardMapper->insert($board);
|
||||
$this->board = $board;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBoard(): Board {
|
||||
return $this->board;
|
||||
}
|
||||
|
||||
public function importAcl(): self {
|
||||
$aclList = $this->getImportSystem()->getAclList();
|
||||
foreach ($aclList as $acl) {
|
||||
$this->aclMapper->insert($acl);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function importLabels(): self {
|
||||
$this->getImportSystem()->importLabels();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createLabel($title, $color, $boardId): Label {
|
||||
$label = new Label();
|
||||
$label->setTitle($title);
|
||||
$label->setColor($color);
|
||||
$label->setBoardId($boardId);
|
||||
return $this->labelMapper->insert($label);
|
||||
}
|
||||
|
||||
public function importStacks(): self {
|
||||
$stack = $this->getImportSystem()->getStacks();
|
||||
foreach ($stack as $code => $stack) {
|
||||
$this->stackMapper->insert($stack);
|
||||
$this->getImportSystem()->updateStack($code, $stack);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function importCards(): self {
|
||||
$cards = $this->getImportSystem()->getCards();
|
||||
foreach ($cards as $code => $card) {
|
||||
$this->cardMapper->insert($card);
|
||||
$this->getImportSystem()->updateCard($code, $card);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assignCardToLabel($cardId, $labelId): self {
|
||||
$this->cardMapper->assignLabel(
|
||||
$cardId,
|
||||
$labelId
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assignCardsToLabels(): self {
|
||||
$this->getImportSystem()->assignCardsToLabels();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function importComments(): self {
|
||||
$this->getImportSystem()->importComments();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insertComment($cardId, IComment $comment): IComment {
|
||||
$comment->setObject('deckCard', (string) $cardId);
|
||||
$comment->setVerb('comment');
|
||||
// Check if parent is a comment on the same card
|
||||
if ($comment->getParentId() !== '0') {
|
||||
try {
|
||||
$comment = $this->commentsManager->get($comment->getParentId());
|
||||
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) {
|
||||
throw new CommentNotFoundException();
|
||||
}
|
||||
} catch (CommentNotFoundException $e) {
|
||||
throw new BadRequestException('Invalid parent id: The parent comment was not found or belongs to a different card');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$qb = $this->dbConn->getQueryBuilder();
|
||||
|
||||
$values = [
|
||||
'parent_id' => $qb->createNamedParameter($comment->getParentId()),
|
||||
'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
|
||||
'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
|
||||
'actor_type' => $qb->createNamedParameter($comment->getActorType()),
|
||||
'actor_id' => $qb->createNamedParameter($comment->getActorId()),
|
||||
'message' => $qb->createNamedParameter($comment->getMessage()),
|
||||
'verb' => $qb->createNamedParameter($comment->getVerb()),
|
||||
'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
|
||||
'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
|
||||
'object_type' => $qb->createNamedParameter($comment->getObjectType()),
|
||||
'object_id' => $qb->createNamedParameter($comment->getObjectId()),
|
||||
'reference_id' => $qb->createNamedParameter($comment->getReferenceId())
|
||||
];
|
||||
|
||||
$affectedRows = $qb->insert('comments')
|
||||
->values($values)
|
||||
->execute();
|
||||
|
||||
if ($affectedRows > 0) {
|
||||
$comment->setId((string)$qb->getLastInsertId());
|
||||
}
|
||||
return $comment;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new BadRequestException('Invalid input values');
|
||||
} catch (CommentNotFoundException $e) {
|
||||
throw new NotFoundException('Could not create comment.');
|
||||
}
|
||||
}
|
||||
|
||||
public function importParticipants() {
|
||||
$this->getImportSystem()->importParticipants();
|
||||
}
|
||||
|
||||
public function setData(\stdClass $data): self {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a config
|
||||
*
|
||||
* @param string $configName
|
||||
* @param mixed $value
|
||||
* @return self
|
||||
*/
|
||||
public function setConfig(string $configName, $value): self {
|
||||
if (!$this->config) {
|
||||
$this->setConfigInstance(new \stdClass);
|
||||
}
|
||||
$this->config->$configName = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a config
|
||||
*
|
||||
* @param string $configName config name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $configName = null) {
|
||||
if (!is_object($this->config)) {
|
||||
return;
|
||||
}
|
||||
if (!$configName) {
|
||||
return $this->config;
|
||||
}
|
||||
if (!property_exists($this->config, $configName)) {
|
||||
return;
|
||||
}
|
||||
return $this->config->$configName;
|
||||
}
|
||||
|
||||
public function setConfigInstance(\stdClass $config): self {
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validateOwner(): self {
|
||||
$owner = $this->userManager->get($this->getConfig('owner'));
|
||||
if (!$owner) {
|
||||
throw new \LogicException('Owner "' . $this->getConfigboardImportService->getConfig('owner')->getUID() . '" not found on Nextcloud. Check setting json.');
|
||||
}
|
||||
$this->setConfig('owner', $owner);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validateUsers(): self {
|
||||
$this->getImportSystem()->validateUsers();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
use OC\Comments\Comment;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
@@ -38,9 +39,7 @@ use OCP\IL10N;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class TrelloImportService extends AImportService {
|
||||
/** @var BoardService */
|
||||
private $boardService;
|
||||
class BoardImportTrelloService extends ABoardImportService {
|
||||
/** @var LabelService */
|
||||
private $labelService;
|
||||
/** @var StackMapper */
|
||||
@@ -57,8 +56,6 @@ class TrelloImportService extends AImportService {
|
||||
private $userManager;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
/** @var Board */
|
||||
private $board;
|
||||
/**
|
||||
* Array of stacks
|
||||
*
|
||||
@@ -75,12 +72,6 @@ class TrelloImportService extends AImportService {
|
||||
private $cards = [];
|
||||
/** @var IUser[] */
|
||||
private $members = [];
|
||||
/**
|
||||
* Data object created from JSON of origin system
|
||||
*
|
||||
* @var \StdClass
|
||||
*/
|
||||
private $data;
|
||||
|
||||
public function __construct(
|
||||
BoardService $boardService,
|
||||
@@ -104,31 +95,21 @@ class TrelloImportService extends AImportService {
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function setData(\stdClass $data) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function validateOwner(): void {
|
||||
$owner = $this->userManager->get($this->getConfig('owner'));
|
||||
if (!$owner) {
|
||||
throw new \LogicException('Owner "' . $this->getConfig('owner')->getUID() . '" not found on Nextcloud. Check setting json.');
|
||||
}
|
||||
$this->setConfig('owner', $owner);
|
||||
public function validate(): ABoardImportService {
|
||||
$this->boardImportTrelloService->validateOwner();
|
||||
$this->boardImportTrelloService->validateUsers();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ABoardImportService
|
||||
*/
|
||||
public function validateUsers() {
|
||||
if (empty($this->getConfig('uidRelation'))) {
|
||||
return;
|
||||
public function validateUsers(): self {
|
||||
if (empty($this->getImportService()->getConfig('uidRelation'))) {
|
||||
return $this;
|
||||
}
|
||||
foreach ($this->getConfig('uidRelation') as $trelloUid => $nextcloudUid) {
|
||||
$user = array_filter($this->data->members, function ($u) use ($trelloUid) {
|
||||
foreach ($this->getImportService()->getConfig('uidRelation') as $trelloUid => $nextcloudUid) {
|
||||
$user = array_filter($this->getImportService()->getData()->members, function ($u) use ($trelloUid) {
|
||||
return $u->username === $trelloUid;
|
||||
});
|
||||
if (!$user) {
|
||||
@@ -137,29 +118,35 @@ class TrelloImportService extends AImportService {
|
||||
if (!is_string($nextcloudUid)) {
|
||||
throw new \LogicException('User on setting uidRelation must be a string');
|
||||
}
|
||||
$this->getConfig('uidRelation')->$trelloUid = $this->userManager->get($nextcloudUid);
|
||||
if (!$this->getConfig('uidRelation')->$trelloUid) {
|
||||
$this->getImportService()->getConfig('uidRelation')->$trelloUid = $this->userManager->get($nextcloudUid);
|
||||
if (!$this->getImportService()->getConfig('uidRelation')->$trelloUid) {
|
||||
throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
|
||||
}
|
||||
$user = current($user);
|
||||
$this->members[$user->id] = $this->getConfig('uidRelation')->$trelloUid;
|
||||
$this->members[$user->id] = $this->getImportService()->getConfig('uidRelation')->$trelloUid;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assignUsersToBoard(): void {
|
||||
/**
|
||||
* @return Acl[]
|
||||
*/
|
||||
public function getAclList(): array {
|
||||
$return = [];
|
||||
foreach ($this->members as $member) {
|
||||
if ($member->getUID() === $this->getConfig('owner')->getUID()) {
|
||||
if ($member->getUID() === $this->getImportService()->getConfig('owner')->getUID()) {
|
||||
continue;
|
||||
}
|
||||
$acl = new Acl();
|
||||
$acl->setBoardId($this->board->getId());
|
||||
$acl->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$acl->setType(Acl::PERMISSION_TYPE_USER);
|
||||
$acl->setParticipant($member->getUID());
|
||||
$acl->setPermissionEdit(false);
|
||||
$acl->setPermissionShare(false);
|
||||
$acl->setPermissionManage(false);
|
||||
$this->aclMapper->insert($acl);
|
||||
$return[] = $acl;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function checklistItem($item): string {
|
||||
@@ -181,14 +168,17 @@ class TrelloImportService extends AImportService {
|
||||
return $checklist_string;
|
||||
}
|
||||
|
||||
public function importCards(): void {
|
||||
/**
|
||||
* @return Card[]
|
||||
*/
|
||||
public function getCards(): array {
|
||||
$checklists = [];
|
||||
foreach ($this->data->checklists as $checklist) {
|
||||
foreach ($this->getImportService()->getData()->checklists as $checklist) {
|
||||
$checklists[$checklist->idCard][$checklist->id] = $this->formulateChecklistText($checklist);
|
||||
}
|
||||
$this->data->checklists = $checklists;
|
||||
$this->getImportService()->getData()->checklists = $checklists;
|
||||
|
||||
foreach ($this->data->cards as $trelloCard) {
|
||||
foreach ($this->getImportService()->getData()->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'));
|
||||
@@ -196,7 +186,7 @@ class TrelloImportService extends AImportService {
|
||||
$card->setDeletedAt($lastModified->format('U'));
|
||||
}
|
||||
if ((count($trelloCard->idChecklists) !== 0)) {
|
||||
foreach ($this->data->checklists[$trelloCard->id] as $checklist) {
|
||||
foreach ($this->getImportService()->getData()->checklists[$trelloCard->id] as $checklist) {
|
||||
$trelloCard->desc .= "\n" . $checklist;
|
||||
}
|
||||
}
|
||||
@@ -206,24 +196,25 @@ class TrelloImportService extends AImportService {
|
||||
$card->setStackId($this->stacks[$trelloCard->idList]->getId());
|
||||
$card->setType('plain');
|
||||
$card->setOrder($trelloCard->idShort);
|
||||
$card->setOwner($this->getConfig('owner')->getUID());
|
||||
$card->setOwner($this->getImportService()->getConfig('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);
|
||||
}
|
||||
return $this->cards;
|
||||
}
|
||||
|
||||
public function updateCard($cardTrelloId, Card $card): self {
|
||||
$this->cards[$cardTrelloId] = $card;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return ABoardImportService
|
||||
*/
|
||||
private function appendAttachmentsToDescription($trelloCard) {
|
||||
if (empty($trelloCard->attachments)) {
|
||||
@@ -236,86 +227,92 @@ class TrelloImportService extends AImportService {
|
||||
$name = $attachment->name === $attachment->url ? null : $attachment->name;
|
||||
$trelloCard->desc .= "| {$attachment->url} | {$name} | {$attachment->date} |\n";
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function assignToMember(Card $card, $trelloCard): void {
|
||||
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);
|
||||
public function importParticipants(): ABoardImportService {
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
foreach ($trelloCard->idMembers as $idMember) {
|
||||
$assignment = new Assignment();
|
||||
$assignment->setCardId($this->cards[$trelloCard->id]->getId());
|
||||
$assignment->setParticipant($this->members[$idMember]->getUID());
|
||||
$assignment->setType(Assignment::TYPE_USER);
|
||||
$assignment = $this->assignmentMapper->insert($assignment);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
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;
|
||||
public function importComments(): ABoardImportService {
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
$comments = array_filter(
|
||||
$this->getImportService()->getData()->actions,
|
||||
function ($a) use ($trelloCard) {
|
||||
return $a->type === 'commentCard' && $a->data->card->id === $trelloCard->id;
|
||||
}
|
||||
);
|
||||
foreach ($comments as $trelloComment) {
|
||||
if (!empty($this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username})) {
|
||||
$actor = $this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username}->getUID();
|
||||
} else {
|
||||
$actor = $this->getImportService()->getConfig('owner')->getUID();
|
||||
}
|
||||
$comment = new Comment();
|
||||
$comment
|
||||
->setActor('users', $actor)
|
||||
->setMessage($this->replaceUsernames($trelloComment->data->text), 0)
|
||||
->setCreationDateTime(
|
||||
\DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloComment->date)
|
||||
);
|
||||
$this->getImportService()->insertComment(
|
||||
$this->cards[$trelloCard->id]->getId(),
|
||||
$comment
|
||||
);
|
||||
}
|
||||
);
|
||||
foreach ($comments as $trelloComment) {
|
||||
if (!empty($this->getConfig('uidRelation')->{$trelloComment->memberCreator->username})) {
|
||||
$actor = $this->getConfig('uidRelation')->{$trelloComment->memberCreator->username}->getUID();
|
||||
} else {
|
||||
$actor = $this->getConfig('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();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function replaceUsernames($text) {
|
||||
foreach ($this->getConfig('uidRelation') as $trello => $nextcloud) {
|
||||
foreach ($this->getImportService()->getConfig('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()
|
||||
);
|
||||
public function assignCardsToLabels(): self {
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
foreach ($trelloCard->labels as $label) {
|
||||
$this->getImportService()->assignCardToLabel(
|
||||
$this->cards[$trelloCard->id]->getId(),
|
||||
$this->labels[$label->id]->getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function importStacks(): void {
|
||||
$this->stacks = [];
|
||||
foreach ($this->data->lists as $order => $list) {
|
||||
/**
|
||||
* @return Stack[]
|
||||
*/
|
||||
public function getStacks(): array {
|
||||
$return = [];
|
||||
foreach ($this->getImportService()->getData()->lists as $order => $list) {
|
||||
$stack = new Stack();
|
||||
if ($list->closed) {
|
||||
$stack->setDeletedAt(time());
|
||||
}
|
||||
$stack->setTitle($list->name);
|
||||
$stack->setBoardId($this->board->getId());
|
||||
$stack->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$stack->setOrder($order + 1);
|
||||
$stack = $this->stackMapper->insert($stack);
|
||||
$this->stacks[$list->id] = $stack;
|
||||
$return[$list->id] = $stack;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function updateStack($id, $stack): self {
|
||||
$this->stacks[$id] = $stack;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function translateColor($color): string {
|
||||
@@ -345,45 +342,28 @@ class TrelloImportService extends AImportService {
|
||||
}
|
||||
}
|
||||
|
||||
public function importBoard(): void {
|
||||
$this->board = $this->boardService->create(
|
||||
$this->data->name,
|
||||
$this->getConfig('owner')->getUID(),
|
||||
$this->getConfig('color')
|
||||
);
|
||||
public function getBoard(): Board {
|
||||
$board = new Board();
|
||||
$board->setTitle($this->getImportService()->getData()->name);
|
||||
$board->setOwner($this->getImportService()->getConfig('owner')->getUID());
|
||||
$board->setColor($this->getImportService()->getConfig('color'));
|
||||
return $board;
|
||||
}
|
||||
|
||||
public function importLabels(): void {
|
||||
$this->labels = [];
|
||||
foreach ($this->data->labels as $label) {
|
||||
public function importLabels(): self {
|
||||
foreach ($this->getImportService()->getData()->labels as $label) {
|
||||
if (empty($label->name)) {
|
||||
$labelTitle = 'Unnamed ' . $label->color . ' label';
|
||||
} else {
|
||||
$labelTitle = $label->name;
|
||||
}
|
||||
$newLabel = $this->labelService->create(
|
||||
$newLabel = $this->getImportService()->createLabel(
|
||||
$labelTitle,
|
||||
$this->translateColor($label->color),
|
||||
$this->board->getId()
|
||||
$this->getImportService()->getBoard()->getId()
|
||||
);
|
||||
$this->labels[$label->id] = $newLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public 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->getConfig('owner')->getUID());
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
24
lib/Service/fixtures/config-trello-schema.json
Normal file
24
lib/Service/fixtures/config-trello-schema.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user