diff --git a/appinfo/info.xml b/appinfo/info.xml index 0fc31a0b03..dd25aafee2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -26,7 +26,7 @@ Share your tables and views with users and groups within your cloud. Have a good time and manage whatever you want. ]]> - 2.0.0-alpha.1 + 2.0.0-alpha.2 agpl Florian Steffens Tables diff --git a/cypress/e2e/tables-sharing-link.cy.js b/cypress/e2e/tables-sharing-link.cy.js new file mode 100644 index 0000000000..9c92f86fc3 --- /dev/null +++ b/cypress/e2e/tables-sharing-link.cy.js @@ -0,0 +1,74 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +describe('Public link sharing', () => { + let localUser + let tableId + const tableTitle = 'Public Share Test Table' + + before(() => { + cy.createRandomUser().then(user => { + localUser = user + cy.login(localUser) + }) + }) + + it('Create, access and delete a public link share', () => { + cy.login(localUser) + cy.visit('apps/tables') + + // Create a table + cy.get('[data-cy="navigationCreateTableIcon"]').click({ force: true }) + cy.get('.modal__content input[type="text"]').clear().type(tableTitle) + cy.get('.tile').contains('ToDo').click({ force: true }) + cy.get('[data-cy="createTableSubmitBtn"]').scrollIntoView().click() + cy.loadTable(tableTitle) + + // Open share sidebar + cy.get('[data-cy="customTableAction"] button').click() + cy.get('[data-cy="dataTableShareBtn"]').click() + + cy.contains('Public links').should('be.visible') + + // Create public link + + cy.intercept('POST', '**/apps/tables/api/2/tables/*/share').as('createShare') + cy.get('button').contains('Create public link').click() + cy.get('.sharing-entry-link__create-form button').contains('Create').click() + + cy.wait('@createShare').then((interception) => { + expect(interception.response.statusCode).to.eq(200) + const shareToken = interception.response.body.ocs.data.shareToken + expect(shareToken).to.be.a('string') + + // Access the public link + cy.clearCookies() + cy.visit(`apps/tables/s/${shareToken}`) + + // Verify public view + cy.get('.public-table-view').should('be.visible') + cy.clickOnTableThreeDotMenu('Export as CSV') + + // Verify we cannot edit (simple check: no edit controls or just read-only mode) + // PublicView passes :can-edit-rows="false" etc. + // We can check that the "Add row" button is missing. + cy.get('[data-cy="addRowBtn"]').should('not.exist') + + // Login again to delete share + cy.login(localUser) + cy.visit('apps/tables') + cy.loadTable(tableTitle) + + cy.get('[data-cy="customTableAction"] button').click() + cy.get('[data-cy="dataTableShareBtn"]').click() + + cy.get('[data-cy="sharingEntryLinkTitle"]').should('be.visible') + cy.get('[data-cy="sharingEntryLinkDeleteButton"]').click() + + cy.get('[data-cy="sharingEntryLinkTitle"]').should('not.exist') + }) + }) +}) + diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 938ae00bcc..a56d214524 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -24,6 +24,7 @@ use OCA\Tables\Listener\WhenTableTransferredAuditLogListener; use OCA\Tables\Listener\WhenViewDeletedAuditLogListener; use OCA\Tables\Middleware\PermissionMiddleware; +use OCA\Tables\Middleware\ShareControlMiddleware; use OCA\Tables\Reference\ContentReferenceProvider; use OCA\Tables\Reference\ReferenceProvider; use OCA\Tables\Search\SearchTablesProvider; @@ -92,6 +93,7 @@ public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerMiddleware(PermissionMiddleware::class); + $context->registerMiddleware(ShareControlMiddleware::class); } public function boot(IBootContext $context): void { diff --git a/lib/Constants/ShareReceiverType.php b/lib/Constants/ShareReceiverType.php index 481044cce1..558bf5cb64 100644 --- a/lib/Constants/ShareReceiverType.php +++ b/lib/Constants/ShareReceiverType.php @@ -14,4 +14,5 @@ class ShareReceiverType { public const USER = 'user'; public const GROUP = 'group'; public const CIRCLE = 'circle'; + public const LINK = 'link'; } diff --git a/lib/Controller/ACommonColumnsController.php b/lib/Controller/ACommonColumnsController.php new file mode 100644 index 0000000000..c4d2ed8e50 --- /dev/null +++ b/lib/Controller/ACommonColumnsController.php @@ -0,0 +1,35 @@ +service->findAllByTable($nodeId, $overriddenUserid); + } + if ($nodeType === 'view') { + return $this->service->findAllByView($nodeId, $overriddenUserid); + } + throw new BadRequestError('Invalid node type provided'); + } +} diff --git a/lib/Controller/ApiColumnsController.php b/lib/Controller/ApiColumnsController.php index e1284ba746..d642b6bd81 100644 --- a/lib/Controller/ApiColumnsController.php +++ b/lib/Controller/ApiColumnsController.php @@ -7,13 +7,16 @@ namespace OCA\Tables\Controller; use OCA\Tables\AppInfo\Application; +use OCA\Tables\Db\Column; use OCA\Tables\Dto\Column as ColumnDto; +use OCA\Tables\Errors\BadRequestError; 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\ShareService; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; @@ -24,15 +27,16 @@ /** * @psalm-import-type TablesColumn from ResponseDefinitions */ -class ApiColumnsController extends AOCSController { - private ColumnService $service; +class ApiColumnsController extends ACommonColumnsController { public function __construct( IRequest $request, LoggerInterface $logger, ColumnService $service, IL10N $n, - string $userId) { + string $userId, + protected ShareService $shareService, + ) { parent::__construct($request, $logger, $n, $userId); $this->service = $service; } @@ -44,9 +48,12 @@ public function __construct( * * @param int $nodeId Node ID * @param 'table'|'view' $nodeType Node type - * @return DataResponse, array{}>|DataResponse + * @return DataResponse, + * array{}>|DataResponse * * 200: View deleted + * 400: Invalid input arguments * 403: No permissions * 404: Not found */ @@ -54,13 +61,7 @@ public function __construct( #[RequirePermission(permission: Application::PERMISSION_READ)] public function index(int $nodeId, string $nodeType): DataResponse { try { - if ($nodeType === 'table') { - $columns = $this->service->findAllByTable($nodeId); - } elseif ($nodeType === 'view') { - $columns = $this->service->findAllByView($nodeId); - } else { - $columns = null; - } + $columns = $this->getColumnsFromTableOrView($nodeType, $nodeId); return new DataResponse($this->service->formatColumns($columns)); } catch (PermissionError $e) { return $this->handlePermissionError($e); @@ -68,6 +69,8 @@ public function index(int $nodeId, string $nodeType): DataResponse { return $this->handleError($e); } catch (NotFoundError $e) { return $this->handleNotFoundError($e); + } catch (BadRequestError $e) { + return $this->handleBadRequestError($e); } } @@ -75,7 +78,9 @@ public function index(int $nodeId, string $nodeType): DataResponse { * [api v2] Get a column object * * @param int $id Column ID - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * * 200: Column returned * 403: No permissions @@ -111,10 +116,14 @@ public function show(int $id): DataResponse { * @param float|null $numberMax Max * @param 'progress'|'stars'|null $subtype Subtype for the new column * @param string|null $description Description - * @param list|null $selectedViewIds View IDs where this columns should be added - * @param array $customSettings Custom settings for the column + * @param list|null $selectedViewIds View IDs where this columns + * should be added + * @param array $customSettings Custom settings for the + * column * - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * * 200: Column created * 403: No permission @@ -161,14 +170,19 @@ public function createNumberColumn(int $baseNodeId, string $title, ?float $numbe * @param string|null $textDefault Default * @param string|null $textAllowedPattern Allowed regex pattern * @param int|null $textMaxLength Max raw text length - * @param bool|null $textUnique Whether the text value must be unique, if column is a text + * @param bool|null $textUnique Whether the text value must be unique, if + * column is a text * @param 'progress'|'stars'|null $subtype Subtype for the new column * @param string|null $description Description - * @param list|null $selectedViewIds View IDs where this columns should be added + * @param list|null $selectedViewIds View IDs where this columns + * should be added * @param boolean $mandatory Is mandatory * @param 'table'|'view' $baseNodeType Context type of the column creation - * @param array $customSettings Custom settings for the column - * @return DataResponse|DataResponse + * @param array $customSettings Custom settings for the + * column + * @return DataResponse|DataResponse * * 200: Column created * 403: No permission @@ -210,16 +224,23 @@ public function createTextColumn(int $baseNodeId, string $title, ?string $textDe * * @param int $baseNodeId Context of the column creation * @param string $title Title - * @param string $selectionOptions Json array{id: int, label: string} with options that can be selected, eg [{"id": 1, "label": "first"},{"id": 2, "label": "second"}] - * @param string|null $selectionDefault Json int|list for default selected option(s), eg 5 or ["1", "8"] + * @param string $selectionOptions Json array{id: int, label: string} with + * options that can be selected, eg [{"id": 1, "label": "first"},{"id": + * 2, "label": "second"}] + * @param string|null $selectionDefault Json int|list for default + * selected option(s), eg 5 or ["1", "8"] * @param 'progress'|'stars'|null $subtype Subtype for the new column * @param string|null $description Description - * @param list|null $selectedViewIds View IDs where this columns should be added + * @param list|null $selectedViewIds View IDs where this columns + * should be added * @param boolean $mandatory Is mandatory * @param 'table'|'view' $baseNodeType Context type of the column creation - * @param array $customSettings Custom settings for the column + * @param array $customSettings Custom settings for the + * column * - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * * 200: Column created * 403: No permission @@ -259,15 +280,20 @@ public function createSelectionColumn(int $baseNodeId, string $title, string $se * * @param int $baseNodeId Context of the column creation * @param string $title Title - * @param 'today'|'now'|null $datetimeDefault For a subtype 'date' you can set 'today'. For a main type or subtype 'time' you can set to 'now'. + * @param 'today'|'now'|null $datetimeDefault For a subtype 'date' you can + * set 'today'. For a main type or subtype 'time' you can set to 'now'. * @param 'progress'|'stars'|null $subtype Subtype for the new column * @param string|null $description Description - * @param list|null $selectedViewIds View IDs where this columns should be added + * @param list|null $selectedViewIds View IDs where this columns + * should be added * @param boolean $mandatory Is mandatory * @param 'table'|'view' $baseNodeType Context type of the column creation - * @param array $customSettings Custom settings for the column + * @param array $customSettings Custom settings for the + * column * - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * * 200: Column created * 403: No permission @@ -304,19 +330,25 @@ public function createDatetimeColumn(int $baseNodeId, string $title, ?string $da * * @param int $baseNodeId Context of the column creation * @param string $title Title - * @param string|null $usergroupDefault Json array{id: string, type: int}, eg [{"id": "admin", "type": 0}, {"id": "user1", "type": 0}] - * @param boolean $usergroupMultipleItems Whether you can select multiple users or/and groups + * @param string|null $usergroupDefault Json array{id: string, type: int}, + * eg [{"id": "admin", "type": 0}, {"id": "user1", "type": 0}] + * @param boolean $usergroupMultipleItems Whether you can select multiple + * users or/and groups * @param boolean $usergroupSelectUsers Whether you can select users * @param boolean $usergroupSelectGroups Whether you can select groups * @param boolean $usergroupSelectTeams Whether you can select teams * @param boolean $showUserStatus Whether to show the user's status * @param string|null $description Description - * @param list|null $selectedViewIds View IDs where this columns should be added + * @param list|null $selectedViewIds View IDs where this columns + * should be added * @param boolean $mandatory Is mandatory * @param 'table'|'view' $baseNodeType Context type of the column creation - * @param array $customSettings Custom settings for the column + * @param array $customSettings Custom settings for the + * column * - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * * 200: Column created * 403: No permission diff --git a/lib/Controller/ApiPublicColumnsController.php b/lib/Controller/ApiPublicColumnsController.php new file mode 100644 index 0000000000..cdd3be9571 --- /dev/null +++ b/lib/Controller/ApiPublicColumnsController.php @@ -0,0 +1,86 @@ +, array{}>|DataResponse + * + * 200: Columns are returned + * 400: Invalid request parameters + * 403: No permissions + * 404: Not found + * 500: Internal error + */ + #[PublicPage] + #[ApiRoute(verb: 'GET', url: '/api/2/public/{token}/columns')] + #[OpenAPI] + public function indexByPublicLink(string $token): DataResponse { + try { + $shareToken = new ShareToken($token); + } catch (InvalidArgumentException $e) { + return $this->handleBadRequestError(new BadRequestError( + 'Invalid share token', $e->getCode(), $e + )); + } + + try { + $share = $this->shareService->findByToken($shareToken); + $columns = $this->getColumnsFromTableOrView($share->getNodeType(), $share->getNodeId(), ''); + + $formattedTableColumns = $this->service->formatColumnsForPublicShare($columns); + return new DataResponse($formattedTableColumns); + } catch (PermissionError $e) { + return $this->handlePermissionError($e); + } catch (InternalError $e) { + return $this->handleError($e); + } catch (NotFoundError $e) { + return $this->handleNotFoundError($e); + } catch (BadRequestError $e) { + return $this->handleBadRequestError($e); + } + } +} diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 28109a392c..3a33b64a6c 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -7,16 +7,23 @@ namespace OCA\Tables\Controller; +use InvalidArgumentException; use OCA\Tables\AppInfo\Application; +use OCA\Tables\Errors\NotFoundError; +use OCA\Tables\Service\ShareService; +use OCA\Tables\Service\ValueObject\ShareToken; use OCA\Text\Event\LoadEditor; use OCA\Viewer\Event\LoadViewer; use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\FrontpageRoute; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; +use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IL10N; use OCP\INavigationManager; use OCP\IRequest; use OCP\Util; @@ -28,6 +35,8 @@ public function __construct( protected IEventDispatcher $eventDispatcher, protected INavigationManager $navigationManager, protected IInitialState $initialState, + protected ShareService $shareService, + protected IL10N $l, ) { parent::__construct(Application::APP_ID, $request); } @@ -40,10 +49,7 @@ public function __construct( #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] public function index(): TemplateResponse { Util::addScript(Application::APP_ID, 'tables-main'); - Util::addStyle(Application::APP_ID, 'grid'); - Util::addStyle(Application::APP_ID, 'modal'); - Util::addStyle(Application::APP_ID, 'tiptap'); - Util::addStyle(Application::APP_ID, 'tables-style'); + $this->loadStyles(); if (class_exists(LoadViewer::class)) { $this->eventDispatcher->dispatchTyped(new LoadViewer()); @@ -72,4 +78,42 @@ public function context(int $contextId): TemplateResponse { return $this->index(); } + + #[PublicPage] + #[NoCSRFRequired] + #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] + #[FrontpageRoute(verb: 'GET', url: '/s/{token}')] + public function linkShare(string $token): TemplateResponse { + try { + $shareToken = new ShareToken($token); + $this->shareService->findByToken($shareToken); + } catch (InvalidArgumentException|NotFoundError $e) { + + return new TemplateResponse( + Application::APP_ID, + 'error', + ['message' => $this->l->t('This share does not exist or is no longer available')], + TemplateResponse::RENDER_AS_PUBLIC + ); + } + + Util::addScript(Application::APP_ID, 'tables-main'); + $this->loadStyles(); + + $this->initialState->provideInitialState('shareToken', (string)$shareToken); + + + if (class_exists(LoadEditor::class)) { + $this->eventDispatcher->dispatchTyped(new LoadEditor()); + } + + return new TemplateResponse(Application::APP_ID, 'main', [], TemplateResponse::RENDER_AS_PUBLIC); + } + + protected function loadStyles(): void { + Util::addStyle(Application::APP_ID, 'grid'); + Util::addStyle(Application::APP_ID, 'modal'); + Util::addStyle(Application::APP_ID, 'tiptap'); + Util::addStyle(Application::APP_ID, 'tables-style'); + } } diff --git a/lib/Controller/PublicRowOCSController.php b/lib/Controller/PublicRowOCSController.php new file mode 100644 index 0000000000..6937b51c00 --- /dev/null +++ b/lib/Controller/PublicRowOCSController.php @@ -0,0 +1,91 @@ +, array{}>|DataResponse + * + * 200: Rows are returned + * 400: Invalid request parameters + * 403: No permissions + * 404: Not found + * 500: Internal error + */ + #[PublicPage] + #[AssertShareToken] + #[ApiRoute(verb: 'GET', url: '/api/2/public/{token}/rows', requirements: ['token' => '[a-zA-Z0-9]{16}'])] + #[OpenAPI] + public function getRows(string $token, ?int $limit, ?int $offset): DataResponse { + try { + $shareToken = new ShareToken($token); + $share = $this->shareService->findByToken($shareToken); + + $limit = $limit !== null ? max(0, min(500, $limit)) : null; + $offset = $offset !== null ? max(0, $offset) : null; + + $nodeType = ConversionHelper::stringNodeType2Const($share->getNodeType()); + if ($nodeType === Application::NODE_TYPE_TABLE) { + $rows = $this->rowService->findAllByTable($share->getNodeId(), '', $limit, $offset); + } elseif ($nodeType === Application::NODE_TYPE_VIEW) { + $rows = $this->rowService->findAllByView($share->getNodeId(), '', $limit, $offset); + } + + $formattedRows = $this->rowService->formatRowsForPublicShare($rows); + return new DataResponse($formattedRows); + } catch (PermissionError $e) { + return $this->handlePermissionError($e); + } catch (InternalError $e) { + return $this->handleError($e); + } catch (NotFoundError $e) { + return $this->handleNotFoundError($e); + } catch (BadRequestError $e) { + return $this->handleBadRequestError($e); + } + } +} diff --git a/lib/Controller/ShareOCSController.php b/lib/Controller/ShareOCSController.php new file mode 100644 index 0000000000..023816dac0 --- /dev/null +++ b/lib/Controller/ShareOCSController.php @@ -0,0 +1,110 @@ +|DataResponse + * + * 200: Link share created + * 400: Invalid request parameters + * 403: No permissions + * 404: Not found + * 500: Internal error + */ + #[NoAdminRequired] + #[RequirePermission(permission: Application::PERMISSION_MANAGE, typeParam: 'nodeCollection')] + #[ApiRoute(verb: 'POST', url: '/api/2/{nodeCollection}/{nodeId}/share')] + public function createLinkShare( + string $nodeCollection, + int $nodeId, + ?string $password = null, + ): DataResponse { + try { + $collection = ConversionHelper::stringNodeType2Const($nodeCollection); + if ($collection === Application::NODE_TYPE_TABLE) { + $node = $this->tableService->find($nodeId); + } else { + $node = $this->viewService->find($nodeId); + } + } catch (NotFoundError $e) { + return $this->handleNotFoundError($e); + } catch (InternalError $e) { + return $this->handleError($e); + } catch (PermissionError $e) { + return $this->handlePermissionError($e); + } + + if ($password !== null) { + $event = new ValidatePasswordPolicyEvent($password, PasswordContext::SHARING); + try { + $this->eventDispatcher->dispatchTyped($event); + } catch (\Exception $e) { + $error = new BadRequestError($e->getMessage(), $e->getCode(), $e); + return $this->handleBadRequestError($error); + } + } + + try { + $share = $this->shareService->createLinkShare($node, $password); + } catch (InternalError $e) { + return $this->handleError($e); + } + + return new DataResponse([ + 'shareToken' => $share->getToken(), + 'url' => $this->urlGenerator->linkToRouteAbsolute('tables.page.linkShare', ['token' => $share->getToken()]), + ]); + } +} diff --git a/lib/Db/Share.php b/lib/Db/Share.php index 33fb936d99..7f89380618 100644 --- a/lib/Db/Share.php +++ b/lib/Db/Share.php @@ -28,6 +28,10 @@ * @method setNodeId(int $nodeId) * @method getNodeType(): string * @method setNodeType(string $nodeType) + * @method \string getToken() + * @method setToken(string $token) + * @method getPassword(): string + * @method setPassword(string $password) * @method getPermissionRead(): bool * @method setPermissionRead(bool $permissionRead) * @method getPermissionCreate(): bool @@ -50,6 +54,8 @@ class Share extends EntitySuper implements JsonSerializable { protected ?string $receiverType = null; // user, group, circle protected ?int $nodeId = null; protected ?string $nodeType = null; + protected ?string $token = null; + protected ?string $password = null; protected ?bool $permissionRead = null; protected ?bool $permissionCreate = null; protected ?bool $permissionUpdate = null; @@ -92,6 +98,8 @@ public function jsonSerialize(): array { 'receiver' => $this->receiver, 'receiverDisplayName' => $this->receiverDisplayName, 'receiverType' => $this->receiverType, + 'token' => $this->token, + 'password' => $this->password, 'createdAt' => $this->createdAt, 'lastEditAt' => $this->lastEditAt, ]; diff --git a/lib/Db/ShareMapper.php b/lib/Db/ShareMapper.php index 05b39831df..288005bb76 100644 --- a/lib/Db/ShareMapper.php +++ b/lib/Db/ShareMapper.php @@ -7,6 +7,7 @@ namespace OCA\Tables\Db; +use OCA\Tables\Service\ValueObject\ShareToken; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Db\QBMapper; @@ -41,6 +42,17 @@ public function find(int $id): Share { return $this->findEntity($qb); } + /** + * @throws DoesNotExistException + */ + public function findByToken(ShareToken $token): Share { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->table) + ->where($qb->expr()->eq('token', $qb->createNamedParameter((string)$token, IQueryBuilder::PARAM_STR))); + return $this->findEntity($qb); + } + /** * find share for a node * look for all receiver types or limit it to one given type diff --git a/lib/Helper/ConversionHelper.php b/lib/Helper/ConversionHelper.php index f4880c9474..04b0015343 100644 --- a/lib/Helper/ConversionHelper.php +++ b/lib/Helper/ConversionHelper.php @@ -9,6 +9,8 @@ use InvalidArgumentException; use OCA\Tables\AppInfo\Application; +use OCA\Tables\Db\Table; +use OCA\Tables\Db\View; class ConversionHelper { @@ -33,4 +35,11 @@ public static function stringNodeType2Const(string $nodeType): int { default => throw new InvalidArgumentException('Invalid node type'), }; } + + public static function object2String(Table|View $node): string { + if ($node instanceof Table) { + return 'table'; + } + return 'view'; + } } diff --git a/lib/Middleware/Attribute/AssertShareToken.php b/lib/Middleware/Attribute/AssertShareToken.php new file mode 100644 index 0000000000..4fa8df9711 --- /dev/null +++ b/lib/Middleware/Attribute/AssertShareToken.php @@ -0,0 +1,16 @@ +getAttributes(AssertShareToken::class); + if ($shallAssertShareToken) { + $this->assertShareTokenIsValidAndExisting($this->request->getParam('token', '')); + } + + } + + /** + * @throws NotFoundError + * @throws InvalidArgumentException + */ + public function assertShareTokenIsValidAndExisting(string $tokenInput): void { + $shareToken = new ShareToken($tokenInput); + $this->shareService->findByToken($shareToken); + } + + public function afterException($controller, $methodName, \Exception $exception): DataResponse { + if ($exception instanceof InvalidArgumentException) { + return new DataResponse(['message' => $exception->getMessage()], Http::STATUS_BAD_REQUEST); + } + if ($exception instanceof NotFoundError) { + return new DataResponse(['message' => $exception->getMessage()], Http::STATUS_NOT_FOUND); + } + throw $exception; + } +} diff --git a/lib/Migration/Version000200Date20220428000000.php b/lib/Migration/Version000200Date20220428000000.php index 66f0da9bfd..67eef3d332 100644 --- a/lib/Migration/Version000200Date20220428000000.php +++ b/lib/Migration/Version000200Date20220428000000.php @@ -54,6 +54,14 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt 'notnull' => true, 'length' => 50 ]); + $table->addColumn('token', Types::STRING, [ + 'notnull' => false, + 'length' => 64 + ]); + $table->addColumn('password', Types::STRING, [ + 'notnull' => false, + 'length' => 255 + ]); $table->addColumn('permission_read', Types::BOOLEAN, [ 'notnull' => false, @@ -83,6 +91,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt 'notnull' => true, ]); $table->setPrimaryKey(['id']); + $table->addIndex(['node_id', 'node_type'], 'shares_node_idx'); + $table->addIndex(['receiver', 'receiver_type'], 'shares_receiver_idx'); } return $schema; diff --git a/lib/Migration/Version1000Date20251208192653.php b/lib/Migration/Version1000Date20251208192653.php new file mode 100644 index 0000000000..70d8b84e5f --- /dev/null +++ b/lib/Migration/Version1000Date20251208192653.php @@ -0,0 +1,51 @@ +hasTable($tableName)) { + return null; + } + + $table = $schema->getTable($tableName); + if (!$table->hasColumn('token')) { + $table->addColumn('token', Types::STRING, [ + 'notnull' => false, + 'length' => 64 + ]); + } + + if (!$table->hasColumn('password')) { + $table->addColumn('password', Types::STRING, [ + 'notnull' => false, + 'length' => 255 + ]); + } + + if (!$table->hasIndex('shares_token_idx')) { + $table->addIndex(['token'], 'shares_token_idx'); + } + + return $schema; + } +} diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 9111ba55f5..4ab08bdcd0 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -83,6 +83,18 @@ * data: ?array{columnId: int, value: mixed}, * } * + * @psalm-type TablesPublicRow = array{ + * id: int, + * createdAt: string, + * lastEditAt: string, + * data: ?array{columnId: int, value: mixed}, + * } + * + * @psalm-type TablesLinkShare = array{ + * shareToken: string, + * url: string, + * } + * * @psalm-type TablesShare = array{ * id: int, * sender: string, @@ -105,8 +117,10 @@ * title: string, * tableId: int, * createdBy: string, + * createdByDisplayName: string, * createdAt: string, * lastEditBy: string, + * lastEditByDisplayName: string, * lastEditAt: string, * type: string, * subtype: string, @@ -143,6 +157,46 @@ * }, * } * + * @psalm-type TablesPublicColumn = array{ + * id: int, + * title: string, + * createdAt: string, + * lastEditAt: string, + * type: string, + * subtype: string, + * mandatory: bool, + * description: string, + * orderWeight: int, + * numberDefault: float, + * numberMin: float, + * numberMax: float, + * numberDecimals: int, + * numberPrefix: string, + * numberSuffix: string, + * textDefault: string, + * textAllowedPattern: string, + * textMaxLength: int, + * textUnique: bool, + * selectionOptions: string, + * selectionDefault: string, + * datetimeDefault: string, + * usergroupDefault: string, + * usergroupMultipleItems: bool, + * usergroupSelectUsers: bool, + * usergroupSelectGroups: bool, + * usergroupSelectTeams: bool, + * showUserStatus: bool, + * viewColumnInformation: ?array{ + * columnId: int, + * order: int, + * readonly: bool, + * mandatory: bool, + * }, + * customSettings: ?array{ + * width: int, + * }, + * } + * * @psalm-type TablesImportState = array{ * found_columns_count: int, * matching_columns_count: int, diff --git a/lib/Service/ColumnService.php b/lib/Service/ColumnService.php index 54e8f55ca9..10bc09bf10 100644 --- a/lib/Service/ColumnService.php +++ b/lib/Service/ColumnService.php @@ -132,6 +132,22 @@ public function formatColumns(array $columns): array { return array_map(fn (Column $item) => $item->jsonSerialize(), $columns); } + /** + * @param Column[] $columns + * @return array + */ + public function formatColumnsForPublicShare(array $columns): array { + return array_map(static function (Column $column): array { + $columnData = $column->jsonSerialize(); + unset($columnData['tableId']); + unset($columnData['createdBy']); + unset($columnData['createdByDisplayName']); + unset($columnData['lastEditBy']); + unset($columnData['lastEditByDisplayName']); + return $columnData; + }, $columns); + } + /** * @throws NotFoundError * @throws InternalError diff --git a/lib/Service/RowService.php b/lib/Service/RowService.php index 34b7b401b3..8cb0234053 100644 --- a/lib/Service/RowService.php +++ b/lib/Service/RowService.php @@ -38,6 +38,7 @@ /** * @psalm-import-type TablesRow from ResponseDefinitions + * @psalm-import-type TablesPublicRow from ResponseDefinitions */ class RowService extends SuperService { private array $tmpRows = []; // holds already loaded rows as a small cache @@ -66,6 +67,20 @@ public function formatRows(array $rows): array { return array_map(fn (Row2 $row) => $row->jsonSerialize(), $rows); } + /** + * @param Row2[] $rows + * @return TablesPublicRow[] + */ + public function formatRowsForPublicShare(array $rows): array { + return array_map(static function (Row2 $row): array { + $rowData = $row->jsonSerialize(); + unset($rowData['tableId']); + unset($rowData['createdBy']); + unset($rowData['lastEditBy']); + return $rowData; + }, $rows); + } + /** * @param int $tableId * @param string $userId @@ -237,9 +252,11 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R } /** - * When inserting rows into views we try to prefill columns that are not accessible by reasonable defaults + * When inserting rows into views we try to prefill columns that are not + * accessible by reasonable defaults * - * This might not work in all cases, but for single filter rules this is the sanest to ensure the row is actually part of the view + * This might not work in all cases, but for single filter rules this is + * the sanest to ensure the row is actually part of the view */ private function enhanceWithViewDefaults(?View $view, RowDataInput $data): RowDataInput { if ($view === null) { @@ -746,8 +763,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 diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index a7eedf2469..1825dd2a2f 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -12,6 +12,7 @@ use DateTime; use InvalidArgumentException; +use OCA\Tables\AppInfo\Application; use OCA\Tables\Constants\ShareReceiverType; use OCA\Tables\Db\Context; use OCA\Tables\Db\ContextNavigation; @@ -26,15 +27,18 @@ use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; use OCA\Tables\Helper\CircleHelper; +use OCA\Tables\Helper\ConversionHelper; use OCA\Tables\Helper\GroupHelper; use OCA\Tables\Helper\UserHelper; use OCA\Tables\Model\Permissions; use OCA\Tables\ResponseDefinitions; +use OCA\Tables\Service\ValueObject\ShareToken; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Db\TTransactional; use OCP\DB\Exception; use OCP\IDBConnection; +use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; use Throwable; @@ -44,44 +48,21 @@ class ShareService extends SuperService { use TTransactional; - protected ShareMapper $mapper; - - protected TableMapper $tableMapper; - - protected ViewMapper $viewMapper; - - protected UserHelper $userHelper; - - protected GroupHelper $groupHelper; - - protected CircleHelper $circleHelper; - - private ContextNavigationMapper $contextNavigationMapper; - - private IDBConnection $dbc; - public function __construct( PermissionsService $permissionsService, LoggerInterface $logger, ?string $userId, - ShareMapper $shareMapper, - TableMapper $tableMapper, - ViewMapper $viewMapper, - UserHelper $userHelper, - GroupHelper $groupHelper, - CircleHelper $circleHelper, - ContextNavigationMapper $contextNavigationMapper, - IDBConnection $dbc, + protected ShareMapper $mapper, + protected TableMapper $tableMapper, + protected ViewMapper $viewMapper, + protected UserHelper $userHelper, + protected GroupHelper $groupHelper, + protected CircleHelper $circleHelper, + private ContextNavigationMapper $contextNavigationMapper, + private IDBConnection $dbc, + protected ISecureRandom $secureRandom, ) { parent::__construct($logger, $userId, $permissionsService); - $this->mapper = $shareMapper; - $this->tableMapper = $tableMapper; - $this->viewMapper = $viewMapper; - $this->userHelper = $userHelper; - $this->groupHelper = $groupHelper; - $this->circleHelper = $circleHelper; - $this->contextNavigationMapper = $contextNavigationMapper; - $this->dbc = $dbc; } @@ -140,6 +121,50 @@ public function find(int $id):Share { } } + /** + * @throws NotFoundError + */ + public function findByToken(ShareToken $token): Share { + try { + return $this->mapper->findByToken($token); + } catch (DoesNotExistException $e) { + throw new NotFoundError($e->getMessage()); + } + } + + /** + * @throws InternalError + */ + public function createLinkShare(Table|View $node, ?string $password = null): Share { + for ($i = 0; $i < 3; $i++) { + // there is the theoretical chance, that an existing share token would be re-used, + // so we take up to three attempts to try to generate it. + try { + return $this->create( + $node->getId(), + ConversionHelper::object2String($node), + '', + ShareReceiverType::LINK, + true, + false, + false, + false, + false, + Application::NAV_ENTRY_MODE_HIDDEN, + $this->generateShareToken(), + $password, + ); + } catch (InternalError $e) { + } + } + throw $e; + } + + protected function generateShareToken(): ShareToken { + $shareToken = $this->secureRandom->generate(ShareToken::MIN_LENGTH, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS); + return new ShareToken($shareToken); + } + /** * @param string|null $userId * @return array @@ -240,7 +265,7 @@ public function getSharedPermissionsIfSharedWithMe(int $elementId, string $eleme * @return Share * @throws InternalError */ - public function create(int $nodeId, string $nodeType, string $receiver, string $receiverType, bool $permissionRead, bool $permissionCreate, bool $permissionUpdate, bool $permissionDelete, bool $permissionManage, int $displayMode):Share { + public function create(int $nodeId, string $nodeType, string $receiver, string $receiverType, bool $permissionRead, bool $permissionCreate, bool $permissionUpdate, bool $permissionDelete, bool $permissionManage, int $displayMode, ?ShareToken $shareToken = null, ?string $password = null):Share { if (!$this->userId) { $e = new \Exception('No user given.'); $this->logger->error($e->getMessage(), ['exception' => $e]); @@ -260,6 +285,14 @@ public function create(int $nodeId, string $nodeType, string $receiver, string $ $item->setPermissionManage($permissionManage); $item->setCreatedAt($time->format('Y-m-d H:i:s')); $item->setLastEditAt($time->format('Y-m-d H:i:s')); + + if ($shareToken) { + $item->setToken((string)$shareToken); + } + if ($password) { + $item->setPassword($password); + } + try { $newShare = $this->mapper->insert($item); } catch (Exception $e) { @@ -417,6 +450,11 @@ public function delete(int $id): Share { * @return Share */ private function addReceiverDisplayName(Share $share):Share { + if ($share->getReceiverType() === ShareReceiverType::LINK) { + $share->setReceiverDisplayName(''); + return $share; + } + if ($share->getReceiverType() === ShareReceiverType::USER) { $share->setReceiverDisplayName($this->userHelper->getUserDisplayName($share->getReceiver())); } elseif ($share->getReceiverType() === ShareReceiverType::GROUP) { diff --git a/lib/Service/ValueObject/ShareToken.php b/lib/Service/ValueObject/ShareToken.php new file mode 100644 index 0000000000..0ad1b31e58 --- /dev/null +++ b/lib/Service/ValueObject/ShareToken.php @@ -0,0 +1,40 @@ +token); + if ($lengthInBytes < self::MIN_LENGTH + || $lengthInBytes > self::MAX_LENGTH + ) { + throw new InvalidArgumentException('Share token has to be between 16 and 255 bytes long'); + } + + if (preg_match(self::CHARACTER_REGEX, $this->token) === 1) { + throw new InvalidArgumentException('Share token contains invalid characters: ' . self::CHARACTER_REGEX); + } + } + + public function __toString() { + return $this->token; + } +} diff --git a/openapi.json b/openapi.json index 426bf59f1e..477c06e17b 100644 --- a/openapi.json +++ b/openapi.json @@ -75,8 +75,10 @@ "title", "tableId", "createdBy", + "createdByDisplayName", "createdAt", "lastEditBy", + "lastEditByDisplayName", "lastEditAt", "type", "subtype", @@ -120,12 +122,18 @@ "createdBy": { "type": "string" }, + "createdByDisplayName": { + "type": "string" + }, "createdAt": { "type": "string" }, "lastEditBy": { "type": "string" }, + "lastEditByDisplayName": { + "type": "string" + }, "lastEditAt": { "type": "string" }, @@ -370,6 +378,21 @@ } } }, + "LinkShare": { + "type": "object", + "required": [ + "shareToken", + "url" + ], + "properties": { + "shareToken": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, "OCSMeta": { "type": "object", "required": [ @@ -394,6 +417,211 @@ } } }, + "PublicColumn": { + "type": "object", + "required": [ + "id", + "title", + "createdAt", + "lastEditAt", + "type", + "subtype", + "mandatory", + "description", + "orderWeight", + "numberDefault", + "numberMin", + "numberMax", + "numberDecimals", + "numberPrefix", + "numberSuffix", + "textDefault", + "textAllowedPattern", + "textMaxLength", + "textUnique", + "selectionOptions", + "selectionDefault", + "datetimeDefault", + "usergroupDefault", + "usergroupMultipleItems", + "usergroupSelectUsers", + "usergroupSelectGroups", + "usergroupSelectTeams", + "showUserStatus", + "viewColumnInformation", + "customSettings" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "lastEditAt": { + "type": "string" + }, + "type": { + "type": "string" + }, + "subtype": { + "type": "string" + }, + "mandatory": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "orderWeight": { + "type": "integer", + "format": "int64" + }, + "numberDefault": { + "type": "number", + "format": "double" + }, + "numberMin": { + "type": "number", + "format": "double" + }, + "numberMax": { + "type": "number", + "format": "double" + }, + "numberDecimals": { + "type": "integer", + "format": "int64" + }, + "numberPrefix": { + "type": "string" + }, + "numberSuffix": { + "type": "string" + }, + "textDefault": { + "type": "string" + }, + "textAllowedPattern": { + "type": "string" + }, + "textMaxLength": { + "type": "integer", + "format": "int64" + }, + "textUnique": { + "type": "boolean" + }, + "selectionOptions": { + "type": "string" + }, + "selectionDefault": { + "type": "string" + }, + "datetimeDefault": { + "type": "string" + }, + "usergroupDefault": { + "type": "string" + }, + "usergroupMultipleItems": { + "type": "boolean" + }, + "usergroupSelectUsers": { + "type": "boolean" + }, + "usergroupSelectGroups": { + "type": "boolean" + }, + "usergroupSelectTeams": { + "type": "boolean" + }, + "showUserStatus": { + "type": "boolean" + }, + "viewColumnInformation": { + "type": "object", + "nullable": true, + "required": [ + "columnId", + "order", + "readonly", + "mandatory" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "order": { + "type": "integer", + "format": "int64" + }, + "readonly": { + "type": "boolean" + }, + "mandatory": { + "type": "boolean" + } + } + }, + "customSettings": { + "type": "object", + "nullable": true, + "required": [ + "width" + ], + "properties": { + "width": { + "type": "integer", + "format": "int64" + } + } + } + } + }, + "PublicRow": { + "type": "object", + "required": [ + "id", + "createdAt", + "lastEditAt", + "data" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "createdAt": { + "type": "string" + }, + "lastEditAt": { + "type": "string" + }, + "data": { + "type": "object", + "nullable": true, + "required": [ + "columnId", + "value" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "value": { + "type": "object" + } + } + } + } + }, "Row": { "type": "object", "required": [ @@ -7688,13 +7916,12 @@ } } }, - "/ocs/v2.php/apps/tables/api/2/columns/{nodeType}/{nodeId}": { - "get": { - "operationId": "api_columns-index", - "summary": "[api v2] Get all columns for a table or a view", - "description": "Return an empty array if no columns were found", + "/ocs/v2.php/apps/tables/api/2/favorites/{nodeType}/{nodeId}": { + "post": { + "operationId": "api_favorite-create", + "summary": "[api v2] Add a node (table or view) to user favorites", "tags": [ - "api_columns" + "api_favorite" ], "security": [ { @@ -7708,20 +7935,17 @@ { "name": "nodeType", "in": "path", - "description": "Node type", + "description": "any Application::NODE_TYPE_* constant", "required": true, "schema": { - "type": "string", - "enum": [ - "table", - "view" - ] + "type": "integer", + "format": "int64" } }, { "name": "nodeId", "in": "path", - "description": "Node ID", + "description": "identifier of the node", "required": true, "schema": { "type": "integer", @@ -7741,7 +7965,7 @@ ], "responses": { "200": { - "description": "View deleted", + "description": "Tables returned", "content": { "application/json": { "schema": { @@ -7761,10 +7985,7 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Column" - } + "type": "object" } } } @@ -7916,14 +8137,12 @@ } } } - } - }, - "/ocs/v2.php/apps/tables/api/2/columns/{id}": { - "get": { - "operationId": "api_columns-show", - "summary": "[api v2] Get a column object", + }, + "delete": { + "operationId": "api_favorite-destroy", + "summary": "[api v2] Remove a node (table or view) to from favorites", "tags": [ - "api_columns" + "api_favorite" ], "security": [ { @@ -7935,9 +8154,9 @@ ], "parameters": [ { - "name": "id", + "name": "nodeType", "in": "path", - "description": "Column ID", + "description": "any Application::NODE_TYPE_* constant", "required": true, "schema": { "type": "integer", @@ -7945,19 +8164,29 @@ } }, { - "name": "OCS-APIRequest", - "in": "header", - "description": "Required to be true for the API request to pass", + "name": "nodeId", + "in": "path", + "description": "identifier of the node", "required": true, "schema": { - "type": "boolean", - "default": true + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true } } ], "responses": { "200": { - "description": "Column returned", + "description": "Deleted table returned", "content": { "application/json": { "schema": { @@ -7977,7 +8206,7 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "$ref": "#/components/schemas/Column" + "type": "object" } } } @@ -8131,13 +8360,13 @@ } } }, - "/ocs/v2.php/apps/tables/api/2/columns/number": { - "post": { - "operationId": "api_columns-create-number-column", - "summary": "[api v2] Create new numbered column", - "description": "Specify a subtype to use any special numbered column", + "/ocs/v2.php/apps/tables/api/2/contexts": { + "get": { + "operationId": "context-index", + "summary": "[api v2] Get all contexts available to the requesting person", + "description": "Return an empty array if no contexts were found", "tags": [ - "api_columns" + "context" ], "security": [ { @@ -8147,113 +8376,6 @@ "basic_auth": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "baseNodeId", - "title" - ], - "properties": { - "baseNodeId": { - "type": "integer", - "format": "int64", - "description": "Context of the column creation" - }, - "title": { - "type": "string", - "description": "Title" - }, - "numberDefault": { - "type": "number", - "format": "double", - "nullable": true, - "description": "Default value for new rows" - }, - "numberDecimals": { - "type": "integer", - "format": "int64", - "nullable": true, - "description": "Decimals" - }, - "numberPrefix": { - "type": "string", - "nullable": true, - "description": "Prefix" - }, - "numberSuffix": { - "type": "string", - "nullable": true, - "description": "Suffix" - }, - "numberMin": { - "type": "number", - "format": "double", - "nullable": true, - "description": "Min" - }, - "numberMax": { - "type": "number", - "format": "double", - "nullable": true, - "description": "Max" - }, - "subtype": { - "type": "string", - "nullable": true, - "default": null, - "enum": [ - "progress", - "stars" - ], - "description": "Subtype for the new column" - }, - "description": { - "type": "string", - "nullable": true, - "default": null, - "description": "Description" - }, - "selectedViewIds": { - "type": "array", - "nullable": true, - "default": [], - "description": "View IDs where this columns should be added", - "items": { - "type": "integer", - "format": "int64" - } - }, - "mandatory": { - "type": "boolean", - "default": false, - "description": "Is mandatory" - }, - "baseNodeType": { - "type": "string", - "default": "table", - "enum": [ - "table", - "view" - ], - "description": "Context type of the column creation" - }, - "customSettings": { - "type": "object", - "default": {}, - "description": "Custom settings for the column", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - }, "parameters": [ { "name": "OCS-APIRequest", @@ -8268,37 +8390,7 @@ ], "responses": { "200": { - "description": "Column created", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "$ref": "#/components/schemas/Column" - } - } - } - } - } - } - } - }, - "403": { - "description": "No permission", + "description": "reporting in available contexts", "content": { "application/json": { "schema": { @@ -8318,14 +8410,9 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } + "type": "array", + "items": { + "$ref": "#/components/schemas/Context" } } } @@ -8337,49 +8424,6 @@ }, "500": { "description": "", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - }, - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "404": { - "description": "Not found", "content": { "application/json": { "schema": { @@ -8445,15 +8489,12 @@ } } } - } - }, - "/ocs/v2.php/apps/tables/api/2/columns/text": { + }, "post": { - "operationId": "api_columns-create-text-column", - "summary": "[api v2] Create new text column", - "description": "Specify a subtype to use any special text column", + "operationId": "context-create", + "summary": "[api v2] Create a new context and return it", "tags": [ - "api_columns" + "context" ], "security": [ { @@ -8470,1428 +8511,55 @@ "schema": { "type": "object", "required": [ - "baseNodeId", - "title" + "name", + "iconName" ], "properties": { - "baseNodeId": { - "type": "integer", - "format": "int64", - "description": "Context of the column creation" - }, - "title": { - "type": "string", - "description": "Title" - }, - "textDefault": { - "type": "string", - "nullable": true, - "description": "Default" - }, - "textAllowedPattern": { + "name": { "type": "string", - "nullable": true, - "description": "Allowed regex pattern" - }, - "textMaxLength": { - "type": "integer", - "format": "int64", - "nullable": true, - "description": "Max raw text length" - }, - "textUnique": { - "type": "boolean", - "nullable": true, - "default": false, - "description": "Whether the text value must be unique, if column is a text" + "description": "Name of the context" }, - "subtype": { + "iconName": { "type": "string", - "nullable": true, - "default": null, - "enum": [ - "progress", - "stars" - ], - "description": "Subtype for the new column" + "description": "Material design icon name of the context" }, "description": { "type": "string", - "nullable": true, - "default": null, - "description": "Description" + "default": "", + "description": "Descriptive text of the context" }, - "selectedViewIds": { + "nodes": { "type": "array", - "nullable": true, "default": [], - "description": "View IDs where this columns should be added", + "description": "optional nodes to be connected to this context", "items": { - "type": "integer", - "format": "int64" - } - }, - "mandatory": { - "type": "boolean", - "default": false, - "description": "Is mandatory" - }, - "baseNodeType": { - "type": "string", - "default": "table", - "enum": [ - "table", - "view" - ], - "description": "Context type of the column creation" - }, - "customSettings": { - "type": "object", - "default": {}, - "description": "Custom settings for the column", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - }, - "parameters": [ - { - "name": "OCS-APIRequest", - "in": "header", - "description": "Required to be true for the API request to pass", - "required": true, - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "200": { - "description": "Column created", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "$ref": "#/components/schemas/Column" - } - } - } - } - } - } - } - }, - "403": { - "description": "No permission", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - }, - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Current user is not logged in", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - } - } - } - } - } - }, - "/ocs/v2.php/apps/tables/api/2/columns/selection": { - "post": { - "operationId": "api_columns-create-selection-column", - "summary": "[api v2] Create new selection column", - "description": "Specify a subtype to use any special selection column", - "tags": [ - "api_columns" - ], - "security": [ - { - "bearer_auth": [] - }, - { - "basic_auth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "baseNodeId", - "title", - "selectionOptions" - ], - "properties": { - "baseNodeId": { - "type": "integer", - "format": "int64", - "description": "Context of the column creation" - }, - "title": { - "type": "string", - "description": "Title" - }, - "selectionOptions": { - "type": "string", - "description": "Json array{id: int, label: string} with options that can be selected, eg [{\"id\": 1, \"label\": \"first\"},{\"id\": 2, \"label\": \"second\"}]" - }, - "selectionDefault": { - "type": "string", - "nullable": true, - "description": "Json int|list for default selected option(s), eg 5 or [\"1\", \"8\"]" - }, - "subtype": { - "type": "string", - "nullable": true, - "default": null, - "enum": [ - "progress", - "stars" - ], - "description": "Subtype for the new column" - }, - "description": { - "type": "string", - "nullable": true, - "default": null, - "description": "Description" - }, - "selectedViewIds": { - "type": "array", - "nullable": true, - "default": [], - "description": "View IDs where this columns should be added", - "items": { - "type": "integer", - "format": "int64" - } - }, - "mandatory": { - "type": "boolean", - "default": false, - "description": "Is mandatory" - }, - "baseNodeType": { - "type": "string", - "default": "table", - "enum": [ - "table", - "view" - ], - "description": "Context type of the column creation" - }, - "customSettings": { - "type": "object", - "default": {}, - "description": "Custom settings for the column", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - }, - "parameters": [ - { - "name": "OCS-APIRequest", - "in": "header", - "description": "Required to be true for the API request to pass", - "required": true, - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "200": { - "description": "Column created", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "$ref": "#/components/schemas/Column" - } - } - } - } - } - } - } - }, - "403": { - "description": "No permission", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - }, - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Current user is not logged in", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - } - } - } - } - } - }, - "/ocs/v2.php/apps/tables/api/2/columns/datetime": { - "post": { - "operationId": "api_columns-create-datetime-column", - "summary": "[api v2] Create new datetime column", - "description": "Specify a subtype to use any special datetime column", - "tags": [ - "api_columns" - ], - "security": [ - { - "bearer_auth": [] - }, - { - "basic_auth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "baseNodeId", - "title" - ], - "properties": { - "baseNodeId": { - "type": "integer", - "format": "int64", - "description": "Context of the column creation" - }, - "title": { - "type": "string", - "description": "Title" - }, - "datetimeDefault": { - "type": "string", - "nullable": true, - "enum": [ - "today", - "now" - ], - "description": "For a subtype 'date' you can set 'today'. For a main type or subtype 'time' you can set to 'now'." - }, - "subtype": { - "type": "string", - "nullable": true, - "default": null, - "enum": [ - "progress", - "stars" - ], - "description": "Subtype for the new column" - }, - "description": { - "type": "string", - "nullable": true, - "default": null, - "description": "Description" - }, - "selectedViewIds": { - "type": "array", - "nullable": true, - "default": [], - "description": "View IDs where this columns should be added", - "items": { - "type": "integer", - "format": "int64" - } - }, - "mandatory": { - "type": "boolean", - "default": false, - "description": "Is mandatory" - }, - "baseNodeType": { - "type": "string", - "default": "table", - "enum": [ - "table", - "view" - ], - "description": "Context type of the column creation" - }, - "customSettings": { - "type": "object", - "default": {}, - "description": "Custom settings for the column", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - }, - "parameters": [ - { - "name": "OCS-APIRequest", - "in": "header", - "description": "Required to be true for the API request to pass", - "required": true, - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "200": { - "description": "Column created", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "$ref": "#/components/schemas/Column" - } - } - } - } - } - } - } - }, - "403": { - "description": "No permission", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - }, - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Current user is not logged in", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - } - } - } - } - } - }, - "/ocs/v2.php/apps/tables/api/2/columns/usergroup": { - "post": { - "operationId": "api_columns-create-usergroup-column", - "summary": "[api v2] Create new usergroup column", - "tags": [ - "api_columns" - ], - "security": [ - { - "bearer_auth": [] - }, - { - "basic_auth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "baseNodeId", - "title" - ], - "properties": { - "baseNodeId": { - "type": "integer", - "format": "int64", - "description": "Context of the column creation" - }, - "title": { - "type": "string", - "description": "Title" - }, - "usergroupDefault": { - "type": "string", - "nullable": true, - "description": "Json array{id: string, type: int}, eg [{\"id\": \"admin\", \"type\": 0}, {\"id\": \"user1\", \"type\": 0}]" - }, - "usergroupMultipleItems": { - "type": "boolean", - "default": null, - "description": "Whether you can select multiple users or/and groups" - }, - "usergroupSelectUsers": { - "type": "boolean", - "default": null, - "description": "Whether you can select users" - }, - "usergroupSelectGroups": { - "type": "boolean", - "default": null, - "description": "Whether you can select groups" - }, - "usergroupSelectTeams": { - "type": "boolean", - "default": null, - "description": "Whether you can select teams" - }, - "showUserStatus": { - "type": "boolean", - "default": null, - "description": "Whether to show the user's status" - }, - "description": { - "type": "string", - "nullable": true, - "default": null, - "description": "Description" - }, - "selectedViewIds": { - "type": "array", - "nullable": true, - "default": [], - "description": "View IDs where this columns should be added", - "items": { - "type": "integer", - "format": "int64" - } - }, - "mandatory": { - "type": "boolean", - "default": false, - "description": "Is mandatory" - }, - "baseNodeType": { - "type": "string", - "default": "table", - "enum": [ - "table", - "view" - ], - "description": "Context type of the column creation" - }, - "customSettings": { - "type": "object", - "default": {}, - "description": "Custom settings for the column", - "additionalProperties": { - "type": "object" - } - } - } - } - } - } - }, - "parameters": [ - { - "name": "OCS-APIRequest", - "in": "header", - "description": "Required to be true for the API request to pass", - "required": true, - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "200": { - "description": "Column created", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "$ref": "#/components/schemas/Column" - } - } - } - } - } - } - } - }, - "403": { - "description": "No permission", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - }, - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Current user is not logged in", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - } - } - } - } - } - }, - "/ocs/v2.php/apps/tables/api/2/favorites/{nodeType}/{nodeId}": { - "post": { - "operationId": "api_favorite-create", - "summary": "[api v2] Add a node (table or view) to user favorites", - "tags": [ - "api_favorite" - ], - "security": [ - { - "bearer_auth": [] - }, - { - "basic_auth": [] - } - ], - "parameters": [ - { - "name": "nodeType", - "in": "path", - "description": "any Application::NODE_TYPE_* constant", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "nodeId", - "in": "path", - "description": "identifier of the node", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "OCS-APIRequest", - "in": "header", - "description": "Required to be true for the API request to pass", - "required": true, - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "200": { - "description": "Tables returned", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object" - } - } - } - } - } - } - } - }, - "403": { - "description": "No permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Current user is not logged in", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { "type": "object", "required": [ - "meta", - "data" + "id", + "type" ], "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" + "id": { + "type": "integer", + "format": "int64" }, - "data": {} - } - } - } - } - } - } - } - } - }, - "delete": { - "operationId": "api_favorite-destroy", - "summary": "[api v2] Remove a node (table or view) to from favorites", - "tags": [ - "api_favorite" - ], - "security": [ - { - "bearer_auth": [] - }, - { - "basic_auth": [] + "type": { + "type": "integer", + "format": "int64" + }, + "permissions": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } } - ], + }, "parameters": [ - { - "name": "nodeType", - "in": "path", - "description": "any Application::NODE_TYPE_* constant", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "name": "nodeId", - "in": "path", - "description": "identifier of the node", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, { "name": "OCS-APIRequest", "in": "header", @@ -9905,7 +8573,7 @@ ], "responses": { "200": { - "description": "Deleted table returned", + "description": "returning the full context information", "content": { "application/json": { "schema": { @@ -9925,7 +8593,7 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "type": "object" + "$ref": "#/components/schemas/Context" } } } @@ -9934,8 +8602,8 @@ } } }, - "403": { - "description": "No permissions", + "500": { + "description": "", "content": { "application/json": { "schema": { @@ -9972,8 +8640,8 @@ } } }, - "500": { - "description": "", + "400": { + "description": "invalid parameters were supplied", "content": { "application/json": { "schema": { @@ -10010,8 +8678,8 @@ } } }, - "404": { - "description": "Not found", + "403": { + "description": "lacking permissions on a resource", "content": { "application/json": { "schema": { @@ -10079,11 +8747,10 @@ } } }, - "/ocs/v2.php/apps/tables/api/2/contexts": { + "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}": { "get": { - "operationId": "context-index", - "summary": "[api v2] Get all contexts available to the requesting person", - "description": "Return an empty array if no contexts were found", + "operationId": "context-show", + "summary": "[api v2] Get information about the requests context", "tags": [ "context" ], @@ -10096,6 +8763,16 @@ } ], "parameters": [ + { + "name": "contextId", + "in": "path", + "description": "ID of the context", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, { "name": "OCS-APIRequest", "in": "header", @@ -10109,7 +8786,7 @@ ], "responses": { "200": { - "description": "reporting in available contexts", + "description": "returning the full context information", "content": { "application/json": { "schema": { @@ -10129,10 +8806,7 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Context" - } + "$ref": "#/components/schemas/Context" } } } @@ -10179,6 +8853,44 @@ } } }, + "404": { + "description": "context not found or not available anymore", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, "401": { "description": "Current user is not logged in", "content": { @@ -10209,9 +8921,9 @@ } } }, - "post": { - "operationId": "context-create", - "summary": "[api v2] Create a new context and return it", + "put": { + "operationId": "context-update", + "summary": "[api v2] Update an existing context and return it", "tags": [ "context" ], @@ -10224,52 +8936,53 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "name", - "iconName" - ], "properties": { "name": { "type": "string", - "description": "Name of the context" + "nullable": true, + "description": "provide this parameter to set a new name" }, "iconName": { "type": "string", - "description": "Material design icon name of the context" + "nullable": true, + "description": "provide this parameter to set a new icon" }, "description": { "type": "string", - "default": "", - "description": "Descriptive text of the context" + "nullable": true, + "description": "provide this parameter to set a new description" }, "nodes": { - "type": "array", - "default": [], - "description": "optional nodes to be connected to this context", - "items": { - "type": "object", - "required": [ - "id", - "type" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "type": { - "type": "integer", - "format": "int64" - }, - "permissions": { - "type": "integer", - "format": "int64" - } + "type": "object", + "nullable": true, + "description": "provide this parameter to set a new list of nodes.", + "required": [ + "id", + "type", + "permissions", + "order" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "type": { + "type": "integer", + "format": "int64" + }, + "permissions": { + "type": "integer", + "format": "int64" + }, + "order": { + "type": "integer", + "format": "int64" } } } @@ -10279,6 +8992,16 @@ } }, "parameters": [ + { + "name": "contextId", + "in": "path", + "description": "ID of the context", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, { "name": "OCS-APIRequest", "in": "header", @@ -10359,8 +9082,8 @@ } } }, - "400": { - "description": "invalid parameters were supplied", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { @@ -10398,7 +9121,7 @@ } }, "403": { - "description": "lacking permissions on a resource", + "description": "No permissions", "content": { "application/json": { "schema": { @@ -10464,12 +9187,10 @@ } } } - } - }, - "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}": { - "get": { - "operationId": "context-show", - "summary": "[api v2] Get information about the requests context", + }, + "delete": { + "operationId": "context-destroy", + "summary": "[api v2] Delete an existing context and return it", "tags": [ "context" ], @@ -10573,7 +9294,45 @@ } }, "404": { - "description": "context not found or not available anymore", + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", "content": { "application/json": { "schema": { @@ -10639,10 +9398,12 @@ } } } - }, + } + }, + "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/transfer": { "put": { - "operationId": "context-update", - "summary": "[api v2] Update an existing context and return it", + "operationId": "context-transfer", + "summary": "[api v2] Transfer the ownership of a context and return it", "tags": [ "context" ], @@ -10655,55 +9416,26 @@ } ], "requestBody": { - "required": false, + "required": true, "content": { "application/json": { "schema": { "type": "object", + "required": [ + "newOwnerId" + ], "properties": { - "name": { - "type": "string", - "nullable": true, - "description": "provide this parameter to set a new name" - }, - "iconName": { - "type": "string", - "nullable": true, - "description": "provide this parameter to set a new icon" - }, - "description": { + "newOwnerId": { "type": "string", - "nullable": true, - "description": "provide this parameter to set a new description" + "description": "ID of the new owner" }, - "nodes": { - "type": "object", - "nullable": true, - "description": "provide this parameter to set a new list of nodes.", - "required": [ - "id", - "type", - "permissions", - "order" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "type": { - "type": "integer", - "format": "int64" - }, - "permissions": { - "type": "integer", - "format": "int64" - }, - "order": { - "type": "integer", - "format": "int64" - } - } + "newOwnerType": { + "type": "integer", + "format": "int64", + "default": 0, + "description": "any Application::OWNER_TYPE_* constant", + "minimum": 0, + "maximum": 0 } } } @@ -10718,7 +9450,8 @@ "required": true, "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "minimum": 0 } }, { @@ -10734,7 +9467,7 @@ ], "responses": { "200": { - "description": "returning the full context information", + "description": "Ownership transferred", "content": { "application/json": { "schema": { @@ -10801,6 +9534,44 @@ } } }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, "404": { "description": "Not found", "content": { @@ -10839,8 +9610,8 @@ } } }, - "403": { - "description": "No permissions", + "400": { + "description": "Invalid request", "content": { "application/json": { "schema": { @@ -10906,10 +9677,12 @@ } } } - }, - "delete": { - "operationId": "context-destroy", - "summary": "[api v2] Delete an existing context and return it", + } + }, + "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/pages/{pageId}": { + "put": { + "operationId": "context-update-content-order", + "summary": "[api v2] Update the order on a page of a context", "tags": [ "context" ], @@ -10921,6 +9694,39 @@ "basic_auth": [] } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "object", + "description": "content items with it and order values", + "required": [ + "id", + "order" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "order": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + }, "parameters": [ { "name": "contextId", @@ -10932,6 +9738,16 @@ "format": "int64" } }, + { + "name": "pageId", + "in": "path", + "description": "ID of the page", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, { "name": "OCS-APIRequest", "in": "header", @@ -10945,7 +9761,7 @@ ], "responses": { "200": { - "description": "returning the full context information", + "description": "content updated successfully", "content": { "application/json": { "schema": { @@ -11088,6 +9904,44 @@ } } }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, "401": { "description": "Current user is not logged in", "content": { @@ -11119,12 +9973,12 @@ } } }, - "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/transfer": { - "put": { - "operationId": "context-transfer", - "summary": "[api v2] Transfer the ownership of a context and return it", + "/ocs/v2.php/apps/tables/api/2/{nodeCollection}/{nodeId}/rows": { + "post": { + "operationId": "rowocs-create-row", + "summary": "[api v2] Create a new row in a table or a view", "tags": [ - "context" + "rowocs" ], "security": [ { @@ -11141,20 +9995,22 @@ "schema": { "type": "object", "required": [ - "newOwnerId" + "data" ], "properties": { - "newOwnerId": { - "type": "string", - "description": "ID of the new owner" - }, - "newOwnerType": { - "type": "integer", - "format": "int64", - "default": 0, - "description": "any Application::OWNER_TYPE_* constant", - "minimum": 0, - "maximum": 0 + "data": { + "description": "An array containing the column identifiers and their values", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + ] } } } @@ -11163,14 +10019,27 @@ }, "parameters": [ { - "name": "contextId", + "name": "nodeCollection", "in": "path", - "description": "ID of the context", + "description": "Indicates whether to create a row on a table or view", + "required": true, + "schema": { + "type": "string", + "enum": [ + "tables", + "views" + ], + "pattern": "^(tables|views)$" + } + }, + { + "name": "nodeId", + "in": "path", + "description": "The identifier of the targeted table or view", "required": true, "schema": { "type": "integer", - "format": "int64", - "minimum": 0 + "format": "int64" } }, { @@ -11186,7 +10055,7 @@ ], "responses": { "200": { - "description": "Ownership transferred", + "description": "Row returned", "content": { "application/json": { "schema": { @@ -11206,7 +10075,7 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "$ref": "#/components/schemas/Context" + "$ref": "#/components/schemas/Row" } } } @@ -11215,8 +10084,8 @@ } } }, - "500": { - "description": "", + "403": { + "description": "No permissions", "content": { "application/json": { "schema": { @@ -11253,8 +10122,8 @@ } } }, - "403": { - "description": "No permissions", + "400": { + "description": "Invalid request parameters", "content": { "application/json": { "schema": { @@ -11329,8 +10198,8 @@ } } }, - "400": { - "description": "Invalid request", + "500": { + "description": "Internal error", "content": { "application/json": { "schema": { @@ -11398,14 +10267,16 @@ } } }, - "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/pages/{pageId}": { - "put": { - "operationId": "context-update-content-order", - "summary": "[api v2] Update the order on a page of a context", + "/ocs/v2.php/apps/tables/api/2/public/{token}/columns": { + "get": { + "operationId": "api_public_columns-index-by-public-link", + "summary": "[api v2] Get all columns for a table or a view shared by link", + "description": "Return an empty array if no columns were found", "tags": [ - "context" + "api_public_columns" ], "security": [ + {}, { "bearer_auth": [] }, @@ -11413,58 +10284,161 @@ "basic_auth": [] } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "content" - ], - "properties": { - "content": { - "type": "object", - "description": "content items with it and order values", - "required": [ - "id", - "order" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "order": { - "type": "integer", - "format": "int64" - } + "parameters": [ + { + "name": "token", + "in": "path", + "description": "The share token", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Columns are returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicColumn" + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid request parameters", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Internal error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" } } } } } } - }, + } + } + }, + "/ocs/v2.php/apps/tables/api/2/public/{token}/rows": { + "get": { + "operationId": "public_rowocs-get-rows", + "summary": "[api v2] Fetch all rows from a link share", + "tags": [ + "public_rowocs" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], "parameters": [ { - "name": "contextId", + "name": "token", "in": "path", - "description": "ID of the context", + "description": "The share token", "required": true, + "schema": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{16}$" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional: maximum number of results, capped at 500", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "nullable": true } }, { - "name": "pageId", - "in": "path", - "description": "ID of the page", - "required": true, + "name": "offset", + "in": "query", + "description": "Optional: the offset for this operation", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "nullable": true } }, { @@ -11480,7 +10454,7 @@ ], "responses": { "200": { - "description": "content updated successfully", + "description": "Rows are returned", "content": { "application/json": { "schema": { @@ -11500,7 +10474,10 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "$ref": "#/components/schemas/Context" + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicRow" + } } } } @@ -11509,8 +10486,8 @@ } } }, - "500": { - "description": "", + "403": { + "description": "No permissions", "content": { "application/json": { "schema": { @@ -11547,8 +10524,8 @@ } } }, - "404": { - "description": "Not found", + "400": { + "description": "Invalid request parameters", "content": { "application/json": { "schema": { @@ -11585,8 +10562,8 @@ } } }, - "403": { - "description": "No permissions", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { @@ -11623,8 +10600,8 @@ } } }, - "400": { - "description": "Invalid request", + "500": { + "description": "Internal error", "content": { "application/json": { "schema": { @@ -11660,44 +10637,16 @@ } } } - }, - "401": { - "description": "Current user is not logged in", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - } - } } } } }, - "/ocs/v2.php/apps/tables/api/2/{nodeCollection}/{nodeId}/rows": { + "/ocs/v2.php/apps/tables/api/2/{nodeCollection}/{nodeId}/share": { "post": { - "operationId": "rowocs-create-row", - "summary": "[api v2] Create a new row in a table or a view", + "operationId": "shareocs-create-link-share", + "summary": "[api v2] Create a new link share of a table or view", "tags": [ - "rowocs" + "shareocs" ], "security": [ { @@ -11708,28 +10657,17 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "data" - ], "properties": { - "data": { - "description": "An array containing the column identifiers and their values", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": { - "type": "object" - } - } - ] + "password": { + "type": "string", + "nullable": true, + "default": null, + "description": "(Optional) A password to protect the link share with" } } } @@ -11747,8 +10685,7 @@ "enum": [ "tables", "views" - ], - "pattern": "^(tables|views)$" + ] } }, { @@ -11774,7 +10711,7 @@ ], "responses": { "200": { - "description": "Row returned", + "description": "Link share created", "content": { "application/json": { "schema": { @@ -11794,7 +10731,7 @@ "$ref": "#/components/schemas/OCSMeta" }, "data": { - "$ref": "#/components/schemas/Row" + "$ref": "#/components/schemas/LinkShare" } } } diff --git a/src/App.vue b/src/App.vue index 28da579124..586c33e29c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,19 +4,21 @@ --> - + - + + - + + + diff --git a/src/modules/main/sections/PublicView.vue b/src/modules/main/sections/PublicView.vue new file mode 100644 index 0000000000..278d45620c --- /dev/null +++ b/src/modules/main/sections/PublicView.vue @@ -0,0 +1,63 @@ + + + + + + + + + {{ t('tables', 'Export as CSV') }} + + + + + + + + diff --git a/src/modules/sidebar/mixins/shareAPI.js b/src/modules/sidebar/mixins/shareAPI.js index e06a7f62be..00f39045ed 100644 --- a/src/modules/sidebar/mixins/shareAPI.js +++ b/src/modules/sidebar/mixins/shareAPI.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import axios from '@nextcloud/axios' -import { generateUrl } from '@nextcloud/router' +import { generateUrl, generateOcsUrl } from '@nextcloud/router' import '@nextcloud/dialogs/style.css' import displayError from '../../../shared/utils/displayError.js' import ShareTypes from '../../../shared/mixins/shareTypesMixin.js' @@ -15,6 +15,10 @@ export default { data() { return { tablesStore: useTablesStore(), + SHARE_TYPE_USER: 0, + SHARE_TYPE_GROUP: 1, + SHARE_TYPE_LINK: 3, + SHARE_TYPE_CIRCLE: 7, } }, @@ -78,6 +82,36 @@ export default { return true }, + async createLinkShare(password = null) { + const nodeId = this.activeElement.id + const collection = this.isView ? 'views' : 'tables' + + const data = {} + if (password) { + data.password = password + } + + try { + await axios.post(generateOcsUrl(`/apps/tables/api/2/${collection}/${nodeId}/share`), data) + } catch (e) { + displayError(e, t('tables', 'Could not create public link.')) + return false + } + + if (this.isView) { + await this.setViewHasShares({ + viewId: this.activeElement.id, + hasShares: true, + }) + } else { + await this.setTableHasShares({ + tableId: this.isView ? this.activeElement.tableId : this.activeElement.id, + hasShares: true, + }) + } + return true + }, + async removeShareFromBE(shareId) { try { await axios.delete(generateUrl('/apps/tables/share/' + shareId)) @@ -95,12 +129,10 @@ export default { }, isValidShareType(shareType) { - if (shareType === this.SHARE_TYPES.SHARE_TYPE_CIRCLE && !this.isCirclesEnabled) { - return false - } return [ this.SHARE_TYPES.SHARE_TYPE_USER, this.SHARE_TYPES.SHARE_TYPE_GROUP, + this.SHARE_TYPES.SHARE_TYPE_LINK, ...(this.isCirclesEnabled ? [this.SHARE_TYPES.SHARE_TYPE_CIRCLE] : []), ].includes(shareType) }, diff --git a/src/modules/sidebar/partials/SharingEntryLink.vue b/src/modules/sidebar/partials/SharingEntryLink.vue new file mode 100644 index 0000000000..885cde4d1a --- /dev/null +++ b/src/modules/sidebar/partials/SharingEntryLink.vue @@ -0,0 +1,345 @@ + + + + + + + + + + + {{ t('tables', 'Create public link') }} + + + + + + + + {{ t('tables', 'Password protection') }} + + + + + + + + + + + + + + + + + + + + + {{ t('tables', 'Create') }} + + + {{ t('tables', 'Cancel') }} + + + + + + + + + + + + + {{ t('tables', 'Public link') }} + + + {{ t('tables', 'Password protected') }} + + + + + + + + + + + + + + + + + + + + + + + {{ t('tables', 'Delete link') }} + + + + + + + + + + + diff --git a/src/modules/sidebar/partials/SharingLinkList.vue b/src/modules/sidebar/partials/SharingLinkList.vue new file mode 100644 index 0000000000..b8ae5573f6 --- /dev/null +++ b/src/modules/sidebar/partials/SharingLinkList.vue @@ -0,0 +1,83 @@ + + + + {{ t('tables', 'Public links') }} + + + + + + + + + + + + diff --git a/src/modules/sidebar/sections/SidebarSharing.vue b/src/modules/sidebar/sections/SidebarSharing.vue index e7a4eb268e..45140e2f86 100644 --- a/src/modules/sidebar/sections/SidebarSharing.vue +++ b/src/modules/sidebar/sections/SidebarSharing.vue @@ -8,6 +8,11 @@ + @@ -19,15 +24,18 @@ import shareAPI from '../mixins/shareAPI.js' import ShareForm from '../partials/ShareForm.vue' import ShareList from '../partials/ShareList.vue' import ShareInternalLink from '../partials/ShareInternalLink.vue' +import SharingLinkList from '../partials/SharingLinkList.vue' import { getCurrentUser } from '@nextcloud/auth' import { generateUrl } from '@nextcloud/router' import permissionsMixin from '../../../shared/components/ncTable/mixins/permissionsMixin.js' +import { isPublicLinkShare } from '../../../shared/utils/shareUtils.js' export default { components: { ShareForm, ShareList, ShareInternalLink, + SharingLinkList, }, mixins: [shareAPI, permissionsMixin], @@ -37,6 +45,7 @@ export default { // shared with shares: [], + linkShares: [], } }, @@ -74,19 +83,32 @@ export default { getCurrentUser, async loadSharesFromBE() { this.loading = true - this.shares = await this.getSharedWithFromBE() + const allShares = await this.getSharedWithFromBE() + + this.shares = allShares.filter(share => !isPublicLinkShare(share)) + this.linkShares = allShares.filter(share => isPublicLinkShare(share)) + this.loading = false }, async removeShare(share) { await this.removeShareFromBE(share.id) await this.loadSharesFromBE() // If no share is left, remove shared indication - if (this.isView) { - if (this.shares.find(share => ((share.nodeType === 'view' && share.nodeId === this.activeElement.id) || (share.nodeType === 'table' && share.nodeId === this.activeElement.tableId))) === undefined) { + const hasStandardShares = this.shares.some(share => + (this.isView + ? (share.nodeType === 'view' && share.nodeId === this.activeElement.id) + : (share.nodeType === 'table' && share.nodeId === this.activeElement.id)), + ) + const hasLinkShares = this.linkShares.some(share => + (this.isView + ? (share.nodeType === 'view' && share.nodeId === this.activeElement.id) + : (share.nodeType === 'table' && share.nodeId === this.activeElement.id)), + ) + + if (!hasStandardShares && !hasLinkShares) { + if (this.isView) { await this.setViewHasShares({ viewId: this.activeElement.id, hasShares: false }) - } - } else { - if (this.shares.find(share => (share.nodeType === 'table' && share.nodeId === this.activeElement.id)) === undefined) { + } else { await this.setTableHasShares({ tableId: this.activeElement.id, hasShares: false }) } } @@ -101,6 +123,12 @@ export default { await this.updateShareToBE(shareId, data) await this.loadSharesFromBE() }, + async onCreateLinkShare(password) { + const success = await this.createLinkShare(password) + if (success) { + await this.loadSharesFromBE() + } + }, }, } diff --git a/src/pages/PublicTableView.vue b/src/pages/PublicTableView.vue new file mode 100644 index 0000000000..868b94ee36 --- /dev/null +++ b/src/pages/PublicTableView.vue @@ -0,0 +1,79 @@ + + + + + + + + Nextcloud Tables + + + + + + + + + + + + + + + + diff --git a/src/router.js b/src/router.js index 45ce7bd4c7..170921feba 100644 --- a/src/router.js +++ b/src/router.js @@ -9,6 +9,7 @@ import MainViewWrapper from './pages/View.vue' import MainDashboardWrapper from './pages/Table.vue' import Startpage from './pages/Startpage.vue' import Context from './pages/Context.vue' +import PublicTableView from './pages/PublicTableView.vue' Vue.use(Router) @@ -60,5 +61,10 @@ export default new Router({ component: MainViewWrapper, name: 'viewRow', }, + { + path: '/s/:token', + component: PublicTableView, + name: 'publicShare', + }, ], }) diff --git a/src/shared/utils/shareUtils.js b/src/shared/utils/shareUtils.js new file mode 100644 index 0000000000..ce3e4d45d1 --- /dev/null +++ b/src/shared/utils/shareUtils.js @@ -0,0 +1,13 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Checks if a share is a public link share + * @param {object} share + * @return {boolean} + */ +export const isPublicLinkShare = (share) => { + return share.receiverType === 'link' +} diff --git a/src/store/data.js b/src/store/data.js index 80303364b6..5463e62748 100644 --- a/src/store/data.js +++ b/src/store/data.js @@ -26,11 +26,11 @@ export const useDataStore = defineStore('data', { getters: { getColumns: (state) => (isView, elementId) => { - const stateId = genStateKey(isView, elementId) + const stateId = typeof elementId === 'string' && elementId.startsWith('public-') ? elementId : genStateKey(isView, elementId) return state.columns[stateId] ?? [] }, getRows: (state) => (isView, elementId) => { - const stateId = genStateKey(isView, elementId) + const stateId = typeof elementId === 'string' && elementId.startsWith('public-') ? elementId : genStateKey(isView, elementId) return state.rows[stateId] ?? [] }, }, @@ -96,6 +96,30 @@ export const useDataStore = defineStore('data', { return true }, + async loadPublicColumnsFromBE({ token }) { + const stateId = 'public-' + token + this.loading[stateId] = true + let res = null + + try { + res = await axios.get(generateOcsUrl('/apps/tables/api/2/public/' + token + '/columns')) + if (!res?.data?.ocs?.data || !Array.isArray(res.data.ocs.data)) { + throw new Error('Expected array') + } + } catch (e) { + displayError(e, t('tables', 'Could not load columns.')) + return false + } + + const columns = res.data.ocs.data.map(col => parseCol(col)) + // Fix up columns to match expected structure if needed + // Public API might return slightly different structure, but parseCol should handle it if it's standard TableColumn + + set(this.columns, stateId, columns) + this.loading[stateId] = false + return columns + }, + async insertNewColumn({ isView, elementId, data }) { const stateId = genStateKey(isView, elementId) this.loading[stateId] = true @@ -176,6 +200,23 @@ export const useDataStore = defineStore('data', { return true }, + async loadPublicRowsFromBE({ token }) { + const stateId = 'public-' + token + this.loading[stateId] = true + let res + + try { + res = await axios.get(generateOcsUrl('/apps/tables/api/2/public/' + token + '/rows')) + } catch (e) { + displayError(e, t('tables', 'Could not load rows.')) + return false + } + + set(this.rows, stateId, res.data.ocs.data) + this.loading[stateId] = false + return true + }, + removeRows({ isView, elementId }) { const stateId = genStateKey(isView, elementId) set(this.rows, stateId, []) diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index f55c2fe4d4..b661843fbc 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -599,44 +599,7 @@ export type paths = { readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/columns/{nodeType}/{nodeId}": { - readonly parameters: { - readonly query?: never; - readonly header?: never; - readonly path?: never; - readonly cookie?: never; - }; - /** - * [api v2] Get all columns for a table or a view - * @description Return an empty array if no columns were found - */ - readonly get: operations["api_columns-index"]; - readonly put?: never; - readonly post?: never; - readonly delete?: never; - readonly options?: never; - readonly head?: never; - readonly patch?: never; - readonly trace?: never; - }; - readonly "/ocs/v2.php/apps/tables/api/2/columns/{id}": { - readonly parameters: { - readonly query?: never; - readonly header?: never; - readonly path?: never; - readonly cookie?: never; - }; - /** [api v2] Get a column object */ - readonly get: operations["api_columns-show"]; - readonly put?: never; - readonly post?: never; - readonly delete?: never; - readonly options?: never; - readonly head?: never; - readonly patch?: never; - readonly trace?: never; - }; - readonly "/ocs/v2.php/apps/tables/api/2/columns/number": { + readonly "/ocs/v2.php/apps/tables/api/2/favorites/{nodeType}/{nodeId}": { readonly parameters: { readonly query?: never; readonly header?: never; @@ -645,58 +608,56 @@ export type paths = { }; readonly get?: never; readonly put?: never; - /** - * [api v2] Create new numbered column - * @description Specify a subtype to use any special numbered column - */ - readonly post: operations["api_columns-create-number-column"]; - readonly delete?: never; + /** [api v2] Add a node (table or view) to user favorites */ + readonly post: operations["api_favorite-create"]; + /** [api v2] Remove a node (table or view) to from favorites */ + readonly delete: operations["api_favorite-destroy"]; readonly options?: never; readonly head?: never; readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/columns/text": { + readonly "/ocs/v2.php/apps/tables/api/2/contexts": { readonly parameters: { readonly query?: never; readonly header?: never; readonly path?: never; readonly cookie?: never; }; - readonly get?: never; - readonly put?: never; /** - * [api v2] Create new text column - * @description Specify a subtype to use any special text column + * [api v2] Get all contexts available to the requesting person + * @description Return an empty array if no contexts were found */ - readonly post: operations["api_columns-create-text-column"]; + readonly get: operations["context-index"]; + readonly put?: never; + /** [api v2] Create a new context and return it */ + readonly post: operations["context-create"]; readonly delete?: never; readonly options?: never; readonly head?: never; readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/columns/selection": { + readonly "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}": { readonly parameters: { readonly query?: never; readonly header?: never; readonly path?: never; readonly cookie?: never; }; - readonly get?: never; - readonly put?: never; - /** - * [api v2] Create new selection column - * @description Specify a subtype to use any special selection column - */ - readonly post: operations["api_columns-create-selection-column"]; - readonly delete?: never; + /** [api v2] Get information about the requests context */ + readonly get: operations["context-show"]; + /** [api v2] Update an existing context and return it */ + readonly put: operations["context-update"]; + readonly post?: never; + /** [api v2] Delete an existing context and return it */ + readonly delete: operations["context-destroy"]; readonly options?: never; readonly head?: never; readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/columns/datetime": { + readonly "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/transfer": { readonly parameters: { readonly query?: never; readonly header?: never; @@ -704,19 +665,16 @@ export type paths = { readonly cookie?: never; }; readonly get?: never; - readonly put?: never; - /** - * [api v2] Create new datetime column - * @description Specify a subtype to use any special datetime column - */ - readonly post: operations["api_columns-create-datetime-column"]; + /** [api v2] Transfer the ownership of a context and return it */ + readonly put: operations["context-transfer"]; + readonly post?: never; readonly delete?: never; readonly options?: never; readonly head?: never; readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/columns/usergroup": { + readonly "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/pages/{pageId}": { readonly parameters: { readonly query?: never; readonly header?: never; @@ -724,16 +682,16 @@ export type paths = { readonly cookie?: never; }; readonly get?: never; - readonly put?: never; - /** [api v2] Create new usergroup column */ - readonly post: operations["api_columns-create-usergroup-column"]; + /** [api v2] Update the order on a page of a context */ + readonly put: operations["context-update-content-order"]; + readonly post?: never; readonly delete?: never; readonly options?: never; readonly head?: never; readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/favorites/{nodeType}/{nodeId}": { + readonly "/ocs/v2.php/apps/tables/api/2/{nodeCollection}/{nodeId}/rows": { readonly parameters: { readonly query?: never; readonly header?: never; @@ -742,16 +700,15 @@ export type paths = { }; readonly get?: never; readonly put?: never; - /** [api v2] Add a node (table or view) to user favorites */ - readonly post: operations["api_favorite-create"]; - /** [api v2] Remove a node (table or view) to from favorites */ - readonly delete: operations["api_favorite-destroy"]; + /** [api v2] Create a new row in a table or a view */ + readonly post: operations["rowocs-create-row"]; + readonly delete?: never; readonly options?: never; readonly head?: never; readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/contexts": { + readonly "/ocs/v2.php/apps/tables/api/2/public/{token}/columns": { readonly parameters: { readonly query?: never; readonly header?: never; @@ -759,48 +716,11 @@ export type paths = { readonly cookie?: never; }; /** - * [api v2] Get all contexts available to the requesting person - * @description Return an empty array if no contexts were found + * [api v2] Get all columns for a table or a view shared by link + * @description Return an empty array if no columns were found */ - readonly get: operations["context-index"]; + readonly get: operations["api_public_columns-index-by-public-link"]; readonly put?: never; - /** [api v2] Create a new context and return it */ - readonly post: operations["context-create"]; - readonly delete?: never; - readonly options?: never; - readonly head?: never; - readonly patch?: never; - readonly trace?: never; - }; - readonly "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}": { - readonly parameters: { - readonly query?: never; - readonly header?: never; - readonly path?: never; - readonly cookie?: never; - }; - /** [api v2] Get information about the requests context */ - readonly get: operations["context-show"]; - /** [api v2] Update an existing context and return it */ - readonly put: operations["context-update"]; - readonly post?: never; - /** [api v2] Delete an existing context and return it */ - readonly delete: operations["context-destroy"]; - readonly options?: never; - readonly head?: never; - readonly patch?: never; - readonly trace?: never; - }; - readonly "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/transfer": { - readonly parameters: { - readonly query?: never; - readonly header?: never; - readonly path?: never; - readonly cookie?: never; - }; - readonly get?: never; - /** [api v2] Transfer the ownership of a context and return it */ - readonly put: operations["context-transfer"]; readonly post?: never; readonly delete?: never; readonly options?: never; @@ -808,16 +728,16 @@ export type paths = { readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/pages/{pageId}": { + readonly "/ocs/v2.php/apps/tables/api/2/public/{token}/rows": { readonly parameters: { readonly query?: never; readonly header?: never; readonly path?: never; readonly cookie?: never; }; - readonly get?: never; - /** [api v2] Update the order on a page of a context */ - readonly put: operations["context-update-content-order"]; + /** [api v2] Fetch all rows from a link share */ + readonly get: operations["public_rowocs-get-rows"]; + readonly put?: never; readonly post?: never; readonly delete?: never; readonly options?: never; @@ -825,7 +745,7 @@ export type paths = { readonly patch?: never; readonly trace?: never; }; - readonly "/ocs/v2.php/apps/tables/api/2/{nodeCollection}/{nodeId}/rows": { + readonly "/ocs/v2.php/apps/tables/api/2/{nodeCollection}/{nodeId}/share": { readonly parameters: { readonly query?: never; readonly header?: never; @@ -834,8 +754,8 @@ export type paths = { }; readonly get?: never; readonly put?: never; - /** [api v2] Create a new row in a table or a view */ - readonly post: operations["rowocs-create-row"]; + /** [api v2] Create a new link share of a table or view */ + readonly post: operations["shareocs-create-link-share"]; readonly delete?: never; readonly options?: never; readonly head?: never; @@ -863,8 +783,10 @@ export type components = { /** Format: int64 */ readonly tableId: number; readonly createdBy: string; + readonly createdByDisplayName: string; readonly createdAt: string; readonly lastEditBy: string; + readonly lastEditByDisplayName: string; readonly lastEditAt: string; readonly type: string; readonly subtype: string; @@ -948,6 +870,10 @@ export type components = { readonly tables: readonly components["schemas"]["Table"][]; readonly views: readonly components["schemas"]["View"][]; }; + readonly LinkShare: { + readonly shareToken: string; + readonly url: string; + }; readonly OCSMeta: { readonly status: string; readonly statuscode: number; @@ -955,6 +881,66 @@ export type components = { readonly totalitems?: string; readonly itemsperpage?: string; }; + readonly PublicColumn: { + /** Format: int64 */ + readonly id: number; + readonly title: string; + readonly createdAt: string; + readonly lastEditAt: string; + readonly type: string; + readonly subtype: string; + readonly mandatory: boolean; + readonly description: string; + /** Format: int64 */ + readonly orderWeight: number; + /** Format: double */ + readonly numberDefault: number; + /** Format: double */ + readonly numberMin: number; + /** Format: double */ + readonly numberMax: number; + /** Format: int64 */ + readonly numberDecimals: number; + readonly numberPrefix: string; + readonly numberSuffix: string; + readonly textDefault: string; + readonly textAllowedPattern: string; + /** Format: int64 */ + readonly textMaxLength: number; + readonly textUnique: boolean; + readonly selectionOptions: string; + readonly selectionDefault: string; + readonly datetimeDefault: string; + readonly usergroupDefault: string; + readonly usergroupMultipleItems: boolean; + readonly usergroupSelectUsers: boolean; + readonly usergroupSelectGroups: boolean; + readonly usergroupSelectTeams: boolean; + readonly showUserStatus: boolean; + readonly viewColumnInformation: { + /** Format: int64 */ + readonly columnId: number; + /** Format: int64 */ + readonly order: number; + readonly readonly: boolean; + readonly mandatory: boolean; + } | null; + readonly customSettings: { + /** Format: int64 */ + readonly width: number; + } | null; + }; + readonly PublicRow: { + /** Format: int64 */ + readonly id: number; + readonly createdAt: string; + readonly lastEditAt: string; + readonly data: { + /** Format: int64 */ + readonly columnId: number; + readonly value: Record; + } | null; + }; readonly Row: { /** Format: int64 */ readonly id: number; @@ -4781,7 +4767,7 @@ export interface operations { }; }; }; - readonly "api_columns-index": { + readonly "api_favorite-create": { readonly parameters: { readonly query?: never; readonly header: { @@ -4789,16 +4775,16 @@ export interface operations { readonly "OCS-APIRequest": boolean; }; readonly path: { - /** @description Node type */ - readonly nodeType: "table" | "view"; - /** @description Node ID */ + /** @description any Application::NODE_TYPE_* constant */ + readonly nodeType: number; + /** @description identifier of the node */ readonly nodeId: number; }; readonly cookie?: never; }; readonly requestBody?: never; readonly responses: { - /** @description View deleted */ + /** @description Tables returned */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -4807,7 +4793,7 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: readonly components["schemas"]["Column"][]; + readonly data: Record; }; }; }; @@ -4875,7 +4861,7 @@ export interface operations { }; }; }; - readonly "api_columns-show": { + readonly "api_favorite-destroy": { readonly parameters: { readonly query?: never; readonly header: { @@ -4883,14 +4869,16 @@ export interface operations { readonly "OCS-APIRequest": boolean; }; readonly path: { - /** @description Column ID */ - readonly id: number; + /** @description any Application::NODE_TYPE_* constant */ + readonly nodeType: number; + /** @description identifier of the node */ + readonly nodeId: number; }; readonly cookie?: never; }; readonly requestBody?: never; readonly responses: { - /** @description Column returned */ + /** @description Deleted table returned */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -4899,7 +4887,7 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Column"]; + readonly data: Record; }; }; }; @@ -4967,7 +4955,7 @@ export interface operations { }; }; }; - readonly "api_columns-create-number-column": { + readonly "context-index": { readonly parameters: { readonly query?: never; readonly header: { @@ -4977,79 +4965,9 @@ export interface operations { readonly path?: never; readonly cookie?: never; }; - readonly requestBody: { - readonly content: { - readonly "application/json": { - /** - * Format: int64 - * @description Context of the column creation - */ - readonly baseNodeId: number; - /** @description Title */ - readonly title: string; - /** - * Format: double - * @description Default value for new rows - */ - readonly numberDefault?: number | null; - /** - * Format: int64 - * @description Decimals - */ - readonly numberDecimals?: number | null; - /** @description Prefix */ - readonly numberPrefix?: string | null; - /** @description Suffix */ - readonly numberSuffix?: string | null; - /** - * Format: double - * @description Min - */ - readonly numberMin?: number | null; - /** - * Format: double - * @description Max - */ - readonly numberMax?: number | null; - /** - * @description Subtype for the new column - * @default null - * @enum {string|null} - */ - readonly subtype?: "progress" | "stars" | null; - /** - * @description Description - * @default null - */ - readonly description?: string | null; - /** - * @description View IDs where this columns should be added - * @default [] - */ - readonly selectedViewIds?: readonly number[] | null; - /** - * @description Is mandatory - * @default false - */ - readonly mandatory?: boolean; - /** - * @description Context type of the column creation - * @default table - * @enum {string} - */ - readonly baseNodeType?: "table" | "view"; - /** - * @description Custom settings for the column - * @default {} - */ - readonly customSettings?: { - readonly [key: string]: Record; - }; - }; - }; - }; + readonly requestBody?: never; readonly responses: { - /** @description Column created */ + /** @description reporting in available contexts */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -5058,7 +4976,7 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Column"]; + readonly data: readonly components["schemas"]["Context"][]; }; }; }; @@ -5077,38 +4995,6 @@ export interface operations { }; }; }; - /** @description No permission */ - readonly 403: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - /** @description Not found */ - readonly 404: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; readonly 500: { headers: { readonly [name: string]: unknown; @@ -5122,12 +5008,11 @@ export interface operations { }; }; }; - readonly "text/plain": string; }; }; }; }; - readonly "api_columns-create-text-column": { + readonly "context-create": { readonly parameters: { readonly query?: never; readonly header: { @@ -5140,66 +5025,32 @@ export interface operations { readonly requestBody: { readonly content: { readonly "application/json": { + /** @description Name of the context */ + readonly name: string; + /** @description Material design icon name of the context */ + readonly iconName: string; /** - * Format: int64 - * @description Context of the column creation - */ - readonly baseNodeId: number; - /** @description Title */ - readonly title: string; - /** @description Default */ - readonly textDefault?: string | null; - /** @description Allowed regex pattern */ - readonly textAllowedPattern?: string | null; - /** - * Format: int64 - * @description Max raw text length - */ - readonly textMaxLength?: number | null; - /** - * @description Whether the text value must be unique, if column is a text - * @default false - */ - readonly textUnique?: boolean | null; - /** - * @description Subtype for the new column - * @default null - * @enum {string|null} - */ - readonly subtype?: "progress" | "stars" | null; - /** - * @description Description - * @default null + * @description Descriptive text of the context + * @default */ - readonly description?: string | null; + readonly description?: string; /** - * @description View IDs where this columns should be added + * @description optional nodes to be connected to this context * @default [] */ - readonly selectedViewIds?: readonly number[] | null; - /** - * @description Is mandatory - * @default false - */ - readonly mandatory?: boolean; - /** - * @description Context type of the column creation - * @default table - * @enum {string} - */ - readonly baseNodeType?: "table" | "view"; - /** - * @description Custom settings for the column - * @default {} - */ - readonly customSettings?: { - readonly [key: string]: Record; - }; + readonly nodes?: readonly { + /** Format: int64 */ + readonly id: number; + /** Format: int64 */ + readonly type: number; + /** Format: int64 */ + readonly permissions?: number; + }[]; }; }; }; readonly responses: { - /** @description Column created */ + /** @description returning the full context information */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -5208,13 +5059,13 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Column"]; + readonly data: components["schemas"]["Context"]; }; }; }; }; - /** @description Current user is not logged in */ - readonly 401: { + /** @description invalid parameters were supplied */ + readonly 400: { headers: { readonly [name: string]: unknown; }; @@ -5222,13 +5073,15 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; + readonly data: { + readonly message: string; + }; }; }; }; }; - /** @description No permission */ - readonly 403: { + /** @description Current user is not logged in */ + readonly 401: { headers: { readonly [name: string]: unknown; }; @@ -5236,15 +5089,13 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; + readonly data: unknown; }; }; }; }; - /** @description Not found */ - readonly 404: { + /** @description lacking permissions on a resource */ + readonly 403: { headers: { readonly [name: string]: unknown; }; @@ -5272,74 +5123,26 @@ export interface operations { }; }; }; - readonly "text/plain": string; }; }; }; }; - readonly "api_columns-create-selection-column": { + readonly "context-show": { readonly parameters: { readonly query?: never; readonly header: { /** @description Required to be true for the API request to pass */ readonly "OCS-APIRequest": boolean; }; - readonly path?: never; - readonly cookie?: never; - }; - readonly requestBody: { - readonly content: { - readonly "application/json": { - /** - * Format: int64 - * @description Context of the column creation - */ - readonly baseNodeId: number; - /** @description Title */ - readonly title: string; - /** @description Json array{id: int, label: string} with options that can be selected, eg [{"id": 1, "label": "first"},{"id": 2, "label": "second"}] */ - readonly selectionOptions: string; - /** @description Json int|list for default selected option(s), eg 5 or ["1", "8"] */ - readonly selectionDefault?: string | null; - /** - * @description Subtype for the new column - * @default null - * @enum {string|null} - */ - readonly subtype?: "progress" | "stars" | null; - /** - * @description Description - * @default null - */ - readonly description?: string | null; - /** - * @description View IDs where this columns should be added - * @default [] - */ - readonly selectedViewIds?: readonly number[] | null; - /** - * @description Is mandatory - * @default false - */ - readonly mandatory?: boolean; - /** - * @description Context type of the column creation - * @default table - * @enum {string} - */ - readonly baseNodeType?: "table" | "view"; - /** - * @description Custom settings for the column - * @default {} - */ - readonly customSettings?: { - readonly [key: string]: Record; - }; - }; + readonly path: { + /** @description ID of the context */ + readonly contextId: number; }; + readonly cookie?: never; }; + readonly requestBody?: never; readonly responses: { - /** @description Column created */ + /** @description returning the full context information */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -5348,7 +5151,7 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Column"]; + readonly data: components["schemas"]["Context"]; }; }; }; @@ -5367,23 +5170,7 @@ export interface operations { }; }; }; - /** @description No permission */ - readonly 403: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - /** @description Not found */ + /** @description context not found or not available anymore */ readonly 404: { headers: { readonly [name: string]: unknown; @@ -5412,75 +5199,48 @@ export interface operations { }; }; }; - readonly "text/plain": string; }; }; }; }; - readonly "api_columns-create-datetime-column": { + readonly "context-update": { readonly parameters: { readonly query?: never; readonly header: { /** @description Required to be true for the API request to pass */ readonly "OCS-APIRequest": boolean; }; - readonly path?: never; + readonly path: { + /** @description ID of the context */ + readonly contextId: number; + }; readonly cookie?: never; }; - readonly requestBody: { + readonly requestBody?: { readonly content: { readonly "application/json": { - /** - * Format: int64 - * @description Context of the column creation - */ - readonly baseNodeId: number; - /** @description Title */ - readonly title: string; - /** - * @description For a subtype 'date' you can set 'today'. For a main type or subtype 'time' you can set to 'now'. - * @enum {string|null} - */ - readonly datetimeDefault?: "today" | "now" | null; - /** - * @description Subtype for the new column - * @default null - * @enum {string|null} - */ - readonly subtype?: "progress" | "stars" | null; - /** - * @description Description - * @default null - */ + /** @description provide this parameter to set a new name */ + readonly name?: string | null; + /** @description provide this parameter to set a new icon */ + readonly iconName?: string | null; + /** @description provide this parameter to set a new description */ readonly description?: string | null; - /** - * @description View IDs where this columns should be added - * @default [] - */ - readonly selectedViewIds?: readonly number[] | null; - /** - * @description Is mandatory - * @default false - */ - readonly mandatory?: boolean; - /** - * @description Context type of the column creation - * @default table - * @enum {string} - */ - readonly baseNodeType?: "table" | "view"; - /** - * @description Custom settings for the column - * @default {} - */ - readonly customSettings?: { - readonly [key: string]: Record; - }; + /** @description provide this parameter to set a new list of nodes. */ + readonly nodes?: { + /** Format: int64 */ + readonly id: number; + /** Format: int64 */ + readonly type: number; + /** Format: int64 */ + readonly permissions: number; + /** Format: int64 */ + readonly order: number; + } | null; }; }; }; readonly responses: { - /** @description Column created */ + /** @description returning the full context information */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -5489,531 +5249,13 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Column"]; - }; - }; - }; - }; - /** @description Current user is not logged in */ - readonly 401: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; + readonly data: components["schemas"]["Context"]; }; }; }; }; - /** @description No permission */ - readonly 403: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - /** @description Not found */ - readonly 404: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - readonly 500: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - readonly "text/plain": string; - }; - }; - }; - }; - readonly "api_columns-create-usergroup-column": { - readonly parameters: { - readonly query?: never; - readonly header: { - /** @description Required to be true for the API request to pass */ - readonly "OCS-APIRequest": boolean; - }; - readonly path?: never; - readonly cookie?: never; - }; - readonly requestBody: { - readonly content: { - readonly "application/json": { - /** - * Format: int64 - * @description Context of the column creation - */ - readonly baseNodeId: number; - /** @description Title */ - readonly title: string; - /** @description Json array{id: string, type: int}, eg [{"id": "admin", "type": 0}, {"id": "user1", "type": 0}] */ - readonly usergroupDefault?: string | null; - /** - * @description Whether you can select multiple users or/and groups - * @default null - */ - readonly usergroupMultipleItems?: boolean; - /** - * @description Whether you can select users - * @default null - */ - readonly usergroupSelectUsers?: boolean; - /** - * @description Whether you can select groups - * @default null - */ - readonly usergroupSelectGroups?: boolean; - /** - * @description Whether you can select teams - * @default null - */ - readonly usergroupSelectTeams?: boolean; - /** - * @description Whether to show the user's status - * @default null - */ - readonly showUserStatus?: boolean; - /** - * @description Description - * @default null - */ - readonly description?: string | null; - /** - * @description View IDs where this columns should be added - * @default [] - */ - readonly selectedViewIds?: readonly number[] | null; - /** - * @description Is mandatory - * @default false - */ - readonly mandatory?: boolean; - /** - * @description Context type of the column creation - * @default table - * @enum {string} - */ - readonly baseNodeType?: "table" | "view"; - /** - * @description Custom settings for the column - * @default {} - */ - readonly customSettings?: { - readonly [key: string]: Record; - }; - }; - }; - }; - readonly responses: { - /** @description Column created */ - readonly 200: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Column"]; - }; - }; - }; - }; - /** @description Current user is not logged in */ - readonly 401: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; - }; - }; - }; - }; - /** @description No permission */ - readonly 403: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - /** @description Not found */ - readonly 404: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - readonly 500: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - readonly "text/plain": string; - }; - }; - }; - }; - readonly "api_favorite-create": { - readonly parameters: { - readonly query?: never; - readonly header: { - /** @description Required to be true for the API request to pass */ - readonly "OCS-APIRequest": boolean; - }; - readonly path: { - /** @description any Application::NODE_TYPE_* constant */ - readonly nodeType: number; - /** @description identifier of the node */ - readonly nodeId: number; - }; - readonly cookie?: never; - }; - readonly requestBody?: never; - readonly responses: { - /** @description Tables returned */ - readonly 200: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: Record; - }; - }; - }; - }; - /** @description Current user is not logged in */ - readonly 401: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; - }; - }; - }; - }; - /** @description No permissions */ - readonly 403: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - /** @description Not found */ - readonly 404: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - readonly 500: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - }; - }; - readonly "api_favorite-destroy": { - readonly parameters: { - readonly query?: never; - readonly header: { - /** @description Required to be true for the API request to pass */ - readonly "OCS-APIRequest": boolean; - }; - readonly path: { - /** @description any Application::NODE_TYPE_* constant */ - readonly nodeType: number; - /** @description identifier of the node */ - readonly nodeId: number; - }; - readonly cookie?: never; - }; - readonly requestBody?: never; - readonly responses: { - /** @description Deleted table returned */ - readonly 200: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: Record; - }; - }; - }; - }; - /** @description Current user is not logged in */ - readonly 401: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; - }; - }; - }; - }; - /** @description No permissions */ - readonly 403: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - /** @description Not found */ - readonly 404: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - readonly 500: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - }; - }; - readonly "context-index": { - readonly parameters: { - readonly query?: never; - readonly header: { - /** @description Required to be true for the API request to pass */ - readonly "OCS-APIRequest": boolean; - }; - readonly path?: never; - readonly cookie?: never; - }; - readonly requestBody?: never; - readonly responses: { - /** @description reporting in available contexts */ - readonly 200: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: readonly components["schemas"]["Context"][]; - }; - }; - }; - }; - /** @description Current user is not logged in */ - readonly 401: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; - }; - }; - }; - }; - readonly 500: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; - }; - }; - }; - }; - }; - }; - readonly "context-create": { - readonly parameters: { - readonly query?: never; - readonly header: { - /** @description Required to be true for the API request to pass */ - readonly "OCS-APIRequest": boolean; - }; - readonly path?: never; - readonly cookie?: never; - }; - readonly requestBody: { - readonly content: { - readonly "application/json": { - /** @description Name of the context */ - readonly name: string; - /** @description Material design icon name of the context */ - readonly iconName: string; - /** - * @description Descriptive text of the context - * @default - */ - readonly description?: string; - /** - * @description optional nodes to be connected to this context - * @default [] - */ - readonly nodes?: readonly { - /** Format: int64 */ - readonly id: number; - /** Format: int64 */ - readonly type: number; - /** Format: int64 */ - readonly permissions?: number; - }[]; - }; - }; - }; - readonly responses: { - /** @description returning the full context information */ - readonly 200: { - headers: { - readonly [name: string]: unknown; - }; - content: { - readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Context"]; - }; - }; - }; - }; - /** @description invalid parameters were supplied */ - readonly 400: { + /** @description Current user is not logged in */ + readonly 401: { headers: { readonly [name: string]: unknown; }; @@ -6021,15 +5263,13 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; + readonly data: unknown; }; }; }; }; - /** @description Current user is not logged in */ - readonly 401: { + /** @description No permissions */ + readonly 403: { headers: { readonly [name: string]: unknown; }; @@ -6037,13 +5277,15 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; + readonly data: { + readonly message: string; + }; }; }; }; }; - /** @description lacking permissions on a resource */ - readonly 403: { + /** @description Not found */ + readonly 404: { headers: { readonly [name: string]: unknown; }; @@ -6075,7 +5317,7 @@ export interface operations { }; }; }; - readonly "context-show": { + readonly "context-destroy": { readonly parameters: { readonly query?: never; readonly header: { @@ -6118,7 +5360,23 @@ export interface operations { }; }; }; - /** @description context not found or not available anymore */ + /** @description No permissions */ + readonly 403: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly ocs: { + readonly meta: components["schemas"]["OCSMeta"]; + readonly data: { + readonly message: string; + }; + }; + }; + }; + }; + /** @description Not found */ readonly 404: { headers: { readonly [name: string]: unknown; @@ -6151,7 +5409,7 @@ export interface operations { }; }; }; - readonly "context-update": { + readonly "context-transfer": { readonly parameters: { readonly query?: never; readonly header: { @@ -6164,31 +5422,22 @@ export interface operations { }; readonly cookie?: never; }; - readonly requestBody?: { + readonly requestBody: { readonly content: { readonly "application/json": { - /** @description provide this parameter to set a new name */ - readonly name?: string | null; - /** @description provide this parameter to set a new icon */ - readonly iconName?: string | null; - /** @description provide this parameter to set a new description */ - readonly description?: string | null; - /** @description provide this parameter to set a new list of nodes. */ - readonly nodes?: { - /** Format: int64 */ - readonly id: number; - /** Format: int64 */ - readonly type: number; - /** Format: int64 */ - readonly permissions: number; - /** Format: int64 */ - readonly order: number; - } | null; + /** @description ID of the new owner */ + readonly newOwnerId: string; + /** + * Format: int64 + * @description any Application::OWNER_TYPE_* constant + * @default 0 + */ + readonly newOwnerType?: number; }; }; }; readonly responses: { - /** @description returning the full context information */ + /** @description Ownership transferred */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -6202,6 +5451,22 @@ export interface operations { }; }; }; + /** @description Invalid request */ + readonly 400: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly ocs: { + readonly meta: components["schemas"]["OCSMeta"]; + readonly data: { + readonly message: string; + }; + }; + }; + }; + }; /** @description Current user is not logged in */ readonly 401: { headers: { @@ -6265,7 +5530,7 @@ export interface operations { }; }; }; - readonly "context-destroy": { + readonly "context-update-content-order": { readonly parameters: { readonly query?: never; readonly header: { @@ -6275,12 +5540,26 @@ export interface operations { readonly path: { /** @description ID of the context */ readonly contextId: number; + /** @description ID of the page */ + readonly pageId: number; }; readonly cookie?: never; }; - readonly requestBody?: never; + readonly requestBody: { + readonly content: { + readonly "application/json": { + /** @description content items with it and order values */ + readonly content: { + /** Format: int64 */ + readonly id: number; + /** Format: int64 */ + readonly order: number; + }; + }; + }; + }; readonly responses: { - /** @description returning the full context information */ + /** @description content updated successfully */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -6294,6 +5573,22 @@ export interface operations { }; }; }; + /** @description Invalid request */ + readonly 400: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly ocs: { + readonly meta: components["schemas"]["OCSMeta"]; + readonly data: { + readonly message: string; + }; + }; + }; + }; + }; /** @description Current user is not logged in */ readonly 401: { headers: { @@ -6357,7 +5652,7 @@ export interface operations { }; }; }; - readonly "context-transfer": { + readonly "rowocs-create-row": { readonly parameters: { readonly query?: never; readonly header: { @@ -6365,27 +5660,25 @@ export interface operations { readonly "OCS-APIRequest": boolean; }; readonly path: { - /** @description ID of the context */ - readonly contextId: number; + /** @description Indicates whether to create a row on a table or view */ + readonly nodeCollection: "tables" | "views"; + /** @description The identifier of the targeted table or view */ + readonly nodeId: number; }; readonly cookie?: never; }; readonly requestBody: { readonly content: { readonly "application/json": { - /** @description ID of the new owner */ - readonly newOwnerId: string; - /** - * Format: int64 - * @description any Application::OWNER_TYPE_* constant - * @default 0 - */ - readonly newOwnerType?: number; + /** @description An array containing the column identifiers and their values */ + readonly data: string | { + readonly [key: string]: Record; + }; }; }; }; readonly responses: { - /** @description Ownership transferred */ + /** @description Row returned */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -6394,12 +5687,12 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Context"]; + readonly data: components["schemas"]["Row"]; }; }; }; }; - /** @description Invalid request */ + /** @description Invalid request parameters */ readonly 400: { headers: { readonly [name: string]: unknown; @@ -6461,6 +5754,7 @@ export interface operations { }; }; }; + /** @description Internal error */ readonly 500: { headers: { readonly [name: string]: unknown; @@ -6478,7 +5772,7 @@ export interface operations { }; }; }; - readonly "context-update-content-order": { + readonly "api_public_columns-index-by-public-link": { readonly parameters: { readonly query?: never; readonly header: { @@ -6486,43 +5780,90 @@ export interface operations { readonly "OCS-APIRequest": boolean; }; readonly path: { - /** @description ID of the context */ - readonly contextId: number; - /** @description ID of the page */ - readonly pageId: number; + /** @description The share token */ + readonly token: string; }; readonly cookie?: never; }; - readonly requestBody: { - readonly content: { - readonly "application/json": { - /** @description content items with it and order values */ - readonly content: { - /** Format: int64 */ - readonly id: number; - /** Format: int64 */ - readonly order: number; + readonly requestBody?: never; + readonly responses: { + /** @description Columns are returned */ + readonly 200: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": readonly components["schemas"]["PublicColumn"][]; + }; + }; + /** @description Invalid request parameters */ + readonly 400: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly message: string; }; }; }; - }; - readonly responses: { - /** @description content updated successfully */ - readonly 200: { + /** @description No permissions */ + readonly 403: { headers: { readonly [name: string]: unknown; }; content: { readonly "application/json": { - readonly ocs: { - readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Context"]; - }; + readonly message: string; }; }; }; - /** @description Invalid request */ - readonly 400: { + /** @description Not found */ + readonly 404: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly message: string; + }; + }; + }; + /** @description Internal error */ + readonly 500: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly message: string; + }; + }; + }; + }; + }; + readonly "public_rowocs-get-rows": { + readonly parameters: { + readonly query?: { + /** @description Optional: maximum number of results, capped at 500 */ + readonly limit?: number | null; + /** @description Optional: the offset for this operation */ + readonly offset?: number | null; + }; + readonly header: { + /** @description Required to be true for the API request to pass */ + readonly "OCS-APIRequest": boolean; + }; + readonly path: { + /** @description The share token */ + readonly token: string; + }; + readonly cookie?: never; + }; + readonly requestBody?: never; + readonly responses: { + /** @description Rows are returned */ + readonly 200: { headers: { readonly [name: string]: unknown; }; @@ -6530,15 +5871,13 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: { - readonly message: string; - }; + readonly data: readonly components["schemas"]["PublicRow"][]; }; }; }; }; - /** @description Current user is not logged in */ - readonly 401: { + /** @description Invalid request parameters */ + readonly 400: { headers: { readonly [name: string]: unknown; }; @@ -6546,7 +5885,9 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: unknown; + readonly data: { + readonly message: string; + }; }; }; }; @@ -6583,6 +5924,7 @@ export interface operations { }; }; }; + /** @description Internal error */ readonly 500: { headers: { readonly [name: string]: unknown; @@ -6600,7 +5942,7 @@ export interface operations { }; }; }; - readonly "rowocs-create-row": { + readonly "shareocs-create-link-share": { readonly parameters: { readonly query?: never; readonly header: { @@ -6615,18 +5957,19 @@ export interface operations { }; readonly cookie?: never; }; - readonly requestBody: { + readonly requestBody?: { readonly content: { readonly "application/json": { - /** @description An array containing the column identifiers and their values */ - readonly data: string | { - readonly [key: string]: Record; - }; + /** + * @description (Optional) A password to protect the link share with + * @default null + */ + readonly password?: string | null; }; }; }; readonly responses: { - /** @description Row returned */ + /** @description Link share created */ readonly 200: { headers: { readonly [name: string]: unknown; @@ -6635,7 +5978,7 @@ export interface operations { readonly "application/json": { readonly ocs: { readonly meta: components["schemas"]["OCSMeta"]; - readonly data: components["schemas"]["Row"]; + readonly data: components["schemas"]["LinkShare"]; }; }; }; diff --git a/templates/error.php b/templates/error.php new file mode 100644 index 0000000000..07b640fee0 --- /dev/null +++ b/templates/error.php @@ -0,0 +1,86 @@ + + + + + + + + + + t('Share not found')); ?> + t('This share does not exist or is no longer available')); ?> + + t('Back to %s', [$theme->getName()])); ?> + + + \ No newline at end of file
t('This share does not exist or is no longer available')); ?>
+ t('Back to %s', [$theme->getName()])); ?> +