From 36a43e0f4dc57760b7bbba92b579a1bfe357dee7 Mon Sep 17 00:00:00 2001 From: grnd-alt Date: Wed, 26 Jun 2024 12:24:19 +0200 Subject: [PATCH 1/4] add scheme import and export Signed-off-by: grnd-alt --- appinfo/routes.php | 2 + lib/Controller/ApiTablesController.php | 72 ++++++++++++++ lib/Controller/TableScheme.php | 53 ++++++++++ lib/Db/Table.php | 2 + src/modules/modals/CreateTable.vue | 13 +++ src/modules/modals/ImportScheme.vue | 96 +++++++++++++++++++ src/modules/modals/Modals.vue | 7 ++ .../partials/NavigationTableItem.vue | 21 ++++ 8 files changed, 266 insertions(+) create mode 100644 lib/Controller/TableScheme.php create mode 100644 src/modules/modals/ImportScheme.vue diff --git a/appinfo/routes.php b/appinfo/routes.php index 2700b3668..497033d4b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -117,6 +117,8 @@ // -> tables ['name' => 'ApiTables#index', 'url' => '/api/2/tables', 'verb' => 'GET'], ['name' => 'ApiTables#show', 'url' => '/api/2/tables/{id}', 'verb' => 'GET'], + ['name' => 'ApiTables#showScheme', 'url' => '/api/2/tables/scheme/{id}', 'verb' => 'GET'], + ['name' => 'ApiTables#createFromScheme', 'url' => '/api/2/tables/scheme', 'verb' => 'POST'], ['name' => 'ApiTables#create', 'url' => '/api/2/tables', 'verb' => 'POST'], ['name' => 'ApiTables#update', 'url' => '/api/2/tables/{id}', 'verb' => 'PUT'], ['name' => 'ApiTables#destroy', 'url' => '/api/2/tables/{id}', 'verb' => 'DELETE'], diff --git a/lib/Controller/ApiTablesController.php b/lib/Controller/ApiTablesController.php index 0ee061d20..006e93172 100644 --- a/lib/Controller/ApiTablesController.php +++ b/lib/Controller/ApiTablesController.php @@ -7,27 +7,32 @@ use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; use OCA\Tables\ResponseDefinitions; +use OCA\Tables\Service\ColumnService; use OCA\Tables\Service\TableService; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\IL10N; use OCP\IRequest; use Psr\Log\LoggerInterface; +use OCA\Tables\Controller\TableScheme; /** * @psalm-import-type TablesTable from ResponseDefinitions */ class ApiTablesController extends AOCSController { private TableService $service; + private ColumnService $columnService; public function __construct( IRequest $request, LoggerInterface $logger, TableService $service, + ColumnService $columnService, IL10N $n, string $userId) { parent::__construct($request, $logger, $n, $userId); $this->service = $service; + $this->columnService = $columnService; } /** @@ -71,6 +76,73 @@ public function show(int $id): DataResponse { } } + /** + * [api v2] Get a table Scheme + * + * @NoAdminRequired + * + * @param int $id Table ID + * @return DataResponse|DataResponse + * + * 200: Scheme returned + * 403: No permissions + * 404: Not found + */ + public function showScheme(int $id): DataResponse { + try { + $columns = $this->columnService->findAllByTable($id); + $table = $this->service->find($id); + $scheme = new TableScheme($table->getTitle(),$table->getEmoji(),$columns, $table->getDescription()); + return new DataResponse($scheme->jsonSerialize()); + } catch (PermissionError $e) { + return $this->handlePermissionError($e); + } catch (InternalError $e) { + return $this->handleError($e); + } catch (NotFoundError $e) { + return $this->handleNotFoundError($e); + } + } + + /** + * @NoAdminRequired + */ + public function createFromScheme(string $title, string $emoji, string $description, array $columns): DataResponse { + try { + $table = $this->service->create($title,'custom',$emoji, $description); + foreach ($columns as $column) { + $this->columnService->create( + $this->userId, + $table->getId(), + null, + $column['type'], + $column['subtype'], + $column['title'], + $column['mandatory'], + $column['description'], + + $column['textDefault'], + $column['textAllowedPattern'], + $column['textMaxLength'], + + $column['numberPrefix'], + $column['numberSuffix'], + $column['numberDefault'], + $column['numberMin'], + $column['numberMax'], + $column['numberDecimals'], + + $column['selectionOptions'] == [] ? '' : $column['selectionOptions'], + $column['selectionDefault'], + + $column['datetimeDefault'], + [], + );}; + return new DataResponse($table->jsonSerialize()); + } catch( InternalError | Exception $e) { + return $this->handleError($e); + } + } + /** * [api v2] Create a new table and return it * diff --git a/lib/Controller/TableScheme.php b/lib/Controller/TableScheme.php new file mode 100644 index 000000000..9c98071e6 --- /dev/null +++ b/lib/Controller/TableScheme.php @@ -0,0 +1,53 @@ +title = $title; + $this->emoji = $emoji; + $this->columns = $columns; + $this->description = $description; + } + + public function getTitle():string { + return $this->title; + } + + public function jsonSerialize(): array + { + return [ + 'title' => $this->title ?: '', + 'emoji' => $this->emoji, + 'columns' => $this->columns, + 'description' => $this->description ?:'', + ]; + } + + public function getEmoji(): ?string + { + return $this->emoji; + } + + public function getColumns(): ?array + { + return $this->columns; + } + + public function getDescription(): ?string + { + return $this->description; + } +} diff --git a/lib/Db/Table.php b/lib/Db/Table.php index 9f5b1411f..c6b8e6050 100644 --- a/lib/Db/Table.php +++ b/lib/Db/Table.php @@ -14,11 +14,13 @@ * @psalm-import-type TablesView from ResponseDefinitions * * @method getTitle(): string + * @method getId(): int * @method setTitle(string $title) * @method getEmoji(): string * @method setEmoji(string $emoji) * @method getArchived(): bool * @method setArchived(bool $archived) + * @method getDescription(): string * @method setDescription(string $description) * @method getOwnership(): string * @method setOwnership(string $ownership) diff --git a/src/modules/modals/CreateTable.vue b/src/modules/modals/CreateTable.vue index 66ec5d683..bcd7570f4 100644 --- a/src/modules/modals/CreateTable.vue +++ b/src/modules/modals/CreateTable.vue @@ -61,6 +61,14 @@ :tabbable="true" @set-template="setTemplate(template.name)" /> +
+ +
@@ -168,6 +176,11 @@ export default { }, async submit() { if (this.title === '') { + if (this.templateChoice === 'scheme') { + emit('tables:modal:scheme', {}) + this.actionCancel() + return + } showError(t('tables', 'Cannot create new table. Title is missing.')) this.errorTitle = true } else { diff --git a/src/modules/modals/ImportScheme.vue b/src/modules/modals/ImportScheme.vue new file mode 100644 index 000000000..b604a73da --- /dev/null +++ b/src/modules/modals/ImportScheme.vue @@ -0,0 +1,96 @@ + + diff --git a/src/modules/modals/Modals.vue b/src/modules/modals/Modals.vue index 0de238ef4..5f725ca0e 100644 --- a/src/modules/modals/Modals.vue +++ b/src/modules/modals/Modals.vue @@ -37,6 +37,9 @@ :is-element-view="importToElement?.isView" @close="importToElement = null" /> + @@ -48,6 +51,7 @@ import { subscribe, unsubscribe } from '@nextcloud/event-bus' import CreateRow from './CreateRow.vue' +import ImportScheme from './ImportScheme.vue' import DeleteColumn from './DeleteColumn.vue' import EditColumn from './EditColumn.vue' import CreateColumn from './CreateColumn.vue' @@ -71,6 +75,7 @@ export default { DeleteView, CreateTable, Import, + ImportScheme, DeleteRows, ViewSettings, EditRow, @@ -98,6 +103,7 @@ export default { showModalCreateTable: false, showModalCreateContext: false, importToElement: null, + showImportScheme: false, createViewTableId: null, // if null, no modal open tableToDelete: null, viewToDelete: null, @@ -142,6 +148,7 @@ export default { // misc subscribe('tables:modal:import', element => { this.importToElement = element }) + subscribe('tables:modal:scheme', () => { this.showImportScheme = true }) // context subscribe('tables:context:create', () => { this.showModalCreateContext = true }) diff --git a/src/modules/navigation/partials/NavigationTableItem.vue b/src/modules/navigation/partials/NavigationTableItem.vue index 9ec1263eb..116d01d57 100644 --- a/src/modules/navigation/partials/NavigationTableItem.vue +++ b/src/modules/navigation/partials/NavigationTableItem.vue @@ -67,6 +67,13 @@ + + + {{ t('tables','Export') }} + + { + const blob = new Blob([JSON.stringify(res.data.ocs.data)], { type: 'application/json' }) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.download = this.table.title + link.click() + URL.revokeObjectURL(link.href) + }) + }, + async actionShowShare() { emit('tables:sidebar:sharing', { open: true, tab: 'sharing' }) await this.$router.push('/table/' + parseInt(this.table.id)).catch(err => err) From 939867bdc0bbba72456c5577b39e84e7f8f42662 Mon Sep 17 00:00:00 2001 From: grnd-alt Date: Wed, 10 Jul 2024 11:59:44 +0200 Subject: [PATCH 2/4] add views to table scheme add version to table scheme move getScheme to tableService Signed-off-by: grnd-alt --- lib/Controller/ApiTablesController.php | 51 +++++++++++++++++++++----- lib/Controller/TableScheme.php | 27 ++++++++------ lib/Service/TableService.php | 18 ++++++++- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/lib/Controller/ApiTablesController.php b/lib/Controller/ApiTablesController.php index 006e93172..d63bdff04 100644 --- a/lib/Controller/ApiTablesController.php +++ b/lib/Controller/ApiTablesController.php @@ -3,18 +3,22 @@ namespace OCA\Tables\Controller; use Exception; +use OCA\Tables\Db\Column; +use OCA\Tables\Db\View; use OCA\Tables\Errors\InternalError; use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; use OCA\Tables\ResponseDefinitions; use OCA\Tables\Service\ColumnService; use OCA\Tables\Service\TableService; +use OCA\Tables\Service\ViewService; +use OCP\App\IAppManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; +use OCP\IDBConnection; use OCP\IL10N; use OCP\IRequest; use Psr\Log\LoggerInterface; -use OCA\Tables\Controller\TableScheme; /** * @psalm-import-type TablesTable from ResponseDefinitions @@ -22,17 +26,26 @@ class ApiTablesController extends AOCSController { private TableService $service; private ColumnService $columnService; + private ViewService $viewService; + private IAppManager $appManager; + private IDBConnection $db; public function __construct( IRequest $request, LoggerInterface $logger, TableService $service, ColumnService $columnService, + ViewService $viewService, IL10N $n, + IAppManager $appManager, + IDBConnection $db, string $userId) { parent::__construct($request, $logger, $n, $userId); $this->service = $service; $this->columnService = $columnService; + $this->appManager = $appManager; + $this->viewService = $viewService; + $this->db = $db; } /** @@ -90,10 +103,7 @@ public function show(int $id): DataResponse { */ public function showScheme(int $id): DataResponse { try { - $columns = $this->columnService->findAllByTable($id); - $table = $this->service->find($id); - $scheme = new TableScheme($table->getTitle(),$table->getEmoji(),$columns, $table->getDescription()); - return new DataResponse($scheme->jsonSerialize()); + return new DataResponse($this->service->getScheme($id)->jsonSerialize()); } catch (PermissionError $e) { return $this->handlePermissionError($e); } catch (InternalError $e) { @@ -105,10 +115,18 @@ public function showScheme(int $id): DataResponse { /** * @NoAdminRequired + * + * @param string $title + * @param string $emoji + * @param string $description + * @param Column[] $columns + * @param View[] $views + * @return DataResponse */ - public function createFromScheme(string $title, string $emoji, string $description, array $columns): DataResponse { + public function createFromScheme(string $title, string $emoji, string $description, array $columns, array $views): DataResponse { try { - $table = $this->service->create($title,'custom',$emoji, $description); + $this->db->beginTransaction(); + $table = $this->service->create($title, 'custom', $emoji, $description); foreach ($columns as $column) { $this->columnService->create( $this->userId, @@ -136,9 +154,24 @@ public function createFromScheme(string $title, string $emoji, string $descripti $column['datetimeDefault'], [], - );}; + ); + }; + foreach ($views as $view) { + $this->viewService->create( + $view['title'], + $view['emoji'], + $table, + $this->userId, + ); + } + $this->db->commit(); return new DataResponse($table->jsonSerialize()); - } catch( InternalError | Exception $e) { + } catch(InternalError | Exception $e) { + try { + $this->db->rollBack(); + } catch (\OCP\DB\Exception $e) { + return $this->handleError($e); + } return $this->handleError($e); } } diff --git a/lib/Controller/TableScheme.php b/lib/Controller/TableScheme.php index 9c98071e6..d5d093268 100644 --- a/lib/Controller/TableScheme.php +++ b/lib/Controller/TableScheme.php @@ -4,6 +4,8 @@ use JsonSerializable; use OCA\Tables\Db\Column; +use OCA\Tables\Db\View; +use OCP\App\IAppManager; class TableScheme implements JsonSerializable { @@ -12,42 +14,45 @@ class TableScheme implements JsonSerializable { /** @var Column[]|null */ protected ?array $columns = null; + + /** @var View[]|null */ + protected ?array $views = null; protected ?string $description = null; + protected ?string $tablesVersion = null; - public function __construct(string $title, string $emoji, array $columns, string $description) - { + public function __construct(string $title, string $emoji, array $columns, array $view, string $description, IAppManager $appManager) { + $this->tablesVersion = $appManager->getAppVersion("tables"); $this->title = $title; $this->emoji = $emoji; $this->columns = $columns; $this->description = $description; + $this->views = $view; } public function getTitle():string { - return $this->title; + return $this->title | ''; } - public function jsonSerialize(): array - { + public function jsonSerialize(): array { return [ 'title' => $this->title ?: '', 'emoji' => $this->emoji, 'columns' => $this->columns, + 'views' => $this->views, 'description' => $this->description ?:'', + 'tablesVersion' => $this->tablesVersion, ]; } - public function getEmoji(): ?string - { + public function getEmoji(): ?string { return $this->emoji; } - public function getColumns(): ?array - { + public function getColumns(): ?array { return $this->columns; } - public function getDescription(): ?string - { + public function getDescription(): ?string { return $this->description; } } diff --git a/lib/Service/TableService.php b/lib/Service/TableService.php index 2d86a33ca..505a07e1f 100644 --- a/lib/Service/TableService.php +++ b/lib/Service/TableService.php @@ -6,6 +6,7 @@ use DateTime; use OCA\Tables\AppInfo\Application; +use OCA\Tables\Controller\TableScheme; use OCA\Tables\Db\Table; use OCA\Tables\Db\TableMapper; use OCA\Tables\Errors\InternalError; @@ -15,6 +16,7 @@ use OCA\Tables\Event\TableOwnershipTransferredEvent; use OCA\Tables\Helper\UserHelper; use OCA\Tables\ResponseDefinitions; +use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\DB\Exception as OcpDbException; @@ -42,6 +44,7 @@ class TableService extends SuperService { protected FavoritesService $favoritesService; + protected IAppManager $appManager; protected IL10N $l; private ContextService $contextService; @@ -62,9 +65,11 @@ public function __construct( FavoritesService $favoritesService, IEventDispatcher $eventDispatcher, ContextService $contextService, + IAppManager $appManager, IL10N $l, ) { parent::__construct($logger, $userId, $permissionsService); + $this->appManager = $appManager; $this->mapper = $mapper; $this->tableTemplateService = $tableTemplateService; $this->columnService = $columnService; @@ -533,11 +538,22 @@ public function search(string $term, int $limit = 100, int $offset = 0, ?string $this->enhanceTable($table, $userId); } return $tables; - } catch (InternalError | PermissionError |OcpDbException $e) { + } catch (InternalError | PermissionError | OcpDbException $e) { return []; } } + /** + * @throws PermissionError + * @throws NotFoundError + * @throws InternalError + */ + public function getScheme(int $id): TableScheme { + $columns = $this->columnService->findAllByTable($id); + $table = $this->find($id); + return new TableScheme($table->getTitle(), $table->getEmoji(), $columns, $table->getViews(), $table->getDescription(), $this->appManager); + } + // PRIVATE FUNCTIONS --------------------------------------------------------------- /** From dac1643ad126eaff63ba26579a2210c375fec645 Mon Sep 17 00:00:00 2001 From: grnd-alt Date: Wed, 10 Jul 2024 12:00:27 +0200 Subject: [PATCH 3/4] add non ocs getscheme endpoint Signed-off-by: grnd-alt --- appinfo/routes.php | 1 + lib/Controller/Api1Controller.php | 12 + lib/Controller/ApiTablesController.php | 18 +- openapi.json | 329 +++++++++++++++++++++++++ 4 files changed, 354 insertions(+), 6 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 497033d4b..d1314d927 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -20,6 +20,7 @@ ['name' => 'api1#createTable', 'url' => '/api/1/tables', 'verb' => 'POST'], ['name' => 'api1#updateTable', 'url' => '/api/1/tables/{tableId}', 'verb' => 'PUT'], ['name' => 'api1#getTable', 'url' => '/api/1/tables/{tableId}', 'verb' => 'GET'], + ['name' => 'api1#showScheme', 'url' => '/api/1/tables/{tableId}/scheme', 'verb' => 'GET'], ['name' => 'api1#deleteTable', 'url' => '/api/1/tables/{tableId}', 'verb' => 'DELETE'], // -> views ['name' => 'api1#indexViews', 'url' => '/api/1/tables/{tableId}/views', 'verb' => 'GET'], diff --git a/lib/Controller/Api1Controller.php b/lib/Controller/Api1Controller.php index 5426c96c1..642886411 100644 --- a/lib/Controller/Api1Controller.php +++ b/lib/Controller/Api1Controller.php @@ -132,6 +132,18 @@ public function createTable(string $title, ?string $emoji, string $template = 'c } } + /** + * returns table scheme + * @param int $tableId + * @return \OCP\AppFramework\Http\DataResponse + */ + public function showScheme(int $tableId): DataResponse { + return $this->handleError(function () use ($tableId) { + $scheme = $this->tableService->getScheme($tableId); + return new DataResponse($scheme->jsonSerialize(), http::STATUS_OK, ["Content-Disposition" => "attachment", "filename" => $scheme->getTitle() . ".json", "Content-Type" => "application/octet-stream"]); + }); + } + /** * Get a table object * diff --git a/lib/Controller/ApiTablesController.php b/lib/Controller/ApiTablesController.php index d63bdff04..541933ff4 100644 --- a/lib/Controller/ApiTablesController.php +++ b/lib/Controller/ApiTablesController.php @@ -22,6 +22,8 @@ /** * @psalm-import-type TablesTable from ResponseDefinitions + * @psalm-import-type TablesView from ResponseDefinitions + * @psalm-import-type TablesColumn from ResponseDefinitions */ class ApiTablesController extends AOCSController { private TableService $service; @@ -116,12 +118,16 @@ public function showScheme(int $id): DataResponse { /** * @NoAdminRequired * - * @param string $title - * @param string $emoji - * @param string $description - * @param Column[] $columns - * @param View[] $views - * @return DataResponse + * creates table from scheme + * + * @param string $title title of new table + * @param string $emoji emoji + * @param string $description description + * @param array $columns columns + * @param array $views views + * @return DataResponse|DataResponse + * + * 200: Tables returned */ public function createFromScheme(string $title, string $emoji, string $description, array $columns, array $views): DataResponse { try { diff --git a/openapi.json b/openapi.json index b7b06486d..41d22ae9b 100644 --- a/openapi.json +++ b/openapi.json @@ -5699,6 +5699,335 @@ } } }, + "/ocs/v2.php/apps/tables/api/2/tables/scheme/{id}": { + "get": { + "operationId": "api_tables-show-scheme", + "summary": "[api v2] Get a table Scheme", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Table ID", + "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": "Scheme returned", + "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/Table" + } + } + } + } + } + } + } + }, + "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" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/tables/scheme": { + "post": { + "operationId": "api_tables-create-from-scheme", + "summary": "creates table from scheme", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title", + "emoji", + "description", + "columns", + "views" + ], + "properties": { + "title": { + "type": "string", + "description": "title of new table" + }, + "emoji": { + "type": "string", + "description": "emoji" + }, + "description": { + "type": "string", + "description": "description" + }, + "columns": { + "type": "array", + "description": "columns", + "items": { + "$ref": "#/components/schemas/Column" + } + }, + "views": { + "type": "array", + "description": "views", + "items": { + "$ref": "#/components/schemas/View" + } + } + } + } + } + } + }, + "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": "Tables returned", + "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/Table" + } + } + } + } + } + } + } + }, + "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" + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/tables/api/2/tables/{id}/transfer": { "put": { "operationId": "api_tables-transfer", From 92556513544d7a3ee9a69a7cdb2d0f89db8889e2 Mon Sep 17 00:00:00 2001 From: grnd-alt Date: Mon, 22 Jul 2024 11:42:17 +0200 Subject: [PATCH 4/4] add fake form to download tables export Signed-off-by: grnd-alt --- lib/Controller/Api1Controller.php | 31 ++- lib/Controller/ApiTablesController.php | 2 - lib/Controller/TableScheme.php | 7 +- lib/Service/TableService.php | 4 +- openapi.json | 99 +++++++ .../partials/NavigationTableItem.vue | 26 +- src/types/openapi/openapi.ts | 243 ++++++++++++++++++ 7 files changed, 388 insertions(+), 24 deletions(-) diff --git a/lib/Controller/Api1Controller.php b/lib/Controller/Api1Controller.php index 642886411..7a2f33666 100644 --- a/lib/Controller/Api1Controller.php +++ b/lib/Controller/Api1Controller.php @@ -134,14 +134,35 @@ public function createTable(string $title, ?string $emoji, string $template = 'c /** * returns table scheme - * @param int $tableId - * @return \OCP\AppFramework\Http\DataResponse + * + * @NoAdminRequired + * @CORS + * @NoCSRFRequired + * + * @param int $tableId Table ID + * @return DataResponse|DataResponse + * + * 200: Table returned + * 403: No permissions + * 404: Not found */ public function showScheme(int $tableId): DataResponse { - return $this->handleError(function () use ($tableId) { + try { $scheme = $this->tableService->getScheme($tableId); - return new DataResponse($scheme->jsonSerialize(), http::STATUS_OK, ["Content-Disposition" => "attachment", "filename" => $scheme->getTitle() . ".json", "Content-Type" => "application/octet-stream"]); - }); + return new DataResponse($scheme->jsonSerialize(), http::STATUS_OK, ['Content-Disposition' => 'attachment; filename="'.$scheme->getTitle() . '.json"', 'Content-Type' => 'application/octet-stream']); + } catch (PermissionError $e) { + $this->logger->warning('A permission error occurred: ' . $e->getMessage()); + $message = ['message' => $e->getMessage()]; + return new DataResponse($message, Http::STATUS_FORBIDDEN); + } catch (NotFoundError $e) { + $this->logger->warning('A not found error occurred: ' . $e->getMessage()); + $message = ['message' => $e->getMessage()]; + return new DataResponse($message, Http::STATUS_NOT_FOUND); + } catch (InternalError|Exception $e) { + $this->logger->warning('An internal error or exception occurred: '.$e->getMessage()); + $message = ['message' => $e->getMessage()]; + return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR); + } } /** diff --git a/lib/Controller/ApiTablesController.php b/lib/Controller/ApiTablesController.php index 541933ff4..d246636d5 100644 --- a/lib/Controller/ApiTablesController.php +++ b/lib/Controller/ApiTablesController.php @@ -3,8 +3,6 @@ namespace OCA\Tables\Controller; use Exception; -use OCA\Tables\Db\Column; -use OCA\Tables\Db\View; use OCA\Tables\Errors\InternalError; use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; diff --git a/lib/Controller/TableScheme.php b/lib/Controller/TableScheme.php index d5d093268..45349da5b 100644 --- a/lib/Controller/TableScheme.php +++ b/lib/Controller/TableScheme.php @@ -1,11 +1,10 @@ tablesVersion = $appManager->getAppVersion("tables"); + public function __construct(string $title, string $emoji, array $columns, array $view, string $description, string $tablesVersion) { + $this->tablesVersion = $tablesVersion; $this->title = $title; $this->emoji = $emoji; $this->columns = $columns; diff --git a/lib/Service/TableService.php b/lib/Service/TableService.php index 505a07e1f..3e7826cc4 100644 --- a/lib/Service/TableService.php +++ b/lib/Service/TableService.php @@ -6,7 +6,6 @@ use DateTime; use OCA\Tables\AppInfo\Application; -use OCA\Tables\Controller\TableScheme; use OCA\Tables\Db\Table; use OCA\Tables\Db\TableMapper; use OCA\Tables\Errors\InternalError; @@ -15,6 +14,7 @@ use OCA\Tables\Event\TableDeletedEvent; use OCA\Tables\Event\TableOwnershipTransferredEvent; use OCA\Tables\Helper\UserHelper; +use OCA\Tables\Model\TableScheme; use OCA\Tables\ResponseDefinitions; use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; @@ -551,7 +551,7 @@ public function search(string $term, int $limit = 100, int $offset = 0, ?string public function getScheme(int $id): TableScheme { $columns = $this->columnService->findAllByTable($id); $table = $this->find($id); - return new TableScheme($table->getTitle(), $table->getEmoji(), $columns, $table->getViews(), $table->getDescription(), $this->appManager); + return new TableScheme($table->getTitle(), $table->getEmoji(), $columns, $table->getViews(), $table->getDescription(), $this->appManager->getAppVersion("tables")); } // PRIVATE FUNCTIONS --------------------------------------------------------------- diff --git a/openapi.json b/openapi.json index 41d22ae9b..652d6d5cd 100644 --- a/openapi.json +++ b/openapi.json @@ -1143,6 +1143,105 @@ } } }, + "/index.php/apps/tables/api/1/tables/{tableId}/scheme": { + "get": { + "operationId": "api1-show-scheme", + "summary": "returns table scheme", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Table returned", + "headers": { + "Content-Disposition": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "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" + } + } + } + } + } + } + } + } + }, "/index.php/apps/tables/api/1/tables/{tableId}/views": { "get": { "operationId": "api1-index-views", diff --git a/src/modules/navigation/partials/NavigationTableItem.vue b/src/modules/navigation/partials/NavigationTableItem.vue index 116d01d57..5998f798b 100644 --- a/src/modules/navigation/partials/NavigationTableItem.vue +++ b/src/modules/navigation/partials/NavigationTableItem.vue @@ -150,14 +150,13 @@ import StarOutline from 'vue-material-design-icons/StarOutline.vue' import ArchiveArrowDown from 'vue-material-design-icons/ArchiveArrowDown.vue' import ArchiveArrowUpOutline from 'vue-material-design-icons/ArchiveArrowUpOutline.vue' import permissionsMixin from '../../../shared/components/ncTable/mixins/permissionsMixin.js' -import { getCurrentUser } from '@nextcloud/auth' +import { getCurrentUser, getRequestToken } from '@nextcloud/auth' import Connection from 'vue-material-design-icons/Connection.vue' import Import from 'vue-material-design-icons/Import.vue' import NavigationViewItem from './NavigationViewItem.vue' import PlaylistPlus from 'vue-material-design-icons/PlaylistPlus.vue' import IconRename from 'vue-material-design-icons/Rename.vue' -import axios from '@nextcloud/axios' -import { generateOcsUrl } from '@nextcloud/router' +import { generateUrl } from '@nextcloud/router' export default { @@ -246,14 +245,19 @@ export default { }, exportFile() { - axios.get(generateOcsUrl(`/apps/tables/api/2/tables/scheme/${this.table.id}`)).then(res => { - const blob = new Blob([JSON.stringify(res.data.ocs.data)], { type: 'application/json' }) - const link = document.createElement('a') - link.href = URL.createObjectURL(blob) - link.download = this.table.title - link.click() - URL.revokeObjectURL(link.href) - }) + const form = document.createElement('form') + form.method = 'GET' + form.action = generateUrl(`/apps/tables/api/1/tables/${this.table.id}/scheme`) + + const token = document.createElement('input') + token.type = 'hidden' + token.name = 'requesttoken' + token.value = getRequestToken() + + form.appendChild(token) + + document.body.appendChild(form) + form.submit() }, async actionShowShare() { diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index bbf117dbf..b64696958 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -41,6 +41,23 @@ export type paths = { readonly patch?: never; readonly trace?: never; }; + readonly "/index.php/apps/tables/api/1/tables/{tableId}/scheme": { + readonly parameters: { + readonly query?: never; + readonly header?: never; + readonly path?: never; + readonly cookie?: never; + }; + /** returns table scheme */ + readonly get: operations["api1-show-scheme"]; + readonly put?: never; + readonly post?: never; + readonly delete?: never; + readonly options?: never; + readonly head?: never; + readonly patch?: never; + readonly trace?: never; + }; readonly "/index.php/apps/tables/api/1/tables/{tableId}/views": { readonly parameters: { readonly query?: never; @@ -417,6 +434,40 @@ export type paths = { readonly patch?: never; readonly trace?: never; }; + readonly "/ocs/v2.php/apps/tables/api/2/tables/scheme/{id}": { + readonly parameters: { + readonly query?: never; + readonly header?: never; + readonly path?: never; + readonly cookie?: never; + }; + /** [api v2] Get a table Scheme */ + readonly get: operations["api_tables-show-scheme"]; + 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/tables/scheme": { + readonly parameters: { + readonly query?: never; + readonly header?: never; + readonly path?: never; + readonly cookie?: never; + }; + readonly get?: never; + readonly put?: never; + /** creates table from scheme */ + readonly post: operations["api_tables-create-from-scheme"]; + readonly delete?: never; + readonly options?: never; + readonly head?: never; + readonly patch?: never; + readonly trace?: never; + }; readonly "/ocs/v2.php/apps/tables/api/2/tables/{id}/transfer": { readonly parameters: { readonly query?: never; @@ -1118,6 +1169,62 @@ export interface operations { }; }; }; + readonly "api1-show-scheme": { + readonly parameters: { + readonly query?: never; + readonly header?: never; + readonly path: { + /** @description Table ID */ + readonly tableId: number; + }; + readonly cookie?: never; + }; + readonly requestBody?: never; + readonly responses: { + /** @description Table returned */ + readonly 200: { + headers: { + readonly "Content-Disposition"?: string; + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": components["schemas"]["Table"]; + }; + }; + /** @description No permissions */ + readonly 403: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly message: string; + }; + }; + }; + /** @description Not found */ + readonly 404: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly message: string; + }; + }; + }; + readonly 500: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly message: string; + }; + }; + }; + }; + }; readonly "api1-index-views": { readonly parameters: { readonly query?: never; @@ -3581,6 +3688,142 @@ export interface operations { }; }; }; + readonly "api_tables-show-scheme": { + 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 Table ID */ + readonly id: number; + }; + readonly cookie?: never; + }; + readonly requestBody?: never; + readonly responses: { + /** @description Scheme returned */ + readonly 200: { + headers: { + readonly [name: string]: unknown; + }; + content: { + readonly "application/json": { + readonly ocs: { + readonly meta: components["schemas"]["OCSMeta"]; + readonly data: components["schemas"]["Table"]; + }; + }; + }; + }; + /** @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_tables-create-from-scheme": { + 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 title of new table */ + readonly title: string; + /** @description emoji */ + readonly emoji: string; + /** @description description */ + readonly description: string; + /** @description columns */ + readonly columns: readonly components["schemas"]["Column"][]; + /** @description views */ + readonly views: readonly components["schemas"]["View"][]; + }; + }; + }; + 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: components["schemas"]["Table"]; + }; + }; + }; + }; + 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_tables-transfer": { readonly parameters: { readonly query?: never;