diff --git a/appinfo/database.xml b/appinfo/database.xml
index 3eb4495f9..91c9076ba 100644
--- a/appinfo/database.xml
+++ b/appinfo/database.xml
@@ -77,6 +77,14 @@
8false
+
+ deleted_at
+ integer
+ 0
+ 8
+ false
+ true
+ deck_stacks_board_id_index
@@ -167,6 +175,14 @@
booleanfalse
+
+ deleted_at
+ integer
+ 0
+ 8
+ false
+ true
+ deck_cards_stack_id_index
diff --git a/appinfo/info.xml b/appinfo/info.xml
index f2ff32e48..1760084d6 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -14,7 +14,7 @@
- 🚀 Get your project organized
- 0.5.0-dev1
+ 0.5.0-dev2agplJulius HärtlDeck
diff --git a/appinfo/routes.php b/appinfo/routes.php
index cdd2b9d79..90dffb520 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -5,20 +5,20 @@
* @author Julius Härtl
*
* @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 .
- *
+ *
*/
return [
@@ -43,6 +43,7 @@ return [
['name' => 'stack#update', 'url' => '/stacks/{stackId}', 'verb' => 'PUT'],
['name' => 'stack#reorder', 'url' => '/stacks/{stackId}/reorder', 'verb' => 'PUT'],
['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'],
// cards
@@ -50,6 +51,7 @@ return [
['name' => 'card#create', 'url' => '/cards', 'verb' => 'POST'],
['name' => 'card#update', 'url' => '/cards/{cardId}', 'verb' => 'PUT'],
['name' => 'card#delete', 'url' => '/cards/{cardId}', 'verb' => 'DELETE'],
+ ['name' => 'card#deleted', 'url' => '/{boardId}/cards/deleted', 'verb' => 'GET'],
['name' => 'card#rename', 'url' => '/cards/{cardId}/rename', 'verb' => 'PUT'],
['name' => 'card#reorder', 'url' => '/cards/{cardId}/reorder', 'verb' => 'PUT'],
['name' => 'card#archive', 'url' => '/cards/{cardId}/archive', 'verb' => 'PUT'],
diff --git a/css/style.scss b/css/style.scss
index a94b60938..12504fe91 100644
--- a/css/style.scss
+++ b/css/style.scss
@@ -1170,6 +1170,16 @@ input.input-inline {
position: relative;
}
+.board-detail__deleted-list__item {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+
+ * {
+ flex-basis: 20%;
+ }
+}
+
#board-detail-labels {
ul li {
input {
@@ -1215,12 +1225,18 @@ input.input-inline {
.tabHeaders {
clear: both;
- overflow: hidden;
+ overflow: initial;
margin-bottom: 0;
}
.tabsContainer {
margin-top: 15px;
+ height: 100%;
+
+ .tab {
+ height: 100%;
+ overflow: scroll;
+ }
}
.ui-select-offscreen {
diff --git a/js/controller/BoardController.js b/js/controller/BoardController.js
index d8b4c16b7..aa82d15f3 100644
--- a/js/controller/BoardController.js
+++ b/js/controller/BoardController.js
@@ -4,20 +4,20 @@
* @author Julius Härtl
*
* @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 .
- *
+ *
*/
import app from '../app/App.js';
@@ -42,6 +42,15 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
$scope.board = BoardService.getCurrent();
$scope.uploader = FileService.uploader;
+ $scope.$watch(function() {
+ return $state.current;
+ }, function(currentState) {
+ if(currentState.name === 'board.detail') {
+ CardService.fetchDeleted($scope.id);
+ StackService.fetchDeleted($scope.id);
+ }
+ });
+
// workaround for $stateParams changes not being propagated
$scope.$watch(function() {
return $state.params;
@@ -186,20 +195,58 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
});
};
+ $scope.stackDelete = function (stack) {
+ $scope.stackservice.delete(stack.id);
+ };
+
+ $scope.stackUndoDelete = function (deletedStack) {
+ return StackService.undoDelete(deletedStack);
+ };
+
$scope.cardDelete = function (card) {
- OC.dialogs.confirm(t('deck', 'Are you sure you want to delete this card with all of its data?'), t('deck', 'Delete'), function(state) {
- if (!state) {
- return;
- }
- CardService.delete(card.id).then(function () {
- StackService.removeCard(card);
- });
+ CardService.delete(card.id).then(function () {
+ StackService.removeCard(card);
});
};
+
+ $scope.cardOrCardAndStackUndoDelete = function (deletedCard) {
+ var associatedDeletedStack = $scope.stackservice.deleted[deletedCard.stackId];
+ if(associatedDeletedStack !== undefined) {
+ $scope.cardAndStackUndoDeleteAskForConfirmation(deletedCard, associatedDeletedStack);
+ } else {
+ $scope.cardUndoDelete(deletedCard);
+ }
+ };
+
+ $scope.cardAndStackUndoDeleteAskForConfirmation = function(deletedCard, associatedDeletedStack) {
+ OC.dialogs.confirm(
+ t('deck', 'The associated stack is deleted as well, it will be restored as well.'),
+ t('deck', 'Restore associated stack'),
+ function(state) {
+ if (state) {
+ $scope.cardAndStackUndoDelete(deletedCard, associatedDeletedStack);
+ }
+ }
+ );
+ };
+
+ $scope.cardAndStackUndoDelete = function(deletedCard, associatedDeletedStack) {
+ $scope.stackUndoDelete(associatedDeletedStack).then(function() {
+ $scope.cardUndoDelete(deletedCard);
+ });
+ };
+
+ $scope.cardUndoDelete = function(deletedCard) {
+ CardService.undoDelete(deletedCard).then(function() {
+ StackService.addCard(deletedCard);
+ });
+ };
+
$scope.cardArchive = function (card) {
CardService.archive(card);
StackService.removeCard(card);
};
+
$scope.isCurrentUserAssigned = function (card) {
if (! CardService.get(card.id).assignedUsers) {
return false;
@@ -209,6 +256,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
});
return userList.length === 1;
};
+
$scope.cardAssignToMe = function (card) {
CardService.assignUser(card, OC.getCurrentUser().uid)
.then(
@@ -217,6 +265,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
// TODO: remove this jquery call. Fix and use appPopoverMenuUtils instead
$('.popovermenu').addClass('hidden');
};
+
$scope.cardUnassignFromMe = function (card) {
CardService.unassignUser(card, OC.getCurrentUser().uid);
StackService.updateCard(card);
@@ -235,6 +284,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
BoardService.getCurrent().labels.splice(i, 1);
// TODO: remove from cards
};
+
$scope.labelCreate = function (label) {
label.boardId = $scope.id;
LabelService.create(label).then(function (data) {
@@ -254,12 +304,14 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
BoardService.addAcl(sharee);
$scope.status.addSharee = null;
};
+
$scope.aclDelete = function (acl) {
BoardService.deleteAcl(acl).then(function(data) {
$scope.loadDefault();
$scope.refreshData();
});
};
+
$scope.aclUpdate = function (acl) {
BoardService.updateAcl(acl);
};
@@ -383,5 +435,4 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
}
return card.attachmentCount;
};
-
});
diff --git a/js/service/ApiService.js b/js/service/ApiService.js
index 051479417..7cf2c2a37 100644
--- a/js/service/ApiService.js
+++ b/js/service/ApiService.js
@@ -4,35 +4,48 @@
* @author Julius Härtl
*
* @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 .
- *
+ *
*/
import app from '../app/App.js';
-
/** global: oc_defaults */
app.factory('ApiService', function ($http, $q) {
var ApiService = function (http, endpoint) {
+ // Consider renaming endpoint to resource
this.endpoint = endpoint;
- this.baseUrl = OC.generateUrl('/apps/deck/' + endpoint);
+ this.baseUrl = this.generateUrl(this.endpoint);
this.http = http;
this.q = $q;
this.data = {};
+ this.deleted = {};
this.id = null;
this.sorted = [];
};
+ ApiService.prototype.generateUrl = function(path) {
+ return OC.generateUrl('/apps/deck/' + path);
+ };
+
+ ApiService.prototype.tryAllThenDeleted = function(id) {
+ let object = this.data[id];
+ if (object === undefined) {
+ object = this.deleted[id];
+ }
+ return object;
+ };
+
ApiService.prototype.fetchAll = function () {
var deferred = $q.defer();
var self = this;
@@ -48,6 +61,30 @@ app.factory('ApiService', function ($http, $q) {
return deferred.promise;
};
+ ApiService.prototype.fetchDeleted = function (scopeId) {
+ var deferred = $q.defer();
+ var self = this;
+ $http.get(this.generateUrl(scopeId + '/' + this.endpoint + '/deleted')).then(function (response) {
+ var objects = response.data;
+ objects.forEach(function (obj) {
+ if(self.deleted[obj.id] !== undefined) {
+ return;
+ }
+
+ self.deleted[obj.id] = obj;
+
+ if(self.afterFetch !== undefined) {
+ self.afterFetch(obj);
+ }
+ });
+ deferred.resolve(objects);
+ }, function (error) {
+ deferred.reject('Fetching ' + self.endpoint + ' failed');
+ });
+ return deferred.promise;
+ };
+
+
ApiService.prototype.fetchOne = function (id) {
this.id = id;
@@ -104,21 +141,41 @@ app.factory('ApiService', function ($http, $q) {
var self = this;
$http.delete(this.baseUrl + '/' + id).then(function (response) {
- self.remove(id);
+ self.deleted[id] = self.data[id];
+ delete self.data[id];
+
+ let deletedAt = response.data.deletedAt;
+ if (deletedAt !== undefined) {
+ self.deleted[id].deletedAt = deletedAt;
+ }
+
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Deleting ' + self.endpoint + ' failed');
});
return deferred.promise;
-
};
+ ApiService.prototype.undoDelete = function(entity) {
+ var self = this;
+ entity.deletedAt = 0;
+
+ var promise = this.update(entity);
+
+ promise.then(() => {
+ self.data[entity.id] = entity;
+ delete this.deleted[entity.id];
+ });
+
+ return promise;
+ };
// methods for managing data
ApiService.prototype.clear = function () {
this.data = {};
};
+
ApiService.prototype.add = function (entity) {
var element = this.data[entity.id];
if (element === undefined) {
@@ -132,11 +189,7 @@ app.factory('ApiService', function ($http, $q) {
element.status = {};
}
};
- ApiService.prototype.remove = function (id) {
- if (this.data[id] !== undefined) {
- delete this.data[id];
- }
- };
+
ApiService.prototype.addAll = function (entities) {
var self = this;
angular.forEach(entities, function (entity) {
diff --git a/js/service/CardService.js b/js/service/CardService.js
index 51edc4e1d..eb62785f0 100644
--- a/js/service/CardService.js
+++ b/js/service/CardService.js
@@ -4,20 +4,20 @@
* @author Julius Härtl
*
* @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 .
- *
+ *
*/
import app from '../app/App.js';
diff --git a/js/service/StackService.js b/js/service/StackService.js
index a4671a331..1a3224573 100644
--- a/js/service/StackService.js
+++ b/js/service/StackService.js
@@ -4,20 +4,20 @@
* @author Julius Härtl
*
* @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 .
- *
+ *
*/
import app from '../app/App.js';
@@ -27,6 +27,11 @@ app.factory('StackService', function (ApiService, CardService, $http, $q) {
ApiService.call(this, $http, ep, $q);
};
StackService.prototype = angular.copy(ApiService.prototype);
+
+ StackService.prototype.afterFetch = function(stack) {
+ CardService.addAll(stack.cards);
+ };
+
StackService.prototype.fetchAll = function (boardId) {
var deferred = $q.defer();
var self = this;
@@ -129,27 +134,6 @@ app.factory('StackService', function (ApiService, CardService, $http, $q) {
}
};
- // FIXME: Should not show popup but proper undo mechanism
- StackService.prototype.delete = function (id) {
- var deferred = $q.defer();
- var self = this;
-
- OC.dialogs.confirm(t('deck', 'Are you sure you want to delete the stack with all of its data?'), t('deck', 'Delete'), function(state) {
- if (!state) {
- return;
- }
- $http.delete(self.baseUrl + '/' + id).then(function (response) {
- self.remove(id);
- deferred.resolve(response.data);
-
- }, function (error) {
- deferred.reject('Deleting ' + self.endpoint + ' failed');
- });
- });
- return deferred.promise;
- };
-
var service = new StackService($http, 'stacks', $q);
return service;
});
-
diff --git a/lib/Controller/CardController.php b/lib/Controller/CardController.php
index aeadcb6f9..22502f27e 100644
--- a/lib/Controller/CardController.php
+++ b/lib/Controller/CardController.php
@@ -89,10 +89,11 @@ class CardController extends Controller {
* @param $order
* @param $description
* @param $duedate
+ * @param $deletedAt
* @return \OCP\AppFramework\Db\Entity
*/
- public function update($id, $title, $stackId, $type, $order, $description, $duedate) {
- return $this->cardService->update($id, $title, $stackId, $type, $order, $description, $this->userId, $duedate);
+ public function update($id, $title, $stackId, $type, $order, $description, $duedate, $deletedAt) {
+ return $this->cardService->update($id, $title, $stackId, $type, $order, $description, $this->userId, $duedate, $deletedAt);
}
/**
@@ -104,6 +105,15 @@ class CardController extends Controller {
return $this->cardService->delete($cardId);
}
+ /**
+ * @NoAdminRequired
+ * @param $boardId
+ * @return \OCP\AppFramework\Db\Entity
+ */
+ public function deleted($boardId) {
+ return $this->cardService->fetchDeleted($boardId);
+ }
+
/**
* @NoAdminRequired
* @param $cardId
diff --git a/lib/Controller/StackController.php b/lib/Controller/StackController.php
index 3e2e358ed..3063d858f 100644
--- a/lib/Controller/StackController.php
+++ b/lib/Controller/StackController.php
@@ -74,10 +74,11 @@ class StackController extends Controller {
* @param $title
* @param $boardId
* @param $order
+ * @param $deletedAt
* @return \OCP\AppFramework\Db\Entity
*/
- public function update($id, $title, $boardId, $order) {
- return $this->stackService->update($id, $title, $boardId, $order);
+ public function update($id, $title, $boardId, $order, $deletedAt) {
+ return $this->stackService->update($id, $title, $boardId, $order, $deletedAt);
}
/**
@@ -98,4 +99,14 @@ class StackController extends Controller {
public function delete($stackId) {
return $this->stackService->delete($stackId);
}
+
+ /**
+ * @NoAdminRequired
+ * @param $boardId
+ * @return \OCP\AppFramework\Db\Entity
+ */
+ public function deleted($boardId) {
+ return $this->stackService->fetchDeleted($boardId);
+ }
+
}
diff --git a/lib/Db/Card.php b/lib/Db/Card.php
index 21f65d79e..eb5e93911 100644
--- a/lib/Db/Card.php
+++ b/lib/Db/Card.php
@@ -5,20 +5,20 @@
* @author Julius Härtl
*
* @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;
@@ -42,6 +42,7 @@ class Card extends RelationalEntity {
protected $archived = false;
protected $duedate;
protected $notified = false;
+ protected $deletedAt = 0;
private $databaseType = 'sqlite';
@@ -58,6 +59,7 @@ class Card extends RelationalEntity {
$this->addType('createdAt', 'integer');
$this->addType('archived', 'boolean');
$this->addType('notified', 'boolean');
+ $this->addType('deletedAt', 'integer');
$this->addRelation('labels');
$this->addRelation('assignedUsers');
$this->addRelation('attachments');
diff --git a/lib/Db/CardMapper.php b/lib/Db/CardMapper.php
index e0234fc43..31f363398 100644
--- a/lib/Db/CardMapper.php
+++ b/lib/Db/CardMapper.php
@@ -120,10 +120,17 @@ class CardMapper extends DeckMapper implements IPermissionMapper {
public function findAll($stackId, $limit = null, $offset = null) {
$sql = 'SELECT * FROM `*PREFIX*deck_cards`
- WHERE `stack_id` = ? AND NOT archived ORDER BY `order`';
+ WHERE `stack_id` = ? AND NOT archived AND deleted_at = 0 ORDER BY `order`';
return $this->findEntities($sql, [$stackId], $limit, $offset);
}
+ public function findDeleted($boardId, $limit = null, $offset = null) {
+ $sql = 'SELECT c.* FROM `*PREFIX*deck_cards` c
+ INNER JOIN `*PREFIX*deck_stacks` s ON s.id = c.stack_id
+ WHERE `s`.`board_id` = ? AND NOT c.archived AND NOT c.deleted_at = 0 ORDER BY `c`.`order`';
+ return $this->findEntities($sql, [$boardId], $limit, $offset);
+ }
+
public function findAllArchived($stackId, $limit = null, $offset = null) {
$sql = 'SELECT * FROM `*PREFIX*deck_cards` WHERE `stack_id`=? AND archived ORDER BY `last_modified`';
return $this->findEntities($sql, [$stackId], $limit, $offset);
@@ -197,4 +204,4 @@ class CardMapper extends DeckMapper implements IPermissionMapper {
}
-}
\ No newline at end of file
+}
diff --git a/lib/Db/LabelMapper.php b/lib/Db/LabelMapper.php
index 9ed47266b..3107b4310 100644
--- a/lib/Db/LabelMapper.php
+++ b/lib/Db/LabelMapper.php
@@ -5,20 +5,20 @@
* @author Julius Härtl
*
* @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;
diff --git a/lib/Db/Stack.php b/lib/Db/Stack.php
index 8a3d00ffb..f1ba66c9c 100644
--- a/lib/Db/Stack.php
+++ b/lib/Db/Stack.php
@@ -27,12 +27,14 @@ class Stack extends RelationalEntity {
protected $title;
protected $boardId;
+ protected $deletedAt = 0;
protected $cards = array();
protected $order;
public function __construct() {
$this->addType('id', 'integer');
$this->addType('boardId', 'integer');
+ $this->addType('deletedAt', 'integer');
$this->addType('order', 'integer');
}
@@ -47,4 +49,4 @@ class Stack extends RelationalEntity {
}
return $json;
}
-}
\ No newline at end of file
+}
diff --git a/lib/Db/StackMapper.php b/lib/Db/StackMapper.php
index 51d8bf29c..c98d87cc0 100644
--- a/lib/Db/StackMapper.php
+++ b/lib/Db/StackMapper.php
@@ -51,10 +51,17 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
public function findAll($boardId, $limit = null, $offset = null) {
- $sql = 'SELECT * FROM `*PREFIX*deck_stacks` WHERE `board_id` = ? ORDER BY `order`';
+ $sql = 'SELECT * FROM `*PREFIX*deck_stacks` WHERE `board_id` = ? AND deleted_at = 0 ORDER BY `order`';
return $this->findEntities($sql, [$boardId], $limit, $offset);
}
-
+
+
+ public function findDeleted($boardId, $limit = null, $offset = null) {
+ $sql = 'SELECT * FROM `*PREFIX*deck_stacks` s
+ WHERE `s`.`board_id` = ? AND NOT s.deleted_at = 0';
+ return $this->findEntities($sql, [$boardId], $limit, $offset);
+ }
+
public function delete(Entity $entity) {
// delete cards on stack
@@ -73,4 +80,4 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
$entity = $this->find($stackId);
return $entity->getBoardId();
}
-}
\ No newline at end of file
+}
diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php
index 3f9a905cc..ab6baf225 100644
--- a/lib/Service/BoardService.php
+++ b/lib/Service/BoardService.php
@@ -256,4 +256,4 @@ class BoardService {
return $this->aclMapper->delete($acl);
}
-}
\ No newline at end of file
+}
diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php
index a9b7ae7a2..fcf691ecc 100644
--- a/lib/Service/CardService.php
+++ b/lib/Service/CardService.php
@@ -30,6 +30,8 @@ use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\StackMapper;
use OCA\Deck\Notification\NotificationHelper;
+use OCA\Deck\Db\BoardMapper;
+use OCA\Deck\Db\LabelMapper;
use OCA\Deck\NotFoundException;
use OCA\Deck\StatusException;
@@ -38,6 +40,8 @@ class CardService {
private $cardMapper;
private $stackMapper;
+ private $boardMapper;
+ private $labelMapper;
private $permissionService;
private $boardService;
private $notificationHelper;
@@ -45,9 +49,22 @@ class CardService {
private $attachmentService;
private $currentUser;
- public function __construct(CardMapper $cardMapper, StackMapper $stackMapper, PermissionService $permissionService, BoardService $boardService, NotificationHelper $notificationHelper, AssignedUsersMapper $assignedUsersMapper, AttachmentService $attachmentService, $userId) {
+ public function __construct(
+ CardMapper $cardMapper,
+ StackMapper $stackMapper,
+ BoardMapper $boardMapper,
+ LabelMapper $labelMapper,
+ PermissionService $permissionService,
+ BoardService $boardService,
+ NotificationHelper $notificationHelper,
+ AssignedUsersMapper $assignedUsersMapper,
+ AttachmentService $attachmentService,
+ $userId
+ ) {
$this->cardMapper = $cardMapper;
$this->stackMapper = $stackMapper;
+ $this->boardMapper = $boardMapper;
+ $this->labelMapper = $labelMapper;
$this->permissionService = $permissionService;
$this->boardService = $boardService;
$this->notificationHelper = $notificationHelper;
@@ -56,6 +73,22 @@ class CardService {
$this->currentUser = $userId;
}
+ public function enrich($card) {
+ $cardId = $card->getId();
+ $card->setAssignedUsers($this->assignedUsersMapper->find($cardId));
+ $card->setLabels($this->labelMapper->findAssignedLabelsForCard($cardId));
+ $card->setAttachmentCount($this->attachmentService->count($cardId));
+ }
+
+ public function fetchDeleted($boardId) {
+ $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
+ $cards = $this->cardMapper->findDeleted($boardId);
+ foreach ($cards as $card) {
+ $this->enrich($card);
+ }
+ return $cards;
+ }
+
public function find($cardId) {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$card = $this->cardMapper->find($cardId);
@@ -89,10 +122,13 @@ class CardService {
if ($this->boardService->isArchived($this->cardMapper, $id)) {
throw new StatusException('Operation not allowed. This board is archived.');
}
- return $this->cardMapper->delete($this->cardMapper->find($id));
+ $card = $this->cardMapper->find($id);
+ $card->setDeletedAt(time());
+ $this->cardMapper->update($card);
+ return $card;
}
- public function update($id, $title, $stackId, $type, $order, $description, $owner, $duedate) {
+ public function update($id, $title, $stackId, $type, $order, $description, $owner, $duedate, $deletedAt) {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
if ($this->boardService->isArchived($this->cardMapper, $id)) {
throw new StatusException('Operation not allowed. This board is archived.');
@@ -108,6 +144,7 @@ class CardService {
$card->setOwner($owner);
$card->setDescription($description);
$card->setDuedate($duedate);
+ $card->setDeletedAt($deletedAt);
return $this->cardMapper->update($card);
}
diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php
index 30e40cc6e..b64766e79 100644
--- a/lib/Service/StackService.php
+++ b/lib/Service/StackService.php
@@ -5,26 +5,27 @@
* @author Julius Härtl
*
* @license GNU AGPL version 3 or any later version
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
- *
+ *
*/
namespace OCA\Deck\Service;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\CardMapper;
+use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\AssignedUsersMapper;
use OCA\Deck\Db\Stack;
@@ -38,46 +39,67 @@ class StackService {
private $stackMapper;
private $cardMapper;
+ private $boardMapper;
private $labelMapper;
private $permissionService;
private $boardService;
+ private $cardService;
private $assignedUsersMapper;
private $attachmentService;
public function __construct(
StackMapper $stackMapper,
+ BoardMapper $boardMapper,
CardMapper $cardMapper,
LabelMapper $labelMapper,
PermissionService $permissionService,
BoardService $boardService,
+ CardService $cardService,
AssignedUsersMapper $assignedUsersMapper,
AttachmentService $attachmentService
) {
$this->stackMapper = $stackMapper;
+ $this->boardMapper = $boardMapper;
$this->cardMapper = $cardMapper;
$this->labelMapper = $labelMapper;
$this->permissionService = $permissionService;
$this->boardService = $boardService;
+ $this->cardService = $cardService;
$this->assignedUsersMapper = $assignedUsersMapper;
$this->attachmentService = $attachmentService;
}
+ private function enrichStackWithCards($stack) {
+ $cards = $this->cardMapper->findAll($stack->id);
+
+ if(is_null($cards)) {
+ return;
+ }
+
+ foreach ($cards as $card) {
+ $this->cardService->enrich($card);
+ }
+
+ $stack->setCards($cards);
+ }
+
+ private function enrichStacksWithCards($stacks) {
+ foreach ($stacks as $stack) {
+ $this->enrichStackWithCards($stack);
+ }
+ }
+
public function findAll($boardId) {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ);
$stacks = $this->stackMapper->findAll($boardId);
- $labels = $this->labelMapper->getAssignedLabelsForBoard($boardId);
- foreach ($stacks as $stackIndex => $stack) {
- $cards = $this->cardMapper->findAll($stack->id);
- foreach ($cards as $cardIndex => $card) {
- $assignedUsers = $this->assignedUsersMapper->find($card->getId());
- $card->setAssignedUsers($assignedUsers);
- if (array_key_exists($card->id, $labels)) {
- $cards[$cardIndex]->setLabels($labels[$card->id]);
- }
- $card->setAttachmentCount($this->attachmentService->count($card->getId()));
- }
- $stacks[$stackIndex]->setCards($cards);
- }
+ $this->enrichStacksWithCards($stacks);
+ return $stacks;
+ }
+
+ public function fetchDeleted($boardId) {
+ $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
+ $stacks = $this->stackMapper->findDeleted($boardId);
+ $this->enrichStacksWithCards($stacks);
return $stacks;
}
@@ -115,10 +137,18 @@ class StackService {
public function delete($id) {
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
- return $this->stackMapper->delete($this->stackMapper->find($id));
+
+ $stack = $this->stackMapper->find($id);
+ $stack->setDeletedAt(time());
+ $this->stackMapper->update($stack);
+
+ $this->enrichStackWithCards($stack);
+
+ return $stack;
}
- public function update($id, $title, $boardId, $order) {
+
+ public function update($id, $title, $boardId, $order, $deletedAt) {
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
if ($this->boardService->isArchived($this->stackMapper, $id)) {
throw new StatusException('Operation not allowed. This board is archived.');
@@ -127,6 +157,7 @@ class StackService {
$stack->setTitle($title);
$stack->setBoardId($boardId);
$stack->setOrder($order);
+ $stack->setDeletedAt($deletedAt);
return $this->stackMapper->update($stack);
}
@@ -154,4 +185,4 @@ class StackService {
return $result;
}
-}
\ No newline at end of file
+}
diff --git a/templates/part.board.mainView.php b/templates/part.board.mainView.php
index bc5f483c2..26447c9c2 100644
--- a/templates/part.board.mainView.php
+++ b/templates/part.board.mainView.php
@@ -52,7 +52,7 @@
+ ng-click="stackDelete(s)">
diff --git a/tests/unit/Db/BoardMapperTest.php b/tests/unit/Db/BoardMapperTest.php
index 64c878893..f11eb0363 100644
--- a/tests/unit/Db/BoardMapperTest.php
+++ b/tests/unit/Db/BoardMapperTest.php
@@ -5,20 +5,20 @@
* @author Julius Härtl
*
* @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;
@@ -175,4 +175,4 @@ class BoardMapperTest extends MapperTestUtility {
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/Db/CardTest.php b/tests/unit/Db/CardTest.php
index be6472b88..ba8ff7030 100644
--- a/tests/unit/Db/CardTest.php
+++ b/tests/unit/Db/CardTest.php
@@ -5,20 +5,20 @@
* @author Julius Härtl
*
* @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;
@@ -81,6 +81,7 @@ class CardTest extends TestCase {
'attachments' => null,
'attachmentCount' => null,
'assignedUsers' => null,
+ 'deletedAt' => 0
], $card->jsonSerialize());
}
public function testJsonSerializeLabels() {
@@ -103,6 +104,7 @@ class CardTest extends TestCase {
'attachments' => null,
'attachmentCount' => null,
'assignedUsers' => null,
+ 'deletedAt' => 0
], $card->jsonSerialize());
}
@@ -135,7 +137,8 @@ class CardTest extends TestCase {
'attachments' => null,
'attachmentCount' => null,
'assignedUsers' => ['user1'],
+ 'deletedAt' => 0
], $card->jsonSerialize());
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/Db/StackTest.php b/tests/unit/Db/StackTest.php
index a28a732c6..75873d350 100644
--- a/tests/unit/Db/StackTest.php
+++ b/tests/unit/Db/StackTest.php
@@ -5,20 +5,20 @@
* @author Julius Härtl
*
* @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;
@@ -39,6 +39,7 @@ class StackTest extends \Test\TestCase {
'title' => "My Stack",
'order' => 1,
'boardId' => 1,
+ 'deletedAt' => 0
], $board->jsonSerialize());
}
public function testJsonSerializeWithCards() {
@@ -51,6 +52,7 @@ class StackTest extends \Test\TestCase {
'order' => 1,
'boardId' => 1,
'cards' => array("foo", "bar"),
+ 'deletedAt' => 0
], $board->jsonSerialize());
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/Service/CardServiceTest.php b/tests/unit/Service/CardServiceTest.php
index 0f850c006..74f4ee0bf 100644
--- a/tests/unit/Service/CardServiceTest.php
+++ b/tests/unit/Service/CardServiceTest.php
@@ -29,6 +29,8 @@ use OCA\Deck\Db\AssignedUsersMapper;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\StackMapper;
+use OCA\Deck\Db\BoardMapper;
+use OCA\Deck\Db\LabelMapper;
use OCA\Deck\NotFoundException;
use OCA\Deck\Notification\NotificationHelper;
use OCA\Deck\StatusException;
@@ -48,19 +50,36 @@ class CardServiceTest extends TestCase {
private $notificationHelper;
/** @var AssignedUsersMapper|\PHPUnit\Framework\MockObject\MockObject */
private $assignedUsersMapper;
- /** @var BoardService|\PHPUnit\Framework\MockObject\MockObject */
- private $boardService;
+ /** @var BoardService|\PHPUnit\Framework\MockObject\MockObject */
+ private $boardService;
+ /** @var LabelMapper|\PHPUnit\Framework\MockObject\MockObject */
+ private $labelMapper;
+ private $boardMapper;
+ private $attachmentService;
- public function setUp() {
- parent::setUp();
+ public function setUp() {
+ parent::setUp();
$this->cardMapper = $this->createMock(CardMapper::class);
$this->stackMapper = $this->createMock(StackMapper::class);
+ $this->boardMapper = $this->createMock(BoardMapper::class);
+ $this->labelMapper = $this->createMock(LabelMapper::class);
$this->permissionService = $this->createMock(PermissionService::class);
$this->boardService = $this->createMock(BoardService::class);
$this->notificationHelper = $this->createMock(NotificationHelper::class);
$this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
$this->attachmentService = $this->createMock(AttachmentService::class);
- $this->cardService = new CardService($this->cardMapper, $this->stackMapper, $this->permissionService, $this->boardService, $this->notificationHelper, $this->assignedUsersMapper, $this->attachmentService, 'userXY');
+ $this->cardService = new CardService(
+ $this->cardMapper,
+ $this->stackMapper,
+ $this->boardMapper,
+ $this->labelMapper,
+ $this->permissionService,
+ $this->boardService,
+ $this->notificationHelper,
+ $this->assignedUsersMapper,
+ $this->attachmentService,
+ 'user1'
+ );
}
public function testFind() {
@@ -100,13 +119,15 @@ class CardServiceTest extends TestCase {
}
public function testDelete() {
+ $cardToBeDeleted = new Card();
$this->cardMapper->expects($this->once())
->method('find')
- ->willReturn(new Card());
+ ->willReturn($cardToBeDeleted);
$this->cardMapper->expects($this->once())
- ->method('delete')
- ->willReturn(1);
- $this->assertEquals(1, $this->cardService->delete(123));
+ ->method('update')
+ ->willReturn($cardToBeDeleted);
+ $this->cardService->delete(123);
+ $this->assertTrue($cardToBeDeleted->getDeletedAt() <= time(), 'deletedAt is in the past');
}
public function testUpdate() {
@@ -115,7 +136,7 @@ class CardServiceTest extends TestCase {
$card->setArchived(false);
$this->cardMapper->expects($this->once())->method('find')->willReturn($card);
$this->cardMapper->expects($this->once())->method('update')->willReturnCallback(function($c) { return $c; });
- $actual = $this->cardService->update(123, 'newtitle', 234, 'text', 999, 'foo', 'admin', '2017-01-01 00:00:00');
+ $actual = $this->cardService->update(123, 'newtitle', 234, 'text', 999, 'foo', 'admin', '2017-01-01 00:00:00', null);
$this->assertEquals('newtitle', $actual->getTitle());
$this->assertEquals(234, $actual->getStackId());
$this->assertEquals('text', $actual->getType());
@@ -131,7 +152,7 @@ class CardServiceTest extends TestCase {
$this->cardMapper->expects($this->once())->method('find')->willReturn($card);
$this->cardMapper->expects($this->never())->method('update');
$this->setExpectedException(StatusException::class);
- $this->cardService->update(123, 'newtitle', 234, 'text', 999, 'foo', 'admin', '2017-01-01 00:00:00');
+ $this->cardService->update(123, 'newtitle', 234, 'text', 999, 'foo', 'admin', '2017-01-01 00:00:00', null);
}
public function testRename() {
@@ -317,4 +338,4 @@ class CardServiceTest extends TestCase {
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/Service/StackServiceTest.php b/tests/unit/Service/StackServiceTest.php
index d9cc908fc..e013de63d 100644
--- a/tests/unit/Service/StackServiceTest.php
+++ b/tests/unit/Service/StackServiceTest.php
@@ -28,6 +28,7 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\AssignedUsersMapper;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
+use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack;
@@ -48,6 +49,8 @@ class StackServiceTest extends TestCase {
private $stackMapper;
/** @var \PHPUnit\Framework\MockObject\MockObject|CardMapper */
private $cardMapper;
+ /** @var \PHPUnit\Framework\MockObject\MockObject|BoardMapper */
+ private $boardMapper;
/** @var \PHPUnit\Framework\MockObject\MockObject|LabelMapper */
private $labelMapper;
/** @var \PHPUnit\Framework\MockObject\MockObject|PermissionService */
@@ -58,23 +61,30 @@ class StackServiceTest extends TestCase {
private $attachmentService;
/** @var BoardService|\PHPUnit\Framework\MockObject\MockObject */
private $boardService;
+ /** @var CardService|\PHPUnit\Framework\MockObject\MockObject */
+ private $cardService;
public function setUp() {
parent::setUp();
$this->stackMapper = $this->createMock(StackMapper::class);
$this->cardMapper = $this->createMock(CardMapper::class);
- $this->labelMapper = $this->createMock(LabelMapper::class);
+ $this->boardMapper = $this->createMock(BoardMapper::class);
$this->permissionService = $this->createMock(PermissionService::class);
$this->boardService = $this->createMock(BoardService::class);
+ $this->cardService = $this->createMock(CardService::class);
$this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
$this->attachmentService = $this->createMock(AttachmentService::class);
+ $this->labelMapper = $this->createMock(LabelMapper::class);
$this->stackService = new StackService(
$this->stackMapper,
- $this->cardMapper,
- $this->labelMapper,
+ $this->boardMapper,
+ $this->cardMapper,
+ $this->labelMapper,
+
$this->permissionService,
$this->boardService,
+ $this->cardService,
$this->assignedUsersMapper,
$this->attachmentService
);
@@ -83,9 +93,16 @@ class StackServiceTest extends TestCase {
public function testFindAll() {
$this->permissionService->expects($this->once())->method('checkPermission');
$this->stackMapper->expects($this->once())->method('findAll')->willReturn($this->getStacks());
- $this->labelMapper->expects($this->once())->method('getAssignedLabelsForBoard')->willReturn($this->getLabels());
+ $this->cardService->expects($this->atLeastOnce())->method('enrich')->will(
+ $this->returnCallback(
+ function($card) {
+ $card->setLabels($this->getLabels()[$card->getId()]);
+ }
+ )
+ );
$this->cardMapper->expects($this->any())->method('findAll')->willReturn($this->getCards(222));
+
$actual = $this->stackService->findAll(123);
for($stackId=0; $stackId<3; $stackId++) {
for ($cardId=0;$cardId<10;$cardId++) {
@@ -130,8 +147,10 @@ class StackServiceTest extends TestCase {
private function getStacks() {
$s1 = new Stack();
$s1->setId(222);
+ $s1->setBoardId(1);
$s2 = new Stack();
$s2->setId(223);
+ $s1->setBoardId(1);
return [$s1, $s2];
}
private function getCards($stackId=0) {
@@ -158,9 +177,12 @@ class StackServiceTest extends TestCase {
public function testDelete() {
$this->permissionService->expects($this->once())->method('checkPermission');
- $this->stackMapper->expects($this->once())->method('find')->willReturn(new Stack());
- $this->stackMapper->expects($this->once())->method('delete');
+ $stackToBeDeleted = new Stack();
+ $stackToBeDeleted->setId(1);
+ $this->stackMapper->expects($this->once())->method('find')->willReturn($stackToBeDeleted);
+ $this->stackMapper->expects($this->once())->method('update');
$this->stackService->delete(123);
+ $this->assertTrue($stackToBeDeleted->getDeletedAt() <= time(), "deletedAt is in the past");
}
public function testUpdate() {
@@ -172,7 +194,7 @@ class StackServiceTest extends TestCase {
$stack->setTitle('Foo');
$stack->setBoardId(2);
$stack->setOrder(1);
- $result = $this->stackService->update(123, 'Foo', 2, 1);
+ $result = $this->stackService->update(123, 'Foo', 2, 1, null);
$this->assertEquals($stack, $result);
}
@@ -207,4 +229,4 @@ class StackServiceTest extends TestCase {
return $stack;
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/controller/CardControllerTest.php b/tests/unit/controller/CardControllerTest.php
index 6f19db775..904f7b03a 100644
--- a/tests/unit/controller/CardControllerTest.php
+++ b/tests/unit/controller/CardControllerTest.php
@@ -76,7 +76,7 @@ class CardControllerTest extends \Test\TestCase {
->method('update')
->with(1, 'title', 3, 'text', 5, 'foo', $this->userId, '2017-01-01 00:00:00')
->willReturn(1);
- $this->assertEquals(1, $this->controller->update(1, 'title', 3, 'text', 5, 'foo', '2017-01-01 00:00:00'));
+ $this->assertEquals(1, $this->controller->update(1, 'title', 3, 'text', 5, 'foo', '2017-01-01 00:00:00', null));
}
public function testDelete() {
diff --git a/tests/unit/controller/StackControllerTest.php b/tests/unit/controller/StackControllerTest.php
index 01f0f963e..b209178db 100644
--- a/tests/unit/controller/StackControllerTest.php
+++ b/tests/unit/controller/StackControllerTest.php
@@ -81,7 +81,7 @@ class StackControllerTest extends \Test\TestCase {
->method('update')
->with(1, 2, 3, 4)
->willReturn(1);
- $this->assertEquals(1, $this->controller->update(1, 2, 3, 4));
+ $this->assertEquals(1, $this->controller->update(1, 2, 3, 4, null));
}
public function testReorder() {