From 8483df5fbec91e07b817ceef54a3347ef9117647 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 30 Jul 2018 08:42:31 +1000 Subject: [PATCH 1/4] crud and related entities complete --- ckan_connect.info.yml | 0 ckan_connect.links.menu.yml | 0 ckan_connect.module | 0 ckan_connect.permissions.yml | 0 ckan_connect.routing.yml | 0 ckan_connect.services.yml | 0 src/Ckan/CkanApiBase.php | 80 +++++++++++ src/Ckan/CkanApiInterface.php | 38 +++++ src/Ckan/Crud/CkanCrudBase.php | 135 ++++++++++++++++++ src/Ckan/Crud/CkanCrudInterface.php | 18 +++ src/Ckan/Crud/PackageRelationship.php | 48 +++++++ src/Ckan/Crud/ResourceView.php | 38 +++++ src/Ckan/Crud/Tag.php | 33 +++++ src/Ckan/Crud/User.php | 55 +++++++ src/Ckan/Crud/Vocabulary.php | 33 +++++ src/Ckan/Member/CkanMemberBase.php | 23 +++ src/Ckan/Member/GroupMember.php | 35 +++++ src/Ckan/Member/Member.php | 37 +++++ src/Ckan/Member/OrganizationMember.php | 35 +++++ src/Ckan/Patchable/CkanPatchableBase.php | 28 ++++ src/Ckan/Patchable/Group.php | 43 ++++++ src/Ckan/Patchable/Organization.php | 40 ++++++ src/Ckan/Patchable/Package.php | 42 ++++++ src/Ckan/Patchable/Resource.php | 48 +++++++ src/Ckan/Service/AAA.php | 39 +++++ src/Ckan/Service/CkanServiceBase.php | 29 ++++ .../CurrentPackageListWithResources.php | 24 ++++ src/Ckan/Service/FormatAutocomplete.php | 30 ++++ src/Ckan/Service/GroupAutocomplete.php | 30 ++++ src/Ckan/Service/GroupListAuthz.php | 23 +++ src/Ckan/Service/GroupRevisionList.php | 29 ++++ src/Ckan/Service/LicenseList.php | 15 ++ src/Ckan/Service/OrganizationListForUser.php | 24 ++++ src/Ckan/Service/OrganizationRevisionList.php | 29 ++++ src/Ckan/Service/PackageAutocomplete.php | 30 ++++ src/Ckan/Service/PackageRevisionList.php | 29 ++++ src/Ckan/Service/PackageSearch.php | 40 ++++++ src/Ckan/Service/ResourceSearch.php | 33 +++++ src/Ckan/Service/RevisionList.php | 32 +++++ src/Ckan/Service/RevisionShow.php | 29 ++++ src/Ckan/Service/SiteRead.php | 15 ++ src/Ckan/Service/UserAutocomplete.php | 30 ++++ src/Client/CkanClient.php | 53 ++++++- src/Client/CkanClientInterface.php | 19 ++- src/Form/CkanConnectSettingsForm.php | 0 src/Parser/CkanResourceUrlParser.php | 0 src/Parser/CkanResourceUrlParserInterface.php | 0 47 files changed, 1385 insertions(+), 6 deletions(-) mode change 100644 => 100755 ckan_connect.info.yml mode change 100644 => 100755 ckan_connect.links.menu.yml mode change 100644 => 100755 ckan_connect.module mode change 100644 => 100755 ckan_connect.permissions.yml mode change 100644 => 100755 ckan_connect.routing.yml mode change 100644 => 100755 ckan_connect.services.yml create mode 100755 src/Ckan/CkanApiBase.php create mode 100755 src/Ckan/CkanApiInterface.php create mode 100755 src/Ckan/Crud/CkanCrudBase.php create mode 100755 src/Ckan/Crud/CkanCrudInterface.php create mode 100755 src/Ckan/Crud/PackageRelationship.php create mode 100755 src/Ckan/Crud/ResourceView.php create mode 100755 src/Ckan/Crud/Tag.php create mode 100755 src/Ckan/Crud/User.php create mode 100755 src/Ckan/Crud/Vocabulary.php create mode 100755 src/Ckan/Member/CkanMemberBase.php create mode 100755 src/Ckan/Member/GroupMember.php create mode 100755 src/Ckan/Member/Member.php create mode 100755 src/Ckan/Member/OrganizationMember.php create mode 100755 src/Ckan/Patchable/CkanPatchableBase.php create mode 100755 src/Ckan/Patchable/Group.php create mode 100755 src/Ckan/Patchable/Organization.php create mode 100755 src/Ckan/Patchable/Package.php create mode 100755 src/Ckan/Patchable/Resource.php create mode 100644 src/Ckan/Service/AAA.php create mode 100755 src/Ckan/Service/CkanServiceBase.php create mode 100644 src/Ckan/Service/CurrentPackageListWithResources.php create mode 100644 src/Ckan/Service/FormatAutocomplete.php create mode 100644 src/Ckan/Service/GroupAutocomplete.php create mode 100644 src/Ckan/Service/GroupListAuthz.php create mode 100644 src/Ckan/Service/GroupRevisionList.php create mode 100644 src/Ckan/Service/LicenseList.php create mode 100644 src/Ckan/Service/OrganizationListForUser.php create mode 100644 src/Ckan/Service/OrganizationRevisionList.php create mode 100644 src/Ckan/Service/PackageAutocomplete.php create mode 100644 src/Ckan/Service/PackageRevisionList.php create mode 100644 src/Ckan/Service/PackageSearch.php create mode 100644 src/Ckan/Service/ResourceSearch.php create mode 100644 src/Ckan/Service/RevisionList.php create mode 100644 src/Ckan/Service/RevisionShow.php create mode 100644 src/Ckan/Service/SiteRead.php create mode 100644 src/Ckan/Service/UserAutocomplete.php mode change 100644 => 100755 src/Client/CkanClient.php mode change 100644 => 100755 src/Client/CkanClientInterface.php mode change 100644 => 100755 src/Form/CkanConnectSettingsForm.php mode change 100644 => 100755 src/Parser/CkanResourceUrlParser.php mode change 100644 => 100755 src/Parser/CkanResourceUrlParserInterface.php diff --git a/ckan_connect.info.yml b/ckan_connect.info.yml old mode 100644 new mode 100755 diff --git a/ckan_connect.links.menu.yml b/ckan_connect.links.menu.yml old mode 100644 new mode 100755 diff --git a/ckan_connect.module b/ckan_connect.module old mode 100644 new mode 100755 diff --git a/ckan_connect.permissions.yml b/ckan_connect.permissions.yml old mode 100644 new mode 100755 diff --git a/ckan_connect.routing.yml b/ckan_connect.routing.yml old mode 100644 new mode 100755 diff --git a/ckan_connect.services.yml b/ckan_connect.services.yml old mode 100644 new mode 100755 diff --git a/src/Ckan/CkanApiBase.php b/src/Ckan/CkanApiBase.php new file mode 100755 index 0000000..fe0f5ab --- /dev/null +++ b/src/Ckan/CkanApiBase.php @@ -0,0 +1,80 @@ + param_value]. + */ + protected $parameters = []; + + /** + * @var array $validParameters + * An array of valid parameter keys e.g. ['name', 'title', 'private', ...] + */ + protected $validParameters = []; + + /** + * @var array $requiredParameters + * An array of required parameter keys. + */ + protected $requiredParameters = []; + + /** + * {@inheritdoc} + */ + public function setParameters($parameters) { + if ($this->validateParameters($parameters)) { + $this->parameters = $parameters; + return TRUE; + } + // @todo: throw error instead. + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getPath() { + return $this->machineName; + } + + /** + * {@inheritdoc} + */ + public function getParameters() { + return $this->parameters; + } + + /** + * Ensure the parameters are valid and complete. + * + * @param string $action + * The action being requested: create|update|patch. + */ + protected function validateParameters($parameters) { + $keys = array_keys($parameters); + + // Check for invalid parameter keys. + if (!empty(array_diff_key($keys, $this->validParameters))) { + return FALSE; + } + + return TRUE; + } + +} diff --git a/src/Ckan/CkanApiInterface.php b/src/Ckan/CkanApiInterface.php new file mode 100755 index 0000000..1aabe31 --- /dev/null +++ b/src/Ckan/CkanApiInterface.php @@ -0,0 +1,38 @@ + param_value]. + * + * @return bool + */ + public function setParameters($parameters); + + /** + * Get the last part of the API endpoint specific to this CKAN object. + * + * @param string $action + * + * @return string + * The action slug, e.g. package_delete or organisation_create + */ + public function getPath(); + + /** + * Get the parameters to be used by the API call. + * + * @return array + * An associative array of parameters: [param_key => param_value]. + */ + public function getParameters(); + +} diff --git a/src/Ckan/Crud/CkanCrudBase.php b/src/Ckan/Crud/CkanCrudBase.php new file mode 100755 index 0000000..4fcedcd --- /dev/null +++ b/src/Ckan/Crud/CkanCrudBase.php @@ -0,0 +1,135 @@ +id = $id; + } + + /** + * Set the action that the API client should perform with this object. + * + * @param string $action + * + * @return bool + */ + public function setAction($action) { + if (in_array($action, $this->validActions)) { + $this->action = $action; + return TRUE; + } + // @todo: throw error instead. + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getPath() { + if (empty($this->action)){ + // @todo: throw error instead. + return FALSE; + } + return $this->machineName . '_' . $this->action; + } + + /** + * {@inheritdoc} + */ + public function getParameters() { + $actionsRequiringParameters = [ + CkanClient::CKAN_ACTION_CREATE, + CkanClient::CKAN_ACTION_UPDATE, + CkanClient::CKAN_ACTION_PATCH, + ]; + + if (in_array($this->action, $actionsRequiringParameters)) { + if ($this->prepareParameters()) { + return $this->parameters; + } + else { + // @todo: throw error instead. + return FALSE; + } + } + + // Delete and get (list/show) actions only require the ID. + return ['id' => $this->id]; + } + + /** + * {@inheritdoc} + */ + public function getValidActions() { + return $this->validActions; + } + + /** + * @param $action + * + * @return bool + */ + protected function prepareParameters() { + $keys = array_keys($this->parameters); + + // Check for missing required keys (create only). + if ($this->action === CkanClient::CKAN_ACTION_CREATE) { + if (!empty(array_diff_key($this->requiredParameters, $keys))) { + return FALSE; + } + } + + // Update and patch queries must include an ID. + if ($this->action === CkanClient::CKAN_ACTION_UPDATE || $this->action === CkanClient::CKAN_ACTION_PATCH) { + + // This check is the entire reason id is a separate property. + if (empty($this->id)) { + return FALSE; + } + + $this->parameters['id'] = $this->id; + } + return TRUE; + } + +} diff --git a/src/Ckan/Crud/CkanCrudInterface.php b/src/Ckan/Crud/CkanCrudInterface.php new file mode 100755 index 0000000..9dc9984 --- /dev/null +++ b/src/Ckan/Crud/CkanCrudInterface.php @@ -0,0 +1,18 @@ +action === 'list') { + // This object doesn't follow the apparent naming convention in the CKAN + // API. + return $this->machineName . 's_' . $this->action; + } + return parent::getPath(); + } + +} diff --git a/src/Ckan/Crud/ResourceView.php b/src/Ckan/Crud/ResourceView.php new file mode 100755 index 0000000..a9950d9 --- /dev/null +++ b/src/Ckan/Crud/ResourceView.php @@ -0,0 +1,38 @@ +parameters)) { + $valid = FALSE; + } + + return $valid; + } + +} diff --git a/src/Ckan/Crud/Vocabulary.php b/src/Ckan/Crud/Vocabulary.php new file mode 100755 index 0000000..00fae76 --- /dev/null +++ b/src/Ckan/Crud/Vocabulary.php @@ -0,0 +1,33 @@ +parameters); + + // Test for any required parameters. + if (!empty(array_diff_key($this->requiredParameters, $keys))) { + // @todo: throw error instead. + return FALSE; + } + + return $this->parameters; + } + +} diff --git a/src/Ckan/Service/CurrentPackageListWithResources.php b/src/Ckan/Service/CurrentPackageListWithResources.php new file mode 100644 index 0000000..c466588 --- /dev/null +++ b/src/Ckan/Service/CurrentPackageListWithResources.php @@ -0,0 +1,24 @@ +getApiUrl() . '/' . $path; + $options = ['query' => $parameters]; + + if ($this->getApiKey()) { + $options['headers']['Authorization'] = $this->getApiKey(); + } + + $response = $this->httpClient->get($uri, $options) + ->getBody() + ->getContents(); + $response = json_decode($response); + + return $response; + } + + /** + * {@inheritdoc} + */ + public function post($path, array $parameters) { $uri = $this->getApiUrl() . '/' . $path; - $options = ['query' => $query]; + $options = ['form_params' => $parameters]; if ($this->getApiKey()) { $options['headers']['Authorization'] = $this->getApiKey(); } - $response = $this->httpClient->get($uri, $options)->getBody()->getContents(); + $response = $this->httpClient->post($uri, $options) + ->getBody() + ->getContents(); $response = json_decode($response); return $response; } + /** + * Send an action query to the API. + * + * @param string $action + * @param \Drupal\ckan_connect\Ckan\CkanApiInterface $ckanObject + * + * @return mixed|\stdClass|string + */ + public function action(CkanApiInterface $ckanObject, $action = NULL) { + $path = 'action/' . $ckanObject->getActionSlug($action); + $parameters = $ckanObject->getParameters($action); + + // Every action API endpoint on CKAN may be used with a POST request. + return $this->post($path, $parameters); + } + } diff --git a/src/Client/CkanClientInterface.php b/src/Client/CkanClientInterface.php old mode 100644 new mode 100755 index f91255d..0dc1eb3 --- a/src/Client/CkanClientInterface.php +++ b/src/Client/CkanClientInterface.php @@ -46,16 +46,29 @@ public function getApiKey(); public function setApiKey($api_key); /** - * Get data from the CKAN endpoint. + * Get data from a CKAN endpoint. * * @param string $path * The path of the action. - * @param array $query + * @param array $parameters * The key pair parameters. * * @return \stdClass * A response object. */ - public function get($path, array $query = []); + public function get($path, array $parameters = []); + + /** + * Post data to a CKAN endpoint. + * + * @param string $path + * The path of the action. + * @param array $parameters + * The key pair parameters. + * + * @return \stdClass + * A response object. + */ + public function post($path, array $parameters); } diff --git a/src/Form/CkanConnectSettingsForm.php b/src/Form/CkanConnectSettingsForm.php old mode 100644 new mode 100755 diff --git a/src/Parser/CkanResourceUrlParser.php b/src/Parser/CkanResourceUrlParser.php old mode 100644 new mode 100755 diff --git a/src/Parser/CkanResourceUrlParserInterface.php b/src/Parser/CkanResourceUrlParserInterface.php old mode 100644 new mode 100755 From 90b3e7d7fb8de24c15fcecc8652f0f7a2a1f3b90 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 30 Jul 2018 11:38:40 +1000 Subject: [PATCH 2/4] decided on how to handle services --- src/Ckan/CkanRequest.php | 33 +++++++++++++++ src/Ckan/Service/AAA.php | 39 ------------------ src/Ckan/Service/CkanServiceBase.php | 29 -------------- .../CurrentPackageListWithResources.php | 24 ----------- src/Ckan/Service/FormatAutocomplete.php | 30 -------------- src/Ckan/Service/GroupAutocomplete.php | 30 -------------- src/Ckan/Service/GroupListAuthz.php | 23 ----------- src/Ckan/Service/GroupRevisionList.php | 29 -------------- src/Ckan/Service/LicenseList.php | 15 ------- src/Ckan/Service/OrganizationListForUser.php | 24 ----------- src/Ckan/Service/OrganizationRevisionList.php | 29 -------------- src/Ckan/Service/PackageAutocomplete.php | 30 -------------- src/Ckan/Service/PackageRevisionList.php | 29 -------------- src/Ckan/Service/PackageSearch.php | 40 ------------------- src/Ckan/Service/ResourceSearch.php | 33 --------------- src/Ckan/Service/RevisionList.php | 32 --------------- src/Ckan/Service/RevisionShow.php | 29 -------------- src/Ckan/Service/SiteRead.php | 15 ------- src/Ckan/Service/UserAutocomplete.php | 30 -------------- src/Client/CkanClient.php | 2 - 20 files changed, 33 insertions(+), 512 deletions(-) create mode 100644 src/Ckan/CkanRequest.php delete mode 100644 src/Ckan/Service/AAA.php delete mode 100755 src/Ckan/Service/CkanServiceBase.php delete mode 100644 src/Ckan/Service/CurrentPackageListWithResources.php delete mode 100644 src/Ckan/Service/FormatAutocomplete.php delete mode 100644 src/Ckan/Service/GroupAutocomplete.php delete mode 100644 src/Ckan/Service/GroupListAuthz.php delete mode 100644 src/Ckan/Service/GroupRevisionList.php delete mode 100644 src/Ckan/Service/LicenseList.php delete mode 100644 src/Ckan/Service/OrganizationListForUser.php delete mode 100644 src/Ckan/Service/OrganizationRevisionList.php delete mode 100644 src/Ckan/Service/PackageAutocomplete.php delete mode 100644 src/Ckan/Service/PackageRevisionList.php delete mode 100644 src/Ckan/Service/PackageSearch.php delete mode 100644 src/Ckan/Service/ResourceSearch.php delete mode 100644 src/Ckan/Service/RevisionList.php delete mode 100644 src/Ckan/Service/RevisionShow.php delete mode 100644 src/Ckan/Service/SiteRead.php delete mode 100644 src/Ckan/Service/UserAutocomplete.php diff --git a/src/Ckan/CkanRequest.php b/src/Ckan/CkanRequest.php new file mode 100644 index 0000000..0ba6be6 --- /dev/null +++ b/src/Ckan/CkanRequest.php @@ -0,0 +1,33 @@ +machineName = $action; + $this->parameters = $parameters; + } + + /** + * {@inheritdoc} + */ + public function validateParameters($parameters) { + // We don't do any error checking for generic service calls. + return TRUE; + } + +} diff --git a/src/Ckan/Service/AAA.php b/src/Ckan/Service/AAA.php deleted file mode 100644 index dfc6a74..0000000 --- a/src/Ckan/Service/AAA.php +++ /dev/null @@ -1,39 +0,0 @@ -parameters); - - // Test for any required parameters. - if (!empty(array_diff_key($this->requiredParameters, $keys))) { - // @todo: throw error instead. - return FALSE; - } - - return $this->parameters; - } - -} diff --git a/src/Ckan/Service/CurrentPackageListWithResources.php b/src/Ckan/Service/CurrentPackageListWithResources.php deleted file mode 100644 index c466588..0000000 --- a/src/Ckan/Service/CurrentPackageListWithResources.php +++ /dev/null @@ -1,24 +0,0 @@ - Date: Mon, 30 Jul 2018 11:39:03 +1000 Subject: [PATCH 3/4] add readme --- readme.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..03aa32b --- /dev/null +++ b/readme.md @@ -0,0 +1,152 @@ +Drupal CKAN Connect +=================== + +This is a developer module that provides a client for the [CKAN action API](http://docs.ckan.org/en/latest/api/index.html#action-api-reference). + +Usage +----- + +Every API request has three steps: +1. Create a new object related to the CKAN entity or service you wish to interact with. +2. Set any required properties on the object. +3. Use the `ckan_connect.client` service to make the API call using your newly created object. + +### Service calls + +> Technically, service calls can be used for any request to the CKAN API. They do not do any parameter checking and they +all work exactly the same way. If you want parameter validation and automatic handling of IDs where required for entity +actions, use the [CRUD-like objects](#crud-like-objects). + +A service call is easy to use as long as you know exactly what the API is expecting. Just give it the full action name +such as `package_search` or `organization_autocomplete` and any parameters it requires. Parameters can be given in the +construction method, or separately via `CkanApiInterface::setParameters($parameters)`. + +```php + 'search terms', +]; +$package_search->setParameters($parameters); +$client = \Drupal::service('ckan_connect.client'); +$search_result = $client->action($package_search); + +// OR + +$package_search = new CkanRequest('package_search', ['q' => 'search terms']); +$client = \Drupal::service('ckan_connect.client'); +$search_result = $client->action($package_search); + +``` + +That's it. The client uses the API URL and API key you've stored in settings and handles any errors that are thrown by +guzzle or the API itself so that your application doesn't crash. + +### CRUD-like objects + +Where the CKAN endpoints can be grouped together into a set of related functions, they are bunched into one of these +object types: + +- CRUD objects have endpoint actions for Create, Read (`show`), Update and Delete, they are in `\src\Ckan\Crud\*`. They + also allow a `list` action. +- Patchable objects have an additional `patch` action on top of the ones allowed by CRUD objects and are in + `\src\Ckan\Patchable\*`. The `patch` action is like `update` except that it only updates the values provided in the + call, rather than overwriting all values the way `update` does. +- Member objects are basically entity references and can only created or deleted (a subset of CRUD). These are in + `\src\Ckan\Member\*`. + +When creating a new CRUD-like object, note that the creation call expects an ID as a string. You can leave this string +empty if the object is going to be used for a `create` or `list` action but it expects a full CKAN ID for any other +action. + +```php + 'my_organization', +]; + +$new_organization->setParameters($parameters); + +// If we're doing an update or patch call, we only need to provide the new fields +// Update will assume the required 'name' field stays the same, and will remove the +// value of any other field we don't provide. +// Patch will just update the title. +$parameters = [ + 'title' => 'New Title For This Organisation' +]; + +$existing_organization->setParameters($parameters); + +``` + +When you add parameters they will be checked to make sure they are valid for this object, an error will be thrown if +validation fails. + +Because CRUD-like objects can be used for multiple actions and this affects whether the ID is sent to the endpoint or +not, you need to set the action on the object: + +```php +setAction(CkanClient::CKAN_ACTION_CREATE); + +$existing_organization->setAction(CkanClient::CKAN_ACTION_PATCH); +``` + +Now you're ready to make the API call: + +```php +action($new_organization); + +$patch_result = $client->action($existing_organization); +``` + +In summary the code to create a brand new Organization in your CKAN repository could look like this: + +```php +setParameters(['name' => 'my_organisation']); +$new_organization->setAction(CkanClient::CKAN_ACTION_CREATE); +$client = \Drupal::service('ckan_connect.client'); +$create_result = $client->action($new_organization); +``` From 634f6efabcef8b0b367d247ecc2d59412cf1c101 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 30 Jul 2018 12:09:08 +1000 Subject: [PATCH 4/4] fix some namespace issues --- readme.md | 2 +- src/Ckan/CkanRequest.php | 4 +--- src/Ckan/Crud/CkanCrudBase.php | 1 - src/Ckan/Crud/CkanCrudInterface.php | 4 +++- src/Client/CkanClient.php | 6 +++--- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index 03aa32b..24b7a2e 100644 --- a/readme.md +++ b/readme.md @@ -24,7 +24,7 @@ construction method, or separately via `CkanApiInterface::setParameters($paramet ```php getActionSlug($action); - $parameters = $ckanObject->getParameters($action); + public function action(CkanApiInterface $ckanObject) { + $path = 'action/' . $ckanObject->getPath(); + $parameters = $ckanObject->getParameters(); // Every action API endpoint on CKAN may be used with a POST request. return $this->post($path, $parameters);