From 7cf94336db8a6cb802b3a51319fd61a7558912b2 Mon Sep 17 00:00:00 2001 From: Florian Steffens Date: Thu, 7 Sep 2023 15:05:35 +0200 Subject: [PATCH 1/6] API for views adjustments Signed-off-by: Florian Steffens --- lib/Controller/Api1Controller.php | 23 +++++++++-------------- lib/Db/ViewMapper.php | 8 ++++---- lib/Service/ViewService.php | 8 ++++---- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/Controller/Api1Controller.php b/lib/Controller/Api1Controller.php index c3540286d..a9ed58ab7 100644 --- a/lib/Controller/Api1Controller.php +++ b/lib/Controller/Api1Controller.php @@ -124,17 +124,10 @@ public function deleteTable(int $tableId): DataResponse { * @CORS * @NoCSRFRequired */ - public function indexViews(int $tableId, string $keyword = null, int $limit = 100, int $offset = 0): DataResponse { - if ($keyword) { - return $this->handleError(function () use ($keyword, $limit, $offset) { - return $this->viewService->search($keyword, $limit, $offset); - }); - } else { - return $this->handleError(function () use ($tableId) { - return $this->viewService->findAll($this->tableService->find($tableId)); - }); - } - + public function indexViews(int $tableId): DataResponse { + return $this->handleError(function () use ($tableId) { + return $this->viewService->findAll($this->tableService->find($tableId)); + }); } /** @@ -164,9 +157,11 @@ public function getView(int $viewId): DataResponse { * @CORS * @NoCSRFRequired */ - public function updateView(int $viewId, array $data): DataResponse { - return $this->handleError(function () use ($viewId, $data) { - return $this->viewService->update($viewId, $data); + public function updateView(int $viewId, string $data): DataResponse { + $dataNew = json_decode($data, true); + + return $this->handleError(function () use ($viewId, $dataNew) { + return $this->viewService->update($viewId, $dataNew); }); } diff --git a/lib/Db/ViewMapper.php b/lib/Db/ViewMapper.php index abf13c9f4..5b3e0f0ff 100644 --- a/lib/Db/ViewMapper.php +++ b/lib/Db/ViewMapper.php @@ -38,7 +38,7 @@ public function find(int $id, bool $skipEnhancement = false): View { ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))); $view = $this->findEntity($qb); if(!$skipEnhancement) { - $this->enhanceByOwnership($view); + $this->enhanceOwnership($view); } return $view; } @@ -58,7 +58,7 @@ public function findAll(?int $tableId = null): array { } $views = $this->findEntities($qb); foreach($views as $view) { - $this->enhanceByOwnership($view); + $this->enhanceOwnership($view); } return $views; } @@ -119,7 +119,7 @@ public function search(string $term = null, ?string $userId = null, ?int $limit $views = $this->findEntities($qb); foreach($views as $view) { - $this->enhanceByOwnership($view); + $this->enhanceOwnership($view); } return $views; } @@ -129,7 +129,7 @@ public function search(string $term = null, ?string $userId = null, ?int $limit * @return void * @throws InternalError */ - private function enhanceByOwnership(View $view): void { + private function enhanceOwnership(View $view): void { try { $view->setOwnership($this->tableMapper->findOwnership($view->getTableId())); } catch (Exception $e) { diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index 3de353db1..79db4b28e 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -1,4 +1,4 @@ - $value) { - if (!in_array($key, $updatebleColumns)) { - throw new InternalError('Column '.$key.' can not be updated.'); + if (!in_array($key, $updatableParameter)) { + throw new InternalError('View parameter '.$key.' can not be updated.'); } $setterMethod = 'set'.ucfirst($key); $view->$setterMethod($value); From 73c26e08228bf9c2502a6f96478c93abd467b390 Mon Sep 17 00:00:00 2001 From: Florian Steffens Date: Thu, 7 Sep 2023 15:05:53 +0200 Subject: [PATCH 2/6] basic API behat tests Signed-off-by: Florian Steffens --- .../features/api/tablesapi.feature | 29 ++- .../features/bootstrap/FeatureContext.php | 168 +++++++++++++++++- 2 files changed, 185 insertions(+), 12 deletions(-) diff --git a/tests/integration/features/api/tablesapi.feature b/tests/integration/features/api/tablesapi.feature index 1380f52f8..a59291344 100644 --- a/tests/integration/features/api/tablesapi.feature +++ b/tests/integration/features/api/tablesapi.feature @@ -11,17 +11,17 @@ Feature: api/tablesapi | Tutorial | Scenario: User creates, rename and delete a table - Given table "my new awesome table" with emoji "🤓" exists for user "participant1" + Given table "my new awesome table" with emoji "🤓" exists for user "participant1" as "base1" Then user "participant1" has the following tables | my new awesome table | Then user "participant1" updates table with keyword "awesome" set title "renamed table" and optional emoji "🍓" - Then user "participant1" updates table with keyword "renamed table" set title "renamed table without emoji" and optional emoji "" + Then user "participant1" updates table with keyword "renamed table" set title "renamed table without emoji" and optional emoji ""∆ Then user "participant1" deletes table with keyword "without emoji" Then user "participant1" has the following tables | Tutorial | Scenario: Table sharing with a user - Given table "Ready to share" with emoji "🥪" exists for user "participant1" + Given table "Ready to share" with emoji "🥪" exists for user "participant1" as "base1" Then user "participant1" shares table with user "participant2" Then user "participant2" has the following permissions | read | 1 | @@ -46,7 +46,7 @@ Feature: api/tablesapi | Tutorial | Scenario: Table sharing with a group - Given table "Ready to share" with emoji "🥪" exists for user "participant1" + Given table "Ready to share" with emoji "🥪" exists for user "participant1" as "base1" Then user "participant1" shares table with group "phoenix" Then user "participant2" has the following tables | Tutorial | Ready to share | @@ -57,7 +57,7 @@ Feature: api/tablesapi | Tutorial | Scenario: Create and check columns - Given table "Column test" with emoji "🥶" exists for user "participant1" + Given table "Column test" with emoji "🥶" exists for user "participant1" as "base1" Then table has at least following columns Then column "First column" exists with following properties | type | text | @@ -100,7 +100,7 @@ Feature: api/tablesapi Then user "participant1" deletes table with keyword "Column test" Scenario: Create, modify and delete rows - Given table "Rows check" with emoji "👨🏻‍💻" exists for user "participant1" + Given table "Rows check" with emoji "👨🏻‍💻" exists for user "participant1" as "base1" Then column "one" exists with following properties | type | text | | subtype | line | @@ -142,7 +142,7 @@ Feature: api/tablesapi | Col1 | Col2 | Col3 | num | emoji | special | | Val1 | Val2 | Val3 | 1 | 💙 | Ä | | great | news | here | 99 | ⚠️ | Ö | - Given table "Import test" with emoji "👨🏻‍💻" exists for user "participant1" + Given table "Import test" with emoji "👨🏻‍💻" exists for user "participant1" as "base1" When user imports file "/import.csv" into last created table Then import results have the following data | found_columns_count | 6 | @@ -160,3 +160,18 @@ Feature: api/tablesapi | Col1 | Col2 | Col3 | num | emoji | special | | Val1 | Val2 | Val3 | 1 | 💙 | Ä | | great | news | here | 99 | ⚠️ | Ö | + + Scenario: Create, edit and delete views + Given table "View test" with emoji "👨🏻‍💻" exists for user "participant1" as "view-test" + # Then print register + Then table "view-test" has the following views for user "participant1" + # Then print register + When user "participant1" create view "first view" with emoji "⚡️" for "view-test" as "first-view" + Then table "view-test" has the following views for user "participant1" + | first view | + # Then print register + When user "participant1" update view "first-view" with title "updated first view" and emoji "💾" + Then table "view-test" has the following views for user "participant1" + | updated first view | + When user "participant1" deletes view "first-view" + Then table "view-test" has the following views for user "participant1" diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 122557cc5..f17ae432b 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -62,6 +62,14 @@ class FeatureContext implements Context, SnippetAcceptingContext { private ?array $tableColumns = []; private ?array $importResult = null; + // we need some ids to reuse it in some contexts, + // but we can not return them and reuse it in the scenarios, + // that's why we hold a kind of register here + // structure: $name -> item-id + // example for a table: 'test-table' -> 5 + private array $tableIds = []; + private array $viewIds = []; + use CommandLineTrait; /** @@ -158,6 +166,18 @@ public function checkImportResults(TableNode $table): void { } } + /** + * @Then print register + * + */ + public function printRegister(): void { + echo "REGISTER ========================\n"; + echo "Tables --------------------\n"; + print_r($this->tableIds); + echo "Views --------------------\n"; + print_r($this->viewIds); + } + /** * @Then table contains at least following rows * @@ -230,13 +250,88 @@ public function userTables(string $user, TableNode $body = null): void { } /** - * @Given table :table with emoji :emoji exists for user :user + * @Then table :tableName has the following views for user :user + * + * @param string $tableName + * @param string $user + * @param TableNode|null $body + */ + public function tableViews(string $tableName, string $user, TableNode $body = null): void { + $this->setCurrentUser($user); + + $this->sendRequest( + 'GET', + '/apps/tables/api/1/tables/'.$this->tableIds[$tableName].'/views' + ); + + $data = $this->getDataFromResponse($this->response); + + Assert::assertEquals(200, $this->response->getStatusCode()); + + // check if views are empty + if ($body === null) { + Assert::assertCount(0, $data); + return; + } + + // check if given view exists + $titles = []; + foreach ($data as $d) { + $titles[] = $d['title']; + } + foreach ($body->getRows()[0] as $viewTitle) { + Assert::assertTrue(in_array($viewTitle, $titles, true)); + } + } + + /** + * @Given user :user create view :title with emoji :emoji for :tableName as :viewName + * + * @param string $user + * @param string $title + * @param string $tableName + * @param string $viewName + * @param string|null $emoji + */ + public function createView(string $user, string $title, string $tableName, string $viewName, string $emoji = null): void { + $this->setCurrentUser($user); + $this->sendRequest( + 'POST', + '/apps/tables/api/1/tables/'.$this->tableIds[$tableName].'/views', + [ + 'title' => $title, + 'emoji' => $emoji + ] + ); + + $newItem = $this->getDataFromResponse($this->response); + $this->viewIds[$viewName] = $newItem['id']; + + Assert::assertEquals(200, $this->response->getStatusCode()); + Assert::assertEquals($newItem['title'], $title); + Assert::assertEquals($newItem['emoji'], $emoji); + + $this->sendRequest( + 'GET', + '/apps/tables/api/1/views/'.$newItem['id'], + ); + + $itemToVerify = $this->getDataFromResponse($this->response); + + Assert::assertEquals(200, $this->response->getStatusCode()); + Assert::assertEquals($itemToVerify['title'], $title); + Assert::assertEquals($itemToVerify['emoji'], $emoji); + } + + /** + * @Given table :table with emoji :emoji exists for user :user as :tableName * * @param string $user * @param string $title + * @param string $tableName * @param string|null $emoji */ - public function createTable(string $user, string $title, string $emoji = null): void { + public function createTable(string $user, string $title, string $tableName, string $emoji = null): void { $this->setCurrentUser($user); $this->sendRequest( 'POST', @@ -249,6 +344,7 @@ public function createTable(string $user, string $title, string $emoji = null): $newTable = $this->getDataFromResponse($this->response); $this->tableId = $newTable['id']; + $this->tableIds[$tableName] = $newTable['id']; Assert::assertEquals(200, $this->response->getStatusCode()); Assert::assertEquals($newTable['title'], $title); @@ -328,6 +424,71 @@ public function updateTable(string $user, string $title, ?string $emoji, string Assert::assertEquals($tableToVerify['ownership'], $user); } + /** + * @When user :user update view :viewName with title :title and emoji :emoji + * + * @param string $user + * @param string $viewName + * @param string $title + * @param string|null $emoji + */ + public function updateView(string $user, string $viewName, string $title, ?string $emoji): void { + $this->setCurrentUser($user); + + $data = ['title' => $title]; + if ($emoji !== null) { + $data['emoji'] = $emoji; + } + + $this->sendRequest( + 'PUT', + '/apps/tables/api/1/views/'.$this->viewIds[$viewName], + [ 'data' => json_encode($data) ] + ); + + $updatedItem = $this->getDataFromResponse($this->response); + + Assert::assertEquals(200, $this->response->getStatusCode()); + Assert::assertEquals($updatedItem['title'], $title); + Assert::assertEquals($updatedItem['emoji'], $emoji); + + $this->sendRequest( + 'GET', + '/apps/tables/api/1/views/'.$updatedItem['id'], + ); + + $itemToVerify = $this->getDataFromResponse($this->response); + Assert::assertEquals(200, $this->response->getStatusCode()); + Assert::assertEquals($itemToVerify['title'], $title); + Assert::assertEquals($itemToVerify['emoji'], $emoji); + } + + /** + * @When user :user deletes view :viewName + * + * @param string $user + * @param string $viewName + */ + public function deleteView(string $user, string $viewName): void { + $this->setCurrentUser($user); + + $this->sendRequest( + 'DELETE', + '/apps/tables/api/1/views/'.$this->viewIds[$viewName] + ); + $deletedItem = $this->getDataFromResponse($this->response); + + Assert::assertEquals(200, $this->response->getStatusCode()); + + $this->sendRequest( + 'GET', + '/apps/tables/api/1/tables/'.$deletedItem['id'], + ); + Assert::assertEquals(404, $this->response->getStatusCode()); + + unset($this->viewIds[$viewName]); + } + /** * @Then user :user deletes table with keyword :keyword * @@ -658,8 +819,6 @@ public function createRow(TableNode $properties = null): void { $props[$columnId] = $row[1]; } - print_r($props); - $this->sendRequest( 'POST', '/apps/tables/api/1/tables/'.$this->tableId.'/rows', @@ -667,7 +826,6 @@ public function createRow(TableNode $properties = null): void { ); $newRow = $this->getDataFromResponse($this->response); - // var_dump($newRow); $this->rowId = $newRow['id']; Assert::assertEquals(200, $this->response->getStatusCode()); From fea27130e4fee6d1a539e09c4fd6e62d38a0016f Mon Sep 17 00:00:00 2001 From: Florian Steffens Date: Thu, 7 Sep 2023 15:06:05 +0200 Subject: [PATCH 3/6] API docs for views Signed-off-by: Florian Steffens --- APIv1.yaml | 295 +++++++++++++++++- .../features/api/tablesapi.feature | 2 +- 2 files changed, 293 insertions(+), 4 deletions(-) diff --git a/APIv1.yaml b/APIv1.yaml index 4c23967e2..e5809cde0 100644 --- a/APIv1.yaml +++ b/APIv1.yaml @@ -238,10 +238,10 @@ paths: schema: - $ref: "#/components/schemas/columnProperties" - type: - type: string - required: true + type: string + required: true - subtype: - type: string + type: string responses: '200': @@ -255,6 +255,167 @@ paths: '403': description: Not allowed + /tables/{tableId}/views: + get: + summary: Get a list of views for a given table + description: Shipped with v0.6.0. + tags: + - Views + operationId: listTableViews + security: + - basicAuth: [] + parameters: + - name: tableId + in: path + description: Table ID + required: true + schema: + type: integer + responses: + '200': + description: Json array with view objects + content: + application/json: + schema: + $ref: "#/components/schemas/Views" + '401': + description: Not authorized + '403': + description: Not allowed + '404': + description: Not found + post: + summary: Create a view for given table + description: Shipped with v0.6.0. + tags: + - Views + operationId: createTableView + security: + - basicAuth: [] + parameters: + - name: tableId + in: path + description: Related table ID + required: true + example: 5 + schema: + type: integer + - name: title + in: query + description: Title for the view + required: true + schema: + type: string + - name: emoji + in: query + description: Emoji for the view + required: false + schema: + type: string + responses: + '200': + description: View object + content: + application/json: + schema: + $ref: "#/components/schemas/View" + '401': + description: Not authorized + '403': + description: Not allowed + + /views/{viewId}: + get: + summary: Get a view + description: Shipped with v0.6.0. + tags: + - Views + operationId: getView + security: + - basicAuth: [] + parameters: + - name: viewId + in: path + description: View ID + required: true + schema: + type: integer + responses: + '200': + description: Json view object + content: + application/json: + schema: + $ref: "#/components/schemas/View" + '401': + description: Not authorized + '403': + description: Not allowed + '404': + description: Not found + put: + summary: Update a view + description: Shipped with v0.6.0. + tags: + - Views + operationId: updateView + security: + - basicAuth: [] + parameters: + - name: viewId + in: path + description: ID for the view + required: true + schema: + type: integer + - name: values + in: query + description: object of key-value pairs as json-string + required: true + schema: + $ref: "#/components/schemas/viewProperties" + responses: + '200': + description: Updated view object + content: + application/json: + schema: + $ref: "#/components/schemas/View" + '401': + description: Not authorized + '403': + description: Not allowed + '404': + description: Not found + delete: + summary: Delete a view + description: Shipped with v0.6.0. + tags: + - Views + operationId: deleteView + security: + - basicAuth: [] + parameters: + - name: viewId + in: path + description: ID for the requested view + required: true + schema: + type: integer + responses: + '200': + description: Deleted view object + content: + application/json: + schema: + $ref: "#/components/schemas/View" + '401': + description: Not authorized + '403': + description: Not allowed + '404': + description: Not found + /column/{columnId}: get: summary: Get a column @@ -966,6 +1127,99 @@ components: type: integer datetimeDefault: type: string + Views: + type: array + maxItems: 1000 + items: + $ref: "#/components/schemas/View" + View: + type: object + required: + - id + - title + - tableId + - ownership + properties: + id: + type: integer + tableId: + type: integer + createdBy: + type: string + createdAt: + type: string + lastEditBy: + type: string + lastEditAt: + type: string + description: + type: string + emoji: + type: string + columns: + type: array + items: + type: integer + sort: + $ref: "#/components/schemas/Sorting" + isShared: + type: boolean + onSharePermissions: + type: object + properties: + read: + type: boolean + create: + type: boolean + update: + type: boolean + delete: + type: boolean + manage: + type: boolean + hasShares: + type: boolean + rowsCount: + type: integer + ownerDisplayName: + type: string + filter: + $ref: "#/components/schemas/Filters" + Filters: + type: array + maxItems: 1000 + items: + $ref: "#/components/schemas/FilterGroups" + FilterGroups: + type: array + description: Definition of filter groups that are combined with logical OR. + maxItems: 1000 + items: + $ref: "#/components/schemas/Filter" + Filter: + type: object + description: Definition of filter that are combined with logical AND. + required: + - columnId + - operator + - value + properties: + columnId: + type: integer + operator: + type: string + enum: + - contains + - begins-with + - ends-with + - is-equal + - is-greater-than + - is-greater-than-or-equal + - is-lower-than + - is-lower-than-or-equal + - is-empty + value: + type: string RowsSimple: type: array maxItems: 1000 @@ -1042,3 +1296,38 @@ components: type: integer datetimeDefault: type: string + viewProperties: + type: object + properties: + title: + type: string + required: true + emoji: + type: boolean + required: true + description: + type: string + columns: + type: array + items: + type: integer + sort: + $ref: "#/components/schemas/Sorting" + filter: + $ref: "#/components/schemas/Filters" + Sorting: + type: array + maxItems: 1000 + items: + $ref: "#/components/schemas/Sort" + Sort: + type: object + description: Object with sorting definition + properties: + columnId: + type: integer + mode: + type: string + enum: + - ASC + - DESC diff --git a/tests/integration/features/api/tablesapi.feature b/tests/integration/features/api/tablesapi.feature index a59291344..cef6f8e0c 100644 --- a/tests/integration/features/api/tablesapi.feature +++ b/tests/integration/features/api/tablesapi.feature @@ -15,7 +15,7 @@ Feature: api/tablesapi Then user "participant1" has the following tables | my new awesome table | Then user "participant1" updates table with keyword "awesome" set title "renamed table" and optional emoji "🍓" - Then user "participant1" updates table with keyword "renamed table" set title "renamed table without emoji" and optional emoji ""∆ + Then user "participant1" updates table with keyword "renamed table" set title "renamed table without emoji" and optional emoji "" Then user "participant1" deletes table with keyword "without emoji" Then user "participant1" has the following tables | Tutorial | From bf60d48c1f61b10c4cde089c68b6cf8eaaf8c0ce Mon Sep 17 00:00:00 2001 From: Florian Steffens Date: Thu, 7 Sep 2023 15:21:12 +0200 Subject: [PATCH 4/6] code cleanup Signed-off-by: Florian Steffens --- lib/Service/ViewService.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index 79db4b28e..b00a940f6 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -1,4 +1,6 @@ - Date: Fri, 8 Sep 2023 08:14:05 +0200 Subject: [PATCH 5/6] fix(api): send data objects as array - had to be stringified before - closes 232 Signed-off-by: Florian Steffens --- lib/Controller/Api1Controller.php | 19 ++++++------------- .../features/api/tablesapi.feature | 3 --- .../features/bootstrap/FeatureContext.php | 6 +++--- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/Controller/Api1Controller.php b/lib/Controller/Api1Controller.php index a9ed58ab7..33d0d2679 100644 --- a/lib/Controller/Api1Controller.php +++ b/lib/Controller/Api1Controller.php @@ -157,11 +157,9 @@ public function getView(int $viewId): DataResponse { * @CORS * @NoCSRFRequired */ - public function updateView(int $viewId, string $data): DataResponse { - $dataNew = json_decode($data, true); - - return $this->handleError(function () use ($viewId, $dataNew) { - return $this->viewService->update($viewId, $dataNew); + public function updateView(int $viewId, array $data): DataResponse { + return $this->handleError(function () use ($viewId, $data) { + return $this->viewService->update($viewId, $data); }); } @@ -494,8 +492,7 @@ public function indexViewRows(int $viewId, ?int $limit, ?int $offset): DataRespo * @CORS * @NoCSRFRequired */ - public function createRowInView(int $viewId, string $data): DataResponse { - $data = json_decode($data); + public function createRowInView(int $viewId, array $data): DataResponse { $dataNew = []; foreach ($data as $key => $value) { $dataNew[] = [ @@ -514,9 +511,7 @@ public function createRowInView(int $viewId, string $data): DataResponse { * @CORS * @NoCSRFRequired */ - public function createRowInTable(int $tableId, string $data): DataResponse { - $data = json_decode($data, true); - + public function createRowInTable(int $tableId, array $data): DataResponse { $dataNew = []; foreach ($data as $key => $value) { $dataNew[] = [ @@ -546,9 +541,7 @@ public function getRow(int $rowId): DataResponse { * @CORS * @NoCSRFRequired */ - public function updateRow(int $rowId, ?int $viewId, string $data): DataResponse { - $data = json_decode($data, true); - + public function updateRow(int $rowId, ?int $viewId, array $data): DataResponse { $dataNew = []; foreach ($data as $key => $value) { $dataNew[] = [ diff --git a/tests/integration/features/api/tablesapi.feature b/tests/integration/features/api/tablesapi.feature index cef6f8e0c..f80e1ba95 100644 --- a/tests/integration/features/api/tablesapi.feature +++ b/tests/integration/features/api/tablesapi.feature @@ -134,9 +134,6 @@ Feature: api/tablesapi Then user deletes last created row Then user "participant1" deletes table with keyword "Rows check" - - - Scenario: Import csv table Given file "/import.csv" exists for user "participant1" with following data | Col1 | Col2 | Col3 | num | emoji | special | diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index f17ae432b..e0f9cb413 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -443,7 +443,7 @@ public function updateView(string $user, string $viewName, string $title, ?strin $this->sendRequest( 'PUT', '/apps/tables/api/1/views/'.$this->viewIds[$viewName], - [ 'data' => json_encode($data) ] + [ 'data' => $data ] ); $updatedItem = $this->getDataFromResponse($this->response); @@ -822,7 +822,7 @@ public function createRow(TableNode $properties = null): void { $this->sendRequest( 'POST', '/apps/tables/api/1/tables/'.$this->tableId.'/rows', - ['data' => json_encode($props)] + ['data' => $props] ); $newRow = $this->getDataFromResponse($this->response); @@ -880,7 +880,7 @@ public function updateRow(TableNode $properties = null): void { $this->sendRequest( 'PUT', '/apps/tables/api/1/rows/'.$this->rowId, - ['data' => json_encode($props)] + ['data' => $props] ); $row = $this->getDataFromResponse($this->response); From fcc72771cfcff4e72f738045d0957e96ac4b59e2 Mon Sep 17 00:00:00 2001 From: Florian Steffens Date: Fri, 8 Sep 2023 09:53:22 +0200 Subject: [PATCH 6/6] let the api accept the old stringified data object OR the new json object directly (brings compatibility) Signed-off-by: Florian Steffens --- lib/Controller/Api1Controller.php | 45 +++++++++++- .../features/api/tablesapi.feature | 36 ++++++++++ .../features/bootstrap/FeatureContext.php | 70 ++++++++++++++++++- 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/lib/Controller/Api1Controller.php b/lib/Controller/Api1Controller.php index 33d0d2679..d37485972 100644 --- a/lib/Controller/Api1Controller.php +++ b/lib/Controller/Api1Controller.php @@ -1,5 +1,7 @@ userId = $userId; $this->v1Api = $v1Api; $this->logger = $logger; + $this->l10N = $l10N; } // Tables @@ -491,8 +498,19 @@ public function indexViewRows(int $viewId, ?int $limit, ?int $offset): DataRespo * @NoAdminRequired * @CORS * @NoCSRFRequired + * + * @param array|string $data */ - public function createRowInView(int $viewId, array $data): DataResponse { + public function createRowInView(int $viewId, $data): DataResponse { + if(is_string($data)) { + $data = json_decode($data, true); + } + if(!is_array($data)) { + $this->logger->warning('createRowInView not possible, data array invalid.'); + $message = ['message' => $this->l10N->t('Could not create row.')]; + return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR); + } + $dataNew = []; foreach ($data as $key => $value) { $dataNew[] = [ @@ -510,8 +528,19 @@ public function createRowInView(int $viewId, array $data): DataResponse { * @NoAdminRequired * @CORS * @NoCSRFRequired + * + * @param array|string $data */ - public function createRowInTable(int $tableId, array $data): DataResponse { + public function createRowInTable(int $tableId, $data): DataResponse { + if(is_string($data)) { + $data = json_decode($data, true); + } + if(!is_array($data)) { + $this->logger->warning('createRowInTable not possible, data array invalid.'); + $message = ['message' => $this->l10N->t('Could not create row.')]; + return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR); + } + $dataNew = []; foreach ($data as $key => $value) { $dataNew[] = [ @@ -540,8 +569,18 @@ public function getRow(int $rowId): DataResponse { * @NoAdminRequired * @CORS * @NoCSRFRequired + * + * @param array|string $data */ - public function updateRow(int $rowId, ?int $viewId, array $data): DataResponse { + public function updateRow(int $rowId, ?int $viewId, $data): DataResponse { + if(is_string($data)) { + $data = json_decode($data, true); + } + if(!is_array($data)) { + $this->logger->warning('updateRow not possible, data array invalid.'); + $message = ['message' => $this->l10N->t('Could not update row.')]; + return new DataResponse($message, Http::STATUS_INTERNAL_SERVER_ERROR); + } $dataNew = []; foreach ($data as $key => $value) { $dataNew[] = [ diff --git a/tests/integration/features/api/tablesapi.feature b/tests/integration/features/api/tablesapi.feature index f80e1ba95..a76c19e90 100644 --- a/tests/integration/features/api/tablesapi.feature +++ b/tests/integration/features/api/tablesapi.feature @@ -134,6 +134,42 @@ Feature: api/tablesapi Then user deletes last created row Then user "participant1" deletes table with keyword "Rows check" + Scenario: Create, modify and delete rows (legacy interface) + Given table "Rows check legacy" with emoji "👨🏻‍💻" exists for user "participant1" as "base1" + Then column "one" exists with following properties + | type | text | + | subtype | line | + | mandatory | 0 | + | description | This is a description! | + Then column "two" exists with following properties + | type | number | + | mandatory | 1 | + | numberDefault | 10 | + | description | This is a description! | + Then column "three" exists with following properties + | type | selection | + | subtype | check | + | mandatory | 1 | + | description | This is a description! | + Then column "four" exists with following properties + | type | datetime | + | subtype | date | + | mandatory | 0 | + | description | This is a description! | + Then row exists with following values via legacy interface + | one | AHA | + | two | 88 | + | three | 1 | + | four | 2023-12-24 | + Then set following values for last created row via legacy interface + | one | AHA! | + | two | 99 | + | three | 0 | + | four | 2020-02-04 | + Then user deletes last created row + Then user "participant1" deletes table with keyword "Rows check" + + Scenario: Import csv table Given file "/import.csv" exists for user "participant1" with following data | Col1 | Col2 | Col3 | num | emoji | special | diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index e0f9cb413..8f2a252ae 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -845,6 +845,44 @@ public function createRow(TableNode $properties = null): void { } } + /** + * @Then row exists with following values via legacy interface + * + * @param TableNode|null $properties + */ + public function createRowLegacy(TableNode $properties = null): void { + $props = []; + foreach ($properties->getRows() as $row) { + $columnId = $this->tableColumns[$row[0]]; + $props[$columnId] = $row[1]; + } + + $this->sendRequest( + 'POST', + '/apps/tables/api/1/tables/'.$this->tableId.'/rows', + ['data' => json_encode($props)] + ); + + $newRow = $this->getDataFromResponse($this->response); + $this->rowId = $newRow['id']; + + Assert::assertEquals(200, $this->response->getStatusCode()); + foreach ($newRow['data'] as $cell) { + Assert::assertEquals($props[$cell['columnId']], $cell['value']); + } + + $this->sendRequest( + 'GET', + '/apps/tables/api/1/rows/'.$newRow['id'], + ); + + $rowToVerify = $this->getDataFromResponse($this->response); + Assert::assertEquals(200, $this->response->getStatusCode()); + foreach ($rowToVerify['data'] as $cell) { + Assert::assertEquals($props[$cell['columnId']], $cell['value']); + } + } + /** * @Then user deletes last created row */ @@ -902,12 +940,42 @@ public function updateRow(TableNode $properties = null): void { } } + /** + * @Then set following values for last created row via legacy interface + * + * @param TableNode|null $properties + */ + public function updateRowLegacy(TableNode $properties = null): void { + $props = []; + foreach ($properties->getRows() as $row) { + $columnId = $this->tableColumns[$row[0]]; + $props[$columnId] = $row[1]; + } + $this->sendRequest( + 'PUT', + '/apps/tables/api/1/rows/'.$this->rowId, + ['data' => json_encode($props)] + ); + $row = $this->getDataFromResponse($this->response); + Assert::assertEquals(200, $this->response->getStatusCode()); + foreach ($row['data'] as $cell) { + Assert::assertEquals($props[$cell['columnId']], $cell['value']); + } + $this->sendRequest( + 'GET', + '/apps/tables/api/1/rows/'.$row['id'], + ); - + $rowToVerify = $this->getDataFromResponse($this->response); + Assert::assertEquals(200, $this->response->getStatusCode()); + foreach ($rowToVerify['data'] as $cell) { + Assert::assertEquals($props[$cell['columnId']], $cell['value']); + } + } /* * User management