diff --git a/bower.json b/bower.json index cfb0c1a9..4c0df17d 100644 --- a/bower.json +++ b/bower.json @@ -17,6 +17,7 @@ "jquery-timeago": "^1.6.1", "toastr": "^2.1.3", "tagmanager": "^3.0.2", - "eonasdan-bootstrap-datetimepicker": "^4.17.47" + "eonasdan-bootstrap-datetimepicker": "^4.17.47", + "swagger-ui": "^3.13.4" } } diff --git a/composer.json b/composer.json index 2ddf8647..030d242b 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "slim/slim": "^3.8", "morris/lessql": "^0.3.4", "pavlakis/slim-cli": "^1.0", - "rubellum/slim-blade-view": "^0.1.1" + "rubellum/slim-blade-view": "^0.1.1", + "tuupola/cors-middleware": "^0.7.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 02449b65..e1b7f8f8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "6398ecdbf578b85c595fc1be9a40c021", + "content-hash": "42031c0b205b7ce7efb4b6eb95a0096a", "packages": [ { "name": "container-interop/container-interop", @@ -104,6 +104,58 @@ ], "time": "2018-01-09T20:05:19+00:00" }, + { + "name": "http-interop/http-factory", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/http-interop/http-factory.git", + "reference": "c2587cc0a6f74987fefb5b8074acfd32c69a4b0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/http-interop/http-factory/zipball/c2587cc0a6f74987fefb5b8074acfd32c69a4b0f", + "reference": "c2587cc0a6f74987fefb5b8074acfd32c69a4b0f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Interop\\Http\\Factory\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2017-03-24T14:48:51+00:00" + }, { "name": "illuminate/container", "version": "v5.6.17", @@ -442,6 +494,61 @@ ], "time": "2018-01-27T13:18:21+00:00" }, + { + "name": "neomerx/cors-psr7", + "version": "v1.0.12", + "source": { + "type": "git", + "url": "https://github.com/neomerx/cors-psr7.git", + "reference": "24944f39483d1a89f66ae9d58cca9f82b8815b35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/neomerx/cors-psr7/zipball/24944f39483d1a89f66ae9d58cca9f82b8815b35", + "reference": "24944f39483d1a89f66ae9d58cca9f82b8815b35", + "shasum": "" + }, + "require": { + "php": ">=5.6.0", + "psr/http-message": "^1.0", + "psr/log": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.9", + "phpunit/phpunit": "^5.7", + "scrutinizer/ocular": "^1.1", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Neomerx\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "neomerx", + "email": "info@neomerx.com" + } + ], + "description": "Framework agnostic (PSR-7) CORS implementation (www.w3.org/TR/cors/)", + "homepage": "https://github.com/neomerx/cors-psr7", + "keywords": [ + "Cross Origin Resource Sharing", + "Cross-Origin Resource Sharing", + "cors", + "neomerx", + "psr-7", + "psr7", + "w3.org", + "www.w3.org" + ], + "time": "2017-09-03T22:31:57+00:00" + }, { "name": "nesbot/carbon", "version": "1.26.4", @@ -775,6 +882,112 @@ ], "time": "2016-08-06T14:39:51+00:00" }, + { + "name": "psr/http-server-handler", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "439d92054dc06097f2406ec074a2627839955a02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/439d92054dc06097f2406ec074a2627839955a02", + "reference": "439d92054dc06097f2406ec074a2627839955a02", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "time": "2018-01-22T17:04:15+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "ea17eb1fb2c8df6db919cc578451a8013c6a0ae5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/ea17eb1fb2c8df6db919cc578451a8013c6a0ae5", + "reference": "ea17eb1fb2c8df6db919cc578451a8013c6a0ae5", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "time": "2018-01-22T17:08:31+00:00" + }, { "name": "psr/log", "version": "1.0.2", @@ -1222,6 +1435,166 @@ "description": "Symfony Translation Component", "homepage": "https://symfony.com", "time": "2018-02-22T10:50:29+00:00" + }, + { + "name": "tuupola/callable-handler", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/tuupola/callable-handler.git", + "reference": "5141efa1e974687a3fa53338811a988198f50662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tuupola/callable-handler/zipball/5141efa1e974687a3fa53338811a988198f50662", + "reference": "5141efa1e974687a3fa53338811a988198f50662", + "shasum": "" + }, + "require": { + "php": "^7.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "codedungeon/phpunit-result-printer": "^0.4.4", + "overtrue/phplint": "^1.0", + "phpunit/phpunit": "^6.5", + "squizlabs/php_codesniffer": "^3.2", + "tuupola/http-factory": "^0.3.0", + "zendframework/zend-diactoros": "^1.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tuupola\\Middleware\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mika Tuupola", + "email": "tuupola@appelsiini.net", + "homepage": "https://appelsiini.net/", + "role": "Developer" + } + ], + "description": "Compatibility layer for PSR-7 double pass and PSR-15 middlewares.", + "homepage": "https://github.com/tuupola/callable-handler", + "keywords": [ + "middleware", + "psr-15", + "psr-7" + ], + "time": "2018-01-23T04:07:25+00:00" + }, + { + "name": "tuupola/cors-middleware", + "version": "0.7.0", + "source": { + "type": "git", + "url": "https://github.com/tuupola/cors-middleware.git", + "reference": "b0e2b7acacf22acae6ba029ee424fd6c073bb443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tuupola/cors-middleware/zipball/b0e2b7acacf22acae6ba029ee424fd6c073bb443", + "reference": "b0e2b7acacf22acae6ba029ee424fd6c073bb443", + "shasum": "" + }, + "require": { + "neomerx/cors-psr7": "^1.0", + "php": "^7.1", + "psr/http-server-middleware": "^1.0", + "tuupola/callable-handler": "^0.3.0", + "tuupola/http-factory": "^0.3.0" + }, + "require-dev": { + "codedungeon/phpunit-result-printer": "^0.4.4", + "equip/dispatch": "dev-approved-psr15 as 1.0.x-dev", + "overtrue/phplint": "^1.0", + "phpunit/phpunit": "^6.5", + "squizlabs/php_codesniffer": "^3.2", + "zendframework/zend-diactoros": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tuupola\\Middleware\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mika Tuupola", + "email": "tuupola@appelsiini.net", + "homepage": "http://www.appelsiini.net/", + "role": "Developer" + } + ], + "description": "PSR-7 and PSR-15 CORS middleware", + "homepage": "https://github.com/tuupola/cors-middleware", + "keywords": [ + "cors", + "middleware", + "psr-15", + "psr-7" + ], + "time": "2018-01-25T02:29:07+00:00" + }, + { + "name": "tuupola/http-factory", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/tuupola/http-factory.git", + "reference": "57b2e19ff3f4af0bbee4e31fd282689be351f1ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tuupola/http-factory/zipball/57b2e19ff3f4af0bbee4e31fd282689be351f1ad", + "reference": "57b2e19ff3f4af0bbee4e31fd282689be351f1ad", + "shasum": "" + }, + "require": { + "http-interop/http-factory": "^0.3.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.3.0", + "overtrue/phplint": "^0.2.1", + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tuupola\\Http\\Factory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mika Tuupola", + "email": "tuupola@appelsiini.net", + "homepage": "http://www.appelsiini.net/", + "role": "Developer" + } + ], + "description": "Lightweight autodiscovering PSR-17 HTTP factories", + "homepage": "https://github.com/tuupola/http-factory", + "keywords": [ + "http", + "psr-17", + "psr-7" + ], + "time": "2017-07-15T22:03:15+00:00" } ], "packages-dev": [], diff --git a/controllers/OpenApiController.php b/controllers/OpenApiController.php new file mode 100644 index 00000000..4e6942de --- /dev/null +++ b/controllers/OpenApiController.php @@ -0,0 +1,23 @@ +AppContainer->view->render($response, 'apidoc'); + } + + public function DocumentationSpec(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + $applicationService = new ApplicationService(); + + $specJson = json_decode(file_get_contents(__DIR__ . '/../helpers/grocy.openapi.json')); + $specJson->info->version = $applicationService->GetInstalledVersion(); + + return $this->ApiResponse($specJson); + } +} diff --git a/helpers/grocy.openapi.json b/helpers/grocy.openapi.json new file mode 100644 index 00000000..c10b8824 --- /dev/null +++ b/helpers/grocy.openapi.json @@ -0,0 +1,47 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "grocy REST API", + "description": "xxx", + "version": "xxx" + }, + "servers": [ + { + "url": "xxx" + } + ], + "paths": { + "/get-objects/{entity}": { + "get": { + "description": "Returns all objects of the given entity", + "parameters": [ + { + "in": "path", + "name": "entity", + "required": true, + "description": "A valid entity name", + "schema": { + "$ref": "#/components/schemas/Entity" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "Entity": { + "type": "string", + "enum": [ + "product", + "habit" + ] + } + } + } +} diff --git a/public/viewjs/apidoc.js b/public/viewjs/apidoc.js new file mode 100644 index 00000000..f8497e60 --- /dev/null +++ b/public/viewjs/apidoc.js @@ -0,0 +1,18 @@ +$(function () +{ + const swaggerUi = SwaggerUIBundle({ + url: U('/api/get-open-api-specification'), + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: 'StandaloneLayout' + }); + + window.ui = swaggerUi; +}); diff --git a/routes.php b/routes.php index ba8e7354..e828c272 100644 --- a/routes.php +++ b/routes.php @@ -32,7 +32,6 @@ $app->group('', function() $this->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList'); $this->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm'); - // Habit routes $this->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview'); $this->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution'); @@ -46,10 +45,15 @@ $app->group('', function() $this->get('/batteries', 'Grocy\Controllers\BatteriesController:BatteriesList'); $this->get('/battery/{batteryId}', 'Grocy\Controllers\BatteriesController:BatteryEditForm'); + + // Other routes + $this->get('/apidoc', 'Grocy\Controllers\OpenApiController:DocumentationUi'); })->add(new SessionAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName())); $app->group('/api', function() { + $this->get('/get-open-api-specification', 'Grocy\Controllers\OpenApiController:DocumentationSpec'); + $this->get('/get-objects/{entity}', 'Grocy\Controllers\GenericEntityApiController:GetObjects'); $this->get('/get-object/{entity}/{objectId}', 'Grocy\Controllers\GenericEntityApiController:GetObject'); $this->post('/add-object/{entity}', 'Grocy\Controllers\GenericEntityApiController:AddObject'); diff --git a/views/apidoc.blade.php b/views/apidoc.blade.php new file mode 100644 index 00000000..3df710b3 --- /dev/null +++ b/views/apidoc.blade.php @@ -0,0 +1,17 @@ +@extends('layout.default') + +@section('title', $L('REST API documentation')) +@section('viewJsName', 'apidoc') + +@section('content') +
+@stop + +@push('pageStyles') + +@endpush + +@push('pageScripts') + + +@endpush diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index 5aa710ef..68d208b3 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -9,7 +9,7 @@ - + @yield('title') | grocy @@ -24,6 +24,7 @@ + @stack('pageStyles') + @stack('pageScripts') @stack('componentScripts') @if(file_exists(__DIR__ . '/../../data/add_before_end_body.html'))