diff --git a/appinfo/database.xml b/appinfo/database.xml index 3eb4495f9..e9b7586ab 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -167,6 +167,14 @@ boolean false + + deleted_at + integer + 0 + 8 + false + true + deck_cards_stack_id_index diff --git a/appinfo/routes.php b/appinfo/routes.php index cdd2b9d79..47a550f5b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -50,6 +50,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' => '/cards/deleted/{boardId}', '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/js/controller/BoardController.js b/js/controller/BoardController.js index d8b4c16b7..bce35642f 100644 --- a/js/controller/BoardController.js +++ b/js/controller/BoardController.js @@ -42,6 +42,8 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St $scope.board = BoardService.getCurrent(); $scope.uploader = FileService.uploader; + $scope.deletedCards = []; + // workaround for $stateParams changes not being propagated $scope.$watch(function() { return $state.params; @@ -136,6 +138,14 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St }); }; + $scope.loadDeletedCards = function() { + CardService.fetchDeleted($scope.id).then(function (data) { + $scope.deletedCards = data; + }, function (error) { + $scope.statusservice.setError('Error occured', error); + }); + } + $scope.loadDefault = function () { StackService.fetchAll($scope.id).then(function (data) { $scope.statusservice.releaseWaiting(); @@ -193,6 +203,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St } CardService.delete(card.id).then(function () { StackService.removeCard(card); + $scope.loadDeletedCards(); }); }); }; @@ -384,4 +395,5 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St return card.attachmentCount; }; + $scope.loadDeletedCards(); }); diff --git a/js/service/ApiService.js b/js/service/ApiService.js index 051479417..2705739a7 100644 --- a/js/service/ApiService.js +++ b/js/service/ApiService.js @@ -114,6 +114,18 @@ app.factory('ApiService', function ($http, $q) { }; + ApiService.prototype.softDelete = function (id) { + var deferred = $q.defer(); + var self = this; + + $http.delete(this.baseUrl + '/' + id).then(function (response) { + self.data[id].deletedAt = response.data.deletedAt; + deferred.resolve(response.data); + }, function (error) { + deferred.reject('Deleting ' + self.endpoint + ' failed'); + }); + return deferred.promise; + }; // methods for managing data ApiService.prototype.clear = function () { diff --git a/js/service/CardService.js b/js/service/CardService.js index 51edc4e1d..614ef1e8e 100644 --- a/js/service/CardService.js +++ b/js/service/CardService.js @@ -27,6 +27,8 @@ app.factory('CardService', function (ApiService, $http, $q) { }; CardService.prototype = angular.copy(ApiService.prototype); + CardService.prototype.delete = CardService.prototype.softDelete; + CardService.prototype.reorder = function (card, order) { var deferred = $q.defer(); var self = this; @@ -172,6 +174,22 @@ app.factory('CardService', function (ApiService, $http, $q) { return deferred.promise; }; + CardService.prototype.fetchDeleted = function (boardId) { + + var deferred = $q.defer(); + var self = this; + $http.get(this.baseUrl + '/deleted/' + boardId).then(function (response) { + var objects = response.data; + return objects; + deferred.resolve(self.data); + }, function (error) { + deferred.reject('Fetching ' + self.endpoint + ' failed'); + }); + return deferred.promise; + + }; + + var service = new CardService($http, 'cards', $q); return service; }); diff --git a/lib/Controller/CardController.php b/lib/Controller/CardController.php index aeadcb6f9..1084ece45 100644 --- a/lib/Controller/CardController.php +++ b/lib/Controller/CardController.php @@ -104,6 +104,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/Db/Card.php b/lib/Db/Card.php index 21f65d79e..f065fc10a 100644 --- a/lib/Db/Card.php +++ b/lib/Db/Card.php @@ -42,6 +42,7 @@ class Card extends RelationalEntity { protected $archived = false; protected $duedate; protected $notified = false; + protected $deletedAt; 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..7cb5d1f57 100644 --- a/lib/Db/CardMapper.php +++ b/lib/Db/CardMapper.php @@ -124,6 +124,13 @@ class CardMapper extends DeckMapper implements IPermissionMapper { 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 AND c.deleted_at <= ? ORDER BY `c`.`order`'; + return $this->findEntities($sql, [$boardId, time()], $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/Service/CardService.php b/lib/Service/CardService.php index a9b7ae7a2..8b040f4e7 100644 --- a/lib/Service/CardService.php +++ b/lib/Service/CardService.php @@ -56,6 +56,10 @@ class CardService { $this->currentUser = $userId; } + public function fetchDeleted($boardId) { + return $this->cardMapper->findDeleted($boardId); + } + public function find($cardId) { $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $card = $this->cardMapper->find($cardId); @@ -89,7 +93,10 @@ 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) { diff --git a/templates/part.board.sidebarView.php b/templates/part.board.sidebarView.php index 7229af2dd..b168d0cfa 100644 --- a/templates/part.board.sidebarView.php +++ b/templates/part.board.sidebarView.php @@ -14,6 +14,7 @@
@@ -118,4 +119,13 @@
+ +
+
    +
  • +{{123}} +{{deletedCard}} +
  • +
+