diff --git a/appinfo/database.xml b/appinfo/database.xml index 50af46975..a49526952 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -38,6 +38,14 @@ boolean false + + deleted_at + integer + 0 + 8 + false + true + diff --git a/appinfo/info.xml b/appinfo/info.xml index 05f092643..6db4b30dd 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -16,7 +16,7 @@ 💥 This is still alpha software: it may not be stable enough for production! - 0.1.4.1 + 0.1.4.2agplJulius HärtlDeck @@ -30,6 +30,9 @@ + + OCA\Deck\Cron\DeleteCron + OCA\Deck\Migration\UnknownUsers diff --git a/appinfo/routes.php b/appinfo/routes.php index 52ddf0455..2f0e32af5 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -31,6 +31,7 @@ return [ ['name' => 'board#read', 'url' => '/boards/{boardId}', 'verb' => 'GET'], ['name' => 'board#update', 'url' => '/boards/{boardId}', 'verb' => 'PUT'], ['name' => 'board#delete', 'url' => '/boards/{boardId}', 'verb' => 'DELETE'], + ['name' => 'board#deleteUndo', 'url' => '/boards/{boardId}/deleteUndo', 'verb' => 'POST'], ['name' => 'board#getUserPermissions', 'url' => '/boards/{boardId}/permissions', 'verb' => 'GET'], ['name' => 'board#addAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'POST'], ['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'PUT'], diff --git a/css/style.css b/css/style.css index 0345bf781..6d5417436 100644 --- a/css/style.css +++ b/css/style.css @@ -705,6 +705,10 @@ button.button-inline:hover { width: 50%; } +#boardlist tr.deleted td * { + opacity: 0.5; +} + #boardlist td form { display: flex; width: 100%; @@ -722,11 +726,17 @@ button.button-inline:hover { width: 32px; } -#boardlist .popovermenu { - top: 9px; - right: -36px; +#boardlist td .app-popover-menu-utils { + float: right; } +#boardlist .popovermenu { + top: 33px; + right: -5px; +} + + + /** * Board details */ diff --git a/js/app/Config.js b/js/app/Config.js index 845979171..68cfcb471 100644 --- a/js/app/Config.js +++ b/js/app/Config.js @@ -37,9 +37,10 @@ app.config(function ($provide, $routeProvider, $interpolateProvider, $httpProvid $stateProvider .state('list', { - url: "/", + url: "/:filter", templateUrl: "/boardlist.mainView.html", controller: 'ListController', + reloadOnSearch: false, params: { filter: { value: '', dynamic: true } } diff --git a/js/controller/BoardController.js b/js/controller/BoardController.js index 0ad8211d9..46260f2eb 100644 --- a/js/controller/BoardController.js +++ b/js/controller/BoardController.js @@ -114,7 +114,6 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St // Handle initial Loading BoardService.fetchOne($scope.id).then(function (data) { - BoardService.getPermissions(); $scope.statusservice.releaseWaiting(); }, function (error) { $scope.statusservice.setError('Error occured', error); diff --git a/js/controller/ListController.js b/js/controller/ListController.js index db21ee0d7..6068b78c3 100644 --- a/js/controller/ListController.js +++ b/js/controller/ListController.js @@ -21,7 +21,7 @@ * */ -app.controller('ListController', function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams) { +app.controller('ListController', function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams, $state) { $scope.boards = []; $scope.newBoard = {}; $scope.status = { @@ -58,7 +58,7 @@ app.controller('ListController', function ($scope, $location, $filter, BoardServ } else { $scope.boardservice.sorted = $filter('cardFilter')($scope.boardservice.sorted, {archived: false}); } - $scope.boardservice.sorted = $filter('orderBy')($scope.boardservice.sorted, 'title'); + $scope.boardservice.sorted = $filter('orderBy')($scope.boardservice.sorted, ['deletedAt', 'title']); }; $scope.$state = $stateParams; @@ -71,6 +71,13 @@ app.controller('ListController', function ($scope, $location, $filter, BoardServ $scope.newBoard.color = color; }; + $scope.gotoBoard = function(board) { + if(board.deletedAt > 0) { + return false; + } + return $state.go('board', {boardId: board.id}); + }; + $scope.boardCreate = function() { if(!$scope.newBoard.title || !$scope.newBoard.color) { $scope.status.addBoard=false; @@ -109,24 +116,15 @@ app.controller('ListController', function ($scope, $location, $filter, BoardServ }; $scope.boardDelete = function(board) { - var boardId = board.id; - $scope.status.deleteUndo[boardId] = 10; - $scope.boardDeleteCountdown = function () { - if($scope.status.deleteUndo[boardId] > 0) { - $scope.status.deleteUndo[boardId]--; - $timeout($scope.boardDeleteCountdown, 1000); - } - if($scope.status.deleteUndo[boardId] === 0) { - BoardService.delete(board.id).then(function (data) { - $scope.filterData(); - }); - } - }; - $timeout($scope.boardDeleteCountdown, 1000); + BoardService.delete(board.id).then(function (data) { + $scope.filterData(); + }); }; $scope.boardDeleteUndo = function (board) { - delete $scope.status.deleteUndo[board.id]; + BoardService.deleteUndo(board.id).then(function (data) { + $scope.filterData(); + }) }; }); diff --git a/js/service/BoardService.js b/js/service/BoardService.js index 84034e990..1ebb9ef3e 100644 --- a/js/service/BoardService.js +++ b/js/service/BoardService.js @@ -26,6 +26,33 @@ app.factory('BoardService', function(ApiService, $http, $q){ }; BoardService.prototype = angular.copy(ApiService.prototype); + BoardService.prototype.delete = 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; + }; + + BoardService.prototype.deleteUndo = function (id) { + var deferred = $q.defer(); + var self = this; + var _id = id; + $http.post(this.baseUrl + '/' + id + '/deleteUndo').then(function (response) { + self.data[_id].deletedAt = 0; + console.log(self.data[_id]); + deferred.resolve(response.data); + }, function (error) { + deferred.reject('Deleting ' + self.endpoint + ' failed'); + }); + return deferred.promise; + }; + BoardService.prototype.searchUsers = function (search) { var deferred = $q.defer(); var self = this; @@ -151,17 +178,6 @@ app.factory('BoardService', function(ApiService, $http, $q){ return deferred.promise; }; - BoardService.prototype.getPermissions = function() { - var board = this.getCurrent(); - var deferred = $q.defer(); - $http.get(this.baseUrl + '/' + board.id + '/permissions').then(function (response) { - board.permissions = response.data; - deferred.resolve(response.data); - }, function (error) { - deferred.reject('Error fetching board permissions ' + board); - }); - }; - BoardService.prototype.canRead = function() { if(!this.getCurrent() || !this.getCurrent().permissions) { return false; diff --git a/lib/Controller/BoardController.php b/lib/Controller/BoardController.php index 52c9641df..f497455d0 100644 --- a/lib/Controller/BoardController.php +++ b/lib/Controller/BoardController.php @@ -108,6 +108,14 @@ class BoardController extends Controller { public function delete($boardId) { return $this->boardService->delete($boardId); } + /** + * @NoAdminRequired + * @param $boardId + * @return \OCP\AppFramework\Db\Entity + */ + public function deleteUndo($boardId) { + return $this->boardService->deleteUndo($boardId); + } /** * @NoAdminRequired diff --git a/lib/Cron/DeleteCron.php b/lib/Cron/DeleteCron.php new file mode 100644 index 000000000..cdf35cedb --- /dev/null +++ b/lib/Cron/DeleteCron.php @@ -0,0 +1,49 @@ + + * + * @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 . + * + */ + +/** + * Created by PhpStorm. + * User: jus + * Date: 16.05.17 + * Time: 12:34 + */ + +namespace OCA\Deck\Cron; + +use OC\BackgroundJob\Job; +use OCA\Deck\Db\BoardMapper; + +class DeleteCron extends Job { + + public function __construct(BoardMapper $boardMapper) { + $this->boardMapper = $boardMapper; + } + + protected function run($argument) { + $boards = $this->boardMapper->findToDelete(); + foreach ($boards as $board) { + $this->boardMapper->delete($board); + } + } + +} \ No newline at end of file diff --git a/lib/Db/Board.php b/lib/Db/Board.php index 3c14c2177..93510ddeb 100644 --- a/lib/Db/Board.php +++ b/lib/Db/Board.php @@ -36,11 +36,13 @@ class Board extends RelationalEntity implements JsonSerializable { protected $acl = []; protected $permissions = []; protected $shared; + protected $deletedAt; public function __construct() { $this->addType('id', 'integer'); $this->addType('shared', 'integer'); $this->addType('archived', 'boolean'); + $this->addType('deletedAt', 'integer'); $this->addRelation('labels'); $this->addRelation('acl'); $this->addRelation('shared'); diff --git a/lib/Db/BoardMapper.php b/lib/Db/BoardMapper.php index e1b2d0598..0c16d9d6c 100644 --- a/lib/Db/BoardMapper.php +++ b/lib/Db/BoardMapper.php @@ -59,7 +59,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { * @return \OCP\AppFramework\Db\Entity if not found */ public function find($id, $withLabels = false, $withAcl = false) { - $sql = 'SELECT id, title, owner, color, archived FROM `*PREFIX*deck_boards` ' . + $sql = 'SELECT id, title, owner, color, archived, deleted_at FROM `*PREFIX*deck_boards` ' . 'WHERE `id` = ?'; $board = $this->findEntity($sql, [$id]); @@ -87,8 +87,8 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { * @return array */ public function findAllByUser($userId, $limit = null, $offset = null) { - $sql = 'SELECT id, title, owner, color, archived, 0 as shared FROM `*PREFIX*deck_boards` WHERE owner = ? UNION ' . - 'SELECT boards.id, title, owner, color, archived, 1 as shared FROM `*PREFIX*deck_boards` as boards ' . + $sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared FROM `*PREFIX*deck_boards` WHERE owner = ? UNION ' . + 'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared FROM `*PREFIX*deck_boards` as boards ' . 'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ?'; $entries = $this->findEntities($sql, [$userId, $userId, Acl::PERMISSION_TYPE_USER, $userId], $limit, $offset); /* @var Board $entry */ @@ -112,7 +112,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { if (count($groups) <= 0) { return []; } - $sql = 'SELECT boards.id, title, owner, color, archived, 2 as shared FROM `*PREFIX*deck_boards` as boards ' . + $sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared FROM `*PREFIX*deck_boards` as boards ' . 'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND ('; for ($i = 0; $i < count($groups); $i++) { $sql .= 'acl.participant = ? '; @@ -135,6 +135,15 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { return $this->findEntities($sql, []); } + public function findToDelete() { + // add buffer of 5 min + $timeLimit = time()-(60*5); + $sql = 'SELECT id, title, owner, color, archived, deleted_at FROM `*PREFIX*deck_boards` ' . + 'WHERE `deleted_at` > 0 AND `deleted_at` < ?'; + \OC::$server->getLogger()->error($sql); + return $this->findEntities($sql, [$timeLimit]); + } + public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ \OCP\AppFramework\Db\Entity $entity) { // delete acl diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php index 0aac91b07..e7037553c 100644 --- a/lib/Service/BoardService.php +++ b/lib/Service/BoardService.php @@ -107,6 +107,23 @@ class BoardService { return $board->getArchived(); } + public function isDeleted($mapper, $id) { + try { + if ($mapper instanceof IPermissionMapper) { + $boardId = $mapper->findBoardId($id); + } else { + $boardId = $id; + } + if ($boardId === null) { + return false; + } + } catch (DoesNotExistException $exception) { + return false; + } + $board = $this->find($boardId); + return $board->getDeletedAt() > 0; + } + public function create($title, $userId, $color) { @@ -139,7 +156,17 @@ class BoardService { public function delete($id) { $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ); - return $this->boardMapper->delete($this->find($id)); + $board = $this->find($id); + $board->setDeletedAt(time()); + $this->boardMapper->update($board); + return $board; + } + + public function deleteUndo($id) { + $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ); + $board = $this->find($id); + $board->setDeletedAt(0); + $this->boardMapper->update($board); } public function update($id, $title, $color, $archived) { diff --git a/templates/part.boardlist.php b/templates/part.boardlist.php index 32892494a..35380a435 100644 --- a/templates/part.boardlist.php +++ b/templates/part.boardlist.php @@ -9,12 +9,12 @@ - - + - + diff --git a/templates/part.navigation.php b/templates/part.navigation.php index 8d151fb81..96c9264fb 100644 --- a/templates/part.navigation.php +++ b/templates/part.navigation.php @@ -1,7 +1,7 @@
+
{{ b.title }}{{ b.title }}
@@ -22,8 +22,9 @@
-
- +
+
+
+
+ +