1
0
Fork 0
mirror of synced 2024-06-28 19:20:25 +12:00
appwrite/app/controllers/api/graphql.php

291 lines
9.5 KiB
PHP
Raw Normal View History

2020-01-04 10:16:26 +13:00
<?php
2020-06-23 00:04:19 +12:00
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;
2020-06-29 05:31:21 +12:00
use Utopia\App;
use Utopia\Validator;
2020-01-04 10:16:26 +13:00
/**
* TODO:
* 1. Map all objects, object-params, object-fields
* 2. Parse GraphQL request payload (use: https://github.com/webonyx/graphql-php)
* 3. Route request to relevant controllers (of REST API?) / resolvers and aggergate data
2021-01-17 19:36:11 +13:00
* 4. Handle scope authentication
* 5. Handle errors
* 6. Return response
* 7. Write tests!
2020-06-23 00:04:19 +12:00
*
* Demo
* curl -H "Content-Type: application/json" http://localhost/v1/graphql -d '{"query": "query { echo(message: \"Hello World\") }" }'
*
* Explorers:
* - https://shopify.dev/tools/graphiql-admin-api
* - https://developer.github.com/v4/explorer/
* - http://localhost:4000
*
* Docs
* - Overview
2020-06-24 16:08:10 +12:00
* - Clients
*
* - Queries
* - Mutations
*
2020-06-23 00:04:19 +12:00
* - Objects
2020-01-04 10:16:26 +13:00
*/
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);
}
2020-06-25 00:55:05 +12:00
function getArgType($validator, bool $required, $utopia, $injections) {
$validator = (\is_callable($validator)) ? call_user_func_array($validator, $utopia->getResources($injections)) : $validator;
$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 = Type::string();
break;
}
if ($required) {
$type = Type::nonNull($type);
}
return $type;
}
function getArgs(array $params, $utopia) {
$args = [];
foreach ($params as $key => $value) {
$args[$key] = [
'type' => getArgType($value['validator'],!$value['optional'], $utopia, $value['injections']),
'description' => $value['description'],
'defaultValue' => $value['default']
];
}
return $args;
}
function buildSchema($utopia, $response) {
$start = microtime(true);
global $typeMapping;
$queryFields = [];
$mutationFields = [];
foreach($utopia->getRoutes() as $method => $routes ){
foreach($routes as $route) {
$namespace = $route->getLabel('sdk.namespace', '');
if ($namespace == 'users') {
$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 ) {
$responseModel = $response->getModel($responseModelName);
createTypeMapping($responseModel, $response);
$type = $typeMapping[$responseModel->getType()];
var_dump("Type Created : ${type}");
$args = getArgs($route->getParams(), $utopia);
var_dump("Args Generated : ${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);
var_dump("********************** ARGS *******************");
var_dump($args);
var_dump("**************** OUTPUT ************");
var_dump($response->getPayload());
return $response->getPayload();
}
];
if ($method == 'GET') {
$queryFields[$methodName] = $field;
} else if ($method == 'POST' || $method == 'PUT' || $method == 'PATCH' || $method == 'DELETE') {
$mutationFields[$methodName] = $field;
}
var_dump("Processed route : ${method} : {$route->getURL()}");
} else {
var_dump("Skipping route : {$route->getURL()}");
2020-06-25 00:55:05 +12:00
}
}
}
}
ksort($queryFields);
ksort($mutationFields);
$queryType = new ObjectType([
'name' => 'Query',
'description' => 'The root of all your queries',
'fields' => $queryFields
]);
$mutationType = new ObjectType([
'name' => 'Mutation',
'description' => 'The root of all your mutations',
'fields' => $mutationFields
]);
2020-06-25 00:55:05 +12:00
$schema = new Schema([
'query' => $queryType,
'mutation' => $mutationType
]);
2020-06-23 00:04:19 +12:00
$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')
->inject('request')
->inject('response')
->inject('utopia')
2021-02-26 10:56:38 +13:00
->action(function ($request, $response, $utopia) {
// 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);
2020-06-23 00:04:19 +12:00
$query = $request->getPayload('query', '');
$variables = $request->getPayload('variables', null);
$response->setContentType(Response::CONTENT_TYPE_NULL);
2020-06-23 00:04:19 +12:00
try {
$rootValue = [];
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variables);
$output = $result->toArray();
2021-02-26 10:56:38 +13:00
var_dump("********** OUTPUT *********");
var_dump($output);
2020-06-23 00:04:19 +12:00
} catch (\Exception $error) {
$output = [
'errors' => [
[
'message' => $error->getMessage().'xxx',
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTrace(),
]
]
];
}
$response->json($output);
echo "\n"; //TODO REMOVE THIS
2020-01-04 10:16:26 +13:00
}
2020-06-23 00:04:19 +12:00
);