diff --git a/app/init.php b/app/init.php index aca2eb1297..8f12868315 100644 --- a/app/init.php +++ b/app/init.php @@ -1059,6 +1059,49 @@ App::setResource('promiseAdapter', function ($register) { return $register->get('promiseAdapter'); }, ['register']); -App::setResource('schema', function ($utopia, $project, $dbForProject) { - return Schema::build($utopia, $project->getId(), $dbForProject); -}, ['utopia', 'project', 'dbForProject']); +App::setResource('schema', function ($utopia, $dbForProject) { + + $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; + }; + + $attributes = function (int $limit, int $offset) use ($dbForProject) { + $attrs = Authorization::skip(fn() => $dbForProject->find('attributes', [ + Query::limit($limit), + Query::offset($offset), + ])); + + return \array_map(function ($attr) { + return $attr->getArrayCopy(); + }, $attrs); + }; + + $urls = [ + 'list' => function (string $collectionId, array $args) { + return "/v1/database/collections/{$collectionId}/documents"; + }, + 'create' => function (string $collectionId, array $args) { + return "/v1/database/collections/{$collectionId}/documents"; + }, + 'read' => function (string $collectionId, array $args) { + return "/v1/database/collections/{$collectionId}/documents/{$args['documentId']}"; + }, + 'update' => function (string $collectionId, array $args) { + return "/v1/database/collections/{$collectionId}/documents/{$args['documentId']}"; + }, + 'delete' => function (string $collectionId, array $args) { + return "/v1/database/collections/{$collectionId}/documents/{$args['documentId']}"; + }, + ]; + + return Schema::build( + $utopia, + $complexity, + $attributes, + $urls + ); +}, ['utopia', 'dbForProject']); diff --git a/src/Appwrite/GraphQL/Resolvers.php b/src/Appwrite/GraphQL/Resolvers.php index d88e25349b..42d4e52738 100644 --- a/src/Appwrite/GraphQL/Resolvers.php +++ b/src/Appwrite/GraphQL/Resolvers.php @@ -7,8 +7,6 @@ use Appwrite\Promises\Swoole; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Utopia\App; -use Utopia\Database\Database; -use Utopia\Database\ID; use Utopia\Exception; use Utopia\Route; @@ -63,7 +61,6 @@ class Resolvers * 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 @@ -93,16 +90,17 @@ class Resolvers public static function documentGet( App $utopia, string $databaseId, - string $collectionId + string $collectionId, + callable $url ): callable { return static fn($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $type, $args) { + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { $utopia = $utopia->getResource('utopia:graphql', true); $request = $utopia->getResource('request', true); $response = $utopia->getResource('response', true); $request->setMethod('GET'); - $request->setURI("/v1/database/collections/{$collectionId}/documents/{$args['documentId']}"); + $request->setURI($url($collectionId, $args)); self::resolve($utopia, $request, $response, $resolve, $reject); } @@ -121,15 +119,16 @@ class Resolvers App $utopia, string $databaseId, string $collectionId, + callable $url ): callable { return static fn($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $type, $args) { + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { $utopia = $utopia->getResource('utopia:graphql', true); $request = $utopia->getResource('request', true); $response = $utopia->getResource('response', true); $request->setMethod('GET'); - $request->setURI("/v1/database/collections/{$collectionId}/documents"); + $request->setURI($url($collectionId, $args)); $request->setGet([ 'queries' => $args['queries'], ]); @@ -155,21 +154,22 @@ class Resolvers App $utopia, string $databaseId, string $collectionId, + callable $url ): callable { return static fn($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $type, $args) { + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { $utopia = $utopia->getResource('utopia:graphql', true); $request = $utopia->getResource('request', true); $response = $utopia->getResource('response', true); - $id = $args['id'] ?? ID::unique(); + $id = $args['id'] ?? 'unique()'; $permissions = $args['permissions'] ?? null; unset($args['id']); unset($args['permissions']); $request->setMethod('POST'); - $request->setURI("/v1/databases/$databaseId/collections/$collectionId/documents"); + $request->setURI($url($collectionId, $args)); // Order must be the same as the route params $request->setPost([ @@ -197,9 +197,10 @@ class Resolvers App $utopia, string $databaseId, string $collectionId, + callable $url ): callable { return static fn($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $type, $args) { + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { $utopia = $utopia->getResource('utopia:graphql', true); $request = $utopia->getResource('request', true); $response = $utopia->getResource('response', true); @@ -211,7 +212,7 @@ class Resolvers unset($args['permissions']); $request->setMethod('PATCH'); - $request->setURI("/v1/databases/$databaseId/collections/$collectionId/documents/$documentId"); + $request->setURI($url($collectionId, $args)); // Order must be the same as the route params $request->setPost([ @@ -238,10 +239,11 @@ class Resolvers public static function documentDelete( App $utopia, string $databaseId, - string $collectionId + string $collectionId, + callable $url ): callable { return static fn($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $type, $args) { + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { $utopia = $utopia->getResource('utopia:graphql', true); $request = $utopia->getResource('request', true); $response = $utopia->getResource('response', true); @@ -249,7 +251,7 @@ class Resolvers $documentId = $args['id']; $request->setMethod('DELETE'); - $request->setURI("/v1/databases/$databaseId/collections/$collectionId/documents/$documentId"); + $request->setURI($url($collectionId, $args)); self::resolve($utopia, $request, $response, $resolve, $reject); } diff --git a/src/Appwrite/GraphQL/Schema.php b/src/Appwrite/GraphQL/Schema.php index 09d5ee9b3b..4fa8b0450d 100644 --- a/src/Appwrite/GraphQL/Schema.php +++ b/src/Appwrite/GraphQL/Schema.php @@ -3,14 +3,10 @@ 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 @@ -23,8 +19,9 @@ class Schema */ public static function build( App $utopia, - string $projectId, - Database $dbForProject + callable $complexity, + callable $attributes, + array $urls ): GQLSchema { App::setResource('utopia:graphql', static function () use ($utopia) { return $utopia; @@ -34,8 +31,16 @@ class Schema return self::$schema; } - $api = static::api($utopia); - //$collections = static::collections($utopia, $dbForProject); + $api = static::api( + $utopia, + $complexity + ); + //$collections = static::collections( + // $utopia, + // $complexity, + // $attributes, + // $urls + //); $queries = \array_merge_recursive( $api['query'], @@ -69,7 +74,7 @@ class Schema * @return array * @throws \Exception */ - protected static function api(App $utopia): array + protected static function api(App $utopia, callable $complexity): array { Mapper::init($utopia ->getResource('response') @@ -90,7 +95,7 @@ class Schema continue; } - foreach (Mapper::route($utopia, $route) as $field) { + foreach (Mapper::route($utopia, $route, $complexity) as $field) { switch ($route->getMethod()) { case 'GET': $queries[$name] = $field; @@ -119,43 +124,37 @@ class Schema * queries and mutations for the collections they make up. * * @param App $utopia - * @param Database $dbForProject + * @param callable $getAttributes * @return array * @throws \Exception */ protected static function collections( App $utopia, - Database $dbForProject + callable $complexity, + callable $attributes, + array $urls ): 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); + while (!empty($attrs = $attributes($limit, $offset))) { foreach ($attrs as $attr) { - if ($attr->getAttribute('status') !== 'available') { + if ($attr['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'); + $databaseId = $attr['databaseId']; + $collectionId = $attr['collectionId']; + $key = $attr['key']; + $type = $attr['type']; + $array = $attr['array']; + $required = $attr['required']; + $default = $attr['default']; $escapedKey = str_replace('$', '_', $key); $collections[$collectionId][$escapedKey] = [ - 'type' => Mapper::fromCollectionAttribute( + 'type' => Mapper::attribute( $type, $array, $required @@ -174,35 +173,29 @@ class Schema ]); $attributes = \array_merge( $attributes, - Mapper::argumentsFor('mutate') + Mapper::args('mutate') ); $queryFields[$collectionId . 'Get'] = [ 'type' => $objectType, - 'args' => Mapper::argumentsFor('id'), + 'args' => Mapper::args('id'), 'resolve' => Resolvers::documentGet( $utopia, - $dbForProject, $databaseId, - $collectionId + $collectionId, + $urls['get'], ) ]; $queryFields[$collectionId . 'List'] = [ 'type' => Type::listOf($objectType), - 'args' => Mapper::argumentsFor('list'), + 'args' => Mapper::args('list'), 'resolve' => Resolvers::documentList( $utopia, - $dbForProject, $databaseId, - $collectionId + $collectionId, + $urls['list'], ), - '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; - }, + 'complexity' => $complexity, ]; $mutationFields[$collectionId . 'Create'] = [ @@ -210,15 +203,15 @@ class Schema 'args' => $attributes, 'resolve' => Resolvers::documentCreate( $utopia, - $dbForProject, $databaseId, $collectionId, + $urls['create'], ) ]; $mutationFields[$collectionId . 'Update'] = [ 'type' => $objectType, 'args' => \array_merge( - Mapper::argumentsFor('id'), + Mapper::args('id'), \array_map( fn($attr) => $attr['type'] = Type::getNullableType($attr['type']), $attributes @@ -226,19 +219,19 @@ class Schema ), 'resolve' => Resolvers::documentUpdate( $utopia, - $dbForProject, $databaseId, $collectionId, + $urls['update'], ) ]; $mutationFields[$collectionId . 'Delete'] = [ - 'type' => Mapper::fromResponseModel(Response::MODEL_NONE), - 'args' => Mapper::argumentsFor('id'), + 'type' => Mapper::model('none'), + 'args' => Mapper::args('id'), 'resolve' => Resolvers::documentDelete( $utopia, - $dbForProject, $databaseId, - $collectionId + $collectionId, + $urls['delete'], ) ]; }