appwrite/src/Appwrite/GraphQL/Schema.php

281 lines
8.7 KiB
PHP
Raw Normal View History

2022-10-12 01:04:11 +00:00
<?php
namespace Appwrite\GraphQL;
use Appwrite\GraphQL\Types\Mapper;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as GQLSchema;
2024-06-10 16:56:29 +00:00
use Appwrite\Utopia\Response;
use Utopia\DI\Container;
use Utopia\Http\Response as UtopiaHttpResponse;
use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse;
2022-10-17 00:27:15 +00:00
use Utopia\Exception;
2024-03-08 12:57:20 +00:00
use Utopia\Http\Http;
2024-03-07 14:29:42 +00:00
use Utopia\Http\Route;
2024-06-10 16:56:29 +00:00
use Utopia\Http\Request;
2022-10-12 01:04:11 +00:00
class Schema
{
2024-06-10 16:56:29 +00:00
protected ?GQLSchema $schema = null;
protected array $dirty = [];
2022-10-12 01:04:11 +00:00
/**
2022-10-17 00:27:15 +00:00
*
2024-06-10 16:56:29 +00:00
* @param Http $http
* @param callable $complexity Function to calculate complexity
* @param callable $attributes Function to get attributes
* @param array $urls Array of functions to get urls for specific method types
* @param array $params Array of functions to build parameters for specific method types
2022-10-17 00:27:15 +00:00
* @return GQLSchema
* @throws Exception
2022-10-12 01:04:11 +00:00
*/
2024-06-10 16:56:29 +00:00
public function build(
Http $http,
Request $request,
UtopiaHttpResponse $response,
Container $container,
callable $complexity,
callable $attributes,
2022-10-17 00:27:15 +00:00
array $urls,
array $params,
2022-10-12 01:04:11 +00:00
): GQLSchema {
2024-06-10 16:56:29 +00:00
if (!empty($this->schema)) {
return $this->schema;
2022-10-12 01:04:11 +00:00
}
2024-06-10 16:56:29 +00:00
$api = $this->api(
$http,
$request,
$response,
$container,
$complexity
);
2024-06-10 16:56:29 +00:00
// $collections = $this->collections(
// $http,
// $complexity,
// $request,
// $response,
// $attributes,
// $urls,
// $params,
// );
2022-10-12 01:04:11 +00:00
$queries = \array_merge_recursive(
$api['query'],
2024-06-10 16:56:29 +00:00
//$collections['query']
2022-10-12 01:04:11 +00:00
);
$mutations = \array_merge_recursive(
$api['mutation'],
2024-06-10 16:56:29 +00:00
//$collections['mutation']
2022-10-12 01:04:11 +00:00
);
\ksort($queries);
\ksort($mutations);
2024-06-10 16:56:29 +00:00
return $this->schema = new GQLSchema([
2022-10-12 01:04:11 +00:00
'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.
*
2024-06-10 16:56:29 +00:00
* @param Http $http
* @param Request $request
* @param UtopiaSwooleResponse $response
2022-12-08 00:10:35 +00:00
* @param callable $complexity
2022-10-12 01:04:11 +00:00
* @return array
2024-06-10 16:56:29 +00:00
* @throws \Exception
2022-10-12 01:04:11 +00:00
*/
2024-06-10 16:56:29 +00:00
protected function api(Http $http, Request $request, UtopiaHttpResponse $response, Container $container, callable $complexity): array
2022-10-12 01:04:11 +00:00
{
2024-06-10 16:56:29 +00:00
Mapper::init((new Response($response))->getModels());
$mapper = new Mapper();
2022-10-12 01:04:11 +00:00
$queries = [];
$mutations = [];
2024-06-10 16:56:29 +00:00
foreach ($http->getRoutes() as $routes) {
2022-10-12 01:04:11 +00:00
foreach ($routes as $route) {
/** @var Route $route */
$namespace = $route->getLabel('sdk.namespace', '');
2024-02-25 10:13:28 +00:00
$method = $route->getLabel('sdk.method', '');
$name = $namespace . \ucfirst($method);
2022-10-12 01:04:11 +00:00
2024-02-25 10:13:28 +00:00
if (empty($name)) {
continue;
2022-10-12 01:04:11 +00:00
}
2024-06-10 16:56:29 +00:00
foreach ($mapper->route($http, $route, $request, $response, $container, $complexity) as $field) {
2024-02-25 10:13:28 +00:00
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()}");
2022-10-12 01:04:11 +00:00
}
}
}
}
return [
'query' => $queries,
'mutation' => $mutations
];
}
/**
* Iterates all of a projects attributes and builds GraphQL
* queries and mutations for the collections they make up.
*
2024-06-10 16:56:29 +00:00
* @param Http $http
2022-12-08 00:10:35 +00:00
* @param callable $complexity
* @param callable $attributes
* @param array $urls
* @param array $params
2022-10-12 01:04:11 +00:00
* @return array
* @throws \Exception
*/
protected static function collections(
2024-06-10 16:56:29 +00:00
Http $http,
callable $complexity,
callable $attributes,
2022-10-17 00:27:15 +00:00
array $urls,
array $params,
2022-10-12 01:04:11 +00:00
): array {
$collections = [];
$queryFields = [];
$mutationFields = [];
$limit = 1000;
$offset = 0;
while (!empty($attrs = $attributes($limit, $offset))) {
2022-10-12 01:04:11 +00:00
foreach ($attrs as $attr) {
if ($attr['status'] !== 'available') {
2022-10-12 01:04:11 +00:00
continue;
}
$databaseId = $attr['databaseId'];
$collectionId = $attr['collectionId'];
$key = $attr['key'];
$type = $attr['type'];
$array = $attr['array'];
$required = $attr['required'];
$default = $attr['default'];
2022-10-21 00:53:54 +00:00
$escapedKey = str_replace('$', '', $key);
2022-10-12 01:04:11 +00:00
$collections[$collectionId][$escapedKey] = [
'type' => Mapper::attribute(
2022-10-12 01:04:11 +00:00
$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::args('mutate')
2022-10-12 01:04:11 +00:00
);
$queryFields[$collectionId . 'Get'] = [
'type' => $objectType,
'args' => Mapper::args('id'),
2022-10-12 01:04:11 +00:00
'resolve' => Resolvers::documentGet(
2024-06-10 16:56:29 +00:00
$http,
2022-10-12 01:04:11 +00:00
$databaseId,
$collectionId,
$urls['get'],
2022-10-12 01:04:11 +00:00
)
];
$queryFields[$collectionId . 'List'] = [
'type' => Type::listOf($objectType),
'args' => Mapper::args('list'),
2022-10-12 01:04:11 +00:00
'resolve' => Resolvers::documentList(
2024-06-10 16:56:29 +00:00
$http,
2022-10-12 01:04:11 +00:00
$databaseId,
$collectionId,
$urls['list'],
2022-10-17 00:27:15 +00:00
$params['list'],
2022-10-12 01:04:11 +00:00
),
'complexity' => $complexity,
2022-10-12 01:04:11 +00:00
];
$mutationFields[$collectionId . 'Create'] = [
'type' => $objectType,
'args' => $attributes,
'resolve' => Resolvers::documentCreate(
2024-06-10 16:56:29 +00:00
$http,
2022-10-12 01:04:11 +00:00
$databaseId,
$collectionId,
$urls['create'],
2022-10-17 00:27:15 +00:00
$params['create'],
2022-10-12 01:04:11 +00:00
)
];
$mutationFields[$collectionId . 'Update'] = [
'type' => $objectType,
'args' => \array_merge(
Mapper::args('id'),
2022-10-12 01:04:11 +00:00
\array_map(
2024-06-10 16:56:29 +00:00
fn($attr) => $attr['type'] = Type::getNullableType($attr['type']),
2022-10-12 01:04:11 +00:00
$attributes
)
),
'resolve' => Resolvers::documentUpdate(
2024-06-10 16:56:29 +00:00
$http,
2022-10-12 01:04:11 +00:00
$databaseId,
$collectionId,
$urls['update'],
2022-10-17 00:27:15 +00:00
$params['update'],
2022-10-12 01:04:11 +00:00
)
];
$mutationFields[$collectionId . 'Delete'] = [
'type' => Mapper::model('none'),
'args' => Mapper::args('id'),
2022-10-12 01:04:11 +00:00
'resolve' => Resolvers::documentDelete(
2024-06-10 16:56:29 +00:00
$http,
2022-10-12 01:04:11 +00:00
$databaseId,
$collectionId,
$urls['delete'],
2022-10-12 01:04:11 +00:00
)
];
}
$offset += $limit;
}
return [
'query' => $queryFields,
'mutation' => $mutationFields
];
}
2024-06-10 16:56:29 +00:00
public function setDirty(string $projectId): void
{
2024-06-10 16:56:29 +00:00
$this->dirty[$projectId] = true;
}
2022-10-12 01:04:11 +00:00
}