Files
nextcloud-tables/lib/Controller/Api1Controller.php
2025-07-16 21:29:21 +02:00

1656 lines
67 KiB
PHP

<?php
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/** @noinspection DuplicatedCode */
namespace OCA\Tables\Controller;
use Exception;
use InvalidArgumentException;
use OCA\Tables\Api\V1Api;
use OCA\Tables\AppInfo\Application;
use OCA\Tables\Db\ViewMapper;
use OCA\Tables\Dto\Column as ColumnDto;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
use OCA\Tables\Middleware\Attribute\RequirePermission;
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\ColumnService;
use OCA\Tables\Service\ImportService;
use OCA\Tables\Service\RowService;
use OCA\Tables\Service\ShareService;
use OCA\Tables\Service\TableService;
use OCA\Tables\Service\ViewService;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataResponse;
use OCP\IL10N;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type TablesTable from ResponseDefinitions
* @psalm-import-type TablesView from ResponseDefinitions
* @psalm-import-type TablesShare from ResponseDefinitions
* @psalm-import-type TablesColumn from ResponseDefinitions
* @psalm-import-type TablesRow from ResponseDefinitions
* @psalm-import-type TablesImportState from ResponseDefinitions
* @psalm-import-type TablesContextNavigation from ResponseDefinitions
*/
class Api1Controller extends ApiController {
private TableService $tableService;
private ShareService $shareService;
private ColumnService $columnService;
private RowService $rowService;
private ImportService $importService;
private ViewService $viewService;
private ViewMapper $viewMapper;
private IL10N $l10N;
private V1Api $v1Api;
private ?string $userId;
protected LoggerInterface $logger;
use Errors;
public function __construct(
IRequest $request,
TableService $service,
ShareService $shareService,
ColumnService $columnService,
RowService $rowService,
ImportService $importService,
ViewService $viewService,
ViewMapper $viewMapper,
V1Api $v1Api,
LoggerInterface $logger,
IL10N $l10N,
?string $userId,
) {
parent::__construct(Application::APP_ID, $request);
$this->tableService = $service;
$this->shareService = $shareService;
$this->columnService = $columnService;
$this->rowService = $rowService;
$this->importService = $importService;
$this->viewService = $viewService;
$this->viewMapper = $viewMapper;
$this->userId = $userId;
$this->v1Api = $v1Api;
$this->logger = $logger;
$this->l10N = $l10N;
}
// Tables
/**
* Returns all Tables
*
* @return DataResponse<Http::STATUS_OK, list<TablesTable>, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Tables returned
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function index(): DataResponse {
try {
return new DataResponse($this->tableService->formatTables($this->tableService->findAll($this->userId)));
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Create a new table and return it
*
* @param string $title Title of the table
* @param string|null $emoji Emoji for the table
* @param string $template Template to use if wanted
*
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Tables returned
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createTable(string $title, ?string $emoji, string $template = 'custom'): DataResponse {
try {
return new DataResponse($this->tableService->create($title, $template, $emoji)->jsonSerialize());
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* returns table scheme
*
* @param int $tableId Table ID
* @return DataResponse<Http::STATUS_OK, TablesTable, array{'Content-Disposition'?:string,'Content-Type'?:string}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Table returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function showScheme(int $tableId): DataResponse {
try {
$scheme = $this->tableService->getScheme($tableId, $this->userId);
return new DataResponse($scheme->jsonSerialize(), http::STATUS_OK, ['Content-Disposition' => 'attachment; filename="' . $scheme->getTitle() . '.json"', 'Content-Type' => 'application/octet-stream']);
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage());
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage());
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (InternalError|Exception $e) {
$this->logger->warning('An internal error or exception occurred: ' . $e->getMessage());
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Get a table object
*
* @param int $tableId Table ID
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Table returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getTable(int $tableId): DataResponse {
try {
return new DataResponse($this->tableService->find($tableId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Update tables properties
*
* @param int $tableId Table ID
* @param string|null $title New table title
* @param string|null $emoji New table emoji
* @param bool $archived Whether the table is archived
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Tables returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function updateTable(int $tableId, ?string $title = null, ?string $emoji = null, ?bool $archived = false): DataResponse {
try {
return new DataResponse($this->tableService->update($tableId, $title, $emoji, null, $archived, $this->userId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete a table
*
* @param int $tableId Table ID
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Deleted table returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function deleteTable(int $tableId): DataResponse {
try {
return new DataResponse($this->tableService->delete($tableId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
// Views
/**
* Get all views for a table
*
* @param int $tableId Table ID
* @return DataResponse<Http::STATUS_OK, list<TablesView>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Views returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexViews(int $tableId): DataResponse {
try {
return new DataResponse($this->viewService->formatViews($this->viewService->findAll($this->tableService->find($tableId))));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Create a new view for a table
*
* @param int $tableId Table ID that will hold the view
* @param string $title Title for the view
* @param string|null $emoji Emoji for the view
*
* @return DataResponse<Http::STATUS_OK, TablesView, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View created
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createView(int $tableId, string $title, ?string $emoji): DataResponse {
try {
return new DataResponse($this->viewService->create($title, $emoji, $this->tableService->find($tableId))->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Get a view object
*
* @param int $viewId View ID
* @return DataResponse<Http::STATUS_OK, TablesView, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getView(int $viewId): DataResponse {
try {
return new DataResponse($this->viewService->find($viewId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Update a view via key-value sets
*
* @param int $viewId View ID
* @param array{key: 'title'|'emoji'|'description', value: string}|array{key: 'columns', value: list<int>}|array{key: 'sort', value: array{columnId: int, mode: 'ASC'|'DESC'}}|array{key: 'filter', value: array{columnId: int, operator: 'begins-with'|'ends-with'|'contains'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal'|'is-empty', value: string|int|float}} $data key-value pairs
* @return DataResponse<Http::STATUS_OK, TablesView, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND|Http::STATUS_BAD_REQUEST|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: View updated
* 400: Invalid data
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function updateView(int $viewId, array $data): DataResponse {
try {
return new DataResponse($this->viewService->update($viewId, $data)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InvalidArgumentException $e) {
$this->logger->warning('An invalid request occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_BAD_REQUEST);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete a view
*
* @param int $viewId View ID
* @return DataResponse<Http::STATUS_OK, TablesView, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View deleted
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function deleteView(int $viewId): DataResponse {
try {
return new DataResponse($this->viewService->delete($viewId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
// Shares
/**
* Get a share object
*
* @param int $shareId Share ID
* @return DataResponse<Http::STATUS_OK, TablesShare, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Share returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getShare(int $shareId): DataResponse {
try {
return new DataResponse($this->shareService->find($shareId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Get all shares for a view
* Will be empty if view does not exist
*
* @param int $viewId View ID
* @return DataResponse<Http::STATUS_OK, list<TablesShare>, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Shares returned
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexViewShares(int $viewId): DataResponse {
try {
return new DataResponse($this->shareService->formatShares($this->shareService->findAll('view', $viewId)));
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Get all shares for a table
* Will be empty if table does not exist
*
* @param int $tableId Table ID
* @return DataResponse<Http::STATUS_OK, list<TablesShare>, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Shares returned
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexTableShares(int $tableId): DataResponse {
try {
return new DataResponse($this->shareService->formatShares($this->shareService->findAll('table', $tableId)));
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Create a new share
*
* @param int $nodeId Node ID
* @param 'table'|'view'|'context' $nodeType Node type
* @param string $receiver Receiver ID
* @param 'user'|'group' $receiverType Receiver type
* @param bool $permissionRead Permission if receiver can read data
* @param bool $permissionCreate Permission if receiver can create data
* @param bool $permissionUpdate Permission if receiver can update data
* @param bool $permissionDelete Permission if receiver can delete data
* @param bool $permissionManage Permission if receiver can manage node
* @param int $displayMode context shares only, whether it should appear in nav bar. 0: no, 1: recipients, 2: all (default). Cf. Application::NAV_ENTRY_MODE_*.
* @psalm-param int<0, 2> $displayMode
* @return DataResponse<Http::STATUS_OK, TablesShare, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Share returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE)]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createShare(
int $nodeId,
string $nodeType,
string $receiver,
string $receiverType,
bool $permissionRead = false,
bool $permissionCreate = false,
bool $permissionUpdate = false,
bool $permissionDelete = false,
bool $permissionManage = false,
int $displayMode = 2,
): DataResponse {
try {
return new DataResponse(
$this->shareService->create(
$nodeId,
$nodeType,
$receiver,
$receiverType,
$permissionRead,
$permissionCreate,
$permissionUpdate,
$permissionDelete,
$permissionManage,
$displayMode
)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Delete a share
*
* @param int $shareId Share ID
* @return DataResponse<Http::STATUS_OK, TablesShare, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View deleted
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function deleteShare(int $shareId): DataResponse {
try {
return new DataResponse($this->shareService->delete($shareId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Update a share permission
*
* @param int $shareId Share ID
* @param string $permissionType Permission type that should be changed
* @param bool $permissionValue New permission value
* @return DataResponse<Http::STATUS_OK, TablesShare, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View deleted
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function updateSharePermissions(int $shareId, string $permissionType, bool $permissionValue): DataResponse {
try {
return new DataResponse($this->shareService->updatePermission($shareId, $permissionType, $permissionValue)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Updates the display mode of a context share
*
* @param int $shareId Share ID
* @param int $displayMode The new value for the display mode of the nav bar icon. 0: hidden, 1: visible for recipients, 2: visible for all
* @param string $target "default" to set the default, "self" to set an override for the authenticated user
* @return DataResponse<Http::STATUS_OK, TablesContextNavigation, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Display mode updated
* 400: Invalid parameter
* 403: No permissions
* 404: Share not found
*
* @psalm-param int<0, 2> $displayMode
* @psalm-param ("default"|"self") $target
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function updateShareDisplayMode(int $shareId, int $displayMode, string $target = 'default'): DataResponse {
if ($target === 'default') {
$userId = '';
} elseif ($target === 'self') {
$userId = $this->userId;
} else {
$error = 'target parameter must be either "default" or "self"';
$this->logger->warning(sprintf('An internal error or exception occurred: %s', $error));
$message = ['message' => $error];
return new DataResponse($message, Http::STATUS_BAD_REQUEST);
}
try {
return new DataResponse($this->shareService->updateDisplayMode($shareId, $displayMode, $userId)->jsonSerialize());
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
}
}
// Columns
/**
* Get all columns for a table or a underlying view
* Return an empty array if no columns were found
*
* @param int $tableId Table ID
* @param int|null $viewId View ID
* @return DataResponse<Http::STATUS_OK, list<TablesColumn>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View deleted
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexTableColumns(int $tableId, ?int $viewId): DataResponse {
try {
try {
// permission check in service class
// they might be missing legally when only the view was shared
$columns = $this->columnService->findAllByTable($tableId);
} catch (PermissionError $e) {
if (!$viewId) {
throw $e;
}
$view = $this->viewService->find($viewId, false, $this->userId);
if ($tableId !== $view->getTableId()) {
throw new PermissionError('Given table is not a parent of the given view.');
}
$columns = $this->columnService->findAllByManagedView($view, $this->userId);
}
return new DataResponse($this->columnService->formatColumns($columns));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->warning('The view could not be found: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Get all columns for a view
* Return an empty array if no columns were found
*
* @param int $viewId View ID
* @return DataResponse<Http::STATUS_OK, list<TablesColumn>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View deleted
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexViewColumns(int $viewId): DataResponse {
try {
return new DataResponse($this->columnService->formatColumns($this->columnService->findAllByView($viewId)));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Create a column
*
* @param int|null $tableId Table ID
* @param int|null $viewId View ID
* @param string $title Title
* @param 'text'|'number'|'datetime'|'select'|'usergroup' $type Column main type
* @param string|null $subtype Column sub type
* @param bool $mandatory Is the column mandatory
* @param string|null $description Description
* @param string|null $numberPrefix Prefix if the column is a number field
* @param string|null $numberSuffix Suffix if the column is a number field
* @param float|null $numberDefault Default number, if column is a number
* @param float|null $numberMin Min value, if column is a number
* @param float|null $numberMax Max number, if column is a number
* @param int|null $numberDecimals Number of decimals, if column is a number
* @param string|null $textDefault Default text, if column is a text
* @param string|null $textAllowedPattern Allowed pattern (regex) for text columns (not yet implemented)
* @param int|null $textMaxLength Max length, if column is a text
* @param string|null $selectionOptions Options for a selection (json array{id: int, label: string})
* @param string|null $selectionDefault Default option IDs for a selection (json list<int>)
* @param string|null $datetimeDefault Default value, if column is datetime
* @param string|null $usergroupDefault Default value, if column is usergroup (json array{id: string, type: int})
* @param bool|null $usergroupMultipleItems Can select multiple users or/and groups, if column is usergroup
* @param bool|null $usergroupSelectUsers Can select users, if column type is usergroup
* @param bool|null $usergroupSelectGroups Can select groups, if column type is usergroup
* @param bool|null $usergroupSelectTeams Can select teams, if column type is usergroup
* @param bool|null $usergroupShowUserStatus Whether to show the user's status, if column type is usergroup
* @param list<int>|null $selectedViewIds View IDs where this column should be added to be presented
*
* @return DataResponse<Http::STATUS_OK, TablesColumn, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Column created
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createColumn(
?int $tableId,
?int $viewId,
string $title,
string $type,
?string $subtype,
bool $mandatory,
?string $description,
?string $numberPrefix,
?string $numberSuffix,
?float $numberDefault,
?float $numberMin,
?float $numberMax,
?int $numberDecimals,
?string $textDefault,
?string $textAllowedPattern,
?int $textMaxLength,
?string $selectionOptions = '',
?string $selectionDefault = '',
?string $datetimeDefault = '',
?string $usergroupDefault = '',
?bool $usergroupMultipleItems = null,
?bool $usergroupSelectUsers = null,
?bool $usergroupSelectGroups = null,
?bool $usergroupSelectTeams = null,
?bool $usergroupShowUserStatus = null,
?array $selectedViewIds = [],
): DataResponse {
try {
return new DataResponse($this->columnService->create(
$this->userId,
$tableId,
$viewId,
new ColumnDto(
title: $title,
type: $type,
subtype: $subtype,
mandatory: $mandatory,
description: $description,
textDefault: $textDefault,
textAllowedPattern: $textAllowedPattern,
textMaxLength: $textMaxLength,
numberDefault: $numberDefault,
numberMin: $numberMin,
numberMax: $numberMax,
numberDecimals: $numberDecimals,
numberPrefix: $numberPrefix,
numberSuffix: $numberSuffix,
selectionOptions: $selectionOptions,
selectionDefault: $selectionDefault,
datetimeDefault: $datetimeDefault,
usergroupDefault: $usergroupDefault,
usergroupMultipleItems: $usergroupMultipleItems,
usergroupSelectUsers: $usergroupSelectUsers,
usergroupSelectGroups: $usergroupSelectGroups,
usergroupSelectTeams: $usergroupSelectTeams,
showUserStatus: $usergroupShowUserStatus
),
$selectedViewIds
)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (DoesNotExistException $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Update a column
*
* @param int $columnId Column ID that will be updated
* @param string|null $title Title
* @param string|null $subtype Column sub type
* @param bool $mandatory Is the column mandatory
* @param string|null $description Description
* @param string|null $numberPrefix Prefix if the column is a number field
* @param string|null $numberSuffix Suffix if the column is a number field
* @param float|null $numberDefault Default number, if column is a number
* @param float|null $numberMin Min value, if column is a number
* @param float|null $numberMax Max number, if column is a number
* @param int|null $numberDecimals Number of decimals, if column is a number
* @param string|null $textDefault Default text, if column is a text
* @param string|null $textAllowedPattern Allowed pattern (regex) for text columns (not yet implemented)
* @param int|null $textMaxLength Max length, if column is a text
* @param string|null $selectionOptions Options for a selection (json array{id: int, label: string})
* @param string|null $selectionDefault Default option IDs for a selection (json list<int>)
* @param string|null $datetimeDefault Default value, if column is datetime
* @param string|null $usergroupDefault Default value, if column is usergroup
* @param bool|null $usergroupMultipleItems Can select multiple users or/and groups, if column is usergroup
* @param bool|null $usergroupSelectUsers Can select users, if column type is usergroup
* @param bool|null $usergroupSelectGroups Can select groups, if column type is usergroup
* @param bool|null $usergroupSelectTeams Can select teams, if column type is usergroup
* @param bool|null $usergroupShowUserStatus Whether to show the user's status, if column type is usergroup
*
* @return DataResponse<Http::STATUS_OK, TablesColumn, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Updated column
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function updateColumn(
int $columnId,
?string $title,
?string $subtype,
?bool $mandatory,
?string $description,
?string $numberPrefix,
?string $numberSuffix,
?float $numberDefault,
?float $numberMin,
?float $numberMax,
?int $numberDecimals,
?string $textDefault,
?string $textAllowedPattern,
?int $textMaxLength,
?string $selectionOptions,
?string $selectionDefault,
?string $datetimeDefault,
?string $usergroupDefault,
?bool $usergroupMultipleItems,
?bool $usergroupSelectUsers,
?bool $usergroupSelectGroups,
?bool $usergroupSelectTeams,
?bool $usergroupShowUserStatus,
): DataResponse {
try {
$item = $this->columnService->update(
$columnId,
$this->userId,
new ColumnDto(
title: $title,
subtype: $subtype,
mandatory: $mandatory,
description: $description,
textDefault: $textDefault,
textAllowedPattern: $textAllowedPattern,
textMaxLength: $textMaxLength,
numberDefault: $numberDefault,
numberMin: $numberMin,
numberMax: $numberMax,
numberDecimals: $numberDecimals,
numberPrefix: $numberPrefix,
numberSuffix: $numberSuffix,
selectionOptions: $selectionOptions,
selectionDefault: $selectionDefault,
datetimeDefault: $datetimeDefault,
usergroupDefault: $usergroupDefault,
usergroupMultipleItems: $usergroupMultipleItems,
usergroupSelectUsers: $usergroupSelectUsers,
usergroupSelectGroups: $usergroupSelectGroups,
usergroupSelectTeams: $usergroupSelectTeams,
showUserStatus: $usergroupShowUserStatus
)
);
return new DataResponse($item->jsonSerialize());
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Returns a column object
*
* @param int $columnId Wanted Column ID
* @return DataResponse<Http::STATUS_OK, TablesColumn, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Column returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getColumn(int $columnId): DataResponse {
try {
return new DataResponse($this->columnService->find($columnId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete a column
*
* @param int $columnId Wanted Column ID
* @return DataResponse<Http::STATUS_OK, TablesColumn, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Deleted column returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function deleteColumn(int $columnId): DataResponse {
try {
return new DataResponse($this->columnService->delete($columnId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* List all rows values for a table, first row are the column titles
*
* @param int $tableId Table ID
* @param int|null $limit Limit
* @param int|null $offset Offset
* @return DataResponse<Http::STATUS_OK, list<string>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Row values returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexTableRowsSimple(int $tableId, ?int $limit, ?int $offset): DataResponse {
try {
return new DataResponse($this->v1Api->getData($tableId, $limit, $offset, $this->userId));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* List all rows for a table
*
* @param int $tableId Table ID
* @param int|null $limit Limit
* @param int|null $offset Offset
* @return DataResponse<Http::STATUS_OK, list<TablesRow>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Rows returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexTableRows(int $tableId, ?int $limit, ?int $offset): DataResponse {
try {
return new DataResponse($this->rowService->formatRows($this->rowService->findAllByTable($tableId, $this->userId, $limit, $offset)));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* List all rows for a view
*
* @param int $viewId View ID
* @param int|null $limit Limit
* @param int|null $offset Offset
* @return DataResponse<Http::STATUS_OK, list<TablesRow>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Rows returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function indexViewRows(int $viewId, ?int $limit, ?int $offset): DataResponse {
try {
return new DataResponse($this->rowService->formatRows($this->rowService->findAllByView($viewId, $this->userId, $limit, $offset)));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Create a row within a view
*
* @param int $viewId View ID
* @param string|array<string, mixed> $data Data as key - value store
* @return DataResponse<Http::STATUS_OK, TablesRow, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Row returned
* 403: No permissions
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createRowInView(int $viewId, $data): DataResponse {
if (is_string($data)) {
$data = json_decode($data, true);
}
if (!is_array($data)) {
$this->logger->warning('createRowInView not possible, data array invalid.');
$message = ['message' => $this->l10N->t('Could not create row.')];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
$dataNew = [];
foreach ($data as $key => $value) {
$dataNew[] = [
'columnId' => (int)$key,
'value' => $value
];
}
try {
return new DataResponse($this->rowService->create(null, $viewId, $dataNew)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Create a row within a table
*
* @param int $tableId Table ID
* @param string|array<string, mixed> $data Data as key - value store
* @return DataResponse<Http::STATUS_OK, TablesRow, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Row returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createRowInTable(int $tableId, $data): DataResponse {
if (is_string($data)) {
$data = json_decode($data, true);
}
if (!is_array($data)) {
$this->logger->warning('createRowInTable not possible, data array invalid.');
$message = ['message' => $this->l10N->t('Could not create row.')];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
$dataNew = [];
foreach ($data as $key => $value) {
$dataNew[] = [
'columnId' => (int)$key,
'value' => $value
];
}
try {
return new DataResponse($this->rowService->create($tableId, null, $dataNew)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|Exception $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Get a row
*
* @param int $rowId Row ID
* @return DataResponse<Http::STATUS_OK, TablesRow, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Row returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getRow(int $rowId): DataResponse {
try {
return new DataResponse($this->rowService->find($rowId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Update a row
*
* @param int $rowId Row ID
* @param int|null $viewId View ID
* @param string|array<string, mixed> $data Data as key - value store
*
* @return DataResponse<Http::STATUS_OK, TablesRow, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Updated row returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function updateRow(int $rowId, ?int $viewId, $data): DataResponse {
if (is_string($data)) {
$data = json_decode($data, true);
}
if (!is_array($data)) {
$this->logger->warning('updateRow not possible, data array invalid.');
$message = ['message' => $this->l10N->t('Could not update row.')];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
$dataNew = [];
foreach ($data as $key => $value) {
$dataNew[] = [
'columnId' => (int)$key,
'value' => $value
];
}
try {
return new DataResponse($this->rowService->updateSet($rowId, $viewId, $dataNew, $this->userId)->jsonSerialize());
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
/**
* Delete a row
*
* @param int $rowId Row ID
*
* @return DataResponse<Http::STATUS_OK, TablesRow, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Deleted row returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function deleteRow(int $rowId): DataResponse {
try {
return new DataResponse($this->rowService->delete($rowId, null, $this->userId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Delete a row within a view
*
* @param int $rowId Row ID
* @param int $viewId View ID
* @return DataResponse<Http::STATUS_OK, TablesRow, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Deleted row returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function deleteRowByView(int $rowId, int $viewId): DataResponse {
try {
return new DataResponse($this->rowService->delete($rowId, $viewId, $this->userId)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Import from file in to a table
*
* @param int $tableId Table ID
* @param string $path Path to file
* @param bool $createMissingColumns Create missing columns
* @return DataResponse<Http::STATUS_OK, TablesImportState, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Import status returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function importInTable(int $tableId, string $path, bool $createMissingColumns = true): DataResponse {
try {
// minimal permission is checked, creating columns requires MANAGE permissions - currently tested on service layer
return new DataResponse($this->importService->import($tableId, null, $path, $createMissingColumns));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|MultipleObjectsReturnedException $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError|DoesNotExistException $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Import from file in to a table
*
* @param int $viewId View ID
* @param string $path Path to file
* @param bool $createMissingColumns Create missing columns
* @return DataResponse<Http::STATUS_OK, TablesImportState, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Import status returned
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function importInView(int $viewId, string $path, bool $createMissingColumns = true): DataResponse {
try {
// minimal permission is checked, creating columns requires MANAGE permissions - currently tested on service layer
return new DataResponse($this->importService->import(null, $viewId, $path, $createMissingColumns));
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError|MultipleObjectsReturnedException $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError|DoesNotExistException $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
// Api Function for backward compatibility
/**
* Create a share for a table
*
* @param int $tableId Table ID
* @param string $receiver Receiver ID
* @param 'user'|'group' $receiverType Receiver type
* @param bool $permissionRead Permission if receiver can read data
* @param bool $permissionCreate Permission if receiver can create data
* @param bool $permissionUpdate Permission if receiver can update data
* @param bool $permissionDelete Permission if receiver can delete data
* @param bool $permissionManage Permission if receiver can manage table
* @return DataResponse<Http::STATUS_OK, TablesShare, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: View deleted
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createTableShare(int $tableId, string $receiver, string $receiverType, bool $permissionRead, bool $permissionCreate, bool $permissionUpdate, bool $permissionDelete, bool $permissionManage): DataResponse {
try {
return new DataResponse(
$this->shareService->create(
$tableId,
'table',
$receiver,
$receiverType,
$permissionRead,
$permissionCreate,
$permissionUpdate,
$permissionDelete,
$permissionManage,
Application::NAV_ENTRY_MODE_ALL
)->jsonSerialize()
);
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (NotFoundError $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
/**
* Create a new column for a table
*
* @param int $tableId Table ID
* @param string $title Title
* @param 'text'|'number'|'datetime'|'select'|'usergroup' $type Column main type
* @param string|null $subtype Column sub type
* @param bool $mandatory Is the column mandatory
* @param string|null $description Description
* @param string|null $numberPrefix Prefix if the column is a number field
* @param string|null $numberSuffix Suffix if the column is a number field
* @param float|null $numberDefault Default number, if column is a number
* @param float|null $numberMin Min value, if column is a number
* @param float|null $numberMax Max number, if column is a number
* @param int|null $numberDecimals Number of decimals, if column is a number
* @param string|null $textDefault Default text, if column is a text
* @param string|null $textAllowedPattern Allowed pattern (regex) for text columns (not yet implemented)
* @param int|null $textMaxLength Max length, if column is a text
* @param string|null $selectionOptions Options for a selection (json array{id: int, label: string})
* @param string|null $selectionDefault Default option IDs for a selection (json list<int>)
* @param string|null $datetimeDefault Default value, if column is datetime
* @param string|null $usergroupDefault Default value, if column is usergroup
* @param bool|null $usergroupMultipleItems Can select multiple users or/and groups, if column is usergroup
* @param bool|null $usergroupSelectUsers Can select users, if column type is usergroup
* @param bool|null $usergroupSelectGroups Can select groups, if column type is usergroup
* @param bool|null $usergroupSelectTeams Can select teams, if column type is usergroup
* @param bool|null $usergroupShowUserStatus Whether to show the user's status, if column type is usergroup
* @param list<int>|null $selectedViewIds View IDs where this column should be added to be presented
*
* @return DataResponse<Http::STATUS_OK, TablesColumn, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Column created
* 403: No permissions
* 404: Not found
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createTableColumn(
int $tableId,
string $title,
string $type,
?string $subtype,
bool $mandatory,
?string $description,
?string $numberPrefix,
?string $numberSuffix,
?float $numberDefault,
?float $numberMin,
?float $numberMax,
?int $numberDecimals,
?string $textDefault,
?string $textAllowedPattern,
?int $textMaxLength,
?string $selectionOptions = '',
?string $selectionDefault = '',
?string $datetimeDefault = '',
?string $usergroupDefault = '',
?bool $usergroupMultipleItems = null,
?bool $usergroupSelectUsers = null,
?bool $usergroupSelectGroups = null,
?bool $usergroupSelectTeams = null,
?bool $usergroupShowUserStatus = null,
?array $selectedViewIds = [],
): DataResponse {
try {
$item = $this->columnService->create(
$this->userId,
$tableId,
null,
new ColumnDto(
title: $title,
type: $type,
subtype: $subtype,
mandatory: $mandatory,
description: $description,
textDefault: $textDefault,
textAllowedPattern: $textAllowedPattern,
textMaxLength: $textMaxLength,
numberDefault: $numberDefault,
numberMin: $numberMin,
numberMax: $numberMax,
numberDecimals: $numberDecimals,
numberPrefix: $numberPrefix,
numberSuffix: $numberSuffix,
selectionOptions: $selectionOptions,
selectionDefault: $selectionDefault,
datetimeDefault: $datetimeDefault,
usergroupDefault: $usergroupDefault,
usergroupMultipleItems: $usergroupMultipleItems,
usergroupSelectUsers: $usergroupSelectUsers,
usergroupSelectGroups: $usergroupSelectGroups,
usergroupSelectTeams: $usergroupSelectTeams,
showUserStatus: $usergroupShowUserStatus
),
$selectedViewIds
);
return new DataResponse($item->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_FORBIDDEN);
} catch (InternalError $e) {
$this->logger->error('An internal error or exception occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR);
} catch (DoesNotExistException $e) {
$this->logger->info('A not found error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}
}