Restructure schema building
This commit is contained in:
parent
39fcbe4d76
commit
b6621f5e87
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Promises\CoroutinePromiseAdapter;
|
||||
use Appwrite\GraphQL\Promises\Adapter;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
|
@ -85,7 +85,7 @@ App::post('/v1/graphql/upload')
|
|||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param CoroutinePromiseAdapter $promiseAdapter
|
||||
* @param Adapter $promiseAdapter
|
||||
* @param Type\Schema $schema
|
||||
* @return void
|
||||
* @throws Exception
|
||||
|
@ -93,7 +93,7 @@ App::post('/v1/graphql/upload')
|
|||
function executeRequest(
|
||||
Appwrite\Utopia\Request $request,
|
||||
Appwrite\Utopia\Response $response,
|
||||
CoroutinePromiseAdapter $promiseAdapter,
|
||||
Adapter $promiseAdapter,
|
||||
Type\Schema $schema
|
||||
): void {
|
||||
$query = $request->getParams();
|
||||
|
|
24
app/init.php
24
app/init.php
|
@ -21,12 +21,6 @@ error_reporting(E_ALL);
|
|||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\SMS\Adapter\Mock;
|
||||
use Appwrite\SMS\Adapter\Telesign;
|
||||
use Appwrite\SMS\Adapter\TextMagic;
|
||||
use Appwrite\SMS\Adapter\Twilio;
|
||||
use Appwrite\SMS\Adapter\Msg91;
|
||||
use Appwrite\SMS\Adapter\Vonage;
|
||||
use Appwrite\DSN\DSN;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
|
@ -36,15 +30,20 @@ use Appwrite\Event\Mail;
|
|||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\PDO;
|
||||
use Appwrite\GraphQL\SchemaBuilder;
|
||||
use Appwrite\GraphQL\Promises\CoroutinePromiseAdapter;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\SMS\Adapter\Mock;
|
||||
use Appwrite\SMS\Adapter\Msg91;
|
||||
use Appwrite\SMS\Adapter\Telesign;
|
||||
use Appwrite\SMS\Adapter\TextMagic;
|
||||
use Appwrite\SMS\Adapter\Twilio;
|
||||
use Appwrite\SMS\Adapter\Vonage;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\Database\ID;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOConfig;
|
||||
|
@ -58,9 +57,10 @@ use Utopia\Config\Config;
|
|||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Logger;
|
||||
|
@ -632,7 +632,7 @@ $register->set('cache', function () {
|
|||
return $redis;
|
||||
});
|
||||
$register->set('promiseAdapter', function () {
|
||||
return new CoroutinePromiseAdapter();
|
||||
return new Swoole();
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -1061,5 +1061,5 @@ App::setResource('promiseAdapter', function ($register) {
|
|||
}, ['register']);
|
||||
|
||||
App::setResource('schema', function ($utopia, $project, $dbForProject) {
|
||||
return SchemaBuilder::buildSchema($utopia, $project->getId(), $dbForProject);
|
||||
return Schema::build($utopia, $project->getId(), $dbForProject);
|
||||
}, ['utopia', 'project', 'dbForProject']);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Promises\CoroutinePromise;
|
||||
use Appwrite\Promises\Swoole;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
|
@ -14,17 +14,17 @@ use Utopia\Route;
|
|||
class Resolvers
|
||||
{
|
||||
/**
|
||||
* Create a resolver for a given {@see Route}.
|
||||
* Create a resolver for a given API {@see Route}.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param ?Route $route
|
||||
* @return callable
|
||||
*/
|
||||
public static function resolveAPIRequest(
|
||||
public static function api(
|
||||
App $utopia,
|
||||
?Route $route,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new CoroutinePromise(
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $route, $args, $context, $info) {
|
||||
$utopia = $utopia->getResource('current', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
|
@ -57,22 +57,23 @@ class Resolvers
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for getting a document in a specified database and collection.
|
||||
* Create a resolver for a document in a specified database and collection with a specific method type.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param Database $dbForProject
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param string $methodType
|
||||
* @return callable
|
||||
*/
|
||||
public static function resolveDocument(
|
||||
public static function document(
|
||||
App $utopia,
|
||||
Database $dbForProject,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
string $methodType,
|
||||
): callable {
|
||||
return [self::class, 'resolveDocument' . \ucfirst($methodType)](
|
||||
return [self::class, 'document' . \ucfirst($methodType)](
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
|
@ -89,13 +90,13 @@ class Resolvers
|
|||
* @param string $collectionId
|
||||
* @return callable
|
||||
*/
|
||||
public static function resolveDocumentGet(
|
||||
public static function documentGet(
|
||||
App $utopia,
|
||||
Database $dbForProject,
|
||||
string $databaseId,
|
||||
string $collectionId
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new CoroutinePromise(
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $dbForProject, $databaseId, $collectionId, $type, $args) {
|
||||
$utopia = $utopia->getResource('current', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
|
@ -120,13 +121,13 @@ class Resolvers
|
|||
* @param string $collectionId
|
||||
* @return callable
|
||||
*/
|
||||
public static function resolveDocumentList(
|
||||
public static function documentList(
|
||||
App $utopia,
|
||||
Database $dbForProject,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new CoroutinePromise(
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $dbForProject, $databaseId, $collectionId, $type, $args) {
|
||||
$utopia = $utopia->getResource('current', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
|
@ -159,13 +160,13 @@ class Resolvers
|
|||
* @param string $collectionId
|
||||
* @return callable
|
||||
*/
|
||||
public static function resolveDocumentCreate(
|
||||
public static function documentCreate(
|
||||
App $utopia,
|
||||
Database $dbForProject,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new CoroutinePromise(
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $dbForProject, $databaseId, $collectionId, $type, $args) {
|
||||
$utopia = $utopia->getResource('current', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
|
@ -204,13 +205,13 @@ class Resolvers
|
|||
* @param string $collectionId
|
||||
* @return callable
|
||||
*/
|
||||
public static function resolveDocumentUpdate(
|
||||
public static function documentUpdate(
|
||||
App $utopia,
|
||||
Database $dbForProject,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new CoroutinePromise(
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $dbForProject, $databaseId, $collectionId, $type, $args) {
|
||||
$utopia = $utopia->getResource('current', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
|
@ -249,13 +250,13 @@ class Resolvers
|
|||
* @param string $collectionId
|
||||
* @return callable
|
||||
*/
|
||||
public static function resolveDocumentDelete(
|
||||
public static function documentDelete(
|
||||
App $utopia,
|
||||
Database $dbForProject,
|
||||
string $databaseId,
|
||||
string $collectionId
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new CoroutinePromise(
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $dbForProject, $databaseId, $collectionId, $type, $args) {
|
||||
$utopia = $utopia->getResource('current', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
|
@ -279,10 +280,12 @@ class Resolvers
|
|||
* @param Response $response
|
||||
* @param callable $resolve
|
||||
* @param callable $reject
|
||||
* @param callable|null $beforeResolve
|
||||
* @param callable|null $beforeReject
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function resolve(
|
||||
private static function resolve(
|
||||
App $utopia,
|
||||
Request $request,
|
||||
Response $response,
|
||||
|
@ -318,7 +321,7 @@ class Resolvers
|
|||
if ($beforeReject) {
|
||||
$payload = $beforeReject($payload);
|
||||
}
|
||||
$reject(new GQLException(
|
||||
$reject(new Exception(
|
||||
message: $payload['message'],
|
||||
code: $response->getStatusCode()
|
||||
));
|
||||
|
|
253
src/Appwrite/GraphQL/Schema.php
Normal file
253
src/Appwrite/GraphQL/Schema.php
Normal file
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Types\Mapper;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema as GQLSchema;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Route;
|
||||
|
||||
class Schema
|
||||
{
|
||||
protected static ?GQLSchema $schema = null;
|
||||
protected static bool $dirty = false;
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function build(
|
||||
App $utopia,
|
||||
string $projectId,
|
||||
Database $dbForProject
|
||||
): GQLSchema {
|
||||
App::setResource('utopia:self', static function () use ($utopia) {
|
||||
return $utopia;
|
||||
});
|
||||
|
||||
if (!self::$dirty && self::$schema) {
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
$api = static::api($utopia);
|
||||
//$collections = static::collections($utopia, $dbForProject);
|
||||
|
||||
$queries = \array_merge_recursive(
|
||||
$api['query'],
|
||||
//$collections['query']
|
||||
);
|
||||
$mutations = \array_merge_recursive(
|
||||
$api['mutation'],
|
||||
//$collections['mutation']
|
||||
);
|
||||
|
||||
\ksort($queries);
|
||||
\ksort($mutations);
|
||||
|
||||
return static::$schema = new GQLSchema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => $queries
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'fields' => $mutations
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function iterates all API routes and builds a GraphQL
|
||||
* schema defining types and resolvers for all response models.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function api(App $utopia): array
|
||||
{
|
||||
Mapper::init($utopia
|
||||
->getResource('response')
|
||||
->getModels());
|
||||
|
||||
$queries = [];
|
||||
$mutations = [];
|
||||
|
||||
foreach ($utopia->getRoutes() as $routes) {
|
||||
foreach ($routes as $route) {
|
||||
/** @var Route $route */
|
||||
|
||||
$namespace = $route->getLabel('sdk.namespace', '');
|
||||
$method = $route->getLabel('sdk.method', '');
|
||||
$name = $namespace . \ucfirst($method);
|
||||
|
||||
if (empty($name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Mapper::route($utopia, $route) as $field) {
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$queries[$name] = $field;
|
||||
break;
|
||||
case 'POST':
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$mutations[$name] = $field;
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("Unsupported method: {$route->getMethod()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'query' => $queries,
|
||||
'mutation' => $mutations
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates all of a projects attributes and builds GraphQL
|
||||
* queries and mutations for the collections they make up.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param Database $dbForProject
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function collections(
|
||||
App $utopia,
|
||||
Database $dbForProject
|
||||
): array {
|
||||
$collections = [];
|
||||
$queryFields = [];
|
||||
$mutationFields = [];
|
||||
$limit = 1000;
|
||||
$offset = 0;
|
||||
$count = 0;
|
||||
|
||||
while (
|
||||
!empty($attrs = Authorization::skip(fn() => $dbForProject->find('attributes', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
])))
|
||||
) {
|
||||
$count += count($attrs);
|
||||
|
||||
foreach ($attrs as $attr) {
|
||||
if ($attr->getAttribute('status') !== 'available') {
|
||||
continue;
|
||||
}
|
||||
$databaseId = $attr->getAttribute('databaseId');
|
||||
$collectionId = $attr->getAttribute('collectionId');
|
||||
$key = $attr->getAttribute('key');
|
||||
$type = $attr->getAttribute('type');
|
||||
$array = $attr->getAttribute('array');
|
||||
$required = $attr->getAttribute('required');
|
||||
$default = $attr->getAttribute('default');
|
||||
$escapedKey = str_replace('$', '_', $key);
|
||||
$collections[$collectionId][$escapedKey] = [
|
||||
'type' => Mapper::fromCollectionAttribute(
|
||||
$type,
|
||||
$array,
|
||||
$required
|
||||
),
|
||||
'defaultValue' => $default,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($collections as $collectionId => $attributes) {
|
||||
$objectType = new ObjectType([
|
||||
'name' => $collectionId,
|
||||
'fields' => \array_merge(
|
||||
["_id" => ['type' => Type::string()]],
|
||||
$attributes
|
||||
),
|
||||
]);
|
||||
$attributes = \array_merge(
|
||||
$attributes,
|
||||
Mapper::argumentsFor('mutate')
|
||||
);
|
||||
|
||||
$queryFields[$collectionId . 'Get'] = [
|
||||
'type' => $objectType,
|
||||
'args' => Mapper::argumentsFor('id'),
|
||||
'resolve' => Resolvers::documentGet(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
)
|
||||
];
|
||||
$queryFields[$collectionId . 'List'] = [
|
||||
'type' => Type::listOf($objectType),
|
||||
'args' => Mapper::argumentsFor('list'),
|
||||
'resolve' => Resolvers::documentList(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
),
|
||||
'complexity' => function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
$query = Query::getByType($queries, Query::TYPE_LIMIT)[0] ?? null;
|
||||
$limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
|
||||
|
||||
return $complexity * $limit;
|
||||
},
|
||||
];
|
||||
|
||||
$mutationFields[$collectionId . 'Create'] = [
|
||||
'type' => $objectType,
|
||||
'args' => $attributes,
|
||||
'resolve' => Resolvers::documentCreate(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Update'] = [
|
||||
'type' => $objectType,
|
||||
'args' => \array_merge(
|
||||
Mapper::argumentsFor('id'),
|
||||
\array_map(
|
||||
fn($attr) => $attr['type'] = Type::getNullableType($attr['type']),
|
||||
$attributes
|
||||
)
|
||||
),
|
||||
'resolve' => Resolvers::documentUpdate(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Delete'] = [
|
||||
'type' => Mapper::fromResponseModel(Response::MODEL_NONE),
|
||||
'args' => Mapper::argumentsFor('id'),
|
||||
'resolve' => Resolvers::documentDelete(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
)
|
||||
];
|
||||
}
|
||||
$offset += $limit;
|
||||
}
|
||||
|
||||
return [
|
||||
'query' => $queryFields,
|
||||
'mutation' => $mutationFields
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,300 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Route;
|
||||
|
||||
class SchemaBuilder
|
||||
{
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function buildSchema(
|
||||
App $utopia,
|
||||
string $projectId,
|
||||
Database $dbForProject
|
||||
): Schema {
|
||||
App::setResource('current', static fn() => $utopia);
|
||||
|
||||
/** @var Registry $register */
|
||||
$register = $utopia->getResource('register');
|
||||
$appVersion = App::getEnv('_APP_VERSION');
|
||||
|
||||
$apiSchemaKey = 'apiSchema';
|
||||
$apiVersionKey = 'apiSchemaVersion';
|
||||
$collectionSchemaKey = $projectId . 'CollectionSchema';
|
||||
$collectionsDirtyKey = $projectId . 'SchemaDirty';
|
||||
$fullSchemaKey = $projectId . 'FullSchema';
|
||||
|
||||
$schemaVersion = $register->has($apiVersionKey) ? $register->get($apiVersionKey) : '';
|
||||
$collectionSchemaDirty = $register->has($collectionsDirtyKey) ? $register->get($collectionsDirtyKey) : true;
|
||||
$apiSchemaDirty = \version_compare($appVersion, $schemaVersion, "!=");
|
||||
|
||||
if (
|
||||
!$collectionSchemaDirty
|
||||
&& !$apiSchemaDirty
|
||||
&& $register->has($fullSchemaKey)
|
||||
) {
|
||||
return $register->get($fullSchemaKey);
|
||||
}
|
||||
|
||||
if ($register->has($apiSchemaKey) && !$apiSchemaDirty) {
|
||||
$apiSchema = $register->get($apiSchemaKey);
|
||||
} else {
|
||||
$apiSchema = &self::buildAPISchema($utopia);
|
||||
$register->set($apiSchemaKey, static function &() use (&$apiSchema) {
|
||||
return $apiSchema;
|
||||
});
|
||||
$register->set($apiVersionKey, static fn() => $appVersion);
|
||||
}
|
||||
|
||||
if ($register->has($collectionSchemaKey) && !$collectionSchemaDirty) {
|
||||
$collectionSchema = $register->get($collectionSchemaKey);
|
||||
} else {
|
||||
$collectionSchema = &self::buildCollectionSchema($utopia, $dbForProject);
|
||||
$register->set($collectionSchemaKey, static function &() use (&$collectionSchema) {
|
||||
return $collectionSchema;
|
||||
});
|
||||
$register->set($collectionsDirtyKey, static fn() => false);
|
||||
}
|
||||
|
||||
$queryFields = \array_merge_recursive(
|
||||
$apiSchema['query'],
|
||||
$collectionSchema['query']
|
||||
);
|
||||
$mutationFields = \array_merge_recursive(
|
||||
$apiSchema['mutation'],
|
||||
$collectionSchema['mutation']
|
||||
);
|
||||
|
||||
\ksort($queryFields);
|
||||
\ksort($mutationFields);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => $queryFields
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'fields' => $mutationFields
|
||||
])
|
||||
]);
|
||||
|
||||
$register->set($fullSchemaKey, static fn() => $schema);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function iterates all API routes and builds a GraphQL
|
||||
* schema defining types and resolvers for all response models.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function &buildAPISchema(App $utopia): array
|
||||
{
|
||||
$models = $utopia
|
||||
->getResource('response')
|
||||
->getModels();
|
||||
|
||||
TypeMapper::init($models);
|
||||
|
||||
$queries = [];
|
||||
$mutations = [];
|
||||
|
||||
foreach (App::getRoutes() as $type => $routes) {
|
||||
foreach ($routes as $route) {
|
||||
/** @var Route $route */
|
||||
|
||||
$namespace = $route->getLabel('sdk.namespace', '');
|
||||
$method = $route->getLabel('sdk.method', '');
|
||||
$name = $namespace . \ucfirst($method);
|
||||
|
||||
if (empty($name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (TypeMapper::fromRoute($utopia, $route) as $field) {
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$queries[$name] = $field;
|
||||
break;
|
||||
case 'POST':
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$mutations[$name] = $field;
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("Unsupported method: {$route->getMethod()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$schema = [
|
||||
'query' => $queries,
|
||||
'mutation' => $mutations
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates all of a projects attributes and builds GraphQL
|
||||
* queries and mutations for the collections they make up.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param Database $dbForProject
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function &buildCollectionSchema(
|
||||
App $utopia,
|
||||
Database $dbForProject
|
||||
): array {
|
||||
$collections = [];
|
||||
$queryFields = [];
|
||||
$mutationFields = [];
|
||||
$limit = 1000;
|
||||
$offset = 0;
|
||||
$count = 0;
|
||||
|
||||
$wg = new WaitGroup();
|
||||
|
||||
while (
|
||||
!empty($attrs = Authorization::skip(fn() => $dbForProject->find('attributes', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
])))
|
||||
) {
|
||||
$wg->add();
|
||||
$count += count($attrs);
|
||||
\go(function () use ($utopia, $dbForProject, &$collections, &$queryFields, &$mutationFields, $limit, &$offset, $attrs, $wg) {
|
||||
foreach ($attrs as $attr) {
|
||||
if ($attr->getAttribute('status') !== 'available') {
|
||||
continue;
|
||||
}
|
||||
$databaseId = $attr->getAttribute('databaseId');
|
||||
$collectionId = $attr->getAttribute('collectionId');
|
||||
$key = $attr->getAttribute('key');
|
||||
$type = $attr->getAttribute('type');
|
||||
$array = $attr->getAttribute('array');
|
||||
$required = $attr->getAttribute('required');
|
||||
$default = $attr->getAttribute('default');
|
||||
$escapedKey = str_replace('$', '_', $key);
|
||||
$collections[$collectionId][$escapedKey] = [
|
||||
'type' => TypeMapper::fromCollectionAttribute(
|
||||
$type,
|
||||
$array,
|
||||
$required
|
||||
),
|
||||
'defaultValue' => $default,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($collections as $collectionId => $attributes) {
|
||||
$objectType = new ObjectType([
|
||||
'name' => $collectionId,
|
||||
'fields' => \array_merge(
|
||||
["_id" => ['type' => Type::string()]],
|
||||
$attributes
|
||||
),
|
||||
]);
|
||||
$attributes = \array_merge(
|
||||
$attributes,
|
||||
TypeMapper::argumentsFor('mutate')
|
||||
);
|
||||
|
||||
$queryFields[$collectionId . 'Get'] = [
|
||||
'type' => $objectType,
|
||||
'args' => TypeMapper::argumentsFor('id'),
|
||||
'resolve' => Resolvers::resolveDocumentGet(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
)
|
||||
];
|
||||
$queryFields[$collectionId . 'List'] = [
|
||||
'type' => Type::listOf($objectType),
|
||||
'args' => TypeMapper::argumentsFor('list'),
|
||||
'resolve' => Resolvers::resolveDocumentList(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
),
|
||||
'complexity' => function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
$query = Query::getByType($queries, Query::TYPE_LIMIT)[0] ?? null;
|
||||
$limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
|
||||
|
||||
return $complexity * $limit;
|
||||
},
|
||||
];
|
||||
|
||||
$mutationFields[$collectionId . 'Create'] = [
|
||||
'type' => $objectType,
|
||||
'args' => $attributes,
|
||||
'resolve' => Resolvers::resolveDocumentCreate(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Update'] = [
|
||||
'type' => $objectType,
|
||||
'args' => \array_merge(
|
||||
TypeMapper::argumentsFor('id'),
|
||||
\array_map(
|
||||
fn($attr) => $attr['type'] = Type::getNullableType($attr['type']),
|
||||
$attributes
|
||||
)
|
||||
),
|
||||
'resolve' => Resolvers::resolveDocumentUpdate(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Delete'] = [
|
||||
'type' => TypeMapper::fromResponseModel(Response::MODEL_NONE),
|
||||
'args' => TypeMapper::argumentsFor('id'),
|
||||
'resolve' => Resolvers::resolveDocumentDelete(
|
||||
$utopia,
|
||||
$dbForProject,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
)
|
||||
];
|
||||
}
|
||||
$wg->done();
|
||||
});
|
||||
$offset += $limit;
|
||||
}
|
||||
$wg->wait();
|
||||
|
||||
$schema = [
|
||||
'query' => $queryFields,
|
||||
'mutation' => $mutationFields
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue