2021-03-10 07:42:45 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Appwrite\GraphQL;
|
|
|
|
|
|
|
|
|
|
use Appwrite\GraphQL\Types\JsonType;
|
|
|
|
|
use Appwrite\Utopia\Response;
|
|
|
|
|
use Appwrite\Utopia\Response\Model;
|
2022-04-05 13:48:51 +00:00
|
|
|
use GraphQL\Error\Error;
|
|
|
|
|
use GraphQL\Error\FormattedError;
|
2021-03-10 07:42:45 +00:00
|
|
|
use GraphQL\Type\Definition\ObjectType;
|
|
|
|
|
use GraphQL\Type\Definition\Type;
|
|
|
|
|
use GraphQL\Type\Schema;
|
2021-03-18 19:26:45 +00:00
|
|
|
use Utopia\CLI\Console;
|
2022-04-06 23:23:20 +00:00
|
|
|
use Utopia\Database\Database;
|
|
|
|
|
use Utopia\Database\Document;
|
|
|
|
|
use Utopia\Database\Validator\Authorization;
|
|
|
|
|
use Utopia\Registry\Registry;
|
2021-03-10 07:42:45 +00:00
|
|
|
|
2022-04-05 13:48:51 +00:00
|
|
|
class Builder
|
|
|
|
|
{
|
|
|
|
|
protected static ?JsonType $jsonParser = null;
|
2021-03-10 07:42:45 +00:00
|
|
|
|
2022-04-05 13:48:51 +00:00
|
|
|
protected static array $typeMapping = [];
|
2021-03-10 07:42:45 +00:00
|
|
|
|
2021-03-12 18:00:41 +00:00
|
|
|
/**
|
2022-04-05 13:48:51 +00:00
|
|
|
* Function to initialise the typeMapping array with the base cases of the recursion
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function init()
|
2021-03-12 18:00:41 +00:00
|
|
|
{
|
2021-03-10 07:42:45 +00:00
|
|
|
self::$typeMapping = [
|
|
|
|
|
Model::TYPE_BOOLEAN => Type::boolean(),
|
|
|
|
|
Model::TYPE_STRING => Type::string(),
|
|
|
|
|
Model::TYPE_INTEGER => Type::int(),
|
|
|
|
|
Model::TYPE_FLOAT => Type::float(),
|
2021-03-12 18:00:41 +00:00
|
|
|
Model::TYPE_JSON => self::json(),
|
2021-03-17 21:17:50 +00:00
|
|
|
Response::MODEL_NONE => self::json(),
|
2021-03-12 18:00:41 +00:00
|
|
|
Response::MODEL_ANY => self::json(),
|
2021-03-10 07:42:45 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 18:00:41 +00:00
|
|
|
/**
|
2022-04-05 13:48:51 +00:00
|
|
|
* Function to create a singleton for $jsonParser
|
|
|
|
|
*
|
|
|
|
|
* @return JsonType
|
|
|
|
|
*/
|
|
|
|
|
public static function json()
|
2021-03-12 18:00:41 +00:00
|
|
|
{
|
2021-03-16 20:05:48 +00:00
|
|
|
if (is_null(self::$jsonParser)) {
|
2021-03-12 18:00:41 +00:00
|
|
|
self::$jsonParser = new JsonType();
|
|
|
|
|
}
|
|
|
|
|
return self::$jsonParser;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-04-05 13:48:51 +00:00
|
|
|
* If the map already contains the type, end the recursion and return.
|
|
|
|
|
* Iterate through all the rules in the response model. Each rule is of the form
|
|
|
|
|
* [
|
|
|
|
|
* [KEY 1] => [
|
|
|
|
|
* 'type' => A string from Appwrite/Utopia/Response
|
|
|
|
|
* 'description' => A description of the type
|
|
|
|
|
* 'default' => A default value for this type
|
|
|
|
|
* 'example' => An example of this type
|
|
|
|
|
* 'require' => a boolean representing whether this field is required
|
|
|
|
|
* 'array' => a boolean representing whether this field is an array
|
|
|
|
|
* ],
|
|
|
|
|
* [KEY 2] => [
|
|
|
|
|
* ],
|
|
|
|
|
* [KEY 3] => [
|
|
|
|
|
* ] .....
|
|
|
|
|
* ]
|
|
|
|
|
* If there are any field names containing characters other than a-z, A-Z, 0-9, _ ,
|
|
|
|
|
* we need to remove all those characters. Currently Appwrite's Response model has only the
|
|
|
|
|
* $ sign which is prohibited by the GraphQL spec. So we're only replacing that. We need to replace this with a regex
|
|
|
|
|
* based approach.
|
|
|
|
|
*
|
|
|
|
|
* @param Model $model
|
|
|
|
|
* @param Response $response
|
|
|
|
|
* @return Type
|
|
|
|
|
*/
|
2021-03-16 20:05:48 +00:00
|
|
|
static function getTypeMapping(Model $model, Response $response): Type
|
2021-03-12 18:00:41 +00:00
|
|
|
{
|
2021-03-16 20:05:48 +00:00
|
|
|
if (isset(self::$typeMapping[$model->getType()])) {
|
|
|
|
|
return self::$typeMapping[$model->getType()];
|
|
|
|
|
}
|
2021-03-10 07:42:45 +00:00
|
|
|
|
|
|
|
|
$rules = $model->getRules();
|
|
|
|
|
$name = $model->getType();
|
|
|
|
|
$fields = [];
|
|
|
|
|
$type = null;
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2021-03-10 07:42:45 +00:00
|
|
|
foreach ($rules as $key => $props) {
|
2021-03-23 18:00:42 +00:00
|
|
|
$keyWithoutSpecialChars = str_replace('$', '_', $key);
|
2021-03-10 07:42:45 +00:00
|
|
|
if (isset(self::$typeMapping[$props['type']])) {
|
|
|
|
|
$type = self::$typeMapping[$props['type']];
|
|
|
|
|
} else {
|
|
|
|
|
try {
|
|
|
|
|
$complexModel = $response->getModel($props['type']);
|
2021-03-16 20:05:48 +00:00
|
|
|
$type = self::getTypeMapping($complexModel, $response);
|
2022-04-05 13:48:51 +00:00
|
|
|
} catch (\Exception $e) {
|
2021-03-18 19:41:38 +00:00
|
|
|
Console::error("Could Not find model for : {$props['type']}");
|
2021-03-10 07:42:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ($props['array']) {
|
|
|
|
|
$type = Type::listOf($type);
|
|
|
|
|
}
|
|
|
|
|
$fields[$keyWithoutSpecialChars] = [
|
|
|
|
|
'type' => $type,
|
|
|
|
|
'description' => $props['description'],
|
2021-03-11 15:44:04 +00:00
|
|
|
'resolve' => function ($object, $args, $context, $info) use ($key) {
|
2021-03-10 07:42:45 +00:00
|
|
|
return $object[$key];
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
$objectType = [
|
2022-04-05 13:48:51 +00:00
|
|
|
'name' => $name,
|
2021-03-10 07:42:45 +00:00
|
|
|
'fields' => $fields
|
|
|
|
|
];
|
2022-04-05 13:48:51 +00:00
|
|
|
self::$typeMapping[$name] = new ObjectType($objectType);
|
|
|
|
|
|
2021-03-16 20:05:48 +00:00
|
|
|
return self::$typeMapping[$name];
|
2021-03-10 07:42:45 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-05 13:48:51 +00:00
|
|
|
/**
|
|
|
|
|
* Function to map a Utopia\Validator to a valid GraphQL Type
|
|
|
|
|
*
|
|
|
|
|
* @param $validator
|
|
|
|
|
* @param bool $required
|
|
|
|
|
* @param $utopia
|
|
|
|
|
* @param $injections
|
|
|
|
|
* @return GraphQL\Type\Definition\Type
|
|
|
|
|
*/
|
|
|
|
|
protected static function getArgType($validator, bool $required, $utopia, $injections): Type
|
2021-03-18 19:41:38 +00:00
|
|
|
{
|
2021-03-10 07:42:45 +00:00
|
|
|
$validator = (\is_callable($validator)) ? call_user_func_array($validator, $utopia->getResources($injections)) : $validator;
|
|
|
|
|
$type = [];
|
|
|
|
|
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
2022-04-05 13:48:51 +00:00
|
|
|
case 'Utopia\Validator\Email':
|
|
|
|
|
case 'Utopia\Validator\Host':
|
|
|
|
|
case 'Utopia\Validator\Length':
|
|
|
|
|
case 'Appwrite\Auth\Validator\Password':
|
|
|
|
|
case 'Utopia\Validator\URL':
|
|
|
|
|
case 'Appwrite\Database\Validator\UID':
|
|
|
|
|
case 'Appwrite\Storage\Validator\File':
|
|
|
|
|
case 'Utopia\Validator\WhiteList':
|
2021-03-10 07:42:45 +00:00
|
|
|
case 'Utopia\Validator\Text':
|
|
|
|
|
$type = Type::string();
|
|
|
|
|
break;
|
|
|
|
|
case 'Utopia\Validator\Boolean':
|
|
|
|
|
$type = Type::boolean();
|
|
|
|
|
break;
|
|
|
|
|
case 'Utopia\Validator\ArrayList':
|
2021-03-12 18:00:41 +00:00
|
|
|
$type = Type::listOf(self::json());
|
2021-03-10 07:42:45 +00:00
|
|
|
break;
|
|
|
|
|
case 'Utopia\Validator\Numeric':
|
2022-04-05 13:48:51 +00:00
|
|
|
case 'Utopia\Validator\Range':
|
2021-03-10 07:42:45 +00:00
|
|
|
$type = Type::int();
|
|
|
|
|
break;
|
2022-04-05 13:48:51 +00:00
|
|
|
case 'Utopia\Validator\Assoc':
|
2021-03-10 07:42:45 +00:00
|
|
|
default:
|
2021-03-16 13:34:11 +00:00
|
|
|
$type = self::json();
|
2021-03-10 07:42:45 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2021-03-10 07:42:45 +00:00
|
|
|
if ($required) {
|
|
|
|
|
$type = Type::nonNull($type);
|
|
|
|
|
}
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2021-03-10 07:42:45 +00:00
|
|
|
return $type;
|
|
|
|
|
}
|
2021-11-25 08:04:39 +00:00
|
|
|
|
2022-04-05 13:48:51 +00:00
|
|
|
public static function appendSchema($schema, $dbForProject): Schema
|
|
|
|
|
{
|
|
|
|
|
Console::log("[INFO] Appending GraphQL Database Schema...");
|
|
|
|
|
$start = microtime(true);
|
|
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$db = self::buildCollectionsSchema($dbForProject);
|
2022-04-05 13:48:51 +00:00
|
|
|
|
|
|
|
|
$queryFields = $schema->getQueryType()?->getFields() ?? [];
|
|
|
|
|
$mutationFields = $schema->getMutationType()?->getFields() ?? [];
|
|
|
|
|
|
|
|
|
|
$queryFields = \array_merge($queryFields, $db['query']);
|
|
|
|
|
$mutationFields = \array_merge($mutationFields, $db['mutation']);
|
|
|
|
|
|
|
|
|
|
ksort($queryFields);
|
|
|
|
|
ksort($mutationFields);
|
|
|
|
|
|
|
|
|
|
$schema = new Schema([
|
|
|
|
|
'query' => new ObjectType([
|
|
|
|
|
'name' => 'Query',
|
|
|
|
|
'description' => 'The root of all queries',
|
|
|
|
|
'fields' => $queryFields
|
|
|
|
|
]),
|
|
|
|
|
'mutation' => new ObjectType([
|
|
|
|
|
'name' => 'Mutation',
|
|
|
|
|
'description' => 'The root of all mutations',
|
|
|
|
|
'fields' => $mutationFields
|
|
|
|
|
])
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$time_elapsed_secs = microtime(true) - $start;
|
|
|
|
|
Console::log("[INFO] Time Taken To Append Database to API Schema : ${time_elapsed_secs}s");
|
|
|
|
|
|
|
|
|
|
return $schema;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
/**
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
*/
|
2022-04-05 13:48:51 +00:00
|
|
|
public static function buildSchema($utopia, $response, $register, $dbForProject): Schema
|
|
|
|
|
{
|
2022-04-06 23:23:20 +00:00
|
|
|
$db = self::buildCollectionsSchema($dbForProject, $register);
|
|
|
|
|
$api = self::buildAPISchema($utopia, $response, $register);
|
2022-04-05 13:48:51 +00:00
|
|
|
|
|
|
|
|
$queryFields = \array_merge($api['query'], $db['query']);
|
|
|
|
|
$mutationFields = \array_merge($api['mutation'], $db['mutation']);
|
|
|
|
|
|
|
|
|
|
ksort($queryFields);
|
|
|
|
|
ksort($mutationFields);
|
|
|
|
|
|
|
|
|
|
return new Schema([
|
|
|
|
|
'query' => new ObjectType([
|
|
|
|
|
'name' => 'Query',
|
2022-04-06 23:23:20 +00:00
|
|
|
'description' => 'The root of all queries',
|
2022-04-05 13:48:51 +00:00
|
|
|
'fields' => $queryFields
|
|
|
|
|
]),
|
|
|
|
|
'mutation' => new ObjectType([
|
|
|
|
|
'name' => 'Mutation',
|
2022-04-06 23:23:20 +00:00
|
|
|
'description' => 'The root of all mutations',
|
2022-04-05 13:48:51 +00:00
|
|
|
'fields' => $mutationFields
|
|
|
|
|
])
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-25 08:04:39 +00:00
|
|
|
/**
|
2022-04-05 13:48:51 +00:00
|
|
|
* This function goes through all the project attributes and builds a
|
|
|
|
|
* GraphQL schema for all the collections they make up.
|
2021-11-25 08:04:39 +00:00
|
|
|
*
|
2022-04-06 23:23:20 +00:00
|
|
|
* @param Database $dbForProject
|
2022-04-05 13:48:51 +00:00
|
|
|
* @return array
|
2022-04-06 23:23:20 +00:00
|
|
|
* @throws \Exception
|
2021-11-25 08:04:39 +00:00
|
|
|
*/
|
2022-04-06 23:23:20 +00:00
|
|
|
public static function buildCollectionsSchema(Database $dbForProject, Registry &$register): array
|
2021-11-25 08:04:39 +00:00
|
|
|
{
|
|
|
|
|
Console::log("[INFO] Building GraphQL Database Schema...");
|
|
|
|
|
$start = microtime(true);
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$collections = [];
|
2022-04-05 13:48:51 +00:00
|
|
|
$queryFields = [];
|
|
|
|
|
$mutationFields = [];
|
2022-04-06 23:23:20 +00:00
|
|
|
$offset = 0;
|
|
|
|
|
|
|
|
|
|
Authorization::skip(function () use ($mutationFields, $queryFields, $collections, $register, $offset, $dbForProject) {
|
|
|
|
|
while (!empty($attrs = $dbForProject->find(
|
|
|
|
|
'attributes',
|
|
|
|
|
limit: $dbForProject->getAttributeLimit(),
|
|
|
|
|
offset: $offset
|
|
|
|
|
))) {
|
|
|
|
|
go(function ($attrs, $dbForProject, $register, $collections, $queryFields, $mutationFields) {
|
|
|
|
|
foreach ($attrs as $attr) {
|
|
|
|
|
go(function ($attr, &$collections) {
|
|
|
|
|
/** @var Document $attr */
|
|
|
|
|
|
|
|
|
|
$collectionId = $attr->getAttribute('collectionId');
|
|
|
|
|
|
|
|
|
|
if (isset(self::$typeMapping[$collectionId])) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ($attr->getAttribute('status') !== 'available') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-11-25 08:04:39 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$key = $attr->getAttribute('key');
|
|
|
|
|
$type = $attr->getAttribute('type');
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$escapedKey = str_replace('$', '_', $key);
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$collections[$collectionId][$escapedKey] = [
|
|
|
|
|
'type' => $type,
|
|
|
|
|
'resolve' => function ($object, $args, $context, $info) use ($key) {
|
|
|
|
|
return $object->getAttribute($key);
|
|
|
|
|
}
|
|
|
|
|
];
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
}, $attr, $collections);
|
|
|
|
|
}
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
foreach ($collections as $collectionId => $attributes) {
|
|
|
|
|
go(function ($collectionId, $attributes, $dbForProject, $register, &$queryFields, &$mutationFields) {
|
|
|
|
|
if (isset(self::$typeMapping[$collectionId])) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$objectType = new ObjectType([
|
|
|
|
|
'name' => \ucfirst($collectionId),
|
|
|
|
|
'fields' => $attributes
|
|
|
|
|
]);
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
self::$typeMapping[$collectionId] = $objectType;
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$mutateArgs = [];
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
foreach ($attributes as $name => $attribute) {
|
|
|
|
|
$mutateArgs[$name] = [
|
|
|
|
|
'type' => $attribute['type']
|
|
|
|
|
];
|
|
|
|
|
}
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$idArgs = [
|
|
|
|
|
'id' => [
|
|
|
|
|
'type' => Type::string()
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$listArgs = [
|
|
|
|
|
'limit' => [
|
|
|
|
|
'type' => Type::int()
|
|
|
|
|
],
|
|
|
|
|
'offset' => [
|
|
|
|
|
'type' => Type::int()
|
|
|
|
|
],
|
|
|
|
|
'cursor' => [
|
|
|
|
|
'type' => Type::string()
|
|
|
|
|
],
|
|
|
|
|
'orderAttributes' => [
|
|
|
|
|
'type' => Type::listOf(Type::string())
|
|
|
|
|
],
|
|
|
|
|
'orderType' => [
|
|
|
|
|
'types' => Type::listOf(Type::string())
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
self::createCollectionGetQuery($collectionId, $register, $dbForProject, $idArgs, $queryFields);
|
|
|
|
|
self::createCollectionListQuery($collectionId, $register, $dbForProject, $listArgs, $queryFields);
|
|
|
|
|
self::createCollectionCreateMutation($collectionId, $register, $dbForProject, $mutateArgs, $mutationFields);
|
|
|
|
|
self::createCollectionUpdateMutation($collectionId, $register, $dbForProject, $mutateArgs, $mutationFields);
|
|
|
|
|
self::createCollectionDeleteMutation($collectionId, $register, $dbForProject, $idArgs, $mutationFields);
|
|
|
|
|
|
|
|
|
|
}, $collectionId, $attributes, $dbForProject, $register, $queryFields, $mutationFields);
|
|
|
|
|
}
|
|
|
|
|
}, $attrs, $dbForProject, $register, $collections, $queryFields, $mutationFields);
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
$offset += $dbForProject->getAttributeLimit();
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-04-05 13:48:51 +00:00
|
|
|
|
|
|
|
|
$time_elapsed_secs = microtime(true) - $start;
|
|
|
|
|
Console::log("[INFO] Time Taken To Build Database Schema : ${time_elapsed_secs}s");
|
2022-04-06 23:23:20 +00:00
|
|
|
Console::info('[INFO] Schema : ' . json_encode([
|
|
|
|
|
'query' => $queryFields,
|
|
|
|
|
'mutation' => $mutationFields
|
|
|
|
|
]));
|
2022-04-05 13:48:51 +00:00
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'query' => $queryFields,
|
|
|
|
|
'mutation' => $mutationFields
|
|
|
|
|
];
|
2021-11-25 08:04:39 +00:00
|
|
|
}
|
2021-03-18 19:53:24 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
private static function createCollectionGetQuery($collectionId, $register, $dbForProject, $args, &$queryFields)
|
|
|
|
|
{
|
|
|
|
|
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
|
|
|
|
|
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
|
|
|
|
|
try {
|
|
|
|
|
$resolve($dbForProject->getDocument($collectionId, $args['id']));
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
$reject($e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
$get = [
|
|
|
|
|
'type' => \ucfirst($collectionId),
|
|
|
|
|
'args' => $args,
|
|
|
|
|
'resolve' => $resolve
|
|
|
|
|
];
|
|
|
|
|
$queryFields['get' . \ucfirst($collectionId)] = $get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function createCollectionListQuery($collectionId, $register, $dbForProject, $args, &$queryFields)
|
|
|
|
|
{
|
|
|
|
|
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
|
|
|
|
|
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
|
|
|
|
|
try {
|
|
|
|
|
$resolve($dbForProject->getCollection($collectionId));
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
$reject($e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
$list = [
|
|
|
|
|
'type' => \ucfirst($collectionId),
|
|
|
|
|
'args' => $args,
|
|
|
|
|
'resolve' => $resolve
|
|
|
|
|
];
|
|
|
|
|
$queryFields['list' . \ucfirst($collectionId)] = $list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function createCollectionCreateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields)
|
|
|
|
|
{
|
|
|
|
|
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
|
|
|
|
|
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
|
|
|
|
|
try {
|
|
|
|
|
$resolve($dbForProject->createDocument($collectionId, new Document($args)));
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
$reject($e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
$create = [
|
|
|
|
|
'type' => \ucfirst($collectionId),
|
|
|
|
|
'args' => $args,
|
|
|
|
|
'resolve' => $resolve
|
|
|
|
|
];
|
|
|
|
|
$mutationFields['create' . \ucfirst($collectionId)] = $create;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function createCollectionUpdateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields)
|
|
|
|
|
{
|
|
|
|
|
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
|
|
|
|
|
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
|
|
|
|
|
try {
|
|
|
|
|
$resolve($dbForProject->updateDocument($collectionId, $args['id'], new Document($args)));
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
$reject($e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$update = [
|
|
|
|
|
'type' => \ucfirst($collectionId),
|
|
|
|
|
'args' => $args,
|
|
|
|
|
'resolve' => $resolve
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$mutationFields['update' . \ucfirst($collectionId)] = $update;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static function createCollectionDeleteMutation($collectionId, $register, $dbForProject, $args, &$mutationFields)
|
|
|
|
|
{
|
|
|
|
|
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
|
|
|
|
|
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
|
|
|
|
|
try {
|
|
|
|
|
$resolve($dbForProject->deleteDocument($collectionId, $args['id']));
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
$reject($e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
$delete = [
|
|
|
|
|
'type' => \ucfirst($collectionId),
|
|
|
|
|
'args' => $args,
|
|
|
|
|
'resolve' => $resolve
|
|
|
|
|
];
|
|
|
|
|
$mutationFields['delete' . \ucfirst($collectionId)] = $delete;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 18:00:41 +00:00
|
|
|
/**
|
2022-04-05 13:48:51 +00:00
|
|
|
* This function goes through all the REST endpoints in the API and builds a
|
|
|
|
|
* GraphQL schema for all those routes whose response model is neither empty nor NONE
|
|
|
|
|
*
|
|
|
|
|
* @param $utopia
|
|
|
|
|
* @param $response
|
|
|
|
|
* @param $register
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2022-04-06 23:23:20 +00:00
|
|
|
public static function buildAPISchema($utopia, $response, $register): array
|
2022-04-05 13:48:51 +00:00
|
|
|
{
|
|
|
|
|
Console::log("[INFO] Building GraphQL API Schema...");
|
2021-03-10 07:42:45 +00:00
|
|
|
$start = microtime(true);
|
2022-04-05 13:48:51 +00:00
|
|
|
|
2021-03-12 18:09:25 +00:00
|
|
|
self::init();
|
2021-03-10 07:42:45 +00:00
|
|
|
$queryFields = [];
|
|
|
|
|
$mutationFields = [];
|
2021-03-12 18:09:25 +00:00
|
|
|
|
2022-04-05 13:48:51 +00:00
|
|
|
foreach ($utopia->getRoutes() as $method => $routes) {
|
|
|
|
|
foreach ($routes as $route) {
|
2021-03-10 07:42:45 +00:00
|
|
|
$namespace = $route->getLabel('sdk.namespace', '');
|
2022-04-06 23:23:20 +00:00
|
|
|
$methodName = $namespace . \ucfirst($route->getLabel('sdk.method', ''));
|
|
|
|
|
$responseModelName = $route->getLabel('sdk.response.model', "none");
|
|
|
|
|
|
|
|
|
|
Console::info("Namespace: $namespace");
|
|
|
|
|
Console::info("Method: $methodName");
|
|
|
|
|
Console::info("Response Model: $responseModelName");
|
|
|
|
|
Console::info("Raw routes: " . \json_encode($routes));
|
|
|
|
|
Console::info("Raw route: " . \json_encode($route));
|
2021-03-18 19:26:45 +00:00
|
|
|
|
2022-04-06 23:23:20 +00:00
|
|
|
if ($responseModelName !== "none") {
|
2021-03-12 18:09:25 +00:00
|
|
|
$responseModel = $response->getModel($responseModelName);
|
2021-03-18 19:53:24 +00:00
|
|
|
|
2021-03-18 19:48:19 +00:00
|
|
|
/* Create a GraphQL type for the current response model */
|
2021-03-16 20:05:48 +00:00
|
|
|
$type = self::getTypeMapping($responseModel, $response);
|
2021-03-18 19:48:19 +00:00
|
|
|
/* Get a description for this type */
|
2021-03-16 14:34:43 +00:00
|
|
|
$description = $route->getDesc();
|
2021-03-18 19:48:19 +00:00
|
|
|
/* Create the args required for this type */
|
|
|
|
|
$args = [];
|
|
|
|
|
foreach ($route->getParams() as $key => $value) {
|
|
|
|
|
$args[$key] = [
|
2022-04-05 13:48:51 +00:00
|
|
|
'type' => self::getArgType($value['validator'], !$value['optional'], $utopia, $value['injections']),
|
2021-03-18 19:48:19 +00:00
|
|
|
'description' => $value['description'],
|
|
|
|
|
'defaultValue' => $value['default']
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
/* Define a resolve function that defines how to fetch data for this type */
|
2022-04-06 23:23:20 +00:00
|
|
|
$resolve = function ($type, $args, $context, $info) use (&$register, $route) {
|
|
|
|
|
return SwoolePromise::create(function (callable $resolve, callable $reject) use (&$register, $route, $args) {
|
2022-04-05 13:48:51 +00:00
|
|
|
$utopia = $register->get('__app');
|
|
|
|
|
$utopia->setRoute($route)->execute($route, $args);
|
|
|
|
|
|
|
|
|
|
$response = $register->get('__response');
|
|
|
|
|
$result = $response->getPayload();
|
|
|
|
|
|
|
|
|
|
if ($response->getCurrentModel() == Response::MODEL_ERROR_DEV) {
|
|
|
|
|
$reject(new ExceptionDev($result['message'], $result['code'], $result['version'], $result['file'], $result['line'], $result['trace']));
|
|
|
|
|
} else if ($response->getCurrentModel() == Response::MODEL_ERROR) {
|
|
|
|
|
$reject(new \Exception($result['message'], $result['code']));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$resolve($result);
|
|
|
|
|
});
|
2021-03-16 14:34:43 +00:00
|
|
|
};
|
2021-03-18 19:26:45 +00:00
|
|
|
|
2021-03-12 18:09:25 +00:00
|
|
|
$field = [
|
|
|
|
|
'type' => $type,
|
2022-04-05 13:48:51 +00:00
|
|
|
'description' => $description,
|
2021-03-12 18:09:25 +00:00
|
|
|
'args' => $args,
|
2021-03-16 14:34:43 +00:00
|
|
|
'resolve' => $resolve
|
2021-03-12 18:09:25 +00:00
|
|
|
];
|
2021-03-18 19:26:45 +00:00
|
|
|
|
2021-03-12 18:09:25 +00:00
|
|
|
if ($method == 'GET') {
|
|
|
|
|
$queryFields[$methodName] = $field;
|
|
|
|
|
} else if ($method == 'POST' || $method == 'PUT' || $method == 'PATCH' || $method == 'DELETE') {
|
|
|
|
|
$mutationFields[$methodName] = $field;
|
2021-03-10 07:42:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-12 18:09:25 +00:00
|
|
|
|
2021-03-10 07:42:45 +00:00
|
|
|
$time_elapsed_secs = microtime(true) - $start;
|
2022-04-05 13:48:51 +00:00
|
|
|
Console::log("[INFO] Time Taken To Build API Schema : ${time_elapsed_secs}s");
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'query' => $queryFields,
|
|
|
|
|
'mutation' => $mutationFields
|
|
|
|
|
];
|
2021-03-10 07:42:45 +00:00
|
|
|
}
|
2021-03-18 18:55:43 +00:00
|
|
|
|
2021-03-18 19:53:24 +00:00
|
|
|
/**
|
|
|
|
|
* Function to create an appropriate GraphQL Error Formatter
|
|
|
|
|
* Based on whether we're on a development build or production
|
2022-04-05 13:48:51 +00:00
|
|
|
* build of Appwrite.
|
|
|
|
|
*
|
2021-03-18 19:53:24 +00:00
|
|
|
* @param bool $isDevelopment
|
2022-04-05 13:48:51 +00:00
|
|
|
* @param string $version
|
2021-03-18 19:53:24 +00:00
|
|
|
* @return callable
|
|
|
|
|
*/
|
2022-04-06 23:23:20 +00:00
|
|
|
public static function getErrorFormatter(bool $isDevelopment, string $version): callable
|
2021-03-18 19:26:45 +00:00
|
|
|
{
|
2022-04-06 23:23:20 +00:00
|
|
|
return function (Error $error) use ($isDevelopment, $version) {
|
2021-03-18 19:26:45 +00:00
|
|
|
$formattedError = FormattedError::createFromException($error);
|
2022-04-06 23:23:20 +00:00
|
|
|
|
|
|
|
|
// Previous error represents the actual error thrown by Appwrite server
|
2021-03-18 21:10:15 +00:00
|
|
|
$previousError = $error->getPrevious() ?? $error;
|
2021-03-18 19:53:24 +00:00
|
|
|
$formattedError['code'] = $previousError->getCode();
|
2021-03-18 19:26:45 +00:00
|
|
|
$formattedError['version'] = $version;
|
2021-03-18 19:53:24 +00:00
|
|
|
if ($isDevelopment) {
|
|
|
|
|
$formattedError['file'] = $previousError->getFile();
|
|
|
|
|
$formattedError['line'] = $previousError->getLine();
|
2021-03-18 19:26:45 +00:00
|
|
|
}
|
|
|
|
|
return $formattedError;
|
|
|
|
|
};
|
2021-03-18 18:55:43 +00:00
|
|
|
}
|
2021-03-10 07:42:45 +00:00
|
|
|
}
|