diff --git a/api.yaml b/api.yaml index 3445a8b..87b3c75 100644 --- a/api.yaml +++ b/api.yaml @@ -67,3 +67,6 @@ endpoints: # Prevents API from accessing these config files ignore_files: - streams + patch: + enabled: true + auth: true diff --git a/docs/EVENTS.md b/docs/EVENTS.md index 8239f9a..3f60132 100644 --- a/docs/EVENTS.md +++ b/docs/EVENTS.md @@ -18,6 +18,7 @@ - [Config](#config) - [onApiConfigGetAll](#onapiconfiggetall) - [onApiConfigGet](#onapiconfigget) + - [onApiConfigUpdate](#onapiconfigupdate) - [Plugin](#plugin) - [onApiPluginGetAll](#onapiplugingetall) - [onApiPluginGet](#onapipluginget) @@ -234,6 +235,7 @@ List of `Config` events: - [onApiConfigGetAll](#onapiconfiggetall) - [onApiConfigGet](#onapiconfigget) + - [onApiConfigUpdate](#onapiconfigupdate) Please refer to the example code provided for full documentation of the available properties for each custom event. @@ -267,6 +269,21 @@ function onApiConfigGet(Event $e) { } ``` +#### onApiConfigUpdate + +This event is fired any time the `PATCH /configs/{id}` endpoint is successfully requested. + +```php +function onApiConfigUpdate(Event $e) { + /** + * The GravApi ConfigModel returned in the API response. + * + * @var \GravApi\Models\ConfigModel + */ + $e['config']; +} +``` + ### Plugin Whenever a `Plugin` resource is succesfully requested, there will be a custom event fired by the API plugin including the affected resource as a property of the `Event` itself. diff --git a/docs/postman_collection.json b/docs/postman_collection.json index 7bfa9b1..f97e9da 100644 --- a/docs/postman_collection.json +++ b/docs/postman_collection.json @@ -568,6 +568,57 @@ } }, "response": [] + }, + { + "name": "Update Config", + "request": { + "auth": { + "type": "basic", + "basic": [ + { + "key": "password", + "value": "D3velopment", + "type": "string" + }, + { + "key": "username", + "value": "development", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"title\": \"new site title\",\n\t\"custom_field\": null\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8080/api/configs/site", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "configs", + "site" + ] + } + }, + "response": [] } ], "protocolProfileBehavior": {} diff --git a/docs/specification.yaml b/docs/specification.yaml index 68aa2e3..6d528a0 100644 --- a/docs/specification.yaml +++ b/docs/specification.yaml @@ -583,7 +583,7 @@ paths: tags: - plugins summary: Update a specific plugin - description: The request's body JSON should match the config structure of the plugin which you wish to update. If you wish to remove existing properties from the config, you can set their values to `null` and they will be unset. + description: The request's body JSON should match the config structure of the plugin which you wish to update. If you wish to remove existing properties from the config, you can set their values to `null` and they will be unset. Otherwise, the given values will be merged with the existing config. security: - basic: ['api.super', 'admin.super'] parameters: @@ -708,6 +708,52 @@ paths: examples: NotFound: $ref: '#/components/examples/NotFoundResponse' + patch: + tags: + - configs + summary: Update a specific config file + description: The request's body JSON should match the structure of the config file which you wish to update. If you wish to remove existing properties from the config, you can set their values to `null` and they will be unset. Otherwise, the given values will be merged with the existing config. + security: + - basic: ['api.super', 'api.configs_edit'] + parameters: + - name: id + in: path + description: The id of the config file to update + required: true + schema: + type: string + example: site + requestBody: + content: + application/json: + schema: + type: object + description: This JSON structure should match the desired configuration file's options. + responses: + 200: + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ConfigResponse' + 401: + description: Invalid authentication + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + InvalidAuth: + $ref: '#/components/examples/InvalidAuthResponse' + 404: + description: Configuration file not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + NotFound: + $ref: '#/components/examples/NotFoundResponse' components: securitySchemes: basic: diff --git a/grav/config/plugins/api.yaml b/grav/config/plugins/api.yaml index 8f86862..0acb3b8 100644 --- a/grav/config/plugins/api.yaml +++ b/grav/config/plugins/api.yaml @@ -64,3 +64,6 @@ endpoints: auth: true ignore_files: - streams + patch: + enabled: true + auth: true diff --git a/src/Api.php b/src/Api.php index 9882174..8c47793 100644 --- a/src/Api.php +++ b/src/Api.php @@ -241,6 +241,16 @@ function () use ($config) { ) ); } + + if ($config->configs->patch->enabled) { + $this->patch('/{config}', ConfigHandler::class . ':updateConfig') + ->add( + new AuthMiddleware( + $config->configs->patch, + [Constants::ROLE_CONFIGS_EDIT] + ) + ); + } } ); }); diff --git a/src/Config/Config.php b/src/Config/Config.php index 12175c5..9ba3bcc 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -137,7 +137,7 @@ protected function configureRoute($customRoute = null) // But may want to override it specifically if we've given custom // settings to the Config instance (e.g. for tests) if (isset($customRoute)) { - $this->route = $customRoute; + $this->route = trim($customRoute, '/'); } elseif ($route) { $this->route = trim($route, '/'); } diff --git a/src/Config/Constants.php b/src/Config/Constants.php index 5857170..4cbe1a0 100644 --- a/src/Config/Constants.php +++ b/src/Config/Constants.php @@ -99,6 +99,7 @@ class Constants // Events const EVENT_ON_API_CONFIG_GET_ALL = 'onApiConfigGetAll'; const EVENT_ON_API_CONFIG_GET = 'onApiConfigGet'; + const EVENT_ON_API_CONFIG_UPDATE = 'onApiConfigUpdate'; const EVENT_ON_API_PAGE_GET_ALL = 'onApiPageGetAll'; const EVENT_ON_API_PAGE_GET = 'onApiPageGet'; const EVENT_ON_API_PAGE_FIND = 'onApiPageFind'; diff --git a/src/Handlers/ConfigHandler.php b/src/Handlers/ConfigHandler.php index bbd8f8a..f884d38 100644 --- a/src/Handlers/ConfigHandler.php +++ b/src/Handlers/ConfigHandler.php @@ -3,10 +3,13 @@ use GravApi\Responses\Response; use GravApi\Helpers\ConfigHelper; +use GravApi\Helpers\ArrayHelper; use GravApi\Resources\ConfigResource; use GravApi\Resources\ConfigCollectionResource; use GravApi\Config\Constants; +use GravApi\Models\ConfigModel; use RocketTheme\Toolbox\Event\Event; +use RocketTheme\Toolbox\File\YamlFile; /** * Class ConfigHandler @@ -28,7 +31,7 @@ public function getConfigs($request, $response, $args) public function getConfig($request, $response, $args) { if (!isset($args['config'])) { - return $response->withJson(Response::badRequest('No config `id given!'), 400); + return $response->withJson(Response::badRequest('No config `id` given!'), 400); } $config = ConfigHelper::loadConfig($args['config']); @@ -45,4 +48,46 @@ public function getConfig($request, $response, $args) return $response->withJson($resource->toJson()); } + + public function updateConfig($request, $response, $args) + { + if (!isset($args['config'])) { + return $response->withJson(Response::badRequest('No config `id` given!'), 400); + } + + $existingConfig = ConfigHelper::loadConfig($args['config']); + + // If the config doesn't exist, OR it is present on the filter list + // (i.e. we don't want to allow user access to it) + if (!$existingConfig) { + return $response->withJson(Response::notFound(), 404); + } + + $parsedBody = $request->getParsedBody(); + + // Merge the existing config with the new settings + $data = ArrayHelper::merge( + $existingConfig->data, + $parsedBody + ); + + $configModel = new ConfigModel($args['config'], $data); + + // Save the updates to file + $filename = 'config://' . $configModel->id . '.yaml'; + $file = YamlFile::instance( + $this->grav['locator']->findResource($filename, true, true) + ); + $file->save($configModel->data); + $file->free(); + + // Reload the site config after changes are saved + $this->grav['config']->reload(); + + $resource = new ConfigResource($configModel); + + $this->grav->fireEvent(Constants::EVENT_ON_API_CONFIG_UPDATE, new Event(['config' => $configModel])); + + return $response->withJson($resource->toJson()); + } }