diff --git a/appinfo/info.xml b/appinfo/info.xml index b1eb7e61c..197810305 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Share your tables with users and groups within your cloud. Have a good time and manage whatever you want. ]]> - 0.6.0-dev0 + 0.6.0-dev1 agpl Florian Steffens Tables diff --git a/appinfo/routes.php b/appinfo/routes.php index dba66b010..eeeee7bc3 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -36,7 +36,7 @@ ['name' => 'api1#createColumn', 'url' => '/api/1/views/{viewId}/columns', 'verb' => 'POST'], ['name' => 'api1#updateColumn', 'url' => '/api/1/columns/{columnId}', 'verb' => 'PUT'], ['name' => 'api1#getColumn', 'url' => '/api/1/columns/{columnId}', 'verb' => 'GET'], - ['name' => 'api1#deleteColumn', 'url' => '/api/1/columns/{columnId}', 'verb' => 'DELETE'], + ['name' => 'api1#deleteColumn', 'url' => '/api/1/columns/{columnId}', 'verb' => 'DELETE'], // -> rows ['name' => 'api1#indexTableRowsSimple', 'url' => '/api/1/tables/{tableId}/rows/simple', 'verb' => 'GET'], ['name' => 'api1#indexViewRows', 'url' => '/api/1/views/{viewId}/rows', 'verb' => 'GET'], @@ -63,7 +63,8 @@ ['name' => 'view#destroy', 'url' => '/view/{id}', 'verb' => 'DELETE'], // columns - ['name' => 'column#index', 'url' => '/view/{viewId}/column/{tableId}', 'verb' => 'GET'], + ['name' => 'column#indexTableByView', 'url' => '/column/table/{tableId}/view/{viewId}', 'verb' => 'GET'], + ['name' => 'column#index', 'url' => '/column/table/{tableId}', 'verb' => 'GET'], ['name' => 'column#show', 'url' => '/column/{id}', 'verb' => 'GET'], ['name' => 'column#indexView', 'url' => '/column/view/{viewId}', 'verb' => 'GET'], ['name' => 'column#create', 'url' => '/column', 'verb' => 'POST'], @@ -71,12 +72,14 @@ ['name' => 'column#destroy', 'url' => '/column/{id}', 'verb' => 'DELETE'], // rows + ['name' => 'row#index', 'url' => '/row/table/{tableId}', 'verb' => 'GET'], ['name' => 'row#show', 'url' => '/row/{id}', 'verb' => 'GET'], ['name' => 'row#indexView', 'url' => '/row/view/{viewId}', 'verb' => 'GET'], ['name' => 'row#create', 'url' => '/row', 'verb' => 'POST'], ['name' => 'row#update', 'url' => '/row/{id}/column/{columnId}', 'verb' => 'PUT'], ['name' => 'row#updateSet', 'url' => '/row/{id}', 'verb' => 'PUT'], - ['name' => 'row#destroy', 'url' => '/view/{viewId}/row/{id}', 'verb' => 'DELETE'], + ['name' => 'row#destroyByView', 'url' => '/view/{viewId}/row/{id}', 'verb' => 'DELETE'], + ['name' => 'row#destroy', 'url' => '/table/{tableId}/row/{id}', 'verb' => 'DELETE'], // shares ['name' => 'share#index', 'url' => '/share/table/{tableId}', 'verb' => 'GET'], diff --git a/lib/Controller/ColumnController.php b/lib/Controller/ColumnController.php index 752da202a..41f49aa4f 100644 --- a/lib/Controller/ColumnController.php +++ b/lib/Controller/ColumnController.php @@ -32,7 +32,16 @@ public function __construct( /** * @NoAdminRequired */ - public function index(int $tableId, int $viewId): DataResponse { + public function index(int $tableId, ?int $viewId): DataResponse { + return $this->handleError(function () use ($tableId, $viewId) { + return $this->service->findAllByTable($tableId, $viewId); + }); + } + + /** + * @NoAdminRequired + */ + public function indexTableByView(int $tableId, ?int $viewId): DataResponse { return $this->handleError(function () use ($tableId, $viewId) { return $this->service->findAllByTable($tableId, $viewId); }); @@ -60,7 +69,8 @@ public function show(int $id): DataResponse { * @NoAdminRequired */ public function create( - int $viewId, + ?int $tableId, + ?int $viewId, string $type, ?string $subtype, string $title, @@ -85,6 +95,7 @@ public function create( ?array $selectedViewIds ): DataResponse { return $this->handleError(function () use ( + $tableId, $viewId, $type, $subtype, @@ -110,6 +121,7 @@ public function create( $selectedViewIds) { return $this->service->create( $this->userId, + $tableId, $viewId, $type, $subtype, diff --git a/lib/Controller/RowController.php b/lib/Controller/RowController.php index ee1b08606..60319fc4f 100644 --- a/lib/Controller/RowController.php +++ b/lib/Controller/RowController.php @@ -31,6 +31,15 @@ public function __construct( $this->userId = $userId; } + /** + * @NoAdminRequired + */ + public function index(int $tableId): DataResponse { + return $this->handleError(function () use ($tableId) { + return $this->service->findAllByTable($tableId); + }); + } + /** * @NoAdminRequired */ @@ -53,11 +62,13 @@ public function show(int $id): DataResponse { * @NoAdminRequired */ public function create( - int $viewId, + ?int $tableId, + ?int $viewId, array $data ): DataResponse { - return $this->handleError(function () use ($viewId, $data) { + return $this->handleError(function () use ($tableId, $viewId, $data) { return $this->service->create( + $tableId, $viewId, $data); }); @@ -69,17 +80,20 @@ public function create( public function update( int $id, int $columnId, - int $viewId, + ?int $tableId, + ?int $viewId, string $data ): DataResponse { return $this->handleError(function () use ( $id, + $tableId, $viewId, $columnId, $data ) { return $this->service->update( $id, + $tableId, $viewId, $columnId, $data, @@ -92,17 +106,20 @@ public function update( */ public function updateSet( int $id, - int $viewId, + ?int $tableId, + ?int $viewId, array $data ): DataResponse { return $this->handleError(function () use ( $id, + $tableId, $viewId, $data ) { return $this->service->updateSet( $id, + $tableId, $viewId, $data, $this->userId); @@ -112,9 +129,17 @@ public function updateSet( /** * @NoAdminRequired */ - public function destroy(int $id, int $viewId): DataResponse { + public function destroy(int $id, int $tableId): DataResponse { + return $this->handleError(function () use ($id, $tableId) { + return $this->service->delete($id, $tableId, null, $this->userId); + }); + } + /** + * @NoAdminRequired + */ + public function destroyByView(int $id, int $viewId): DataResponse { return $this->handleError(function () use ($id, $viewId) { - return $this->service->delete($id, $viewId, $this->userId); + return $this->service->delete($id, null, $viewId, $this->userId); }); } } diff --git a/lib/Db/ColumnMapper.php b/lib/Db/ColumnMapper.php index 4ddf4bf58..05631b694 100644 --- a/lib/Db/ColumnMapper.php +++ b/lib/Db/ColumnMapper.php @@ -46,6 +46,24 @@ public function findAllByTable(int $tableID): array { return $this->findEntities($qb); } + /** + * @param integer $tableID + * @return array + * @throws Exception + */ + public function findAllIdsByTable(int $tableID): array { + $qb = $this->db->getQueryBuilder(); + $qb->select('id') + ->from($this->table) + ->where($qb->expr()->eq('table_id', $qb->createNamedParameter($tableID))); + $result = $qb->executeQuery(); + $ids = []; + while ($row = $result->fetch()) { + $ids[] = $row['id']; + } + return $ids; + } + /** * @param array $neededColumnIds * @return array Array with key = columnId and value = [column-type]-[column-subtype] @@ -77,4 +95,26 @@ public function getColumnTypes(array $neededColumnIds): array { } return $out; } + + + /** + * @param int $tableId + * @return int + */ + public function countColumns(int $tableId): int { + $qb = $this->db->getQueryBuilder(); + $qb->select($qb->func()->count('*', 'counter')); + $qb->from($this->table); + $qb->where( + $qb->expr()->eq('table_id', $qb->createNamedParameter($tableId)) + ); + + try { + $result = $this->findOneQuery($qb); + return (int)$result['counter']; + } catch (DoesNotExistException|MultipleObjectsReturnedException|Exception $e) { + $this->logger->warning('Exception occurred: '.$e->getMessage().' Returning 0.'); + return 0; + } + } } diff --git a/lib/Db/RowMapper.php b/lib/Db/RowMapper.php index 2034cad5f..d2145aa79 100644 --- a/lib/Db/RowMapper.php +++ b/lib/Db/RowMapper.php @@ -155,33 +155,13 @@ private function addOrderByRules(IQueryBuilder $qb, $sortArray) { } } - /** - * - */ - public function countRowsForBaseView(View $view): int { - $qb = $this->db->getQueryBuilder(); - $qb->select($qb->func()->count('*', 'counter')); - $qb->from($this->table); - $qb->where( - $qb->expr()->eq('table_id', $qb->createNamedParameter($view->getTableId())) - ); - - try { - $result = $this->findOneQuery($qb); - return (int)$result['counter']; - } catch (DoesNotExistException|MultipleObjectsReturnedException|Exception $e) { - $this->logger->warning('Exception occurred: '.$e->getMessage().' Returning 0.'); - return 0; - } - } - /** * @param View $view * @param $userId * @return int * @throws InternalError */ - public function countRowsForNotBaseView(View $view, $userId): int { + public function countRowsForView(View $view, $userId): int { $qb = $this->db->getQueryBuilder(); $qb->select($qb->func()->count('*', 'counter')) ->from($this->table) @@ -252,6 +232,29 @@ private function addFilterToQuery(IQueryBuilder $qb, View $view, array $neededCo } } + /** + * @param int $tableId + * @param int|null $limit + * @param int|null $offset + * @return array + * @throws Exception + */ + public function findAllByTable(int $tableId, ?int $limit = null, ?int $offset = null): array { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->table) + ->where($qb->expr()->eq('table_id', $qb->createNamedParameter($tableId))); + + if ($limit !== null) { + $qb->setMaxResults($limit); + } + if ($offset !== null) { + $qb->setFirstResult($offset); + } + + return $this->findEntities($qb); + } + /** * @param View $view * @param string $userId diff --git a/lib/Db/ShareMapper.php b/lib/Db/ShareMapper.php index a76c8f5c1..a5aa08ef9 100644 --- a/lib/Db/ShareMapper.php +++ b/lib/Db/ShareMapper.php @@ -104,7 +104,6 @@ public function findAllSharesForNode(string $nodeType, int $nodeId, string $send $qb = $this->db->getQueryBuilder(); $qb->select('*') ->from($this->table) - ->where($qb->expr()->eq('sender', $qb->createNamedParameter($sender, IQueryBuilder::PARAM_STR))) ->andWhere($qb->expr()->eq('node_type', $qb->createNamedParameter($nodeType, IQueryBuilder::PARAM_STR))) ->andWhere($qb->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT))); return $this->findEntities($qb); diff --git a/lib/Db/Table.php b/lib/Db/Table.php index 917ca4fa6..95ed1d50c 100644 --- a/lib/Db/Table.php +++ b/lib/Db/Table.php @@ -8,6 +8,10 @@ /** * @psalm-suppress PropertyNotSetInConstructor * + * @method getTitle(): string + * @method setTitle(string $title) + * @method getEmoji(): string + * @method setEmoji(string $emoji) * @method getOwnership(): string * @method setOwnership(string $ownership) * @method getOwnerDisplayName(): string @@ -20,10 +24,12 @@ * @method setHasShares(bool $hasShares) * @method getRowsCount(): int * @method setRowsCount(int $rowsCount) - * @method getBaseView(): View - * @method setBaseView(View $setBaseView) + * @method getColumnsCount(): int + * @method setColumnsCount(int $rowsCount) * @method getViews(): array - * @method setViews(array $setViews) + * @method setViews(array $views) + * @method getColumns(): array + * @method setColumns(array $columns) * @method getCreatedBy(): string * @method setCreatedBy(string $createdBy) * @method getCreatedAt(): string @@ -35,7 +41,6 @@ */ class Table extends Entity implements JsonSerializable { protected ?string $title = null; - protected ?string $emoji = null; protected ?string $ownership = null; protected ?string $ownerDisplayName = null; @@ -48,8 +53,9 @@ class Table extends Entity implements JsonSerializable { protected ?bool $hasShares = false; protected ?int $rowsCount = 0; - protected ?View $baseView = null; + protected ?int $columnsCount = 0; protected ?array $views = null; + protected ?array $columns = null; public function __construct() { $this->addType('id', 'integer'); @@ -58,6 +64,8 @@ public function __construct() { public function jsonSerialize(): array { return [ 'id' => $this->id, + 'title' => $this->title, + 'emoji' => $this->emoji, 'ownership' => $this->ownership, 'ownerDisplayName' => $this->ownerDisplayName, 'createdBy' => $this->createdBy, @@ -68,7 +76,7 @@ public function jsonSerialize(): array { 'onSharePermissions' => $this->onSharePermissions, 'hasShares' => $this->hasShares, 'rowsCount' => $this->rowsCount, - 'baseView' => $this->baseView, + 'columnsCount' => $this->columnsCount, 'views' => $this->views, ]; } diff --git a/lib/Db/View.php b/lib/Db/View.php index 849d99fde..e2eff3dd6 100644 --- a/lib/Db/View.php +++ b/lib/Db/View.php @@ -24,8 +24,6 @@ * @method setEmoji(string $emoji) * @method getDescription(): string * @method setDescription(string $description) - * @method getIsBaseView(): bool - * @method setIsBaseView(bool $isBaseView) * @method getIsShared(): bool * @method setIsShared(bool $isShared) * @method getOnSharePermissions(): array @@ -51,8 +49,6 @@ class View extends Entity implements JsonSerializable { protected ?string $columns = null; // json protected ?string $sort = null; // json protected ?string $filter = null; // json - - protected ?bool $isBaseView = false; protected ?bool $isShared = null; protected ?array $onSharePermissions = null; protected ?bool $hasShares = false; @@ -112,16 +108,13 @@ public function jsonSerialize(): array { 'lastEditAt' => $this->lastEditAt, 'columns' => $this->getColumnsArray(), 'sort' => $this->getSortArray(), - 'isBaseView' => $this->isBaseView, 'isShared' => !!$this->isShared, 'onSharePermissions' => $this->onSharePermissions, 'hasShares' => $this->hasShares, 'rowsCount' => $this->rowsCount, 'ownerDisplayName' => $this->ownerDisplayName, ]; - if (!$this->isBaseView) { - $serialisedJson['filter'] = $this->getFilterArray(); - } + $serialisedJson['filter'] = $this->getFilterArray(); return $serialisedJson; } diff --git a/lib/Db/ViewMapper.php b/lib/Db/ViewMapper.php index 1df60af8c..abf13c9f4 100644 --- a/lib/Db/ViewMapper.php +++ b/lib/Db/ViewMapper.php @@ -43,27 +43,6 @@ public function find(int $id, bool $skipEnhancement = false): View { return $view; } - /** - * @param int|null $tableId - * @return View - * @throws DoesNotExistException - * @throws Exception - * @throws InternalError - * @throws MultipleObjectsReturnedException - */ - public function findBaseView(?int $tableId = null): View { - $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from($this->table); - if ($tableId !== null) { - $qb->where($qb->expr()->eq('table_id', $qb->createNamedParameter($tableId, IQueryBuilder::PARAM_INT))) - ->andWhere($qb->expr()->eq('is_base_view', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))); - } - $view = $this->findEntity($qb); - $this->enhanceByOwnership($view); - return $view; - } - /** * @param int|null $tableId * @return array @@ -84,27 +63,6 @@ public function findAll(?int $tableId = null): array { return $views; } - /** - * @param int|null $tableId - * @return array - * @throws Exception - * @throws InternalError - */ - public function findAllNotBaseViews(?int $tableId = null): array { - $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from($this->table); - if ($tableId !== null) { - $qb->where($qb->expr()->eq('table_id', $qb->createNamedParameter($tableId, IQueryBuilder::PARAM_INT))) - ->andWhere($qb->expr()->eq('is_base_view', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))); - } - $views = $this->findEntities($qb); - foreach($views as $view) { - $this->enhanceByOwnership($view); - } - return $views; - } - /** * @param string|null $term * @param string|null $userId diff --git a/lib/Migration/Version000600Date20230703000000.php b/lib/Migration/Version000600Date20230703000000.php index a4640b3e4..a68929827 100644 --- a/lib/Migration/Version000600Date20230703000000.php +++ b/lib/Migration/Version000600Date20230703000000.php @@ -48,10 +48,6 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt $table->addColumn('description', Types::TEXT, [ 'notnull' => true, ]); - $table->addColumn('is_base_view', Types::BOOLEAN, [ - 'notnull' => true, - 'default' => false, - ]); $table->addColumn('created_by', Types::STRING, [ 'notnull' => true, 'length' => 64, diff --git a/lib/Service/ColumnService.php b/lib/Service/ColumnService.php index cb411b12b..57e46f0be 100644 --- a/lib/Service/ColumnService.php +++ b/lib/Service/ColumnService.php @@ -157,7 +157,8 @@ public function find(int $id, string $userId = null): Column { */ public function create( ?string $userId, - int $viewId, + ?int $tableId, + ?int $viewId, string $type, ?string $subtype, string $title, @@ -182,8 +183,15 @@ public function create( ?array $selectedViewIds ):Column { // security - $view = $this->viewService->find($viewId); - $table = $this->tableMapper->find($view->getTableId()); + if ($viewId) { + $view = $this->viewService->find($viewId); + $table = $this->tableMapper->find($view->getTableId()); + } else if ($tableId) { + $table = $this->tableMapper->find($tableId); + } else { + throw new InternalError('Cannot update row without table or view in context'); + } + if (!$this->permissionsService->canCreateColumns($table)) { throw new PermissionError('create column at the table id = '.$table->getId().' is not allowed.'); } @@ -214,11 +222,9 @@ public function create( $item->setDatetimeDefault($datetimeDefault); try { $entity = $this->mapper->insert($item); - // Add columns to view(s) - $this->viewService->update($view->getId(), ['columns' => json_encode(array_merge($view->getColumnsArray(), [$entity->getId()]))], $userId, true); - if (!$view->getIsBaseView()){ - $baseView = $this->viewService->findBaseView($table, true); - $this->viewService->update($baseView->getId(), ['columns' => json_encode(array_merge($baseView->getColumnsArray(), [$entity->getId()]))], $userId, true); + if($viewId) { + // Add columns to view(s) + $this->viewService->update($view->getId(), ['columns' => json_encode(array_merge($view->getColumnsArray(), [$entity->getId()]))], $userId, true); } foreach ($selectedViewIds as $viewId) { $view = $this->viewService->find($viewId); @@ -424,10 +430,23 @@ public function findOrCreateColumnsByTitleForTableAsArray(int $viewId, array $ti // if column was not found if($result[$i] === '' && $createUnknownColumns) { $description = $this->l->t('This column was automatically created by the import service.'); - $result[$i] = $this->create($userId, $viewId, 'text', 'line', $title, false, $description, null, null, null, null, null, null, null, null, null, null, null, null, []); + $result[$i] = $this->create($userId, null, $viewId, 'text', 'line', $title, false, $description, null, null, null, null, null, null, null, null, null, null, null, null, []); $countCreatedColumns++; } } return $result; } + + /** + * @param int $tableId + * @return int + * @throws PermissionError + */ + public function getColumnsCount(int $tableId): int { + if ($this->permissionsService->canManageTableById($tableId)) { + return $this->mapper->countColumns($tableId); + } else { + throw new PermissionError('no read access for counting to table id = '.$tableId); + } + } } diff --git a/lib/Service/ImportService.php b/lib/Service/ImportService.php index fc053fd5a..b7f911e0a 100644 --- a/lib/Service/ImportService.php +++ b/lib/Service/ImportService.php @@ -67,7 +67,7 @@ public function import(int $viewId, string $path, bool $createMissingColumns = t if (!$this->permissionsService->canCreateRows($view)) { throw new PermissionError('create row at the view id = '.$viewId.' is not allowed.'); } - if ($createMissingColumns && (!$view->getIsBaseView() || !$this->permissionsService->canManageTableById($view->getTableId()))) { + if ($createMissingColumns && !$this->permissionsService->canManageTableById($view->getTableId())) { throw new PermissionError('create columns at the view id = '.$viewId.' is not allowed.'); } if ($this->userManager->get($this->userId) === null) { @@ -197,7 +197,7 @@ private function createRow(Row $row): void { ]; } try { - $this->rowService->create($this->viewId, $data); + $this->rowService->create(null, $this->viewId, $data); $this->countInsertedRows++; } catch (PermissionError $e) { $this->logger->error('Could not create row while importing, no permission.', ['exception' => $e]); diff --git a/lib/Service/PermissionsService.php b/lib/Service/PermissionsService.php index 22957e363..ef205800b 100644 --- a/lib/Service/PermissionsService.php +++ b/lib/Service/PermissionsService.php @@ -84,6 +84,19 @@ public function canAccessView($view, ?string $userId = null): bool { return false; } + /** + * @param int $elementId + * @param string $nodeType + * @param string|null $userId + * @return bool + * @throws InternalError + */ + public function canManageElementById(int $elementId, string $nodeType = 'table', ?string $userId = null): bool { + if ($nodeType === 'table') return $this->canManageTableById($elementId, $userId); + else if ($nodeType === 'view') return $this->canManageViewById($elementId, $userId); + else throw new InternalError('Cannot read permission for node type '.$nodeType); + } + /** * @param View $view * @param string|null $userId @@ -113,6 +126,22 @@ public function canManageTableById(int $tableId, ?string $userId = null): bool { return $this->canManageTable($table, $userId); } + public function canManageViewById(int $viewId, ?string $userId = null): bool { + try { + $view = $this->viewMapper->find($viewId); + } catch (MultipleObjectsReturnedException $e) { + $this->logger->warning('Multiple tables were found for this id'); + return false; + } catch (DoesNotExistException $e) { + $this->logger->warning('No table was found for this id'); + return false; + } catch (InternalError | Exception $e) { + $this->logger->warning('Error occurred: '.$e->getMessage()); + return false; + } + return $this->canManageView($view, $userId); + } + // ***** COLUMNS permissions ***** @@ -180,8 +209,9 @@ public function canReadRowsByElement($element, string $nodeType, ?string $userId * @param string|null $userId * @return bool */ - public function canCreateRows(View $view, ?string $userId = null): bool { - return $this->checkPermission($view, 'view', 'create', $userId); + public function canCreateRows($element, string $nodeType = 'view', ?string $userId = null): bool { + if ($nodeType === 'table') return $this->checkPermission($element, 'table', 'manage', $userId); + return $this->checkPermission($element, 'view', 'create', $userId); } /** @@ -236,6 +266,15 @@ public function canReadShare(Share $share, ?string $userId = null): bool { if ($userId === '') { return true; } + try { + if ($this->canManageElementById($share->getNodeId(), $share->getNodeType())){ + return true; + } + } catch (InternalError $e) { + $this->logger->warning('Cannot check manage permissions, permission denied'); + return false; + } + if ($share->getSender() === $userId) { return true; @@ -262,36 +301,6 @@ public function canReadShare(Share $share, ?string $userId = null): bool { return false; } - public function canUpdateShare(Share $item, ?string $userId = null): bool { - try { - $userId = $this->preCheckUserId($userId); - } catch (InternalError $e) { - $this->logger->warning('Cannot pre check the user id, permission denied'); - return false; - } - - if ($userId === '') { - return true; - } - - return $item->getSender() === $userId; - } - - public function canDeleteShare(Share $item, ?string $userId = null): bool { - try { - $userId = $this->preCheckUserId($userId); - } catch (InternalError $e) { - $this->logger->warning('Cannot pre check the user id, permission denied'); - return false; - } - - if ($userId === '') { - return true; - } - - return $item->getSender() === $userId; - } - /** * @param int $elementId * @param string|null $elementType diff --git a/lib/Service/RowService.php b/lib/Service/RowService.php index bd6d29250..6f80f38bb 100644 --- a/lib/Service/RowService.php +++ b/lib/Service/RowService.php @@ -4,8 +4,10 @@ use DateTime; use Exception; +use OCA\Tables\Db\ColumnMapper; use OCA\Tables\Db\Row; use OCA\Tables\Db\RowMapper; +use OCA\Tables\Db\TableMapper; use OCA\Tables\Db\View; use OCA\Tables\Db\ViewMapper; use OCA\Tables\Errors\InternalError; @@ -17,13 +19,38 @@ class RowService extends SuperService { private RowMapper $mapper; + private ColumnMapper $columnMapper; private ViewMapper $viewMapper; + private TableMapper $tableMapper; public function __construct(PermissionsService $permissionsService, LoggerInterface $logger, ?string $userId, - RowMapper $mapper, ViewMapper $viewMapper) { + RowMapper $mapper, ColumnMapper $columnMapper, ViewMapper $viewMapper, TableMapper $tableMapper) { parent::__construct($logger, $userId, $permissionsService); $this->mapper = $mapper; + $this->columnMapper = $columnMapper; $this->viewMapper = $viewMapper; + $this->tableMapper = $tableMapper; + } + + /** + * @param int $tableId + * @param ?int $limit + * @param ?int $offset + * @return array + * @throws InternalError + * @throws PermissionError + */ + public function findAllByTable(int $tableId, ?int $limit = null, ?int $offset = null): array { + try { + if ($this->permissionsService->canReadRowsByElementId($tableId, 'table')) { + return $this->mapper->findAllByTable($tableId, $limit, $offset); + } else { + throw new PermissionError('no read access to table id = '.$tableId); + } + } catch (\OCP\DB\Exception $e) { + $this->logger->error($e->getMessage()); + throw new InternalError($e->getMessage()); + } } /** @@ -89,9 +116,11 @@ public function find(int $id): Row { * @noinspection DuplicatedCode */ public function create( - int $viewId, + ?int $tableId, + ?int $viewId, array $data ):Row { + if ($viewId) { $view = $this->viewMapper->find($viewId); // security @@ -99,18 +128,28 @@ public function create( throw new PermissionError('create row at the view id = '.$viewId.' is not allowed.'); } - $viewColumns = $view->getColumnsArray(); + $columns = $view->getColumnsArray(); + } else if ($tableId) { + $table = $this->tableMapper->find($tableId); + // security + if (!$this->permissionsService->canCreateRows($table, 'table')) { + throw new PermissionError('create row at the table id = '.$tableId.' is not allowed.'); + } + $columns = $this->columnMapper->findAllIdsByTable($tableId); + } else { + throw new InternalError('Cannot create row without table or view in context'); + } foreach ($data as $entry) { - if (!in_array($entry['columnId'], $viewColumns)) { - throw new InternalError('Column with id '.$entry['columnId'].' is not part of view with id '.$view->getId()); + if (!in_array($entry['columnId'], $columns)) { + throw new InternalError('Column with id '.$entry['columnId'].' is not part of view with id '.$viewId ?? $tableId); } } $time = new DateTime(); $item = new Row(); $item->setDataArray($data); - $item->setTableId($view->getTableId()); + $item->setTableId($viewId ? $view->getTableId() : $tableId); $item->setCreatedBy($this->userId); $item->setCreatedAt($time->format('Y-m-d H:i:s')); $item->setLastEditBy($this->userId); @@ -134,7 +173,8 @@ public function create( */ public function update( int $id, - int $viewId, + ?int $tableId, + ?int $viewId, int $columnId, string $data, string $userId @@ -142,14 +182,23 @@ public function update( try { $item = $this->find($id); - // security - if (!$this->permissionsService->canUpdateRowsByViewId($viewId)) { - throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); - } - $view = $this->viewMapper->find($viewId); - $rowIds = $this->mapper->getRowIdsOfView($view, $userId); - if(!in_array($id, $rowIds)) { - throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + if ($viewId) { + // security + if (!$this->permissionsService->canUpdateRowsByViewId($viewId)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + $view = $this->viewMapper->find($viewId); + $rowIds = $this->mapper->getRowIdsOfView($view, $userId); + if(!in_array($id, $rowIds)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + } else if ($tableId) { + // security + if (!$this->permissionsService->canUpdateRowsByTableId($tableId)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + } else { + throw new InternalError('Cannot update row without table or view in context'); } $time = new DateTime(); @@ -191,21 +240,31 @@ public function update( */ public function updateSet( int $id, - int $viewId, + ?int $tableId, + ?int $viewId, array $data, string $userId ):Row { try { $item = $this->mapper->find($id); - // security - if (!$this->permissionsService->canUpdateRowsByViewId($viewId)) { - throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); - } - $view = $this->viewMapper->find($viewId); - $rowIds = $this->mapper->getRowIdsOfView($view, $userId); - if(!in_array($id, $rowIds)) { - throw new PermissionError('User should not be able to access row with id = '.$item->getId()); + if ($viewId) { + // security + if (!$this->permissionsService->canUpdateRowsByViewId($viewId)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + $view = $this->viewMapper->find($viewId); + $rowIds = $this->mapper->getRowIdsOfView($view, $userId); + if(!in_array($id, $rowIds)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + } else if ($tableId) { + // security + if (!$this->permissionsService->canUpdateRowsByTableId($tableId)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + } else { + throw new InternalError('Cannot update row without table or view in context'); } $time = new DateTime(); @@ -253,18 +312,27 @@ private function replaceOrAddData(array $dataArray, array $newDataObject): array * @throws NotFoundError * @throws PermissionError */ - public function delete(int $id, int $viewId, string $userId): Row { + public function delete(int $id, ?int $tableId, ?int $viewId, string $userId): Row { try { $item = $this->mapper->find($id); - // security - if (!$this->permissionsService->canDeleteRowsByViewId($viewId)) { - throw new PermissionError('delete row id = '.$item->getId().' is not allowed.'); - } - $view = $this->viewMapper->find($viewId); - $rowIds = $this->mapper->getRowIdsOfView($view, $userId); - if(!in_array($id, $rowIds)) { - throw new PermissionError('User should not be able to access row with id = '.$item->getId()); + if ($viewId) { + // security + if (!$this->permissionsService->canDeleteRowsByViewId($viewId)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + $view = $this->viewMapper->find($viewId); + $rowIds = $this->mapper->getRowIdsOfView($view, $userId); + if(!in_array($id, $rowIds)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + } else if ($tableId) { + // security + if (!$this->permissionsService->canDeleteRowsByTableId($tableId)) { + throw new PermissionError('update row id = '.$item->getId().' is not allowed.'); + } + } else { + throw new InternalError('Cannot update row without table or view in context'); } $this->mapper->delete($item); @@ -342,11 +410,7 @@ public function getRowsCount(int $tableId): int { */ public function getViewRowsCount(View $view, string $userId): int { if ($this->permissionsService->canReadRowsByElementId($view->getId(), 'view')) { - if ($view->getIsBaseView()) { - return $this->mapper->countRowsForBaseView($view); - } else { - return $this->mapper->countRowsForNotBaseView($view, $userId); - } + return $this->mapper->countRowsForView($view, $userId); } else { throw new PermissionError('no read access for counting to view id = '.$view->getId()); } diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index 2c76fe971..2f4834acb 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -226,7 +226,7 @@ public function updatePermission(int $id, string $permission, bool $value): Shar $item = $this->mapper->find($id); // security - if (!$this->permissionsService->canUpdateShare($item)) { + if (!$this->permissionsService->canManageElementById($item->getNodeId(), $item->getNodeType())) { throw new PermissionError('PermissionError: can not update share with id '.$id); } @@ -272,7 +272,7 @@ public function delete(int $id): Share { $item = $this->mapper->find($id); // security - if (!$this->permissionsService->canDeleteShare($item)) { + if (!$this->permissionsService->canManageElementById($item->getNodeId(), $item->getNodeType())) { throw new PermissionError('PermissionError: can not delete share with id '.$id); } diff --git a/lib/Service/TableService.php b/lib/Service/TableService.php index 426a42173..5ce155065 100644 --- a/lib/Service/TableService.php +++ b/lib/Service/TableService.php @@ -157,6 +157,13 @@ private function enhanceTable(Table $table, string $userId): void { $table->setRowsCount(0); } + // add the column count + try { + $table->setColumnsCount($this->columnService->getColumnsCount($table->getId())); + } catch (InternalError|PermissionError $e) { + $table->setRowsCount(0); + } + // set if this is a shared table with you (somebody else shared it with you) // (senseless if we have no user in context) if ($userId !== '') { @@ -167,18 +174,11 @@ private function enhanceTable(Table $table, string $userId): void { } catch (NotFoundError $e) { } } - - // add the corresponding views - try { - $table->setBaseView($this->viewService->findBaseView($table)); - } catch (DoesNotExistException $e) { - // Create new base view if none exists (backward compatibility) - /** @noinspection PhpUndefinedMethodInspection */ - $view = $this->viewService->create($table->getTitle(), $table->getEmoji(), $table, true); - $view = $this->viewService->update($view->getId(), ["columns" => json_encode(array_column($this->columnService->findAllByTable($table->getId()), 'id'))]); - $table->setBaseView($view); + if (!$table->getIsShared() || $table->getOnSharePermissions()['manage']) { + // add the corresponding views if it is an own table, or you have table manage rights + $table->setViews($this->viewService->findAll($table)); } - $table->setViews($this->viewService->findAllNotBaseViews($table)); + } @@ -236,9 +236,10 @@ public function create(string $title, string $template, ?string $emoji, ?string $time = new DateTime(); $item = new Table(); - // Deprecated mandatory attribute - /** @noinspection PhpUndefinedMethodInspection */ - $item->setTitle('TODO: DELETE'); + $item->setTitle($title); + if($emoji) { + $item->setEmoji($emoji); + } $item->setOwnership($userId); $item->setCreatedBy($userId); $item->setLastEditBy($userId); @@ -250,9 +251,8 @@ public function create(string $title, string $template, ?string $emoji, ?string $this->logger->error($e->getMessage()); throw new InternalError($e->getMessage()); } - $baseView = $this->viewService->create($title, $emoji, $newTable, true, $userId); if ($template !== 'custom') { - $table = $this->tableTemplateService->makeTemplate($newTable, $template, $baseView->getId()); + $table = $this->tableTemplateService->makeTemplate($newTable, $template); } else { $table = $this->addOwnerDisplayName($newTable); } diff --git a/lib/Service/TableTemplateService.php b/lib/Service/TableTemplateService.php index d24b41946..86e47baf0 100644 --- a/lib/Service/TableTemplateService.php +++ b/lib/Service/TableTemplateService.php @@ -76,7 +76,7 @@ public function getTemplateList(): array { /** * @param Table $table * @param string $template - * @param int $baseViewId + * @param int $defaultViewId * @return Table * @throws DoesNotExistException * @throws Exception @@ -85,9 +85,9 @@ public function getTemplateList(): array { * @throws NotFoundError * @throws PermissionError */ - public function makeTemplate(Table $table, string $template, int $baseViewId): Table { - $createColumn = function ($params) use ($table, $baseViewId) {return $this->createColumn($params, $baseViewId);}; - $createRow = function ($data) use ($table, $baseViewId) {$this->createRow($baseViewId, $data);}; + public function makeTemplate(Table $table, string $template): Table { + $createColumn = function ($params) use ($table) {return $this->createColumn($params, $table->getId());}; + $createRow = function ($data) use ($table) {$this->createRow($table->getId(), $data);}; $createView = function ($data) use ($table) {$this->createView($table, $data);}; if ($template === 'todo') { $this->makeTodo($createColumn, $createRow); @@ -758,7 +758,7 @@ private function makeStartupTable($createColumn, $createRow):void { /** * @param (mixed)[] $parameters - * @param int $baseViewId + * @param int $defaultViewId * @return Column * @throws Exception * @throws InternalError @@ -766,7 +766,7 @@ private function makeStartupTable($createColumn, $createRow):void { * @throws DoesNotExistException * @throws MultipleObjectsReturnedException */ - private function createColumn(array $parameters, int $baseViewId): ?Column { + private function createColumn(array $parameters, int $tableId): ?Column { if ($this->userId === null) { return null; } @@ -776,8 +776,11 @@ private function createColumn(array $parameters, int $baseViewId): ?Column { // userId $this->userId, - // baseViewId - $baseViewId, + // tableId + $tableId, + + // viewId + null, // column type (isset($parameters['type'])) ? $parameters['type'] : 'text', @@ -843,7 +846,7 @@ private function createColumn(array $parameters, int $baseViewId): ?Column { * @throws InternalError * @throws MultipleObjectsReturnedException */ - private function createRow(int $viewId, array $values): void { + private function createRow(int $tableId, array $values): void { $data = []; foreach ($values as $columnId => $value) { $data[] = [ @@ -852,7 +855,7 @@ private function createRow(int $viewId, array $values): void { ]; } try { - $this->rowService->create($viewId, $data); + $this->rowService->create($tableId, null, $data); } catch (PermissionError $e) { $this->logger->warning('Cannot create row, permission denied: '.$e->getMessage()); } catch (Exception $e) { diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index eea3dfd12..4bec18148 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -57,30 +57,6 @@ public function __construct( * @throws PermissionError */ public function findAll(Table $table, ?string $userId = null): array { - return $this->findAllGeneralised($table, true, $userId); - } - - /** - * @param Table $table - * @param string|null $userId - * @return array - * @throws InternalError - * @throws PermissionError - */ - public function findAllNotBaseViews(Table $table, ?string $userId = null): array { - return $this->findAllGeneralised($table, false, $userId); - } - - /** - * @param Table $table - * @param bool $includeBaseView - * @param string|null $userId - * @return array - * @throws InternalError - * @throws PermissionError - */ - private function findAllGeneralised(Table $table, bool $includeBaseView = true, ?string $userId = null): array { - /** @var string $userId */ $userId = $this->permissionsService->preCheckUserId($userId); // $userId can be set or '' try { @@ -89,7 +65,7 @@ private function findAllGeneralised(Table $table, bool $includeBaseView = true, throw new PermissionError('PermissionError: can not read views for tableId '.$table->getId()); } - $allViews = $includeBaseView ? $this->mapper->findAll($table->getId()) : $this->mapper->findAllNotBaseViews($table->getId()); + $allViews = $this->mapper->findAll($table->getId()); foreach ($allViews as $view) { $this->enhanceView($view, $userId); } @@ -103,39 +79,6 @@ private function findAllGeneralised(Table $table, bool $includeBaseView = true, } } - /** - * @param Table $table - * @param bool $skipTableEnhancement - * @param string|null $userId - * @return View - * @throws InternalError - * @throws PermissionError - * @throws DoesNotExistException - * @throws MultipleObjectsReturnedException - */ - public function findBaseView(Table $table, bool $skipTableEnhancement = false, ?string $userId = null): View { - /** @var string $userId */ - $userId = $this->permissionsService->preCheckUserId($userId); // $userId can be set or '' - - try { - // security - if (!$this->permissionsService->canManageTable($table, $userId)) { - throw new PermissionError('PermissionError: can not read views for tableId '.$table->getId()); - } - $baseView = $this->mapper->findBaseView($table->getId()); - if(!$skipTableEnhancement) { - $this->enhanceView($baseView, $userId); - } - return $baseView; - } catch (\OCP\DB\Exception $e) { - $this->logger->error($e->getMessage(), ['exception' => $e]); - throw new InternalError($e->getMessage()); - } catch (PermissionError $e) { - $this->logger->debug('permission error during looking for views', ['exception' => $e]); - throw new PermissionError($e->getMessage()); - } - } - /** * @param int $id * @param bool $skipEnhancement @@ -190,13 +133,12 @@ public function findSharedViewsWithMe(?string $userId = null): array { * @param string $title * @param string|null $emoji * @param Table $table - * @param bool $isBaseView * @param string|null $userId * @return View * @throws InternalError * @throws PermissionError */ - public function create(string $title, ?string $emoji, Table $table, bool $isBaseView = false, ?string $userId = null): View { + public function create(string $title, ?string $emoji, Table $table, ?string $userId = null): View { /** @var string $userId */ $userId = $this->permissionsService->preCheckUserId($userId, false); // $userId is set @@ -212,7 +154,6 @@ public function create(string $title, ?string $emoji, Table $table, bool $isBase $item->setEmoji($emoji); } $item->setDescription(''); - $item->setIsBaseView($isBaseView); $item->setTableId($table->getId()); $item->setCreatedBy($userId); $item->setLastEditBy($userId); @@ -295,9 +236,6 @@ public function delete(int $id, ?string $userId = null): View { try { $view = $this->mapper->find($id); - if ($view->getIsBaseView()) { - throw new InternalError('To delete the base view, delete the table instead'); - } // security if (!$this->permissionsService->canManageView($view, $userId)) { @@ -402,9 +340,6 @@ private function enhanceView(View $view, string $userId): void { if ($userId !== '') { try { $allShares = $this->shareService->findAll('view', $view->getId()); - if ($view->getIsBaseView()) { - $allShares = array_merge($allShares, $this->shareService->findAll('table', $view->getTableId())); - } $view->setHasShares(count($allShares) !== 0); } catch (InternalError $e) { } @@ -418,23 +353,15 @@ private function enhanceView(View $view, string $userId): void { * @throws InternalError * @throws PermissionError */ - public function deleteAllByTable(Table $table, ?string $userId = null): View { + public function deleteAllByTable(Table $table, ?string $userId = null): void { // security if (!$this->permissionsService->canManageTable($table, $userId)) { throw new PermissionError('delete all rows for table id = '.$table->getId().' is not allowed.'); } $views = $this->findAll($table, $userId); foreach ($views as $view) { - if($view->getIsBaseView()) { - $baseView = $view; - } else { - $this->deleteByObject($view, $userId); - } - } - if (!isset($baseView)) { - throw new InternalError('No base view exists for this table'); + $this->deleteByObject($view, $userId); } - return $this->deleteByObject($baseView, $userId); } /** diff --git a/src/App.vue b/src/App.vue index a978351bc..dab111b50 100644 --- a/src/App.vue +++ b/src/App.vue @@ -37,7 +37,6 @@ export default { }, computed: { ...mapState(['tablesLoading']), - ...mapGetters(['getBaseView']), somethingIsLoading() { return this.tablesLoading || this.loading }, @@ -54,18 +53,13 @@ export default { }, methods: { routing(currentRoute) { - if (currentRoute.name === 'view') { + if (currentRoute.name === 'table') { + this.$store.commit('setActiveTableId', parseInt(currentRoute.params.tableId)) + } else if (currentRoute.name === 'view') { this.$store.commit('setActiveViewId', parseInt(currentRoute.params.viewId)) } else if (currentRoute.name === 'row') { this.$store.commit('setActiveViewId', parseInt(currentRoute.params.viewId)) this.$store.commit('setActiveRowId', parseInt(currentRoute.params.rowId)) - } else if (currentRoute.name === 'table') { - const baseView = this.getBaseView(parseInt(currentRoute.params.tableId)) - if (baseView) { - this.$router.push('/view/' + baseView.id) - } else { - this.$router.push('/') - } } }, }, diff --git a/src/modules/main/modals/CreateColumn.vue b/src/modules/main/modals/CreateColumn.vue index 1bb844f05..9b848aeab 100644 --- a/src/modules/main/modals/CreateColumn.vue +++ b/src/modules/main/modals/CreateColumn.vue @@ -177,7 +177,7 @@ export default { } }, computed: { - ...mapGetters(['activeView']), + ...mapGetters(['activeElement', 'isView']), combinedType: { get() { return this.column.type ? this.column.type + ((this.column.subtype) ? ('-' + this.column.subtype) : '') : null @@ -248,7 +248,8 @@ export default { description: this.column.description, selectedViewIds: this.column.selectedViews.map(view => view.id), mandatory: this.column.mandatory, - viewId: this.activeView.id, + viewId: this.isView ? this.activeElement.id : null, + tableId: !this.isView ? this.activeElement.id : null, } if (this.combinedType === ColumnTypes.TextLine || this.combinedType === ColumnTypes.TextLong) { data.textDefault = this.column.textDefault @@ -281,7 +282,7 @@ export default { showWarning(t('tables', 'Sorry, something went wrong.')) console.debug('axios error', res) } - await this.$store.dispatch('reloadViewsOfTable', { tableId: this.activeView.tableId }) + await this.$store.dispatch('reloadViewsOfTable', { tableId: this.isView ? this.activeElement.tableId : this.activeElement.id }) } catch (e) { console.error(e) showError(t('tables', 'Could not create new column.')) diff --git a/src/modules/main/modals/CreateRow.vue b/src/modules/main/modals/CreateRow.vue index e56080509..909905224 100644 --- a/src/modules/main/modals/CreateRow.vue +++ b/src/modules/main/modals/CreateRow.vue @@ -61,7 +61,7 @@ export default { } }, computed: { - ...mapGetters(['activeView']), + ...mapGetters(['activeElement', 'isView']), nonMetaColumns() { return this.columns.filter(col => col.id >= 0) }, @@ -115,7 +115,11 @@ export default { value, }) } - await this.$store.dispatch('insertNewRow', { viewId: this.activeView.id, data }) + await this.$store.dispatch('insertNewRow', { + viewId: this.isView ? this.activeElement.id : null, + tableId: !this.isView ? this.activeElement.id : null, + data, + }) } catch (e) { console.error(e) showError(t('tables', 'Could not create new row')) diff --git a/src/modules/main/modals/DeleteColumn.vue b/src/modules/main/modals/DeleteColumn.vue index 4ae31c5ed..f391660bc 100644 --- a/src/modules/main/modals/DeleteColumn.vue +++ b/src/modules/main/modals/DeleteColumn.vue @@ -28,7 +28,7 @@ export default { }, }, computed: { - ...mapGetters(['activeView']), + ...mapGetters(['activeElement', 'isView']), deleteDescription() { return t('tables', 'Are you sure you want to delete column "{column}"?', { column: this.columnToDelete.title }) }, @@ -39,7 +39,7 @@ export default { if (!res) { showError(t('tables', 'Error occurred while deleting column "{column}".', { column: this.column.title })) } - await this.$store.dispatch('reloadViewsOfTable', { tableId: this.activeView.tableId }) + await this.$store.dispatch('reloadViewsOfTable', { tableId: this.isView ? this.activeElement.tableId : this.activeElement.id }) this.$emit('cancel') }, }, diff --git a/src/modules/main/modals/EditColumn.vue b/src/modules/main/modals/EditColumn.vue index ccbaebbdb..ffbea7470 100644 --- a/src/modules/main/modals/EditColumn.vue +++ b/src/modules/main/modals/EditColumn.vue @@ -102,6 +102,10 @@ export default { type: Object, default: null, }, + view: { + type: Object, + default: null, + }, }, data() { return { diff --git a/src/modules/main/modals/EditRow.vue b/src/modules/main/modals/EditRow.vue index d0ede222d..3345b15c7 100644 --- a/src/modules/main/modals/EditRow.vue +++ b/src/modules/main/modals/EditRow.vue @@ -27,7 +27,7 @@ {{ t('tables', 'I really want to delete this row!') }} - + {{ t('tables', 'Save') }}
@@ -75,9 +75,9 @@ export default { } }, computed: { - ...mapGetters(['activeView']), + ...mapGetters(['activeElement', 'isView']), showDeleteButton() { - return this.canDeleteData(this.activeView) && !this.localLoading + return this.canDeleteData(this.activeElement) && !this.localLoading }, nonMetaColumns() { return this.columns.filter(col => col.id >= 0) @@ -139,7 +139,12 @@ export default { value, }) } - const res = await this.$store.dispatch('updateRow', { id: this.row.id, viewId: this.activeView.id, data }) + const res = await this.$store.dispatch('updateRow', { + id: this.row.id, + viewId: this.isView ? this.activeElement.id : null, + tableId: !this.isView ? this.activeElement.id : null, + data, + }) if (!res) { showError(t('tables', 'Could not update row')) } @@ -154,7 +159,11 @@ export default { }, async deleteRowAtBE(rowId) { this.localLoading = true - const res = await this.$store.dispatch('removeRow', { rowId, viewId: this.activeView.id }) + const res = await this.$store.dispatch('removeRow', { + rowId, + viewId: this.isView ? this.activeElement.id : null, + tableId: !this.isView ? this.activeElement.id : null, + }) if (!res) { showError(t('tables', 'Could not delete row.')) } diff --git a/src/modules/main/modals/ViewSettings.vue b/src/modules/main/modals/ViewSettings.vue index 859b163f9..26f4a9f7a 100644 --- a/src/modules/main/modals/ViewSettings.vue +++ b/src/modules/main/modals/ViewSettings.vue @@ -1,5 +1,5 @@ @@ -72,9 +72,17 @@ export default { type: Array, default: null, }, - isBaseView: { + viewColumnIds: { + type: Array, + default: null, + }, + generatedColumnIds: { + type: Array, + default: null, + }, + disableHide: { type: Boolean, - default: false, + default: true, }, }, data() { @@ -86,6 +94,10 @@ export default { } }, methods: { + isLocallyRemoved(columnId) { + if (!this.viewColumnIds || !this.generatedColumnIds) return false + return !this.selectedColumns.includes(columnId) && !this.generatedColumnIds.includes(columnId) && this.viewColumnIds.includes(columnId) + }, onToggle(columnId) { if (this.mutableSelectedColumns.includes(columnId)) { this.mutableSelectedColumns.splice(this.mutableSelectedColumns.indexOf(columnId), 1) @@ -126,80 +138,83 @@ export default { diff --git a/src/modules/main/partials/editViewPartials/filter/FilterForm.vue b/src/modules/main/partials/editViewPartials/filter/FilterForm.vue index c306f0539..3b8dc552f 100644 --- a/src/modules/main/partials/editViewPartials/filter/FilterForm.vue +++ b/src/modules/main/partials/editViewPartials/filter/FilterForm.vue @@ -6,6 +6,8 @@
@@ -45,6 +47,14 @@ export default { type: Array, default: null, }, + viewFilters: { + type: Array, + default: null, + }, + generatedFilters: { + type: Array, + default: null, + }, columns: { type: Array, default: null, diff --git a/src/modules/main/partials/editViewPartials/filter/FilterGroup.vue b/src/modules/main/partials/editViewPartials/filter/FilterGroup.vue index 78e8d584b..36cb00a88 100644 --- a/src/modules/main/partials/editViewPartials/filter/FilterGroup.vue +++ b/src/modules/main/partials/editViewPartials/filter/FilterGroup.vue @@ -8,6 +8,7 @@
object[key] === searchObject[key]) + }, + isLocallyAdded(filter) { + if (!this.viewFilterGroup || !this.generatedFilterGroup) return false + return this.generatedFilterGroup.some(e => this.isSameEntry(e, filter)) && !this.viewFilterGroup.some(e => this.isSameEntry(e, filter)) + }, addFilter() { this.mutableFilterGroup.push({ columnId: null, operator: null, value: '' }) }, @@ -79,4 +95,8 @@ export default { border-left: 6px solid var(--color-primary) !important; padding-left: calc(var(--default-grid-baseline) * 2); } + +.locallyAdded { + background-color: var(--color-success-hover); +} diff --git a/src/modules/main/partials/editViewPartials/sort/DeletedSortEntry.vue b/src/modules/main/partials/editViewPartials/sort/DeletedSortEntry.vue new file mode 100644 index 000000000..6570927e3 --- /dev/null +++ b/src/modules/main/partials/editViewPartials/sort/DeletedSortEntry.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/modules/main/partials/editViewPartials/sort/SortEntry.vue b/src/modules/main/partials/editViewPartials/sort/SortEntry.vue index 1f7bf6473..0e851dfd7 100644 --- a/src/modules/main/partials/editViewPartials/sort/SortEntry.vue +++ b/src/modules/main/partials/editViewPartials/sort/SortEntry.vue @@ -78,6 +78,10 @@ export default { } }, watch: { + sortEntry() { + console.debug("Resetting!!!!") + this.reset() + }, selectedColumn() { this.mutableSortEntry.columnId = this.selectedColumn?.id }, diff --git a/src/modules/main/partials/editViewPartials/sort/SortForm.vue b/src/modules/main/partials/editViewPartials/sort/SortForm.vue index 64a2a84e4..36a62ef04 100644 --- a/src/modules/main/partials/editViewPartials/sort/SortForm.vue +++ b/src/modules/main/partials/editViewPartials/sort/SortForm.vue @@ -1,7 +1,17 @@ {{ t('tables', 'Hide column') }} - + {{ t('tables', 'Edit column') }} - + @@ -124,7 +124,7 @@ import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue' import FilterCog from 'vue-material-design-icons/FilterCog.vue' import Magnify from 'vue-material-design-icons/Magnify.vue' import { NcActions, NcActionButton, NcActionInput, NcActionButtonGroup, NcActionCaption, NcActionRadio } from '@nextcloud/vue' -import { mapState, mapGetters } from 'vuex' +import { mapState } from 'vuex' import { AbstractColumn } from '../mixins/columnClass.js' import { FilterIds } from '../mixins/filter.js' import permissionsMixin from '../mixins/permissionsMixin.js' @@ -161,6 +161,14 @@ export default { type: Boolean, default: false, }, + element: { + type: Object, + default: () => {}, + }, + isView: { + type: Boolean, + default: false, + }, }, data() { return { @@ -176,7 +184,6 @@ export default { ...mapState({ viewSetting: state => state.data.viewSetting, }), - ...mapGetters(['activeView']), getOperators() { const possibleOperators = this.column.getPossibleOperators() return possibleOperators diff --git a/src/shared/components/ncTable/partials/TableRow.vue b/src/shared/components/ncTable/partials/TableRow.vue index 71124c9e7..01f54916f 100644 --- a/src/shared/components/ncTable/partials/TableRow.vue +++ b/src/shared/components/ncTable/partials/TableRow.vue @@ -8,7 +8,7 @@ :value="getCellValue(col)" /> - + @@ -72,9 +72,12 @@ export default { type: Object, default: null, }, + view: { + type: Object, + default: () => {}, + }, }, computed: { - ...mapGetters(['activeView']), getSelection: { get: () => { return this.selected }, set: () => { alert('updating selection') }, diff --git a/src/shared/components/ncTable/partials/columnTypePartials/forms/MainForm.vue b/src/shared/components/ncTable/partials/columnTypePartials/forms/MainForm.vue index 9654e44b0..0cf593ffc 100644 --- a/src/shared/components/ncTable/partials/columnTypePartials/forms/MainForm.vue +++ b/src/shared/components/ncTable/partials/columnTypePartials/forms/MainForm.vue @@ -26,7 +26,7 @@
- {{ activeView.isBaseView? t('tables', 'Add column to views') : t('tables', 'Add column to other views') }} + {{ t('tables', 'Add column to other views') }}
view.tableId === this.activeView.tableId && view !== this.activeView && !view.isBaseView).filter(view => !this.localSelectedViews.includes(view)) + if (this.isView) { + return this.views.filter(view => view.tableId === this.activeElement.tableId && view !== this.activeElement).filter(view => !this.localSelectedViews.includes(view)) + } + return this.views.filter(view => view.tableId === this.activeElement.id).filter(view => !this.localSelectedViews.includes(view)) }, }, mounted() { if (this.editColumn) return - if (this.activeView.isBaseView) { + if (!this.isView) { this.localSelectedViews = this.viewsForTable } else { this.localSelectedViews = [] diff --git a/src/shared/components/ncTable/sections/CustomTable.vue b/src/shared/components/ncTable/sections/CustomTable.vue index 5fb491fd5..2fda82862 100644 --- a/src/shared/components/ncTable/sections/CustomTable.vue +++ b/src/shared/components/ncTable/sections/CustomTable.vue @@ -5,8 +5,9 @@ diff --git a/src/store/data.js b/src/store/data.js index 06e245a2a..b99d08050 100644 --- a/src/store/data.js +++ b/src/store/data.js @@ -117,9 +117,14 @@ export default { let res = null try { - if (tableId) { - res = await axios.get(generateUrl('/apps/tables/view/' + viewId + '/column/' + tableId)) - } else { + if (tableId && viewId) { + // Get all table columns. Try to access from view (Test if you have read access for view to read table columns) + res = await axios.get(generateUrl('/apps/tables/column/table/' + tableId + '/view/' + viewId)) + } else if (tableId && !viewId) { + // Get all table columns without view. Table manage rights needed + res = await axios.get(generateUrl('/apps/tables/column/table/' + tableId)) + } else if (!tableId && viewId) { + // Get all view columns. res = await axios.get(generateUrl('/apps/tables/column/view/' + viewId)) } if (!Array.isArray(res.data)) { @@ -135,13 +140,14 @@ export default { commit('setLoading', false) return columns }, - async loadColumnsFromBE({ commit, dispatch }, { view }) { - const columns = await dispatch('getColumnsFromBE', { viewId: view.id }) - let allColumns = columns.concat(MetaColumns.filter(col => view.columns.includes(col.id))) - allColumns = allColumns.sort(function(a, b) { - return view.columns.indexOf(a.id) - view.columns.indexOf(b.id) - }) - + async loadColumnsFromBE({ commit, dispatch }, { view, table }) { + let allColumns = await dispatch('getColumnsFromBE', { tableId: table?.id, viewId: view?.id }) + if (view) { + allColumns = allColumns.concat(MetaColumns.filter(col => view.columns.includes(col.id))) + allColumns = allColumns.sort(function(a, b) { + return view.columns.indexOf(a.id) - view.columns.indexOf(b.id) + }) + } commit('setColumns', allColumns) return true }, @@ -199,12 +205,16 @@ export default { }, // ROWS - async loadRowsFromBE({ commit }, { viewId }) { + async loadRowsFromBE({ commit }, { tableId, viewId }) { commit('setLoading', true) let res = null try { - res = await axios.get(generateUrl('/apps/tables/row/view/' + viewId)) + if (viewId) { + res = await axios.get(generateUrl('/apps/tables/row/view/' + viewId)) + } else { + res = await axios.get(generateUrl('/apps/tables/row/table/' + tableId)) + } } catch (e) { displayError(e, t('tables', 'Could not load rows.')) return false @@ -218,11 +228,11 @@ export default { removeRows({ commit }) { commit('setRows', []) }, - async updateRow({ state, commit, dispatch }, { id, viewId, data }) { + async updateRow({ state, commit, dispatch }, { id, viewId, tableId, data }) { let res = null try { - res = await axios.put(generateUrl('/apps/tables/row/' + id), { viewId, data }) + res = await axios.put(generateUrl('/apps/tables/row/' + id), { viewId, tableId, data }) } catch (e) { console.debug(e?.response) if (e?.response?.data?.message?.startsWith('User should not be able to access row')) { @@ -241,11 +251,11 @@ export default { commit('setRows', [...rows]) return true }, - async insertNewRow({ state, commit, dispatch }, { viewId, data }) { + async insertNewRow({ state, commit, dispatch }, { viewId, tableId, data }) { let res = null try { - res = await axios.post(generateUrl('/apps/tables/row'), { viewId, data }) + res = await axios.post(generateUrl('/apps/tables/row'), { viewId, tableId, data }) } catch (e) { displayError(e, t('tables', 'Could not insert row.')) return false @@ -257,9 +267,10 @@ export default { commit('setRows', [...rows]) return true }, - async removeRow({ state, commit, dispatch }, { rowId, viewId }) { + async removeRow({ state, commit, dispatch }, { rowId, tableId, viewId }) { try { - await axios.delete(generateUrl('/apps/tables/view/' + viewId + '/row/' + rowId)) + if (viewId) await axios.delete(generateUrl('/apps/tables/view/' + viewId + '/row/' + rowId)) + else await axios.delete(generateUrl('/apps/tables/table/' + tableId + '/row/' + rowId)) } catch (e) { if (e?.response?.data?.message?.startsWith('User should not be able to access row')) { showError(t('tables', 'Outdated data. View is reloaded')) diff --git a/src/store/store.js b/src/store/store.js index acba7180a..6a7444197 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -20,25 +20,41 @@ export default new Vuex.Store({ tables: [], views: [], activeViewId: null, + activeTableId: null, activeRowId: null, + activeElementIsView: false, }, getters: { getTable: (state) => (id) => { - return state.tables.filter(table => table.id === id)[0] + return state.tables.find(table => table.id === id) }, getView: (state) => (id) => { - return state.views.filter(view => view.id === id)[0] - }, - getBaseView: (state) => (tableId) => { - return state.views.filter(view => view.tableId === tableId).find(view => view.isBaseView) + return state.views.find(view => view.id === id) }, activeView(state) { - if (state.views && state.views.filter(item => item.id === state.activeViewId).length > 0) { - return state.views.filter(item => item.id === state.activeViewId)[0] + if (state.views && state.activeViewId) { + return state.views.find(item => item.id === state.activeViewId) + } + return null + }, + activeTable(state) { + if (state.tables && state.activeTableId) { + return state.tables.find(item => item.id === state.activeTableId) + } + return null + }, + activeElement(state) { + if (state.activeTableId && state.tables) { + return state.tables.find(item => item.id === state.activeTableId) + } else if (state.views && state.activeViewId) { + return state.views.find(item => item.id === state.activeViewId) } return null }, + isView(state) { + return state.activeElementIsView + }, }, mutations: { setTablesLoading(state, value) { @@ -47,6 +63,15 @@ export default new Vuex.Store({ setActiveViewId(state, viewId) { if (state.activeViewId !== viewId) { state.activeViewId = viewId + state.activeTableId = null + state.activeElementIsView = true + } + }, + setActiveTableId(state, tableId) { + if (state.activeTableId !== tableId) { + state.activeTableId = tableId + state.activeViewId = null + state.activeElementIsView = false } }, setTables(state, tables) { @@ -83,10 +108,9 @@ export default new Vuex.Store({ } else { const tables = state.tables tables.push(res.data) - state.views.push(res.data.baseView) commit('setTables', tables) } - return res.data.baseView.id + return res.data }, async loadTablesFromBE({ commit, state }) { commit('setTablesLoading', true) @@ -97,7 +121,7 @@ export default new Vuex.Store({ // Set Views state.views = [] res.data.forEach(table => { - state.views = state.views.concat([table.baseView, ...table.views]) + if (table.views) state.views = state.views.concat(table.views) }) } catch (e) { displayError(e, t('tables', 'Could not load tables.')) @@ -219,8 +243,8 @@ export default new Vuex.Store({ commit('setTables', [...tables]) return true }, - setTableHasShares({ state, commit, getters }, { elementId, hasShares }) { - const table = getters.getTable(elementId) + setTableHasShares({ state, commit, getters }, { tableId, hasShares }) { + const table = getters.getTable(tableId) table.hasShares = !!hasShares commit('setTable', table) },