1
0
Fork 0
mirror of synced 2024-05-20 04:32:37 +12:00
appwrite/app/controllers/web/home.php

558 lines
22 KiB
PHP
Raw Normal View History

2019-05-09 18:54:39 +12:00
<?php
2020-07-11 01:29:15 +12:00
use Appwrite\Template\Template;
2020-06-29 05:31:21 +12:00
use Utopia\App;
2019-05-09 18:54:39 +12:00
use Utopia\View;
2020-03-29 01:42:16 +13:00
use Utopia\Config\Config;
2020-05-17 17:27:10 +12:00
use Utopia\Validator\WhiteList;
use Utopia\Validator\Range;
2019-05-09 18:54:39 +12:00
2020-06-30 09:43:34 +12:00
App::init(function ($layout) {
2020-06-30 23:09:28 +12:00
/** @var Utopia\View $layout */
2020-06-25 09:16:03 +12:00
$header = new View(__DIR__.'/../../views/home/comps/header.phtml');
$footer = new View(__DIR__.'/../../views/home/comps/footer.phtml');
$footer
2020-06-30 23:09:28 +12:00
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
2020-06-25 09:16:03 +12:00
;
$layout
->setParam('title', APP_NAME)
->setParam('description', '')
->setParam('class', 'home')
->setParam('platforms', Config::getParam('platforms'))
->setParam('header', [$header])
->setParam('footer', [$footer])
;
2020-06-30 09:43:34 +12:00
}, ['layout'], 'home');
2019-05-09 18:54:39 +12:00
2020-06-30 09:43:34 +12:00
App::shutdown(function ($response, $layout) {
2020-10-30 02:50:49 +13:00
/** @var Appwrite\Utopia\Response $response */
2020-06-30 23:09:28 +12:00
/** @var Utopia\View $layout */
2020-07-09 21:11:10 +12:00
$response->html($layout->render());
2020-06-30 09:43:34 +12:00
}, ['response', 'layout'], 'home');
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App::get('/')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($response) {
2020-10-30 02:50:49 +13:00
/** @var Appwrite\Utopia\Response $response */
2020-06-30 23:09:28 +12:00
$response->redirect('/auth/signin');
}, ['response']);
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/signin')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2019-12-17 08:35:33 +13:00
$page = new View(__DIR__.'/../../views/home/auth/signin.phtml');
2019-05-09 18:54:39 +12:00
$layout
2019-09-27 06:47:48 +12:00
->setParam('title', 'Sign In - '.APP_NAME)
2019-05-09 18:54:39 +12:00
->setParam('body', $page);
2020-06-30 23:09:28 +12:00
}, ['layout']);
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/signup')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2019-12-17 08:35:33 +13:00
$page = new View(__DIR__.'/../../views/home/auth/signup.phtml');
2019-05-09 18:54:39 +12:00
$layout
2019-09-27 06:47:48 +12:00
->setParam('title', 'Sign Up - '.APP_NAME)
2019-05-09 18:54:39 +12:00
->setParam('body', $page);
2020-06-30 23:09:28 +12:00
}, ['layout']);
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/recovery')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2019-12-17 08:35:33 +13:00
$page = new View(__DIR__.'/../../views/home/auth/recovery.phtml');
2019-05-09 18:54:39 +12:00
$layout
2019-09-27 06:47:48 +12:00
->setParam('title', 'Password Recovery - '.APP_NAME)
2019-05-09 18:54:39 +12:00
->setParam('body', $page);
2020-06-30 23:09:28 +12:00
}, ['layout']);
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/confirm')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2019-12-17 08:35:33 +13:00
$page = new View(__DIR__.'/../../views/home/auth/confirm.phtml');
2019-05-09 18:54:39 +12:00
$layout
2019-09-27 06:47:48 +12:00
->setParam('title', 'Account Confirmation - '.APP_NAME)
2019-05-09 18:54:39 +12:00
->setParam('body', $page);
2020-06-30 23:09:28 +12:00
}, ['layout']);
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/join')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2019-12-17 08:35:33 +13:00
$page = new View(__DIR__.'/../../views/home/auth/join.phtml');
2019-05-09 18:54:39 +12:00
$layout
2019-09-27 06:47:48 +12:00
->setParam('title', 'Invitation - '.APP_NAME)
2019-05-09 18:54:39 +12:00
->setParam('body', $page);
2020-06-30 23:09:28 +12:00
}, ['layout']);
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/recovery/reset')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2019-12-17 08:35:33 +13:00
$page = new View(__DIR__.'/../../views/home/auth/recovery/reset.phtml');
2019-05-09 18:54:39 +12:00
$layout
2019-09-27 06:47:48 +12:00
->setParam('title', 'Password Reset - '.APP_NAME)
2019-05-09 18:54:39 +12:00
->setParam('body', $page);
2020-06-30 23:09:28 +12:00
}, ['layout']);
2020-04-08 23:00:50 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/oauth2/success')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2020-04-08 23:00:50 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2020-04-09 01:14:52 +12:00
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
2020-04-08 23:00:50 +12:00
$layout
->setParam('title', APP_NAME)
->setParam('body', $page)
->setParam('header', [])
->setParam('footer', [])
;
2020-06-30 23:09:28 +12:00
}, ['layout']);
2020-04-08 23:00:50 +12:00
2020-06-29 05:31:21 +12:00
App::get('/auth/oauth2/failure')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2020-04-08 23:00:50 +12:00
->label('permission', 'public')
->label('scope', 'home')
2020-06-30 23:09:28 +12:00
->action(function ($layout) {
/** @var Utopia\View $layout */
2020-04-09 01:14:52 +12:00
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
2020-04-08 23:00:50 +12:00
$layout
->setParam('title', APP_NAME)
->setParam('body', $page)
->setParam('header', [])
->setParam('footer', [])
;
2020-06-30 23:09:28 +12:00
}, ['layout']);
2020-04-08 23:00:50 +12:00
2020-06-29 05:31:21 +12:00
App::get('/error/:code')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2019-05-09 18:54:39 +12:00
->label('permission', 'public')
->label('scope', 'home')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
2020-06-30 23:09:28 +12:00
->action(function ($code, $layout) {
/** @var Utopia\View $layout */
2019-12-17 08:35:33 +13:00
$page = new View(__DIR__.'/../../views/error.phtml');
2019-05-09 18:54:39 +12:00
$page
->setParam('code', $code)
;
$layout
->setParam('title', 'Error'.' - '.APP_NAME)
2019-05-09 18:54:39 +12:00
->setParam('body', $page);
2020-06-30 23:09:28 +12:00
}, ['layout']);
2020-05-17 17:27:10 +12:00
2020-06-29 05:31:21 +12:00
App::get('/open-api-2.json')
2020-06-26 06:32:12 +12:00
->groups(['web', 'home'])
2020-05-17 17:27:10 +12:00
->label('scope', 'public')
->label('docs', false)
2020-09-11 02:40:14 +12:00
->param('platform', APP_PLATFORM_CLIENT, new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE], true), 'Choose target platform.', true)
->param('extensions', 0, new Range(0, 1), 'Show extra data.', true)
->param('tests', 0, new Range(0, 1), 'Include only test services.', true)
2020-07-01 06:08:02 +12:00
->action(function ($platform, $extensions, $tests, $utopia, $request, $response) {
2020-06-30 23:09:28 +12:00
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
2020-10-30 02:50:49 +13:00
/** @var Appwrite\Utopia\Response $response */
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
$security = [
APP_PLATFORM_CLIENT => ['Project' => []],
APP_PLATFORM_SERVER => ['Project' => [], 'Key' => []],
APP_PLATFORM_CONSOLE => ['Project' => [], 'Key' => []],
];
$platforms = [
'client' => APP_PLATFORM_CLIENT,
'server' => APP_PLATFORM_SERVER,
'all' => APP_PLATFORM_CONSOLE,
];
$keys = [
APP_PLATFORM_CLIENT => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
],
APP_PLATFORM_SERVER => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
],
APP_PLATFORM_CONSOLE => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
'Mode' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Mode',
'description' => '',
'in' => 'header',
],
],
];
/*
* Specifications (v3.0.0):
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
*/
$output = [
'swagger' => '2.0',
'info' => [
'version' => APP_VERSION_STABLE,
'title' => APP_NAME,
'description' => 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)',
'termsOfService' => 'https://appwrite.io/policy/terms',
'contact' => [
'name' => 'Appwrite Team',
'url' => 'https://appwrite.io/support',
'email' => App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
],
'license' => [
'name' => 'BSD-3-Clause',
'url' => 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE',
],
],
2020-07-03 09:48:02 +12:00
'host' => \parse_url(App::getEnv('_APP_HOME', $request->getHostname()), PHP_URL_HOST),
2020-06-30 23:09:28 +12:00
'basePath' => '/v1',
'schemes' => ['https'],
'consumes' => ['application/json', 'multipart/form-data'],
'produces' => ['application/json'],
'securityDefinitions' => $keys[$platform],
'paths' => [],
'definitions' => [
// 'Pet' => [
// 'required' => ['id', 'name'],
// 'properties' => [
// 'id' => [
// 'type' => 'integer',
// 'format' => 'int64',
// ],
// 'name' => [
// 'type' => 'string',
// ],
// 'tag' => [
// 'type' => 'string',
// ],
// ],
// ],
// 'Pets' => array(
// 'type' => 'array',
// 'items' => array(
// '$ref' => '#/definitions/Pet',
// ),
// ),
'Error' => array(
'required' => array(
0 => 'code',
1 => 'message',
),
'properties' => array(
'code' => array(
'type' => 'integer',
'format' => 'int32',
),
'message' => array(
'type' => 'string',
),
),
),
],
'externalDocs' => [
'description' => 'Full API docs, specs and tutorials',
2020-07-03 09:48:02 +12:00
'url' => $request->getProtocol().'://'.$request->getHostname().'/docs',
2020-06-30 23:09:28 +12:00
],
];
if ($extensions) {
if (isset($output['securityDefinitions']['Project'])) {
$output['securityDefinitions']['Project']['extensions'] = ['demo' => '5df5acd0d48c2'];
}
if (isset($output['securityDefinitions']['Key'])) {
$output['securityDefinitions']['Key']['extensions'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2'];
}
if (isset($output['securityDefinitions']['Locale'])) {
$output['securityDefinitions']['Locale']['extensions'] = ['demo' => 'en'];
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
if (isset($output['securityDefinitions']['Mode'])) {
$output['securityDefinitions']['Mode']['extensions'] = ['demo' => ''];
2020-05-17 17:27:10 +12:00
}
2020-06-30 23:09:28 +12:00
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
foreach ($utopia->getRoutes() as $key => $method) {
foreach ($method as $route) { /* @var $route \Utopia\Route */
if (!$route->getLabel('docs', true)) {
continue;
2020-07-15 04:29:49 +12:00
}
if ($route->getLabel('sdk.mock', false)) {
continue;
2020-06-30 23:09:28 +12:00
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) {
continue;
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
$url = \str_replace('/v1', '', $route->getURL());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
$consumes = ['application/json'];
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
if ($hide) {
continue;
}
2020-05-17 17:27:10 +12:00
2020-07-11 01:29:15 +12:00
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__.'/../../../'.$route->getLabel('sdk.description', '')) : null;
2020-06-30 23:09:28 +12:00
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.method', \uniqid()),
'consumes' => [],
'tags' => [$route->getLabel('sdk.namespace', 'default')],
'description' => ($desc) ? \file_get_contents($desc) : '',
// 'responses' => [
// 200 => [
// 'description' => 'An paged array of pets',
// 'schema' => [
// '$ref' => '#/definitions/Pet',
// ],
// ],
// ],
];
if ($extensions) {
$platformList = $route->getLabel('sdk.platform', []);
$temp['extensions'] = [
'weight' => $route->getOrder(),
'cookies' => $route->getLabel('sdk.cookies', false),
'type' => $route->getLabel('sdk.methodType', ''),
2020-07-11 01:29:15 +12:00
'demo' => 'docs/examples/'. Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')).'/'.Template::fromCamelCaseToDash($temp['operationId']).'.md',
2020-06-30 23:09:28 +12:00
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
'rate-limit' => $route->getLabel('abuse-limit', 0),
'rate-time' => $route->getLabel('abuse-time', 3600),
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'scope' => $route->getLabel('scope', ''),
'platforms' => $platformList,
2020-05-17 17:27:10 +12:00
];
2020-06-30 23:09:28 +12:00
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
if ((!empty($scope))) { // && 'public' != $scope
$temp['security'][] = $route->getLabel('sdk.security', $security[$platform]);
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
$requestBody = [
'content' => [
'application/x-www-form-urlencoded' => [
'schema' => [
'type' => 'object',
'properties' => [],
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
'required' => [],
2020-05-17 17:27:10 +12:00
],
2020-06-30 23:09:28 +12:00
],
];
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
foreach ($route->getParams() as $name => $param) {
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $utopia->getResources($param['resources'])) : $param['validator']; /* @var $validator \Utopia\Validator */
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
$node = [
'name' => $name,
'description' => $param['description'],
'required' => !$param['optional'],
];
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
switch ((!empty($validator)) ? \get_class($validator) : '') {
case 'Utopia\Validator\Text':
$node['type'] = 'string';
2020-07-11 01:29:15 +12:00
$node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
2020-06-30 23:09:28 +12:00
break;
case 'Utopia\Validator\Boolean':
$node['type'] = 'boolean';
$node['x-example'] = false;
break;
case 'Appwrite\Database\Validator\UID':
$node['type'] = 'string';
2020-07-11 01:29:15 +12:00
$node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
2020-06-30 23:09:28 +12:00
break;
case 'Utopia\Validator\Email':
$node['type'] = 'string';
$node['format'] = 'email';
$node['x-example'] = 'email@example.com';
break;
case 'Utopia\Validator\URL':
$node['type'] = 'string';
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
break;
case 'Utopia\Validator\JSON':
case 'Utopia\Validator\Mock':
case 'Utopia\Validator\Assoc':
$node['type'] = 'object';
$node['type'] = 'object';
$node['x-example'] = '{}';
//$node['format'] = 'json';
break;
case 'Appwrite\Storage\Validator\File':
$consumes = ['multipart/form-data'];
$node['type'] = 'file';
break;
case 'Utopia\Validator\ArrayList':
$node['type'] = 'array';
$node['collectionFormat'] = 'multi';
$node['items'] = [
'type' => 'string',
];
break;
case 'Appwrite\Auth\Validator\Password':
$node['type'] = 'string';
$node['format'] = 'format';
$node['x-example'] = 'password';
break;
case 'Utopia\Validator\Range': /* @var $validator \Utopia\Validator\Range */
$node['type'] = 'integer';
$node['format'] = 'int32';
$node['x-example'] = $validator->getMin();
break;
case 'Utopia\Validator\Numeric':
$node['type'] = 'integer';
$node['format'] = 'int32';
break;
case 'Utopia\Validator\Length':
$node['type'] = 'string';
break;
case 'Utopia\Validator\Host':
$node['type'] = 'string';
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
break;
case 'Utopia\Validator\WhiteList': /* @var $validator \Utopia\Validator\WhiteList */
$node['type'] = 'string';
$node['x-example'] = $validator->getList()[0];
break;
default:
$node['type'] = 'string';
break;
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
if ($param['optional'] && !\is_null($param['default'])) { // Param has default value
$node['default'] = $param['default'];
2020-05-17 17:27:10 +12:00
}
2020-06-30 23:09:28 +12:00
if (false !== \strpos($url, ':'.$name)) { // Param is in URL path
$node['in'] = 'path';
$temp['parameters'][] = $node;
} elseif ($key == 'GET') { // Param is in query
$node['in'] = 'query';
$temp['parameters'][] = $node;
} else { // Param is in payload
$node['in'] = 'formData';
$temp['parameters'][] = $node;
$requestBody['content']['application/x-www-form-urlencoded']['schema']['properties'][] = $node;
if (!$param['optional']) {
$requestBody['content']['application/x-www-form-urlencoded']['required'][] = $name;
}
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
$url = \str_replace(':'.$name, '{'.$name.'}', $url);
2020-05-17 17:27:10 +12:00
}
2020-06-30 23:09:28 +12:00
$temp['consumes'] = $consumes;
$output['paths'][$url][\strtolower($route->getMethod())] = $temp;
2020-05-17 17:27:10 +12:00
}
2020-06-30 23:09:28 +12:00
}
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
/*foreach ($consoleDB->getMocks() as $mock) {
var_dump($mock['name']);
}*/
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
\ksort($output['paths']);
2020-05-17 17:27:10 +12:00
2020-06-30 23:09:28 +12:00
$response
->json($output);
2020-07-01 06:08:02 +12:00
}, ['utopia', 'request', 'response']);