From 42d6194ed913841eeb3f787dcbe2f75fbd026896 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 5 Mar 2021 00:10:52 +0530 Subject: [PATCH] feat: error handling for graphQL --- .swiftlint.yml | 0 app/config/roles.php | 5 +- app/controllers/api/graphql.php | 120 ++++++++++++++++++++++++------- app/controllers/api/users.php | 4 +- app/controllers/general.php | 8 +++ app/controllers/shared/api.php | 10 ++- app/http.php | 1 + app/init.php | 12 ++-- composer.json | 4 +- composer.lock | 54 +++++++------- docker-compose.yml | 2 +- src/Appwrite/Utopia/Response.php | 12 +++- 12 files changed, 160 insertions(+), 72 deletions(-) create mode 100644 .swiftlint.yml diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/config/roles.php b/app/config/roles.php index 78dd24ad45..e6b297182b 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -7,6 +7,7 @@ $member = [ 'home', 'console', 'account', + 'graphql', 'teams.read', 'teams.write', 'documents.read', @@ -22,6 +23,7 @@ $member = [ ]; $admins = [ + 'graphql', 'teams.read', 'teams.write', 'documents.read', @@ -56,6 +58,7 @@ return [ 'public', 'home', 'console', + 'graphql', 'documents.read', 'files.read', 'locale.read', @@ -82,6 +85,6 @@ return [ ], Auth::USER_ROLE_APP => [ 'label' => 'Application', - 'scopes' => ['health.read'], + 'scopes' => ['health.read', 'graphql'], ], ]; diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php index d295b72a96..fc5d2f8d7c 100644 --- a/app/controllers/api/graphql.php +++ b/app/controllers/api/graphql.php @@ -1,13 +1,19 @@ getType()])) return; - $rules = $model->getRules(); $name = $model->getType(); $fields = []; + $type = null; foreach ($rules as $key => $props) { // Replace this with php regex - $key = str_replace('$', '', $key); + $keyWithoutSpecialChars = str_replace('$', '', $key); if (isset( $typeMapping[$props['type']])) { $type = $typeMapping[$props['type']]; } else { @@ -82,10 +88,30 @@ function createTypeMapping(Model $model, Response $response) { $type = Type::listOf($type); } - $fields[$key] = [ + $fields[$keyWithoutSpecialChars] = [ 'type' => $type, 'description' => $props['description'], + 'resolve' => function ($type, $args, $context, $info) use ($key) { + var_dump("************* RESOLVING FIELD {$info->fieldName} *************"); + // var_dump("isCompositeType : ", Type::isCompositeType($type)); + // var_dump("isLeafType : ", Type::isLeafType($type)); + if ( $type[$key] instanceof stdClass ) { + // var_dump("STD Class"); + return json_encode($type[$key]); + } else { + // var_dump("not stdclass"); + return $type[$key]; + } + } ]; + + // if ($keyWithoutSpecialChars !== $key) { + // $fields[$keyWithoutSpecialChars]['resolve'] = function ($value, $args, $context, $info) use ($key) { + // var_dump("************* RESOLVING FIELD {$info->fieldName} *************"); + // var_dump($value); + // return $value[$key]; + // }; + // } } $objectType = [ @@ -168,6 +194,29 @@ function getArgs(array $params, $utopia) { return $args; } +function isModel($response, Model $model) { + + foreach ($model->getRules() as $key => $rule) { + if (!isset($response[$key])) { + return false; + } + } + return true; +} + +class MySafeException extends \Exception implements ClientAware +{ + public function isClientSafe() + { + return true; + } + + public function getCategory() + { + return 'businessLogic'; + } +} + function buildSchema($utopia, $response) { $start = microtime(true); @@ -178,33 +227,48 @@ function buildSchema($utopia, $response) { foreach($routes as $route) { $namespace = $route->getLabel('sdk.namespace', ''); - if ($namespace == 'users') { + if ($namespace == 'database' || true) { $methodName = $namespace.'_'.$route->getLabel('sdk.method', ''); $responseModelName = $route->getLabel('sdk.response.model', ""); - var_dump("******************************************"); - var_dump("Processing route : ${method} : {$route->getURL()}"); - var_dump("Model Name : ${responseModelName}"); - if ( $responseModelName !== Response::MODEL_NONE && $responseModelName !== Response::MODEL_ANY ) { + // var_dump("******************************************"); + // var_dump("Processing route : ${method} : {$route->getURL()}"); + // var_dump("Model Name : ${responseModelName}"); + if ( $responseModelName !== "" && $responseModelName !== Response::MODEL_NONE && $responseModelName !== Response::MODEL_ANY ) { $responseModel = $response->getModel($responseModelName); createTypeMapping($responseModel, $response); $type = $typeMapping[$responseModel->getType()]; - var_dump("Type Created : ${type}"); + // var_dump("Type Created : ${type}"); $args = getArgs($route->getParams(), $utopia); - var_dump("Args Generated : ${args}"); + // var_dump("Args Generated :"); + // var_dump($args); $field = [ 'type' => $type, 'description' => $route->getDesc(), 'args' => $args, - 'resolve' => function ($args) use (&$utopia, $route, $response) { - var_dump("************* REACHED RESOLVE *****************"); - var_dump($route); - $utopia->execute($route, $args); + 'resolve' => function ($type, $args, $context, $info) use (&$utopia, $route, $response) { + var_dump("************* REACHED RESOLVE FOR {$info->fieldName} *****************"); + // var_dump($route); + + // var_dump("************* CONTEXT *****************"); + // var_dump($context); + + var_dump("********************** ARGS *******************"); var_dump($args); + + $utopia->setRoute($route); + $utopia->execute($route, $args); + var_dump("**************** OUTPUT ************"); - var_dump($response->getPayload()); - return $response->getPayload(); + // var_dump($response->getPayload()); + $result = $response->getPayload(); + + if (isModel($result, $response->getModel(Response::MODEL_ERROR)) || isModel($result, $response->getModel(Response::MODEL_ERROR_DEV))) { + throw new MySafeException($result['message'], $result['code']); + } + + return $result; } ]; @@ -214,9 +278,9 @@ function buildSchema($utopia, $response) { $mutationFields[$methodName] = $field; } - var_dump("Processed route : ${method} : {$route->getURL()}"); + // var_dump("Processed route : ${method} : {$route->getURL()}"); } else { - var_dump("Skipping route : {$route->getURL()}"); + // var_dump("Skipping route : {$route->getURL()}"); } } } @@ -252,16 +316,19 @@ function buildSchema($utopia, $response) { App::post('/v1/graphql') ->desc('GraphQL Endpoint') - ->groups(['api', 'graphql']) - ->label('scope', 'public') + ->label('scope', 'graphql') ->inject('request') ->inject('response') ->inject('utopia') - ->action(function ($request, $response, $utopia) { + ->inject('user') + ->inject('project') + ->middleware(true) + ->action(function ($request, $response, $utopia, $user, $project) { + + // Generate the Schema of the server on startup. // Use the routes from utopia and get the params then construct the queries and mutations. - - $schema = buildSchema($utopia, $response); + $schema = buildSchema($utopia, $response, $request); $query = $request->getPayload('query', ''); $variables = $request->getPayload('variables', null); $response->setContentType(Response::CONTENT_TYPE_NULL); @@ -270,8 +337,8 @@ App::post('/v1/graphql') $rootValue = []; $result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variables); $output = $result->toArray(); - var_dump("********** OUTPUT *********"); - var_dump($output); + // var_dump("********** OUTPUT *********"); + // var_dump($output); } catch (\Exception $error) { $output = [ 'errors' => [ @@ -286,6 +353,5 @@ App::post('/v1/graphql') ]; } $response->json($output); - echo "\n"; //TODO REMOVE THIS } - ); \ No newline at end of file + ); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index efb0041cee..183c50b6b8 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -98,6 +98,8 @@ App::get('/v1/users') /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + var_dump("Running execute method for list users"); + $results = $projectDB->getCollection([ 'limit' => $limit, 'offset' => $offset, @@ -217,7 +219,7 @@ App::get('/v1/users/:userId/sessions') 'sum' => count($sessions), 'sessions' => $sessions ]), Response::MODEL_SESSION_LIST); - }, ['response', 'projectDB', 'locale']); + }); App::get('/v1/users/:userId/logs') ->desc('Get User Logs') diff --git a/app/controllers/general.php b/app/controllers/general.php index 614c400048..22a487cc1d 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -33,6 +33,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo /** @var bool $mode */ /** @var array $clients */ + var_dump("*********** In general.php init *************"); + $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); if (\in_array($localeParam, Config::getParam('locale-codes'))) { @@ -210,6 +212,10 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo // TDOO Check if user is god + var_dump("*********** Allowed Scopes *********"); + var_dump($scopes); + var_dump($scope); + if (!\in_array($scope, $scopes)) { if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS !== $project->getCollection()) { // Check if permission is denied because project is missing throw new Exception('Project not found', 404); @@ -252,6 +258,8 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { /** @var Utopia\View $layout */ /** @var Appwrite\Database\Document $project */ + var_dump("*********** In general.php error *************"); + $route = $utopia->match($request); $template = ($route) ? $route->getLabel('error', null) : null; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 9676801493..051904a5dc 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -22,6 +22,8 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e /** @var Appwrite\Event\Event $deletes */ /** @var Appwrite\Event\Event $functions */ + var_dump("*********** In api.php init *************"); + Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId())); @@ -50,9 +52,9 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e // var_dump($request->getParams()); - foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys - $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); - } + // foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys + // $timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value); + // } $abuse = new Abuse($timeLimit); @@ -125,6 +127,8 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits /** @var Appwrite\Event\Event $functions */ /** @var bool $mode */ + var_dump("*********** In api.php shutdown *************"); + if (!empty($events->getParam('event'))) { if(empty($events->getParam('payload'))) { $events->setParam('payload', $response->getPayload()); diff --git a/app/http.php b/app/http.php index 9e45bc3b52..92ddca1265 100644 --- a/app/http.php +++ b/app/http.php @@ -102,6 +102,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo $app->run($request, $response); } catch (\Throwable $th) { + var_dump("*********** In http.php catching error *************"); Console::error('[Error] Type: '.get_class($th)); Console::error('[Error] Message: '.$th->getMessage()); Console::error('[Error] File: '.$th->getFile()); diff --git a/app/init.php b/app/init.php index 11edd77a2c..f469933b39 100644 --- a/app/init.php +++ b/app/init.php @@ -212,10 +212,6 @@ $register->set('geodb', function () { return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2021-02.mmdb'); }); -$register->set('schema', function () { - -}); - /* * Localization */ @@ -289,14 +285,14 @@ Locale::setLanguage('zh-tw', include __DIR__.'/config/locale/translations/zh-tw. // Runtime Execution +App::setResource('routeToScopeMapping', function($register) { + return new Event(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME); +}, ['register']); + App::setResource('register', function() use ($register) { return $register; }); -App::setResource('schema', function() use ($register) { - return $register->get('schema'); -}); - App::setResource('layout', function($locale) { $layout = new View(__DIR__.'/views/layouts/default.phtml'); $layout->setParam('locale', $locale); diff --git a/composer.json b/composer.json index d16a3f56e6..7454acbc43 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "appwrite/php-clamav": "1.0.*", - "utopia-php/framework": "0.10.0", + "utopia-php/framework": "0.12.0", "utopia-php/abuse": "0.3.*", "utopia-php/analytics": "0.1.*", "utopia-php/audit": "0.5.*", @@ -47,7 +47,7 @@ "utopia-php/domains": "0.2.*", "utopia-php/swoole": "0.2.*", "utopia-php/system": "0.4.*", - "utopia-php/storage": "0.2.*", + "utopia-php/storage": "0.4.*", "webonyx/graphql-php": "14.4.0", "resque/php-resque": "1.3.6", diff --git a/composer.lock b/composer.lock index 9b07609ec9..ac4cc88d24 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "aed281e2643524e5f1320533abb612eb", + "content-hash": "d1b9fa5657767534eefd141136bb3cfd", "packages": [ { "name": "adhocore/jwt", @@ -349,7 +349,7 @@ "issues": "https://github.com/domnikl/statsd-php/issues", "source": "https://github.com/domnikl/statsd-php/tree/master" }, - "abandoned": true, + "abandoned": "slickdeals/statsd", "time": "2020-01-03T14:24:58+00:00" }, { @@ -574,12 +574,12 @@ "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f47ece9e6e8ce74e3be04bef47f46061dc18c095" + "reference": "2f3e4f6cf8fd4aad7624c90a94f0ab38fde25976" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f47ece9e6e8ce74e3be04bef47f46061dc18c095", - "reference": "f47ece9e6e8ce74e3be04bef47f46061dc18c095", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/2f3e4f6cf8fd4aad7624c90a94f0ab38fde25976", + "reference": "2f3e4f6cf8fd4aad7624c90a94f0ab38fde25976", "shasum": "" }, "require": { @@ -641,7 +641,7 @@ "issues": "https://github.com/guzzle/psr7/issues", "source": "https://github.com/guzzle/psr7/tree/1.x" }, - "time": "2020-12-08T11:45:39+00:00" + "time": "2021-03-02T18:57:24+00:00" }, { "name": "influxdb/influxdb-php", @@ -1004,12 +1004,12 @@ "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "dd738d0b4491f32725492cf345f6b501f5922fec" + "reference": "a18c1e692e02b84abbafe4856c3cd7cc6903908c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/dd738d0b4491f32725492cf345f6b501f5922fec", - "reference": "dd738d0b4491f32725492cf345f6b501f5922fec", + "url": "https://api.github.com/repos/php-fig/log/zipball/a18c1e692e02b84abbafe4856c3cd7cc6903908c", + "reference": "a18c1e692e02b84abbafe4856c3cd7cc6903908c", "shasum": "" }, "require": { @@ -1047,7 +1047,7 @@ "support": { "source": "https://github.com/php-fig/log/tree/master" }, - "time": "2020-09-18T06:44:51+00:00" + "time": "2021-03-02T15:02:34+00:00" }, { "name": "ralouphie/getallheaders", @@ -1548,16 +1548,16 @@ }, { "name": "utopia-php/framework", - "version": "0.10.0", + "version": "0.12.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "65909bdb24ef6b6c6751abfdea90caf96bbc6c50" + "reference": "bfdb236f91f4393f6db7faccd2d67550a1e73101" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/65909bdb24ef6b6c6751abfdea90caf96bbc6c50", - "reference": "65909bdb24ef6b6c6751abfdea90caf96bbc6c50", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/bfdb236f91f4393f6db7faccd2d67550a1e73101", + "reference": "bfdb236f91f4393f6db7faccd2d67550a1e73101", "shasum": "" }, "require": { @@ -1591,9 +1591,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.10.0" + "source": "https://github.com/utopia-php/framework/tree/0.12.0" }, - "time": "2020-12-26T12:02:39+00:00" + "time": "2021-03-04T17:14:23+00:00" }, { "name": "utopia-php/locale", @@ -1753,21 +1753,21 @@ }, { "name": "utopia-php/storage", - "version": "0.2.0", + "version": "0.4.3", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "27bfd663c9b2a17ac0911522a87f42bee834df95" + "reference": "9db3ab713a6d392c3c2c799aeea751f6c8dc2ff7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/27bfd663c9b2a17ac0911522a87f42bee834df95", - "reference": "27bfd663c9b2a17ac0911522a87f42bee834df95", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/9db3ab713a6d392c3c2c799aeea751f6c8dc2ff7", + "reference": "9db3ab713a6d392c3c2c799aeea751f6c8dc2ff7", "shasum": "" }, "require": { "php": ">=7.4", - "utopia-php/framework": "0.10.0" + "utopia-php/framework": "0.*.*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1799,9 +1799,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.2.0" + "source": "https://github.com/utopia-php/storage/tree/0.4.3" }, - "time": "2021-01-27T12:21:27+00:00" + "time": "2021-03-02T20:25:02+00:00" }, { "name": "utopia-php/swoole", @@ -5792,12 +5792,12 @@ "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9c89b265ccc4092d58e66d72af5d343ee77a41ae" + "reference": "4631e2c7d2d7132adac9fd84d4c1a98c10a6e049" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9c89b265ccc4092d58e66d72af5d343ee77a41ae", - "reference": "9c89b265ccc4092d58e66d72af5d343ee77a41ae", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/4631e2c7d2d7132adac9fd84d4c1a98c10a6e049", + "reference": "4631e2c7d2d7132adac9fd84d4c1a98c10a6e049", "shasum": "" }, "require": { @@ -5843,7 +5843,7 @@ "issues": "https://github.com/webmozarts/assert/issues", "source": "https://github.com/webmozarts/assert/tree/master" }, - "time": "2021-01-18T12:52:36+00:00" + "time": "2021-02-28T20:01:57+00:00" }, { "name": "webmozart/path-util", diff --git a/docker-compose.yml b/docker-compose.yml index de2e7773bc..4f38b10cd7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,7 +63,7 @@ services: - ./psalm.xml:/usr/src/code/psalm.xml - ./tests:/usr/src/code/tests - ./app:/usr/src/code/app - # - ./vendor:/usr/src/code/vendor + - ./vendor:/usr/src/code/vendor - ./docs:/usr/src/code/docs - ./public:/usr/src/code/public - ./src:/usr/src/code/src diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 2bb7d88017..642a38954c 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -117,7 +117,7 @@ class Response extends SwooleResponse const MODEL_DOMAIN_LIST = 'domainList'; // Content type - const CONTENT_TYPE_NULL = null; + const CONTENT_TYPE_NULL = 'null'; /** * @var Filter @@ -276,7 +276,7 @@ class Response extends SwooleResponse break; default : - throw new Exception("No Response format set."); + $this->json(!empty($output) ? $output : new stdClass()); break; } } @@ -330,6 +330,14 @@ class Response extends SwooleResponse $this->payload = $output; + var_dump("********************** PAYLOAD SET *********************"); + var_dump("Message : {$output['message']}"); + var_dump("Code : {$output['code']}"); + var_dump("Version : {$output['version']}"); + var_dump("File : {$output['file']}"); + var_dump("Line : {$output['line']}"); + var_dump("Trace : "); + return $this->payload; }