cards soft delete wip
Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info> cards: softdelete done; undo delete wip Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info> show deleted cards in board settings sidebar wip Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info> CardMapper#findDeleted: fix bug in entity property assigning Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info>
This commit is contained in:
committed by
Julius Härtl
parent
3e4dedf397
commit
2ef4b55af4
@@ -167,6 +167,14 @@
|
|||||||
<type>boolean</type>
|
<type>boolean</type>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</field>
|
</field>
|
||||||
|
<field>
|
||||||
|
<name>deleted_at</name>
|
||||||
|
<type>integer</type>
|
||||||
|
<default>0</default>
|
||||||
|
<length>8</length>
|
||||||
|
<notnull>false</notnull>
|
||||||
|
<unsigned>true</unsigned>
|
||||||
|
</field>
|
||||||
<index>
|
<index>
|
||||||
<name>deck_cards_stack_id_index</name>
|
<name>deck_cards_stack_id_index</name>
|
||||||
<field>
|
<field>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ return [
|
|||||||
['name' => 'card#create', 'url' => '/cards', 'verb' => 'POST'],
|
['name' => 'card#create', 'url' => '/cards', 'verb' => 'POST'],
|
||||||
['name' => 'card#update', 'url' => '/cards/{cardId}', 'verb' => 'PUT'],
|
['name' => 'card#update', 'url' => '/cards/{cardId}', 'verb' => 'PUT'],
|
||||||
['name' => 'card#delete', 'url' => '/cards/{cardId}', 'verb' => 'DELETE'],
|
['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#rename', 'url' => '/cards/{cardId}/rename', 'verb' => 'PUT'],
|
||||||
['name' => 'card#reorder', 'url' => '/cards/{cardId}/reorder', 'verb' => 'PUT'],
|
['name' => 'card#reorder', 'url' => '/cards/{cardId}/reorder', 'verb' => 'PUT'],
|
||||||
['name' => 'card#archive', 'url' => '/cards/{cardId}/archive', 'verb' => 'PUT'],
|
['name' => 'card#archive', 'url' => '/cards/{cardId}/archive', 'verb' => 'PUT'],
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
|||||||
$scope.board = BoardService.getCurrent();
|
$scope.board = BoardService.getCurrent();
|
||||||
$scope.uploader = FileService.uploader;
|
$scope.uploader = FileService.uploader;
|
||||||
|
|
||||||
|
$scope.deletedCards = [];
|
||||||
|
|
||||||
// workaround for $stateParams changes not being propagated
|
// workaround for $stateParams changes not being propagated
|
||||||
$scope.$watch(function() {
|
$scope.$watch(function() {
|
||||||
return $state.params;
|
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 () {
|
$scope.loadDefault = function () {
|
||||||
StackService.fetchAll($scope.id).then(function (data) {
|
StackService.fetchAll($scope.id).then(function (data) {
|
||||||
$scope.statusservice.releaseWaiting();
|
$scope.statusservice.releaseWaiting();
|
||||||
@@ -193,6 +203,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
|||||||
}
|
}
|
||||||
CardService.delete(card.id).then(function () {
|
CardService.delete(card.id).then(function () {
|
||||||
StackService.removeCard(card);
|
StackService.removeCard(card);
|
||||||
|
$scope.loadDeletedCards();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -384,4 +395,5 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
|||||||
return card.attachmentCount;
|
return card.attachmentCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.loadDeletedCards();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
// methods for managing data
|
||||||
ApiService.prototype.clear = function () {
|
ApiService.prototype.clear = function () {
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ app.factory('CardService', function (ApiService, $http, $q) {
|
|||||||
};
|
};
|
||||||
CardService.prototype = angular.copy(ApiService.prototype);
|
CardService.prototype = angular.copy(ApiService.prototype);
|
||||||
|
|
||||||
|
CardService.prototype.delete = CardService.prototype.softDelete;
|
||||||
|
|
||||||
CardService.prototype.reorder = function (card, order) {
|
CardService.prototype.reorder = function (card, order) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -172,6 +174,22 @@ app.factory('CardService', function (ApiService, $http, $q) {
|
|||||||
return deferred.promise;
|
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);
|
var service = new CardService($http, 'cards', $q);
|
||||||
return service;
|
return service;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -104,6 +104,15 @@ class CardController extends Controller {
|
|||||||
return $this->cardService->delete($cardId);
|
return $this->cardService->delete($cardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @param $boardId
|
||||||
|
* @return \OCP\AppFramework\Db\Entity
|
||||||
|
*/
|
||||||
|
public function deleted($boardId) {
|
||||||
|
return $this->cardService->fetchDeleted($boardId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
* @param $cardId
|
* @param $cardId
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class Card extends RelationalEntity {
|
|||||||
protected $archived = false;
|
protected $archived = false;
|
||||||
protected $duedate;
|
protected $duedate;
|
||||||
protected $notified = false;
|
protected $notified = false;
|
||||||
|
protected $deletedAt;
|
||||||
|
|
||||||
private $databaseType = 'sqlite';
|
private $databaseType = 'sqlite';
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ class Card extends RelationalEntity {
|
|||||||
$this->addType('createdAt', 'integer');
|
$this->addType('createdAt', 'integer');
|
||||||
$this->addType('archived', 'boolean');
|
$this->addType('archived', 'boolean');
|
||||||
$this->addType('notified', 'boolean');
|
$this->addType('notified', 'boolean');
|
||||||
|
$this->addType('deletedAt', 'integer');
|
||||||
$this->addRelation('labels');
|
$this->addRelation('labels');
|
||||||
$this->addRelation('assignedUsers');
|
$this->addRelation('assignedUsers');
|
||||||
$this->addRelation('attachments');
|
$this->addRelation('attachments');
|
||||||
|
|||||||
@@ -124,6 +124,13 @@ class CardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $this->findEntities($sql, [$stackId], $limit, $offset);
|
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) {
|
public function findAllArchived($stackId, $limit = null, $offset = null) {
|
||||||
$sql = 'SELECT * FROM `*PREFIX*deck_cards` WHERE `stack_id`=? AND archived ORDER BY `last_modified`';
|
$sql = 'SELECT * FROM `*PREFIX*deck_cards` WHERE `stack_id`=? AND archived ORDER BY `last_modified`';
|
||||||
return $this->findEntities($sql, [$stackId], $limit, $offset);
|
return $this->findEntities($sql, [$stackId], $limit, $offset);
|
||||||
@@ -197,4 +204,4 @@ class CardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ class CardService {
|
|||||||
$this->currentUser = $userId;
|
$this->currentUser = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function fetchDeleted($boardId) {
|
||||||
|
return $this->cardMapper->findDeleted($boardId);
|
||||||
|
}
|
||||||
|
|
||||||
public function find($cardId) {
|
public function find($cardId) {
|
||||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||||
$card = $this->cardMapper->find($cardId);
|
$card = $this->cardMapper->find($cardId);
|
||||||
@@ -89,7 +93,10 @@ class CardService {
|
|||||||
if ($this->boardService->isArchived($this->cardMapper, $id)) {
|
if ($this->boardService->isArchived($this->cardMapper, $id)) {
|
||||||
throw new StatusException('Operation not allowed. This board is archived.');
|
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) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<ul class="tabHeaders">
|
<ul class="tabHeaders">
|
||||||
<li class="tabHeader" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><a><?php p($l->t('Sharing')); ?></a></li>
|
<li class="tabHeader" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><a><?php p($l->t('Sharing')); ?></a></li>
|
||||||
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><a><?php p($l->t('Tags')); ?></a></li>
|
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><a><?php p($l->t('Tags')); ?></a></li>
|
||||||
|
<li class="tabHeader" ng-class="{'selected': (params.tab==2)}" ui-sref="{tab: 2}"><a><?php p($l->t('Deleted Cards')); ?></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tabsContainer">
|
<div class="tabsContainer">
|
||||||
<div id="tabBoardShare" class="tab" ng-if="params.tab==0 || !params.tab">
|
<div id="tabBoardShare" class="tab" ng-if="params.tab==0 || !params.tab">
|
||||||
@@ -118,4 +119,13 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="board-detail-deleted-cards" class="tab deletedCardsTabView" ng-if="params.tab==2">
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="deletedCard in deletedCards">
|
||||||
|
{{123}}
|
||||||
|
{{deletedCard}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user