From bb050e75d3344bc4865af40280930ec91529070e Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 26 Feb 2021 02:13:21 +0530 Subject: [PATCH] feat: defined logic for schema building --- .env | 2 +- app/controllers/api/graphql.php | 270 +++++++++++++++++++++++--------- docker-compose.yml | 2 +- 3 files changed, 202 insertions(+), 72 deletions(-) diff --git a/.env b/.env index ce43bfd85..4605affb9 100644 --- a/.env +++ b/.env @@ -15,7 +15,7 @@ _APP_DB_PORT=3306 _APP_DB_SCHEMA=appwrite _APP_DB_USER=user _APP_DB_PASS=password -_APP_STORAGE_ANTIVIRUS=enabled +_APP_STORAGE_ANTIVIRUS=disabled _APP_STORAGE_ANTIVIRUS_HOST=clamav _APP_STORAGE_ANTIVIRUS_PORT=3310 _APP_INFLUXDB_HOST=influxdb diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php index 3959e4456..0f5f95775 100644 --- a/app/controllers/api/graphql.php +++ b/app/controllers/api/graphql.php @@ -4,7 +4,11 @@ use GraphQL\GraphQL; use GraphQL\Type\Schema; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; +use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Model; +use GraphQL\Type\Definition\NonNull; use Utopia\App; +use Utopia\Validator; /** * TODO: @@ -34,83 +38,210 @@ use Utopia\App; * - Objects */ +global $typeMapping; + +$typeMapping = [ + Model::TYPE_BOOLEAN => Type::boolean(), + Model::TYPE_STRING => Type::string(), + Model::TYPE_INTEGER => Type::int(), + Model::TYPE_FLOAT => Type::float(), + + // Outliers + Model::TYPE_JSON => Type::string(), + Response::MODEL_ANY => Type::string(), +]; + + +function createTypeMapping(Model $model, Response $response) { + + global $typeMapping; + + // If map already contains this complex type, then simply return + if (isset($typeMapping[$model->getType()])) return; + + + $rules = $model->getRules(); + $name = $model->getType(); + $fields = []; + foreach ($rules as $key => $props) { + // Replace this with php regex + $key = str_replace('$', '', $key); + if (isset( $typeMapping[$props['type']])) { + $type = $typeMapping[$props['type']]; + } else { + try { + $complexModel = $response->getModel($props['type']); + createTypeMapping($complexModel, $response); + $type = $typeMapping[$props['type']]; + } catch (Exception $e) { + var_dump("Could Not find model for : {$props['type']}"); + } + } + + if ($props['array']) { + $type = Type::listOf($type); + } + + $fields[$key] = [ + 'type' => $type, + 'description' => $props['description'], + ]; + } + + $objectType = [ + 'name' => $name, + 'fields' => $fields + ]; + + $typeMapping[$name] = new ObjectType($objectType); +} + + +function getArgType(Validator $validator, bool $required) { + + $type = []; + switch ((!empty($validator)) ? \get_class($validator) : '') { + case 'Utopia\Validator\Text': + $type = Type::string(); + break; + case 'Utopia\Validator\Boolean': + $type = Type::boolean(); + break; + case 'Appwrite\Database\Validator\UID': + $type = Type::string(); + break; + case 'Utopia\Validator\Email': + $type = Type::string(); + break; + case 'Utopia\Validator\URL': + $type = Type::string(); + break; + case 'Utopia\Validator\JSON': + case 'Utopia\Validator\Mock': + case 'Utopia\Validator\Assoc': + $type = Type::string(); + break; + case 'Appwrite\Storage\Validator\File': + $type = Type::string(); + case 'Utopia\Validator\ArrayList': + $type = Type::listOf(Type::string()); + break; + case 'Appwrite\Auth\Validator\Password': + $type = Type::string(); + break; + case 'Utopia\Validator\Range': /* @var $validator \Utopia\Validator\Range */ + $type = Type::int(); + break; + case 'Utopia\Validator\Numeric': + $type = Type::int(); + break; + case 'Utopia\Validator\Length': + $type = Type::string(); + break; + case 'Utopia\Validator\Host': + $type = Type::string(); + break; + case 'Utopia\Validator\WhiteList': /* @var $validator \Utopia\Validator\WhiteList */ + $type = Type::string(); + break; + default: + $type = 'string'; + break; + } + + if ($required) { + $type = Type::nonNull($type); + } + + return $type; +} + +function getArgs(array $params) { + $args = []; + foreach ($params as $key => $value) { + var_dump("Key : ${key}"); + var_dump($value); + $args[$key] = [ + 'type' => getArgType($value['validator'],!$value['optional']), + 'description' => $value['description'], + 'defaultValue' => $value['default'] + ]; + } + return $args; +} + +function buildSchema($utopia, $response) { + $start = microtime(true); + + global $typeMapping; + $fields = []; + foreach($utopia->getRoutes() as $method => $routes ){ + if ($method == "GET") { + foreach($routes as $route) { + $namespace = $route->getLabel('sdk.namespace', ''); + if( true ) { + $methodName = $namespace.'_'.$route->getLabel('sdk.method', ''); + $responseModelName = $route->getLabel('sdk.response.model', Response::MODEL_NONE); + + var_dump("******************************************"); + var_dump("Model Name : ${responseModelName}"); + + if ( $responseModelName !== Response::MODEL_NONE && $responseModelName !== Response::MODEL_ANY ) { + $responseModel = $response->getModel($responseModelName); + createTypeMapping($responseModel, $response); + $fields[$methodName] = [ + 'type' => $typeMapping[$responseModel->getType()], + 'description' => $route->getDesc(), + 'args' => getArgs($route->getParams()), + ]; + + // print_r($fields[$methodName]); + var_dump("Processed route : {$route->getURL()}"); + } else { + var_dump("Skipping route : {$route->getURL()}"); + } + } + } + } + } + + ksort($fields); + + $queryType = new ObjectType([ + 'name' => 'Query', + 'description' => 'The root of all your queries', + 'fields' => $fields + ]); + + $schema = new Schema([ + 'query' => $queryType + ]); + + $time_elapsed_secs = microtime(true) - $start; + + var_dump("Time Taken To Build Schema : ${time_elapsed_secs}s"); + + return $schema; +} + + App::post('/v1/graphql') ->desc('GraphQL Endpoint') ->groups(['api', 'graphql']) ->label('scope', 'public') - ->action( - function () use ($request, $response, $utopia) { - - foreach ($utopia->getRoutes() as $method => $routes) { - var_dump($method); - - foreach ($routes as $key => $route) { - var_dump($key); - } - } - - $userType = new ObjectType([ - 'name' => 'User', - 'fields' => [ - 'name' => [ - 'type' => Type::string(), - ], - ], - ]); - - $queryType = new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'echo' => [ - 'type' => Type::string(), - 'args' => [ - 'message' => ['type' => Type::string()], - ], - 'resolve' => function ($rootValue, $args) { - return $rootValue['prefix'] . $args['message']; - } - ], - 'users' => [ - 'type' => Type::listOf($userType), - //'type' => $userType, - 'args' => [ - 'message' => ['type' => Type::string()], - ], - 'resolve' => function ($rootValue, $args) { - return ['name' => 'Eldad Fux']; - return [ - ['name' => 'Eldad Fux'], - ['name' => 'Sharon Kapon'], - ]; - } - ], - ], - ]); - - $mutationType = new ObjectType([ - 'name' => 'Mutation', - 'fields' => [ - 'sum' => [ - 'type' => Type::int(), - 'args' => [ - 'x' => ['type' => Type::int()], - 'y' => ['type' => Type::int()], - ], - 'resolve' => function ($calc, $args) { - return $args['x'] + $args['y']; - }, - ], - ], - ]); - - $schema = new Schema([ - 'query' => $queryType, - 'mutation' => $mutationType, - ]); + ->inject('request') + ->inject('response') + ->inject('utopia') + ->inject('schema') + ->action(function ($request, $response, $utopia, $schema) { + // 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); $query = $request->getPayload('query', ''); $variables = $request->getPayload('variables', null); + try { $rootValue = []; $result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variables); @@ -128,7 +259,6 @@ App::post('/v1/graphql') ] ]; } - $response->json($output); echo "\n"; //TODO REMOVE THIS } diff --git a/docker-compose.yml b/docker-compose.yml index 9e614f709..5b390ddee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -495,7 +495,7 @@ services: ports: - '9505:4000' environment: - - API_URL=http://192.168.1.102/v1/graphql + - API_URL=http://localhost/v1/graphql image: npalm/graphiql networks: