Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: feat(Api): add v2 OCS Api to get table/view rows #1565

Draft
wants to merge 1 commit into
base: enh/1506/server-side-sorting
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,5 @@
['name' => 'Context#destroy', 'url' => '/api/2/contexts/{contextId}', 'verb' => 'DELETE'],
['name' => 'Context#transfer', 'url' => '/api/2/contexts/{contextId}/transfer', 'verb' => 'PUT'],
['name' => 'Context#updateContentOrder', 'url' => '/api/2/contexts/{contextId}/pages/{pageId}', 'verb' => 'PUT'],

['name' => 'RowOCS#createRow', 'url' => '/api/2/{nodeCollection}/{nodeId}/rows', 'verb' => 'POST', 'requirements' => ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)']],
]
];
57 changes: 54 additions & 3 deletions lib/Controller/RowOCSController.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand All @@ -8,6 +10,7 @@
namespace OCA\Tables\Controller;

use OCA\Tables\AppInfo\Application;
use OCA\Tables\Db\RowQuery;
use OCA\Tables\Errors\BadRequestError;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
Expand All @@ -17,9 +20,13 @@
use OCA\Tables\Model\RowDataInput;
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\RowService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\DB\Exception;
use OCP\IL10N;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
Expand All @@ -42,10 +49,14 @@ public function __construct(
/**
* [api v2] Create a new row in a table or a view
*
* @param 'tables'|'views' $nodeCollection Indicates whether to create a row on a table or view
* @param 'tables'|'views' $nodeCollection Indicates whether to create a
* row on a table or view
* @param int $nodeId The identifier of the targeted table or view
* @param string|array<string, mixed> $data An array containing the column identifiers and their values
* @return DataResponse<Http::STATUS_OK, TablesRow, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
* @param string|array<string, mixed> $data An array containing the column
* identifiers and their values
* @return DataResponse<Http::STATUS_OK, TablesRow,
* array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR,
* array{message: string}, array{}>
*
* 200: Row returned
* 400: Invalid request parameters
Expand All @@ -55,6 +66,7 @@ public function __construct(
*/
#[NoAdminRequired]
#[RequirePermission(permission: Application::PERMISSION_CREATE, typeParam: 'nodeCollection')]
#[ApiRoute(verb: 'POST', url: '/api/2/{nodeCollection}/{nodeId}/rows', requirements: ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)'])]
public function createRow(string $nodeCollection, int $nodeId, mixed $data): DataResponse {
if (is_string($data)) {
$data = json_decode($data, true);
Expand Down Expand Up @@ -86,4 +98,43 @@ public function createRow(string $nodeCollection, int $nodeId, mixed $data): Dat
return $this->handleError($e);
}
}

/**
* @param int<0,max> $nodeId
* @param int<1,500>|null $limit
* @param int<0,max>|null $offset
*/
#[NoAdminRequired]
#[RequirePermission(permission: Application::PERMISSION_READ, typeParam: 'nodeCollection')]
#[ApiRoute(
verb: 'GET',
url: '/api/2/{nodeCollection}/{nodeId}/rows',
requirements: ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)']
)]
public function getRows(string $nodeCollection, int $nodeId, ?int $limit, ?int $offset, ?array $filter, ?array $sort): DataResponse {
$queryData = new RowQuery(
nodeType: $nodeCollection === 'tables' ? Application::NODE_TYPE_TABLE : Application::NODE_TYPE_VIEW,
nodeId: $nodeId,
);
$queryData->setLimit($limit)
->setOffset($offset)
->setFilter($filter)
->setSort($sort)
->setUserId($this->userId);

//FIXME: View`s filter must be applied! In case it is a view.
//TODO: sanity checks and definitions for filter and sort array
//TODO: OpenApi description

try {
$rows = $this->rowService->findAllByQuery($queryData);
return new DataResponse($this->rowService->formatRows($rows));
} catch (InternalError|Exception $e) {
return $this->handleError($e);
} catch (DoesNotExistException $e) {
return $this->handleNotFoundError(new NotFoundError($e->getMessage(), $e->getCode(), $e));
} catch (MultipleObjectsReturnedException $e) {
return $this->handleBadRequestError(new BadRequestError($e->getMessage(), $e->getCode(), $e));
}
}
}
24 changes: 24 additions & 0 deletions lib/Db/Row2Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ private function getWantedRowIds(string $userId, int $tableId, ?array $filter =
}

/**
* @param Column[] $tableColumns
* @param Column[] $columns
* @param int $tableId
* @param int|null $limit
Expand All @@ -178,6 +179,29 @@ public function findAll(array $tableColumns, array $columns, int $tableId, ?int
return $this->getRows($wantedRowIdsArray, $columnIdsArray, $sort ?? []);
}

/**
* @param Column[] $tableColumns
* @param Column[] $columns
* @param int $tableId
* @param RowQuery $queryData
* @return Row2[]
* @throws InternalError
*/
public function findAllByQuery(array $tableColumns, array $columns, int $tableId, RowQuery $queryData): array {
$this->setColumns($columns, $tableColumns);
$columnIdsArray = array_map(static fn (Column $column) => $column->getId(), $columns);

$wantedRowIdsArray = $this->getWantedRowIds(
$queryData->getUserId(),
$tableId,
$queryData->getFilter(),
$queryData->getLimit(),
$queryData->getOffset()
);

return $this->getRows($wantedRowIdsArray, $columnIdsArray, $queryData->getSort() ?? []);
}

/**
* @param array $rowIds
* @param array $columnIds
Expand Down
76 changes: 76 additions & 0 deletions lib/Db/RowQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Tables\Db;

class RowQuery {
protected ?string $userId = null;
protected ?int $limit = null;
protected ?int $offset = null;
protected ?array $filter = null;
protected ?array $sort = null;

public function __construct(
protected int $nodeType,
protected int $nodeId,
) {
}

public function getNodeType(): int {
return $this->nodeType;
}

public function getNodeId(): int {
return $this->nodeId;
}

public function getUserId(): ?string {
return $this->userId;
}

public function setUserId(?string $userId): self {
$this->userId = $userId;
return $this;
}

public function getLimit(): ?int {
return $this->limit;
}

public function setLimit(?int $limit): self {
$this->limit = $limit;
return $this;
}

public function getOffset(): ?int {
return $this->offset;
}

public function setOffset(?int $offset): self {
$this->offset = $offset;
return $this;
}

public function getFilter(): ?array {
return $this->filter;
}

public function setFilter(?array $filter): self {
$this->filter = $filter;
return $this;
}

public function getSort(): ?array {
return $this->sort;
}

public function setSort(?array $sort): self {
$this->sort = $sort;
return $this;
}
}
27 changes: 25 additions & 2 deletions lib/Service/RowService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

namespace OCA\Tables\Service;

use OCA\Tables\AppInfo\Application;
use OCA\Tables\Db\Column;
use OCA\Tables\Db\ColumnMapper;
use OCA\Tables\Db\Row2;
use OCA\Tables\Db\Row2Mapper;
use OCA\Tables\Db\RowQuery;
use OCA\Tables\Db\TableMapper;
use OCA\Tables\Db\View;
use OCA\Tables\Db\ViewMapper;
Expand Down Expand Up @@ -70,6 +72,26 @@ public function formatRows(array $rows): array {
return array_map(fn (Row2 $row) => $row->jsonSerialize(), $rows);
}

/**
* @throws MultipleObjectsReturnedException
* @throws DoesNotExistException
* @throws Exception
* @throws InternalError
* @return Row2[]
*/
public function findAllByQuery(RowQuery $rowQuery): array {
$tableId = $rowQuery->getNodeId();
if ($rowQuery->getNodeType() === Application::NODE_TYPE_VIEW) {
$view = $this->viewMapper->find($rowQuery->getNodeId());
$tableId = $view->getTableId();
$filterColumns = $this->columnMapper->findAll($view->getColumnsArray());
unset($view);
}

$tableColumns = $this->columnMapper->findAllByTable($tableId);
return $this->row2Mapper->findAllByQuery($tableColumns, $filterColumns ?? $tableColumns, $tableId, $rowQuery);
}

/**
* @param int $tableId
* @param string $userId
Expand Down Expand Up @@ -538,8 +560,9 @@ public function deleteAllByTable(int $tableId, ?string $userId = null): void {
* This deletes all data for a column, eg if the columns gets removed
*
* >>> SECURITY <<<
* We do not check if you are allowed to remove this data. That has to be done before!
* Why? Mostly this check will have be run before and we can pass this here due to performance reasons.
* We do not check if you are allowed to remove this data. That has to be
* done before! Why? Mostly this check will have be run before and we can
* pass this here due to performance reasons.
*
* @param Column $column
* @throws InternalError
Expand Down
Loading