diff --git a/config/routes.php b/config/routes.php index a6b6d0147..749fdb6ce 100644 --- a/config/routes.php +++ b/config/routes.php @@ -275,6 +275,26 @@ ['controller' => 'Tags', 'action' => 'index'], ['_name' => 'tags:index'] ); + $routes->connect( + '/tags/delete/{id}', + ['controller' => 'Tags', 'action' => 'delete'], + ['pass' => ['id'], '_name' => 'tags:delete'] + ); + $routes->connect( + '/tags/create', + ['controller' => 'Tags', 'action' => 'create'], + ['_name' => 'tags:create'] + ); + $routes->connect( + '/tags/patch/{id}', + ['controller' => 'Tags', 'action' => 'patch'], + ['pass' => ['id'], '_name' => 'tags:patch'] + ); + $routes->connect( + '/tags/search', + ['controller' => 'Tags', 'action' => 'search'], + ['_name' => 'tags:search'] + ); // view resource by id / uname $routes->connect( diff --git a/resources/js/app/components/tag-form/tag-form.vue b/resources/js/app/components/tag-form/tag-form.vue index 73b0abae6..8c7ecce2c 100644 --- a/resources/js/app/components/tag-form/tag-form.vue +++ b/resources/js/app/components/tag-form/tag-form.vue @@ -165,7 +165,7 @@ export default { headers, method: 'GET', }; - const response = await fetch(`${BEDITA.base}/api/model/tags?filter[name]=${this.name}`, options); + const response = await fetch(`${BEDITA.base}/tags/search?filter[name]=${this.name}`, options); const responseJson = await response.json(); return responseJson?.data?.length > 0; @@ -212,11 +212,11 @@ export default { payload.data.id = this.obj.id; options.body = JSON.stringify(payload); options.method = 'PATCH' - response = await fetch(`${BEDITA.base}/api/model/tags/${this.obj.id}`, options); + response = await fetch(`${BEDITA.base}/tags/patch/${this.obj.id}`, options); } else { options.body = JSON.stringify(payload); options.method = 'POST' - response = await fetch(`${BEDITA.base}/api/model/tags`, options); + response = await fetch(`${BEDITA.base}/tags/create`, options); } if (response.status === 200) { if (this.redir) { @@ -253,14 +253,14 @@ export default { const prefix = t`Error on deleting tag`; try { const options = { - method: 'DELETE', + method: 'POST', credentials: 'same-origin', headers: { 'accept': 'application/json', 'X-CSRF-Token': BEDITA.csrfToken, }, }; - const response = await fetch(`${BEDITA.base}/api/model/tags/${this.obj.id}`, options); + const response = await fetch(`${BEDITA.base}/tags/delete/${this.obj.id}`, options); if (response.status === 200) { this.$el.remove(this.$el); this.dialog?.hide(); diff --git a/resources/js/app/components/tag-picker/tag-picker.vue b/resources/js/app/components/tag-picker/tag-picker.vue index c1ff7e229..1e29de922 100644 --- a/resources/js/app/components/tag-picker/tag-picker.vue +++ b/resources/js/app/components/tag-picker/tag-picker.vue @@ -153,7 +153,7 @@ export default { return callback(null, []); } - const res = await fetch(`${BEDITA.base}/api/model/tags?filter[query]=${searchQuery}&filter[enabled]=1&page_size=30`, API_OPTIONS); + const res = await fetch(`${BEDITA.base}/tags/search?filter[query]=${searchQuery}&filter[enabled]=1&page_size=30`, API_OPTIONS); const json = await res.json(); const tags = [...(json.data || [])]; diff --git a/src/Controller/TagsController.php b/src/Controller/TagsController.php index f0ae11fb6..1fc174a56 100644 --- a/src/Controller/TagsController.php +++ b/src/Controller/TagsController.php @@ -4,8 +4,10 @@ namespace App\Controller; use App\Controller\Model\TagsController as ModelTagsController; +use BEdita\SDK\BEditaClientException; use Cake\Event\EventInterface; use Cake\Http\Response; +use Cake\Utility\Hash; /** * Tags Controller @@ -21,6 +23,7 @@ public function initialize(): void { parent::initialize(); $this->loadComponent('ProjectConfiguration'); + $this->Security->setConfig('unlockedActions', ['create', 'patch', 'delete']); } /** @@ -37,6 +40,110 @@ public function index(): ?Response return null; } + /** + * Create new tag (ajax). + * + * @return \Cake\Http\Response|null + */ + public function create(): ?Response + { + $this->getRequest()->allowMethod(['post']); + $this->viewBuilder()->setClassName('Json'); + $response = $error = null; + try { + $body = (array)$this->getRequest()->getData(); + $response = $this->apiClient->post('/model/tags', json_encode($body)); + } catch (BEditaClientException $e) { + $error = $e->getMessage(); + $this->log($error, 'error'); + $this->set('error', $error); + } + $this->set('response', $response); + $this->set('error', $error); + $this->setSerialize(['response', 'error']); + + return null; + } + + /** + * Delete single tag (ajax). + * + * @param string $id Tag ID. + * @return \Cake\Http\Response|null + */ + public function delete(string $id): ?Response + { + $this->getRequest()->allowMethod(['post']); + $this->viewBuilder()->setClassName('Json'); + $response = $error = null; + try { + $this->apiClient->delete(sprintf('/model/tags/%s', $id)); + $response = 'ok'; + } catch (BEditaClientException $e) { + $error = $e->getMessage(); + $this->log($error, 'error'); + $this->set('error', $error); + } + $this->set('response', $response); + $this->set('error', $error); + $this->setSerialize(['response', 'error']); + + return null; + } + + /** + * Save tag (ajax). + * + * @param string $id Tag ID. + * @return \Cake\Http\Response|null + */ + public function patch(string $id): ?Response + { + $this->getRequest()->allowMethod(['patch']); + $this->viewBuilder()->setClassName('Json'); + $response = $error = null; + try { + $body = (array)$this->getRequest()->getData(); + $this->apiClient->patch(sprintf('/model/tags/%s', $id), json_encode($body)); + $response = 'ok'; + } catch (BEditaClientException $e) { + $error = $e->getMessage(); + $this->log($error, 'error'); + $this->set('error', $error); + } + $this->set('response', $response); + $this->set('error', $error); + $this->setSerialize(['response', 'error']); + + return null; + } + + /** + * Search tags (ajax) + * + * @return \Cake\Http\Response|null + */ + public function search(): ?Response + { + $this->getRequest()->allowMethod(['get']); + $this->viewBuilder()->setClassName('Json'); + $data = $error = null; + try { + $query = $this->getRequest()->getQueryParams(); + $response = $this->apiClient->get('/model/tags', $query); + $data = (array)Hash::get($response, 'data'); + } catch (BEditaClientException $e) { + $error = $e->getMessage(); + $this->log($error, 'error'); + $this->set('error', $error); + } + $this->set('data', $data); + $this->set('error', $error); + $this->setSerialize(['data', 'error']); + + return null; + } + /** * @inheritDoc */ diff --git a/tests/TestCase/Controller/TagsControllerTest.php b/tests/TestCase/Controller/TagsControllerTest.php index a1b5f304e..c02232428 100644 --- a/tests/TestCase/Controller/TagsControllerTest.php +++ b/tests/TestCase/Controller/TagsControllerTest.php @@ -7,6 +7,7 @@ use BEdita\WebTools\ApiClientProvider; use Cake\Http\ServerRequest; use Cake\TestSuite\TestCase; +use Cake\Utility\Hash; /** * {@see \App\Controller\TagsController} Test Case @@ -110,4 +111,101 @@ public function testBeforeRender(): void $this->controller->dispatchEvent('Controller.beforeRender'); static::assertSame(['_name' => 'tags:index'], $this->controller->viewBuilder()->getVar('moduleLink')); } + + /** + * Test `create`, `patch`, `delete`, `search` methods + * + * @return void + * @covers ::create() + * @covers ::patch() + * @covers ::delete() + * @covers ::search() + */ + public function testMulti(): void + { + // create with error + $this->setupController(['environment' => ['REQUEST_METHOD' => 'POST']]); + $this->controller->create(); + static::assertNotEmpty($this->controller->viewBuilder()->getVar('error')); + + // create ok + $data = [ + 'type' => 'tags', + 'attributes' => [ + 'name' => 'my-dummy-test-tag', + 'label' => 'My Dummy Test Tag', + 'labels' => [ + 'default' => 'My Dummy Test Tag', + ], + 'enabled' => false, + ], + ]; + $request = $this->controller->getRequest()->withData('data', $data); + $this->controller->setRequest($request); + $this->controller->create(); + static::assertEmpty($this->controller->viewBuilder()->getVar('error')); + $response = $this->controller->viewBuilder()->getVar('response'); + static::assertNotEmpty($response); + $id = $response['data']['id']; + + // patch with error + $this->setupController(['environment' => ['REQUEST_METHOD' => 'PATCH']]); + $this->controller->getRequest()->withData('name', 'test'); + $this->controller->patch('test'); + static::assertNotEmpty($this->controller->viewBuilder()->getVar('error')); + static::assertNotEquals('ok', $this->controller->viewBuilder()->getVar('response')); + + // patch ok + $data = [ + 'id' => $id, + 'type' => 'tags', + 'attributes' => [ + 'name' => 'my-dummy-test-tag', + 'label' => 'My Dummy Test Tag', + 'labels' => [ + 'default' => 'My Dummy Test Tag', + ], + 'enabled' => true, + ], + ]; + $request = $this->controller->getRequest()->withData('data', $data); + $this->controller->setRequest($request); + $this->controller->patch($id); + static::assertEquals('ok', $this->controller->viewBuilder()->getVar('response')); + static::assertEmpty($this->controller->viewBuilder()->getVar('error')); + + // search with error + $this->setupController(['environment' => ['REQUEST_METHOD' => 'GET']]); + $request = $this->controller->getRequest()->withQueryParams(['filter' => 'wrong']); + $this->controller->setRequest($request); + $this->controller->search(); + static::assertNotEmpty($this->controller->viewBuilder()->getVar('error')); + + // search ok + $request = $this->controller->getRequest()->withQueryParams(['filter' => ['name' => 'my-dummy-test-tag']]); + $this->controller->setRequest($request); + $this->controller->search(); + static::assertEmpty($this->controller->viewBuilder()->getVar('error')); + $response = $this->controller->viewBuilder()->getVar('data'); + static::assertNotEmpty($response); + $actual = (array)Hash::get($response, '0'); + static::assertNotEmpty($actual); + static::assertEquals('my-dummy-test-tag', $actual['attributes']['name']); + static::assertEquals('My Dummy Test Tag', $actual['attributes']['label']); + static::assertEquals('My Dummy Test Tag', $actual['attributes']['labels']['default']); + static::assertEquals(true, $actual['attributes']['enabled']); + static::assertEquals($id, $actual['id']); + + // delete with error + $this->setupController(['environment' => ['REQUEST_METHOD' => 'POST']]); + $this->controller->delete('test'); + static::assertNotEmpty($this->controller->viewBuilder()->getVar('error')); + static::assertNotEquals('ok', $this->controller->viewBuilder()->getVar('response')); + + // delete ok + $this->setupController(['environment' => ['REQUEST_METHOD' => 'POST']]); + $this->controller->delete($id); + static::assertEquals('ok', $this->controller->viewBuilder()->getVar('response')); + static::assertEmpty($this->controller->viewBuilder()->getVar('error')); + } }