From 003df010ddea2f44b05ecc745de5855cbdcd604c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Wed, 29 Apr 2020 12:58:10 +0200 Subject: [PATCH 1/9] clone stacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- appinfo/routes.php | 1 + lib/Controller/StackController.php | 9 +++ lib/Service/StackService.php | 115 ++++++++++++++++++++++++++++- src/components/board/Stack.vue | 7 +- src/services/StackApi.js | 15 ++++ src/store/stack.js | 12 +++ 6 files changed, 157 insertions(+), 2 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 6a1482cd8..f0b10b22a 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -50,6 +50,7 @@ ['name' => 'stack#delete', 'url' => '/stacks/{stackId}', 'verb' => 'DELETE'], ['name' => 'stack#deleted', 'url' => '/{boardId}/stacks/deleted', 'verb' => 'GET'], ['name' => 'stack#archived', 'url' => '/stacks/{boardId}/archived', 'verb' => 'GET'], + ['name' => 'stack#clone', 'url' => '/stacks/{stackId}/clone', 'verb' => 'POST'], // cards ['name' => 'card#read', 'url' => '/cards/{cardId}', 'verb' => 'GET'], diff --git a/lib/Controller/StackController.php b/lib/Controller/StackController.php index 6452ef310..bd1d5fad9 100644 --- a/lib/Controller/StackController.php +++ b/lib/Controller/StackController.php @@ -107,4 +107,13 @@ public function delete($stackId) { public function deleted($boardId) { return $this->stackService->fetchDeleted($boardId); } + + /** + * @NoAdminRequired + * @param $boardId + * @return \OCP\Deck\DB\Board + */ + public function clone($stackId, $boardId) { + return $this->stackService->clone($stackId, $boardId, $this->userId); + } } diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index 7df627e06..e8158241c 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -30,6 +30,7 @@ use OCA\Deck\Db\Acl; use OCA\Deck\Db\AssignedUsersMapper; use OCA\Deck\Db\BoardMapper; +use OCA\Deck\Db\Card; use OCA\Deck\Db\CardMapper; use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\LabelMapper; @@ -38,6 +39,9 @@ use OCA\Deck\StatusException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; +use OCP\IL10N; +use OCA\Deck\Event\FTSEvent; +use OCA\Deck\Service\AssignmentService; class StackService { private $stackMapper; @@ -49,10 +53,13 @@ class StackService { private $cardService; private $assignedUsersMapper; private $attachmentService; + private $activityManager; /** @var EventDispatcherInterface */ private $eventDispatcher; private $changeHelper; + private $l10n; + private $assignmentService; public function __construct( StackMapper $stackMapper, @@ -66,7 +73,9 @@ public function __construct( AttachmentService $attachmentService, ActivityManager $activityManager, EventDispatcherInterface $eventDispatcher, - ChangeHelper $changeHelper + ChangeHelper $changeHelper, + IL10N $l10n, + AssignmentService $assignmentService ) { $this->stackMapper = $stackMapper; $this->boardMapper = $boardMapper; @@ -80,6 +89,8 @@ public function __construct( $this->activityManager = $activityManager; $this->eventDispatcher = $eventDispatcher; $this->changeHelper = $changeHelper; + $this->l10n = $l10n; + $this->assignmentService = $assignmentService; } private function enrichStackWithCards($stack, $since = -1) { @@ -360,4 +371,106 @@ public function reorder($id, $order) { return $result; } + + /** + * @param $id + * @param $boardId + * @param $userId + * @return Stack + * @throws StatusException + * @throws BadRequestException + */ + public function clone($id, $boardId, $userId) { + if (is_numeric($id) === false) { + throw new BadRequestException('stack id must be a number'); + } + if (is_numeric($boardId) === false) { + throw new BadRequestException('board id must be a number'); + } + + $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE); + $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ); + if ($this->boardService->isArchived(null, $boardId)) { + throw new StatusException('Operation not allowed. This board is archived.'); + } + + $stack = $this->stackMapper->find($id); + $board = $this->boardMapper->find($boardId); + + + $newStack = new Stack(); + $newStack->setTitle($stack->getTitle() . ' (' . $this->l10n->t('copy') . ')'); + $newStack->setBoardId($boardId); + $newStack->setOrder($stack->getOrder() +1); + $newStack = $this->stackMapper->insert($newStack); + + $this->activityManager->triggerEvent( + ActivityManager::DECK_OBJECT_BOARD, $newStack, ActivityManager::SUBJECT_STACK_CREATE + ); + $this->changeHelper->boardChanged($boardId); + + $this->eventDispatcher->dispatch( + '\OCA\Deck\Stack::onCreate', + new GenericEvent(null, ['id' => $newStack->getId(), 'stack' => $newStack]) + ); + + $cards = $this->cardMapper->findAll($id); + foreach ($cards as $card) { + + $newCard = new Card(); + $newCard->setTitle($card->getTitle()); + $newCard->setStackId($newStack->getId()); + $newCard->setType($card->getType()); + $newCard->setOrder($card->getOrder()); + $newCard->setOwner($userId); + $newCard->setDescription($card->getDescription()); + $newCard->setDuedate($card->getDuedate()); + + $newCard = $this->cardMapper->insert($newCard); + + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_CREATE); + $this->changeHelper->cardChanged($newCard->getId(), false); + $this->eventDispatcher->dispatch('\OCA\Deck\Card::onCreate', + new FTSEvent( + null, ['id' => $newCard->getId(), 'card' => $newCard, 'userId' => $owner, 'stackId' => $stackId] + ) + ); + + if ($boardId === $stack->getBoardId()) { + $labels = $this->labelMapper->findAll($card->getId()); + $labels = $this->labelMapper->findAssignedLabelsForCard($card->id); + + $l = []; + foreach ($labels as $label) { + $l = $this->cardMapper->assignLabel($newCard->getId(), $label->getId()); + } + $newCard->setLabels($l); + + + $assignedUsers = $this->assignedUsersMapper->find($card->getId()); + /* foreach ($assignedUsers as $assignedUser) { + $u = $this->assignmentService->assignUser($newCard->getId(), $assignedUser->getId()); + $newCard->setAssignedUsers($u); + } */ + + //attachments??? + + + + + } + + } + + $createdCards = $this->cardMapper->findAll($newStack->getId()); + $newStack->setCards($createdCards); + + + + + + + + return $newStack; + } } diff --git a/src/components/board/Stack.vue b/src/components/board/Stack.vue index 27fe84eec..0832d0335 100644 --- a/src/components/board/Stack.vue +++ b/src/components/board/Stack.vue @@ -46,6 +46,9 @@ {{ t('deck', 'Archive all cards') }} + + {{ t('deck', 'Clone list') }} + {{ t('deck', 'Delete list') }} @@ -207,7 +210,6 @@ export default { this.$store.dispatch('deleteStack', stack) }, archiveAllCardsFromStack(stack) { - this.stackTransfer.total = this.cardsByStack.length this.cardsByStack.forEach((card, index) => { this.stackTransfer.current = index @@ -215,6 +217,9 @@ export default { }) this.modalArchivAllCardsShow = false }, + cloneStack(stack) { + this.$store.dispatch('cloneStack', stack) + }, startEditing(stack) { this.copiedStack = Object.assign({}, stack) this.editing = true diff --git a/src/services/StackApi.js b/src/services/StackApi.js index 0ed440a22..c825dee79 100644 --- a/src/services/StackApi.js +++ b/src/services/StackApi.js @@ -140,4 +140,19 @@ export class StackApi { }) } + cloneStack(stack) { + return axios.post(this.url(`/stacks/${stack.id}/clone`), stack) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + } diff --git a/src/store/stack.js b/src/store/stack.js index 3f428e71f..9a90e25cc 100644 --- a/src/store/stack.js +++ b/src/store/stack.js @@ -58,6 +58,9 @@ export default { state.stacks.splice(existingIndex, 1) } }, + /* cloneStack(state, stack) { + this.commit('addStack', stack) + }, */ updateStack(state, stack) { const existingIndex = state.stacks.findIndex(_stack => _stack.id === stack.id) if (existingIndex !== -1) { @@ -97,6 +100,15 @@ export default { commit('addStack', createdStack) }) }, + cloneStack({ commit }, stack) { + apiClient.cloneStack(stack) + .then((stack) => { + for (const j in stack.cards) { + commit('addCard', stack.cards[j]) + } + commit('addStack', stack) + }) + }, deleteStack({ commit }, stack) { apiClient.deleteStack(stack.id) .then((stack) => { From e763ce1fb7e39af8d34c2bff69639d8860786036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Wed, 10 Jun 2020 10:57:15 +0200 Subject: [PATCH 2/9] assign label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- lib/Db/AssignedLabels.php | 38 ++++++++++++++++++++ lib/Db/AssignedLabelsMapper.php | 61 +++++++++++++++++++++++++++++++++ lib/Service/StackService.php | 57 +++++++++++++++++------------- 3 files changed, 132 insertions(+), 24 deletions(-) create mode 100644 lib/Db/AssignedLabels.php create mode 100644 lib/Db/AssignedLabelsMapper.php diff --git a/lib/Db/AssignedLabels.php b/lib/Db/AssignedLabels.php new file mode 100644 index 000000000..68625aecb --- /dev/null +++ b/lib/Db/AssignedLabels.php @@ -0,0 +1,38 @@ + + * + * @author Jakob Röhrl + * + * @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\Db; + +use JsonSerializable; + +class AssignedLabels extends RelationalEntity implements JsonSerializable { + public $id; + protected $label; + protected $cardId; + + public function __construct() { + $this->addType('id', 'integer'); + $this->addType('cardId', 'integer'); + $this->addResolvable('label'); + } +} diff --git a/lib/Db/AssignedLabelsMapper.php b/lib/Db/AssignedLabelsMapper.php new file mode 100644 index 000000000..236976695 --- /dev/null +++ b/lib/Db/AssignedLabelsMapper.php @@ -0,0 +1,61 @@ + + * + * @author Jakob Röhrl + * + * @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\Db; + +use OCP\AppFramework\Db\Entity; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUserManager; + +class AssignedLabelsMapper extends DeckMapper { + private $cardMapper; + private $userManager; + /** + * @var IGroupManager + */ + private $groupManager; + + public function __construct(IDBConnection $db, CardMapper $cardMapper, IUserManager $userManager, IGroupManager $groupManager) { + parent::__construct($db, 'deck_assigned_labels', Labels::class); + $this->cardMapper = $cardMapper; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + } + + /** + * + * @param $cardId + * @return array|Entity + */ + public function find($cardId) { + $sql = 'SELECT l.*,card_id FROM `*PREFIX*deck_assigned_labels` as al ' . + 'INNER JOIN `*PREFIX*deck_labels` as l ON l.id = al.label_id ' . + 'WHERE `card_id` = ?'; + + $labels = $this->findEntities($sql, [$cardId]); + return $labels; + } + +} diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index e8158241c..65214528a 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -29,6 +29,9 @@ use OCA\Deck\BadRequestException; use OCA\Deck\Db\Acl; use OCA\Deck\Db\AssignedUsersMapper; +use OCA\Deck\Db\AssignedUsers; +use OCA\Deck\Db\AssignedLabelsMapper; +use OCA\Deck\Db\AssignedLabels; use OCA\Deck\Db\BoardMapper; use OCA\Deck\Db\Card; use OCA\Deck\Db\CardMapper; @@ -52,6 +55,7 @@ class StackService { private $boardService; private $cardService; private $assignedUsersMapper; + private $assignedLabelsMapper; private $attachmentService; private $activityManager; @@ -70,6 +74,7 @@ public function __construct( BoardService $boardService, CardService $cardService, AssignedUsersMapper $assignedUsersMapper, + AssignedLabelsMapper $assignedLabelsMapper, AttachmentService $attachmentService, ActivityManager $activityManager, EventDispatcherInterface $eventDispatcher, @@ -85,6 +90,7 @@ public function __construct( $this->boardService = $boardService; $this->cardService = $cardService; $this->assignedUsersMapper = $assignedUsersMapper; + $this->assignedLabelsMapper = $assignedLabelsMapper; $this->attachmentService = $attachmentService; $this->activityManager = $activityManager; $this->eventDispatcher = $eventDispatcher; @@ -415,6 +421,7 @@ public function clone($id, $boardId, $userId) { ); $cards = $this->cardMapper->findAll($id); + $c = []; foreach ($cards as $card) { $newCard = new Card(); @@ -437,40 +444,42 @@ public function clone($id, $boardId, $userId) { ); if ($boardId === $stack->getBoardId()) { - $labels = $this->labelMapper->findAll($card->getId()); - $labels = $this->labelMapper->findAssignedLabelsForCard($card->id); - + + $assignedLabels = $this->assignedLabelsMapper->find($card->getId()); $l = []; - foreach ($labels as $label) { - $l = $this->cardMapper->assignLabel($newCard->getId(), $label->getId()); + foreach ($assignedLabels as $assignedLabel) { + + $assignment = $assignedLabel; + $assignment->setCardId($newCard->getId()); + $assignment = $this->assignedLabelsMapper->insert($assignment); + + + // $assignment = new AssignedLabels(); + // $assignment->setCardId($newCard->getId()); + // $assignment->setLabel($assignedLabel); + // $assignment = $this->assignedLabelsMapper->insert($assignment); + $l[] = $assignment; } $newCard->setLabels($l); - $assignedUsers = $this->assignedUsersMapper->find($card->getId()); - /* foreach ($assignedUsers as $assignedUser) { - $u = $this->assignmentService->assignUser($newCard->getId(), $assignedUser->getId()); - $newCard->setAssignedUsers($u); - } */ - - //attachments??? - - - + $u = []; + foreach ($assignedUsers as $assignedUser) { + $assignment = new AssignedUsers(); + $assignment->setCardId($newCard->getId()); + $assignment->setParticipant($assignedUser->getParticipant()); + $assignment->setType($assignedUser->getType()); + $assignment = $this->assignedUsersMapper->insert($assignment); + $u[] = $assignment; + } + $newCard->setAssignedUsers($u); + $c[] = $newCard; } } - $createdCards = $this->cardMapper->findAll($newStack->getId()); - $newStack->setCards($createdCards); - - - - - - - + $newStack->setCards($c); return $newStack; } } From c39fd43b6cc0d0d315ebc2a1f584c8dd16541013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 18 Jun 2020 10:48:33 +0200 Subject: [PATCH 3/9] Fix label assignment insertion and enrich result of clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- appinfo/routes.php | 1 + lib/Db/AssignedLabels.php | 5 ++-- lib/Db/AssignedLabelsMapper.php | 6 ++--- lib/Service/StackService.php | 41 +++++++++++++++------------------ src/store/stack.js | 3 --- 5 files changed, 25 insertions(+), 31 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index f0b10b22a..6948f1b8c 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -101,6 +101,7 @@ ['name' => 'stack_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks', 'verb' => 'POST'], ['name' => 'stack_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'PUT'], ['name' => 'stack_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'DELETE'], + ['name' => 'stack_api#clone', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/clone', 'verb' => 'POST'], ['name' => 'card_api#get', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'GET'], ['name' => 'card_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards', 'verb' => 'POST'], diff --git a/lib/Db/AssignedLabels.php b/lib/Db/AssignedLabels.php index 68625aecb..3b59e451a 100644 --- a/lib/Db/AssignedLabels.php +++ b/lib/Db/AssignedLabels.php @@ -27,12 +27,13 @@ class AssignedLabels extends RelationalEntity implements JsonSerializable { public $id; - protected $label; + protected $labelId; protected $cardId; public function __construct() { $this->addType('id', 'integer'); $this->addType('cardId', 'integer'); - $this->addResolvable('label'); + $this->addType('labelId', 'integer'); + } } diff --git a/lib/Db/AssignedLabelsMapper.php b/lib/Db/AssignedLabelsMapper.php index 236976695..85c957e91 100644 --- a/lib/Db/AssignedLabelsMapper.php +++ b/lib/Db/AssignedLabelsMapper.php @@ -38,7 +38,7 @@ class AssignedLabelsMapper extends DeckMapper { private $groupManager; public function __construct(IDBConnection $db, CardMapper $cardMapper, IUserManager $userManager, IGroupManager $groupManager) { - parent::__construct($db, 'deck_assigned_labels', Labels::class); + parent::__construct($db, 'deck_assigned_labels', AssignedLabels::class); $this->cardMapper = $cardMapper; $this->userManager = $userManager; $this->groupManager = $groupManager; @@ -50,9 +50,7 @@ public function __construct(IDBConnection $db, CardMapper $cardMapper, IUserMana * @return array|Entity */ public function find($cardId) { - $sql = 'SELECT l.*,card_id FROM `*PREFIX*deck_assigned_labels` as al ' . - 'INNER JOIN `*PREFIX*deck_labels` as l ON l.id = al.label_id ' . - 'WHERE `card_id` = ?'; + $sql = 'SELECT * from `*PREFIX*deck_assigned_labels` where `card_id` = ?'; $labels = $this->findEntities($sql, [$cardId]); return $labels; diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index 65214528a..bc19211c6 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -57,7 +57,7 @@ class StackService { private $assignedUsersMapper; private $assignedLabelsMapper; private $attachmentService; - + private $activityManager; /** @var EventDispatcherInterface */ private $eventDispatcher; @@ -407,7 +407,7 @@ public function clone($id, $boardId, $userId) { $newStack = new Stack(); $newStack->setTitle($stack->getTitle() . ' (' . $this->l10n->t('copy') . ')'); $newStack->setBoardId($boardId); - $newStack->setOrder($stack->getOrder() +1); + $newStack->setOrder(999); $newStack = $this->stackMapper->insert($newStack); $this->activityManager->triggerEvent( @@ -421,9 +421,9 @@ public function clone($id, $boardId, $userId) { ); $cards = $this->cardMapper->findAll($id); - $c = []; + $newCardArray = []; foreach ($cards as $card) { - + $newCard = new Card(); $newCard->setTitle($card->getTitle()); $newCard->setStackId($newStack->getId()); @@ -441,45 +441,42 @@ public function clone($id, $boardId, $userId) { new FTSEvent( null, ['id' => $newCard->getId(), 'card' => $newCard, 'userId' => $owner, 'stackId' => $stackId] ) - ); + ); if ($boardId === $stack->getBoardId()) { - + $assignedLabels = $this->assignedLabelsMapper->find($card->getId()); - $l = []; + $newLabelArray = []; foreach ($assignedLabels as $assignedLabel) { - - $assignment = $assignedLabel; + + $assignment = new AssignedLabels(); $assignment->setCardId($newCard->getId()); + $assignment->setLabelId($assignedLabel->getLabelId()); $assignment = $this->assignedLabelsMapper->insert($assignment); - // $assignment = new AssignedLabels(); - // $assignment->setCardId($newCard->getId()); - // $assignment->setLabel($assignedLabel); - // $assignment = $this->assignedLabelsMapper->insert($assignment); - $l[] = $assignment; - } - $newCard->setLabels($l); - + $newLabelArray[] = $assignment; + } + $newCard->setLabels($newLabelArray); + $assignedUsers = $this->assignedUsersMapper->find($card->getId()); - $u = []; + $newUserArray = []; foreach ($assignedUsers as $assignedUser) { $assignment = new AssignedUsers(); $assignment->setCardId($newCard->getId()); $assignment->setParticipant($assignedUser->getParticipant()); $assignment->setType($assignedUser->getType()); $assignment = $this->assignedUsersMapper->insert($assignment); - $u[] = $assignment; + $newUserArray[] = $assignment; } - $newCard->setAssignedUsers($u); - $c[] = $newCard; + $newCard->setAssignedUsers($newUserArray); + $newCardArray[] = $newCard; } } - $newStack->setCards($c); + $this->enrichStackWithCards($newStack); return $newStack; } } diff --git a/src/store/stack.js b/src/store/stack.js index 9a90e25cc..504e3584f 100644 --- a/src/store/stack.js +++ b/src/store/stack.js @@ -58,9 +58,6 @@ export default { state.stacks.splice(existingIndex, 1) } }, - /* cloneStack(state, stack) { - this.commit('addStack', stack) - }, */ updateStack(state, stack) { const existingIndex = state.stacks.findIndex(_stack => _stack.id === stack.id) if (existingIndex !== -1) { From 71780b5578910b7d53832a3a0b7d4cbf74bda668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Fri, 14 Aug 2020 10:09:14 +0200 Subject: [PATCH 4/9] some small changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- docs/API.md | 13 +++++++++++++ lib/Service/StackService.php | 16 ++++++---------- src/components/board/Stack.vue | 14 +++++++++----- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/docs/API.md b/docs/API.md index dfa6d6c66..97db2c0bb 100644 --- a/docs/API.md +++ b/docs/API.md @@ -492,6 +492,19 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit ##### 200 Success +### POST /boards/{boardId}/stacks/{stackId}/clone - Clone a stack + +#### Request parameters + +| Parameter | Type | Description | +| --------- | ------- | ---------------------------------------- | +| boardId | Integer | The id of the board the stack belongs to | +| stackId | Integer | The id of the stack | + +#### Response + +##### 200 Success + ## Cards ### GET /boards/{boardId}/stacks/{stackId}/cards/{cardId} - Get card details diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index bc19211c6..7cd0e2b12 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -44,7 +44,6 @@ use Symfony\Component\EventDispatcher\GenericEvent; use OCP\IL10N; use OCA\Deck\Event\FTSEvent; -use OCA\Deck\Service\AssignmentService; class StackService { private $stackMapper; @@ -63,7 +62,7 @@ class StackService { private $eventDispatcher; private $changeHelper; private $l10n; - private $assignmentService; + private $userId; public function __construct( StackMapper $stackMapper, @@ -80,7 +79,7 @@ public function __construct( EventDispatcherInterface $eventDispatcher, ChangeHelper $changeHelper, IL10N $l10n, - AssignmentService $assignmentService + $userId ) { $this->stackMapper = $stackMapper; $this->boardMapper = $boardMapper; @@ -96,7 +95,7 @@ public function __construct( $this->eventDispatcher = $eventDispatcher; $this->changeHelper = $changeHelper; $this->l10n = $l10n; - $this->assignmentService = $assignmentService; + $this->userId = $userId; } private function enrichStackWithCards($stack, $since = -1) { @@ -381,12 +380,11 @@ public function reorder($id, $order) { /** * @param $id * @param $boardId - * @param $userId * @return Stack * @throws StatusException * @throws BadRequestException */ - public function clone($id, $boardId, $userId) { + public function clone($id, $boardId) { if (is_numeric($id) === false) { throw new BadRequestException('stack id must be a number'); } @@ -395,7 +393,6 @@ public function clone($id, $boardId, $userId) { } $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE); - $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ); if ($this->boardService->isArchived(null, $boardId)) { throw new StatusException('Operation not allowed. This board is archived.'); } @@ -429,7 +426,7 @@ public function clone($id, $boardId, $userId) { $newCard->setStackId($newStack->getId()); $newCard->setType($card->getType()); $newCard->setOrder($card->getOrder()); - $newCard->setOwner($userId); + $newCard->setOwner($this->userId); $newCard->setDescription($card->getDescription()); $newCard->setDuedate($card->getDuedate()); @@ -439,7 +436,7 @@ public function clone($id, $boardId, $userId) { $this->changeHelper->cardChanged($newCard->getId(), false); $this->eventDispatcher->dispatch('\OCA\Deck\Card::onCreate', new FTSEvent( - null, ['id' => $newCard->getId(), 'card' => $newCard, 'userId' => $owner, 'stackId' => $stackId] + null, ['id' => $newCard->getId(), 'card' => $newCard, 'userId' => $this->userId, 'stackId' => $stackId] ) ); @@ -470,7 +467,6 @@ public function clone($id, $boardId, $userId) { $newUserArray[] = $assignment; } $newCard->setAssignedUsers($newUserArray); - $newCardArray[] = $newCard; } diff --git a/src/components/board/Stack.vue b/src/components/board/Stack.vue index 0832d0335..70d8823a1 100644 --- a/src/components/board/Stack.vue +++ b/src/components/board/Stack.vue @@ -42,14 +42,14 @@ value=""> - - + + {{ t('deck', 'Archive all cards') }} - + {{ t('deck', 'Clone list') }} - + {{ t('deck', 'Delete list') }} @@ -218,7 +218,11 @@ export default { this.modalArchivAllCardsShow = false }, cloneStack(stack) { - this.$store.dispatch('cloneStack', stack) + try { + this.$store.dispatch('cloneStack', stack) + } catch (e) { + showError('Could not clone stack: ' + e.response.data.message) + } }, startEditing(stack) { this.copiedStack = Object.assign({}, stack) From 2a4b0a3ed3e1ddd7a5905460a81d20d8439621b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Wed, 2 Sep 2020 08:40:19 +0200 Subject: [PATCH 5/9] security and api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- docs/API.md | 13 +++++++++++++ lib/Service/StackService.php | 7 ++++++- src/components/board/Stack.vue | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/API.md b/docs/API.md index 97db2c0bb..02e0c8ed6 100644 --- a/docs/API.md +++ b/docs/API.md @@ -502,6 +502,19 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit | stackId | Integer | The id of the stack | #### Response +It will return an object of the new stack containing the new cards as well. + +```json + {"title":"l1 (copy)", + "boardId":6, + "deletedAt":0, + "lastModified":0, + "cards": + [{"title":"ME","description":"123","stackId":73,"type":"plain","lastModified":1599028559,"lastEditor":null,"createdAt":1599028559,"labels":[],"assignedUsers":[],"attachments":null,"attachmentCount":0,"owner":{"primaryKey":"root","uid":"root","displayname":"root","type":0},"order":0,"archived":false,"duedate":null,"deletedAt":0,"commentsUnread":0,"id":109,"overdue":0}, + {"title":"ka","description":"","stackId":73,"type":"plain","lastModified":1599028559,"lastEditor":null,"createdAt":1599028559,"labels":[],"assignedUsers":[],"attachments":null,"attachmentCount":0,"owner":{"primaryKey":"root","uid":"root","displayname":"root","type":0},"order":1,"archived":false,"duedate":"2020-08-26T22:00:00+00:00","deletedAt":0,"commentsUnread":0,"id":110,"overdue":3}], + "order":999, + "id":73} +``` ##### 200 Success diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index 7cd0e2b12..33606ec6d 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -393,6 +393,8 @@ public function clone($id, $boardId) { } $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE); + $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ); + if ($this->boardService->isArchived(null, $boardId)) { throw new StatusException('Operation not allowed. This board is archived.'); } @@ -400,6 +402,10 @@ public function clone($id, $boardId) { $stack = $this->stackMapper->find($id); $board = $this->boardMapper->find($boardId); + if ($stack->getBoardId() !== $board->getId()) { + throw new StatusException('Operation not allowed. Stack is not part of this board'); + } + $newStack = new Stack(); $newStack->setTitle($stack->getTitle() . ' (' . $this->l10n->t('copy') . ')'); @@ -418,7 +424,6 @@ public function clone($id, $boardId) { ); $cards = $this->cardMapper->findAll($id); - $newCardArray = []; foreach ($cards as $card) { $newCard = new Card(); diff --git a/src/components/board/Stack.vue b/src/components/board/Stack.vue index 70d8823a1..6377df3a9 100644 --- a/src/components/board/Stack.vue +++ b/src/components/board/Stack.vue @@ -43,10 +43,10 @@ - + {{ t('deck', 'Archive all cards') }} - + {{ t('deck', 'Clone list') }} From 14d90e2ff2e007ede3504e650ae870f4a5021f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Mon, 7 Sep 2020 14:11:35 +0200 Subject: [PATCH 6/9] format json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- docs/API.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/docs/API.md b/docs/API.md index 02e0c8ed6..8e78d566c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -505,15 +505,76 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit It will return an object of the new stack containing the new cards as well. ```json - {"title":"l1 (copy)", - "boardId":6, - "deletedAt":0, - "lastModified":0, - "cards": - [{"title":"ME","description":"123","stackId":73,"type":"plain","lastModified":1599028559,"lastEditor":null,"createdAt":1599028559,"labels":[],"assignedUsers":[],"attachments":null,"attachmentCount":0,"owner":{"primaryKey":"root","uid":"root","displayname":"root","type":0},"order":0,"archived":false,"duedate":null,"deletedAt":0,"commentsUnread":0,"id":109,"overdue":0}, - {"title":"ka","description":"","stackId":73,"type":"plain","lastModified":1599028559,"lastEditor":null,"createdAt":1599028559,"labels":[],"assignedUsers":[],"attachments":null,"attachmentCount":0,"owner":{"primaryKey":"root","uid":"root","displayname":"root","type":0},"order":1,"archived":false,"duedate":"2020-08-26T22:00:00+00:00","deletedAt":0,"commentsUnread":0,"id":110,"overdue":3}], - "order":999, - "id":73} +{ + "title":"l1 (copy)", + "boardId":6, + "deletedAt":0, + "lastModified":0, + "cards":[ + { + "title":"ME", + "description":"123", + "stackId":73, + "type":"plain", + "lastModified":1599028559, + "lastEditor":null, + "createdAt":1599028559, + "labels":[ + + ], + "assignedUsers":[ + + ], + "attachments":null, + "attachmentCount":0, + "owner":{ + "primaryKey":"root", + "uid":"root", + "displayname":"root", + "type":0 + }, + "order":0, + "archived":false, + "duedate":null, + "deletedAt":0, + "commentsUnread":0, + "id":109, + "overdue":0 + }, + { + "title":"ka", + "description":"", + "stackId":73, + "type":"plain", + "lastModified":1599028559, + "lastEditor":null, + "createdAt":1599028559, + "labels":[ + + ], + "assignedUsers":[ + + ], + "attachments":null, + "attachmentCount":0, + "owner":{ + "primaryKey":"root", + "uid":"root", + "displayname":"root", + "type":0 + }, + "order":1, + "archived":false, + "duedate":"2020-08-26T22:00:00+00:00", + "deletedAt":0, + "commentsUnread":0, + "id":110, + "overdue":3 + } + ], + "order":999, + "id":73 +} ``` ##### 200 Success From 24f83b875be94a0cb967236fafe8ec7c6e8ccf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Wed, 16 Sep 2020 08:26:16 +0200 Subject: [PATCH 7/9] test adjustments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- lib/Db/AssignedLabels.php | 1 - lib/Db/AssignedLabelsMapper.php | 3 +-- lib/Service/StackService.php | 5 ----- tests/unit/Service/StackServiceTest.php | 10 +++++++++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/Db/AssignedLabels.php b/lib/Db/AssignedLabels.php index 3b59e451a..d49f05f52 100644 --- a/lib/Db/AssignedLabels.php +++ b/lib/Db/AssignedLabels.php @@ -34,6 +34,5 @@ public function __construct() { $this->addType('id', 'integer'); $this->addType('cardId', 'integer'); $this->addType('labelId', 'integer'); - } } diff --git a/lib/Db/AssignedLabelsMapper.php b/lib/Db/AssignedLabelsMapper.php index 85c957e91..121671659 100644 --- a/lib/Db/AssignedLabelsMapper.php +++ b/lib/Db/AssignedLabelsMapper.php @@ -29,7 +29,7 @@ use OCP\IGroupManager; use OCP\IUserManager; -class AssignedLabelsMapper extends DeckMapper { +class AssignedLabelsMapper extends DeckMapper { private $cardMapper; private $userManager; /** @@ -55,5 +55,4 @@ public function find($cardId) { $labels = $this->findEntities($sql, [$cardId]); return $labels; } - } diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index 33606ec6d..e65e610d4 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -425,7 +425,6 @@ public function clone($id, $boardId) { $cards = $this->cardMapper->findAll($id); foreach ($cards as $card) { - $newCard = new Card(); $newCard->setTitle($card->getTitle()); $newCard->setStackId($newStack->getId()); @@ -446,11 +445,9 @@ public function clone($id, $boardId) { ); if ($boardId === $stack->getBoardId()) { - $assignedLabels = $this->assignedLabelsMapper->find($card->getId()); $newLabelArray = []; foreach ($assignedLabels as $assignedLabel) { - $assignment = new AssignedLabels(); $assignment->setCardId($newCard->getId()); $assignment->setLabelId($assignedLabel->getLabelId()); @@ -472,9 +469,7 @@ public function clone($id, $boardId) { $newUserArray[] = $assignment; } $newCard->setAssignedUsers($newUserArray); - } - } $this->enrichStackWithCards($newStack); diff --git a/tests/unit/Service/StackServiceTest.php b/tests/unit/Service/StackServiceTest.php index 0ec5567ca..d288d7a9f 100644 --- a/tests/unit/Service/StackServiceTest.php +++ b/tests/unit/Service/StackServiceTest.php @@ -35,6 +35,7 @@ use OCA\Deck\Db\StackMapper; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use \Test\TestCase; +use OCP\IL10N; /** * Class StackServiceTest @@ -70,6 +71,8 @@ class StackServiceTest extends TestCase { private $changeHelper; /** @var EventDispatcherInterface */ private $eventDispatcher; + private $l10n; + private $userId; public function setUp(): void { parent::setUp(); @@ -85,6 +88,9 @@ public function setUp(): void { $this->activityManager = $this->createMock(ActivityManager::class); $this->changeHelper = $this->createMock(ChangeHelper::class); $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $this->l10n = $this->createMock(IL10N::class); + $this->userId = "admin"; + $this->stackService = new StackService( $this->stackMapper, @@ -98,7 +104,9 @@ public function setUp(): void { $this->attachmentService, $this->activityManager, $this->eventDispatcher, - $this->changeHelper + $this->changeHelper, + $this->l10n, + $this->userId ); } From caa201dc6c06b822eab60905a9d275533fb5195b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Fri, 16 Oct 2020 11:42:54 +0200 Subject: [PATCH 8/9] faq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- docs/API.md | 8 ++++---- lib/Service/StackService.php | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index 8e78d566c..20c3c6d67 100644 --- a/docs/API.md +++ b/docs/API.md @@ -496,10 +496,10 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit #### Request parameters -| Parameter | Type | Description | -| --------- | ------- | ---------------------------------------- | -| boardId | Integer | The id of the board the stack belongs to | -| stackId | Integer | The id of the stack | +| Parameter | Type | Description | +| --------- | ------- | ------------------------------------------------------- | +| boardId | Integer | The id of the board where the stack should be cloned to | +| stackId | Integer | The id of the stack | #### Response It will return an object of the new stack containing the new cards as well. diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index c0f630f57..71b57116d 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -398,7 +398,7 @@ public function clone($id, $boardId) { } $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE); - $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ); + $this->permissionService->checkPermission(null, $id, Acl::PERMISSION_READ); if ($this->boardService->isArchived(null, $boardId)) { throw new StatusException('Operation not allowed. This board is archived.'); @@ -413,6 +413,9 @@ public function clone($id, $boardId) { $newStack = new Stack(); + + // TODO: Currently cloing is only possible on the same board. + // If we change this and its possible to clone to other boards the 'copy' should be removed from title $newStack->setTitle($stack->getTitle() . ' (' . $this->l10n->t('copy') . ')'); $newStack->setBoardId($boardId); $newStack->setOrder(999); From 65650691b358978db50f173440e01313b7e30c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20R=C3=B6hrl?= Date: Fri, 16 Oct 2020 12:52:47 +0200 Subject: [PATCH 9/9] test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakob Röhrl --- tests/unit/Service/StackServiceTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit/Service/StackServiceTest.php b/tests/unit/Service/StackServiceTest.php index d288d7a9f..4d81ee23d 100644 --- a/tests/unit/Service/StackServiceTest.php +++ b/tests/unit/Service/StackServiceTest.php @@ -33,6 +33,7 @@ use OCA\Deck\Db\LabelMapper; use OCA\Deck\Db\Stack; use OCA\Deck\Db\StackMapper; +use OCA\Deck\Db\AssignedLabelsMapper; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use \Test\TestCase; use OCP\IL10N; @@ -73,6 +74,7 @@ class StackServiceTest extends TestCase { private $eventDispatcher; private $l10n; private $userId; + private $assignedLabelsMapper; public function setUp(): void { parent::setUp(); @@ -90,6 +92,7 @@ public function setUp(): void { $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->l10n = $this->createMock(IL10N::class); $this->userId = "admin"; + $this->assignedLabelsMapper = $this->createMock(AssignedLabelsMapper::class); $this->stackService = new StackService( @@ -106,7 +109,8 @@ public function setUp(): void { $this->eventDispatcher, $this->changeHelper, $this->l10n, - $this->userId + $this->userId, + $this->assassignedLabelsMapper ); }