appwrite/src/Appwrite/GraphQL/Builder.php

724 lines
26 KiB
PHP
Raw Normal View History

2021-03-10 07:42:45 +00:00
<?php
namespace Appwrite\GraphQL;
use Appwrite\GraphQL\Types\JsonType;
use Appwrite\Utopia\Request;
2021-03-10 07:42:45 +00:00
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
2022-04-26 07:49:36 +00:00
use Swoole\Coroutine\WaitGroup;
2022-04-07 06:41:36 +00:00
use Utopia\App;
2021-03-18 19:26:45 +00:00
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
2022-04-12 06:47:40 +00:00
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
2022-04-20 10:30:48 +00:00
use Utopia\Route;
2022-04-08 07:04:15 +00:00
use Utopia\Validator;
2021-03-10 07:42:45 +00:00
use function Co\go;
2022-04-26 07:49:36 +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 = [];
2022-05-04 06:42:12 +00:00
protected static array $defaultDocumentArgs = [];
2021-03-10 07:42:45 +00:00
/**
2022-04-08 07:04:15 +00:00
* Initialise the typeMapping array with the base cases of the recursion
2022-04-05 13:48:51 +00:00
*
* @return void
*/
2022-04-20 10:30:48 +00:00
public static function init(): void
{
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(),
Model::TYPE_JSON => self::json(),
2021-03-17 21:17:50 +00:00
Response::MODEL_NONE => self::json(),
Response::MODEL_ANY => self::json(),
2021-03-10 07:42:45 +00:00
];
2022-05-04 06:42:12 +00:00
self::$defaultDocumentArgs = [
'id' => [
'id' => [
'type' => Type::string(),
],
],
'list' => [
'limit' => [
'type' => Type::int(),
'defaultValue' => 25,
],
'offset' => [
'type' => Type::int(),
'defaultValue' => 0,
],
'cursor' => [
'type' => Type::string(),
'defaultValue' => '',
],
'cursorDirection' => [
'type' => Type::string(),
'defaultValue' => Database::CURSOR_AFTER,
],
'orderAttributes' => [
'type' => Type::listOf(Type::string()),
'defaultValue' => [],
],
'orderType' => [
'type' => Type::listOf(Type::string()),
'defaultValue' => [],
],
],
'mutate' => [
'read' => [
'type' => Type::listOf(Type::string()),
'defaultValue' => ["role:member"],
],
'write' => [
'type' => Type::listOf(Type::string()),
'defaultValue' => ["role:member"],
],
],
];
2021-03-10 07:42:45 +00:00
}
/**
2022-04-08 07:04:15 +00:00
* Create a singleton for $jsonParser
2022-04-05 13:48:51 +00:00
*
* @return JsonType
*/
2022-04-07 06:41:36 +00:00
public static function json(): JsonType
{
2021-03-16 20:05:48 +00:00
if (is_null(self::$jsonParser)) {
self::$jsonParser = new JsonType();
}
return self::$jsonParser;
}
/**
2022-04-08 07:04:15 +00:00
* Create a GraphQL type from a Utopia Model
2022-04-05 13:48:51 +00:00
*
* @param Model $model
* @param Response $response
* @return Type
*/
2022-04-08 07:04:15 +00:00
private static function getModelTypeMapping(Model $model, Response $response): Type
{
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 = [];
2022-04-05 13:48:51 +00:00
if ($model->isAny()) {
$fields['data'] = [
'type' => Type::string(),
'description' => 'Data field',
'resolve' => fn($object, $args, $context, $info) => json_encode($object, JSON_FORCE_OBJECT),
];
}
2021-03-10 07:42:45 +00:00
foreach ($rules as $key => $props) {
2022-04-07 06:41:36 +00:00
$escapedKey = str_replace('$', '_', $key);
$types = \is_array($props['type'])
? $props['type']
: [$props['type']];
foreach ($types as $type) {
if (isset(self::$typeMapping[$type])) {
$type = self::$typeMapping[$type];
} else {
try {
$complexModel = $response->getModel($type);
2022-04-08 07:04:15 +00:00
$type = self::getModelTypeMapping($complexModel, $response);
} catch (\Exception $e) {
Console::error("Could not find model for : {$type}");
}
2021-03-10 07:42:45 +00:00
}
2022-04-07 06:41:36 +00:00
if ($props['array']) {
$type = Type::listOf($type);
2021-03-10 07:42:45 +00:00
}
$fields[$escapedKey] = [
'type' => $type,
'description' => $props['description'],
2022-05-02 07:44:59 +00:00
'resolve' => fn($object, $args, $context, $info) => $object[$key],
];
}
2021-03-10 07:42:45 +00:00
}
$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
/**
2022-04-08 07:04:15 +00:00
* Map a Utopia\Validator to a valid GraphQL Type
2022-04-05 13:48:51 +00:00
*
2022-04-08 07:04:15 +00:00
* @param App $utopia
2022-04-12 06:47:40 +00:00
* @param Validator|callable $validator
* @param bool $required
2022-04-08 07:04:15 +00:00
* @param array $injections
2022-04-07 06:41:36 +00:00
* @return Type
* @throws \Exception
2022-04-05 13:48:51 +00:00
*/
2022-04-08 07:04:15 +00:00
private static function getParameterArgType(
App $utopia,
2022-04-08 07:04:15 +00:00
Validator|callable $validator,
bool $required,
array $injections
): Type {
2022-04-07 06:41:36 +00:00
$validator = \is_callable($validator)
? \call_user_func_array($validator, $utopia->getResources($injections))
: $validator;
2021-03-10 07:42:45 +00:00
switch ((!empty($validator)) ? \get_class($validator) : '') {
2022-04-05 13:48:51 +00:00
case 'Appwrite\Auth\Validator\Password':
2022-04-08 07:04:15 +00:00
case 'Appwrite\Network\Validator\CNAME':
case 'Appwrite\Network\Validator\Domain':
case 'Appwrite\Network\Validator\Email':
case 'Appwrite\Network\Validator\Host':
case 'Appwrite\Network\Validator\IP':
case 'Appwrite\Network\Validator\Origin':
case 'Appwrite\Network\Validator\URL':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
2022-04-05 13:48:51 +00:00
case 'Appwrite\Storage\Validator\File':
2022-04-08 07:04:15 +00:00
case 'Utopia\Database\Validator\Key':
case 'Utopia\Database\Validator\CustomId':
case 'Utopia\Database\Validator\UID':
case 'Utopia\Storage\Validator\File':
case 'Utopia\Validator\File':
case 'Utopia\Validator\HexColor':
case 'Utopia\Validator\Length':
2021-03-10 07:42:45 +00:00
case 'Utopia\Validator\Text':
2022-04-08 07:04:15 +00:00
case 'Utopia\Validator\WhiteList':
2021-03-10 07:42:45 +00:00
$type = Type::string();
break;
case 'Utopia\Validator\Boolean':
$type = Type::boolean();
break;
case 'Utopia\Validator\ArrayList':
2022-05-04 06:43:51 +00:00
$type = Type::listOf(self::getParameterArgType(
$utopia,
$validator->getValidator(),
$required,
$injections
));
2021-03-10 07:42:45 +00:00
break;
case 'Utopia\Validator\Numeric':
2022-04-08 07:04:15 +00:00
case 'Utopia\Validator\Integer':
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-08 07:04:15 +00:00
case 'Utopia\Validator\FloatValidator':
$type = Type::float();
break;
case 'Utopia\Database\Validator\Authorization':
case 'Utopia\Database\Validator\Permissions':
$type = Type::listOf(Type::string());
break;
2022-04-05 13:48:51 +00:00
case 'Utopia\Validator\Assoc':
case 'Utopia\Validator\JSON':
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-08 07:04:15 +00:00
* Map an Attribute type to a valid GraphQL Type
*
2022-04-08 07:04:15 +00:00
* @param string $type
* @param bool $array
* @param bool $required
* @return Type
* @throws \Exception
*/
2022-04-08 07:04:15 +00:00
private static function getAttributeArgType(string $type, bool $array, bool $required): Type
{
if ($array) {
return Type::listOf(self::getAttributeArgType($type, false, $required));
}
$type = match ($type) {
'boolean' => Type::boolean(),
'integer' => Type::int(),
'double' => Type::float(),
default => Type::string(),
};
if ($required) {
$type = Type::nonNull($type);
}
return $type;
}
2022-04-07 06:41:36 +00:00
/**
* @throws \Exception
*/
2022-05-04 06:44:45 +00:00
public static function buildSchema(
App $utopia,
Request $request,
2022-04-12 06:47:40 +00:00
Response $response,
2022-05-04 06:44:45 +00:00
Database $dbForProject,
Document $user,
): Schema {
2022-05-04 06:44:45 +00:00
$apiSchema = self::buildAPISchema($utopia, $request, $response);
$db = self::buildCollectionsSchema($utopia, $request, $response, $dbForProject, $user);
2022-04-05 13:48:51 +00:00
2022-04-08 07:04:15 +00:00
$queryFields = \array_merge_recursive($apiSchema['query'], $db['query']);
$mutationFields = \array_merge_recursive($apiSchema['mutation'], $db['mutation']);
2022-04-05 13:48:51 +00:00
ksort($queryFields);
ksort($mutationFields);
2022-04-08 07:04:15 +00:00
return new Schema([
2022-04-05 13:48:51 +00:00
'query' => new ObjectType([
'name' => 'Query',
'fields' => $queryFields
]),
'mutation' => new ObjectType([
'name' => 'Mutation',
'fields' => $mutationFields
])
]);
}
/**
2022-05-04 06:44:45 +00:00
* This function iterates all API routes and builds a
* GraphQL schema defining types (and resolvers) for all response models
*
* @param App $utopia
* @param Request $request
* @param Response $response
* @return array
* @throws \Exception
*/
2022-05-04 06:44:45 +00:00
public static function buildAPISchema(App $utopia, Request $request, Response $response): array
{
2022-05-04 06:44:45 +00:00
$start = microtime(true);
2022-05-04 06:44:45 +00:00
self::init();
$queryFields = [];
$mutationFields = [];
2022-05-04 06:44:45 +00:00
foreach ($utopia->getRoutes() as $method => $routes) {
foreach ($routes as $route) {
/** @var Route $route */
2022-05-04 06:44:45 +00:00
if (str_starts_with($route->getPath(), '/v1/mock/')) {
continue;
}
2022-06-27 06:46:01 +00:00
2022-05-04 06:44:45 +00:00
$namespace = $route->getLabel('sdk.namespace', '');
$methodName = $namespace . \ucfirst($route->getLabel('sdk.method', ''));
$responseModelNames = $route->getLabel('sdk.response.model', "none");
$responseModels = \is_array($responseModelNames)
? \array_map(static fn($m) => $response->getModel($m), $responseModelNames)
: [$response->getModel($responseModelNames)];
foreach ($responseModels as $responseModel) {
$type = self::getModelTypeMapping($responseModel, $response);
$description = $route->getDesc();
$args = [];
foreach ($route->getParams() as $key => $value) {
$argType = self::getParameterArgType(
$utopia,
$value['validator'],
!$value['optional'],
$value['injections']
);
$args[$key] = [
'type' => $argType,
'description' => $value['description'],
'defaultValue' => $value['default']
];
}
$resolve = self::resolveAPIRequest($utopia, $request, $response, $route);
$field = [
'type' => $type,
'description' => $description,
'args' => $args,
'resolve' => $resolve
];
2022-06-27 06:46:01 +00:00
switch ($method) {
case 'GET':
$queryFields[$methodName] = $field;
break;
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE':
$mutationFields[$methodName] = $field;
break;
default:
throw new \Exception("Unsupported method: $method");
2022-05-04 06:44:45 +00:00
}
}
}
}
$time_elapsed_secs = (microtime(true) - $start) * 1000;
Console::info("[INFO] Built GraphQL REST API Schema in ${time_elapsed_secs}ms");
return [
'query' => $queryFields,
'mutation' => $mutationFields
];
}
/**
* @param App $utopia
* @param Response $response
* @param Request $request
* @param mixed $route
* @return callable
*/
private static function resolveAPIRequest(
App $utopia,
Request $request,
2022-05-04 06:44:45 +00:00
Response $response,
mixed $route,
): callable {
2022-05-04 06:44:45 +00:00
return fn($type, $args, $context, $info) => new CoroutinePromise(
function (callable $resolve, callable $reject) use ($utopia, $request, $response, $route, $args, $context, $info) {
// Mutate the original request object to match route
$swoole = $request->getSwoole();
$swoole->server['request_method'] = $route->getMethod();
$swoole->server['request_uri'] = $route->getPath();
$swoole->server['path_info'] = $route->getPath();
2022-06-30 09:22:54 +00:00
switch ($route->getMethod()) {
case 'GET':
$swoole->get = $args;
break;
default:
$swoole->post = $args;
break;
}
2022-05-04 06:44:45 +00:00
self::resolve($utopia, $swoole, $response, $resolve, $reject);
}
);
}
2021-11-25 08:04:39 +00:00
/**
2022-04-08 07:04:15 +00:00
* This function iterates all a projects attributes and builds
* GraphQL queries and mutations for the collections they make up.
2021-11-25 08:04:39 +00:00
*
2022-04-12 06:47:40 +00:00
* @param App $utopia
* @param Request $request
* @param Response $response
* @param Database $dbForProject
* @param Document|null $user
2022-04-05 13:48:51 +00:00
* @return array
* @throws \Exception
2021-11-25 08:04:39 +00:00
*/
2022-04-08 07:04:15 +00:00
public static function buildCollectionsSchema(
App $utopia,
Request $request,
Response $response,
Database $dbForProject,
2022-04-26 07:49:36 +00:00
?Document $user = null,
): array {
2021-11-25 08:04:39 +00:00
$start = microtime(true);
2022-04-05 13:48:51 +00:00
2022-04-26 07:49:36 +00:00
$userId = $user?->getId();
$collections = [];
2022-04-05 13:48:51 +00:00
$queryFields = [];
$mutationFields = [];
2022-04-26 07:49:36 +00:00
$limit = 1000;
$offset = 0;
2022-05-04 06:47:34 +00:00
$count = 0;
2022-05-03 05:39:47 +00:00
2022-04-26 07:49:36 +00:00
$wg = new WaitGroup();
2022-04-08 11:08:20 +00:00
while (
!empty($attrs = Authorization::skip(fn() => $dbForProject->find(
'attributes',
limit: $limit,
offset: $offset
)))
) {
2022-04-26 07:49:36 +00:00
$wg->add();
2022-05-04 06:47:34 +00:00
$count += count($attrs);
2022-04-26 07:49:36 +00:00
go(function () use ($utopia, $request, $response, $dbForProject, &$collections, &$queryFields, &$mutationFields, $limit, &$offset, $attrs, $userId, $wg) {
foreach ($attrs as $attr) {
$collectionId = $attr->getAttribute('collectionId');
2022-05-04 06:47:34 +00:00
2022-04-26 07:49:36 +00:00
if ($attr->getAttribute('status') !== 'available') {
continue;
}
$key = $attr->getAttribute('key');
$type = $attr->getAttribute('type');
$array = $attr->getAttribute('array');
$required = $attr->getAttribute('required');
$escapedKey = str_replace('$', '_', $key);
$collections[$collectionId][$escapedKey] = [
'type' => self::getAttributeArgType($type, $array, $required),
];
2022-04-08 11:08:20 +00:00
}
2022-04-26 07:49:36 +00:00
foreach ($collections as $collectionId => $attributes) {
$objectType = new ObjectType([
'name' => $collectionId,
'fields' => $attributes
]);
2022-05-04 06:42:12 +00:00
$attributes = \array_merge(
$attributes,
self::$defaultDocumentArgs['mutate']
);
2022-04-08 11:08:20 +00:00
2022-04-26 07:49:36 +00:00
$queryFields[$collectionId . 'Get'] = [
'type' => $objectType,
2022-05-04 06:42:12 +00:00
'args' => self::$defaultDocumentArgs['id'],
2022-05-04 06:47:34 +00:00
'resolve' => self::resolveDocumentGet($utopia, $request, $response, $dbForProject, $collectionId)
2022-04-26 07:49:36 +00:00
];
$queryFields[$collectionId . 'List'] = [
'type' => $objectType,
2022-05-04 06:42:12 +00:00
'args' => self::$defaultDocumentArgs['list'],
2022-05-04 06:47:34 +00:00
'resolve' => self::resolveDocumentList($utopia, $request, $response, $dbForProject, $collectionId)
2022-04-26 07:49:36 +00:00
];
$mutationFields[$collectionId . 'Create'] = [
'type' => $objectType,
'args' => $attributes,
2022-05-04 06:47:34 +00:00
'resolve' => self::resolveDocumentMutate($utopia, $request, $response, $dbForProject, $collectionId, 'POST')
2022-04-26 07:49:36 +00:00
];
$mutationFields[$collectionId . 'Update'] = [
'type' => $objectType,
'args' => $attributes,
2022-05-04 06:47:34 +00:00
'resolve' => self::resolveDocumentMutate($utopia, $request, $response, $dbForProject, $collectionId, 'PATCH')
2022-04-26 07:49:36 +00:00
];
$mutationFields[$collectionId . 'Delete'] = [
'type' => $objectType,
2022-05-04 06:42:12 +00:00
'args' => self::$defaultDocumentArgs['id'],
2022-05-04 06:47:34 +00:00
'resolve' => self::resolveDocumentDelete($utopia, $request, $response, $dbForProject, $collectionId)
2022-04-26 07:49:36 +00:00
];
}
$wg->done();
});
2022-04-08 11:08:20 +00:00
$offset += $limit;
}
2022-04-26 07:49:36 +00:00
$wg->wait();
2022-05-04 06:47:34 +00:00
2022-04-08 07:04:15 +00:00
$time_elapsed_secs = (microtime(true) - $start) * 1000;
2022-06-27 06:46:01 +00:00
Console::info('[INFO] Built GraphQL Project Collection Schema (' . $count . ' attributes) in ' . $time_elapsed_secs . 'ms');
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-05-04 06:46:43 +00:00
private static function resolveDocumentGet(
App $utopia,
Request $request,
2022-04-12 06:47:40 +00:00
Response $response,
Database $dbForProject,
string $collectionId
): callable {
2022-04-08 07:04:15 +00:00
return fn($type, $args, $context, $info) => new CoroutinePromise(
2022-04-12 06:47:40 +00:00
function (callable $resolve, callable $reject) use ($utopia, $request, $response, $dbForProject, $collectionId, $type, $args) {
try {
2022-05-04 06:46:43 +00:00
$swoole = $request->getSwoole();
$swoole->post = [
2022-04-12 06:47:40 +00:00
'collectionId' => $collectionId,
'documentId' => $args['id'],
];
2022-05-04 06:46:43 +00:00
$swoole->server['request_method'] = 'GET';
$swoole->server['request_uri'] = "/v1/database/collections/$collectionId/documents/{$args['id']}";
$swoole->server['path_info'] = "/v1/database/collections/$collectionId/documents/{$args['id']}";
2022-05-03 05:40:12 +00:00
2022-05-04 06:46:43 +00:00
self::resolve($utopia, $swoole, $response, $resolve, $reject);
} catch (\Throwable $e) {
$reject($e);
2022-05-04 06:46:43 +00:00
return;
}
}
);
}
2022-05-04 06:47:04 +00:00
private static function resolveDocumentList(
App $utopia,
Request $request,
2022-05-03 05:40:26 +00:00
Response $response,
Database $dbForProject,
string $collectionId
): callable {
2022-04-08 07:04:15 +00:00
return fn($type, $args, $context, $info) => new CoroutinePromise(
2022-05-03 05:40:26 +00:00
function (callable $resolve, callable $reject) use ($utopia, $request, $response, $dbForProject, $collectionId, $type, $args) {
2022-05-04 06:47:04 +00:00
$swoole = $request->getSwoole();
$swoole->post = [
2022-05-03 05:40:26 +00:00
'collectionId' => $collectionId,
'limit' => $args['limit'],
'offset' => $args['offset'],
'cursor' => $args['cursor'],
'cursorDirection' => $args['cursorDirection'],
'orderAttributes' => $args['orderAttributes'],
'orderType' => $args['orderType'],
];
2022-05-04 06:47:04 +00:00
$swoole->server['request_method'] = 'GET';
$swoole->server['request_uri'] = "/v1/database/collections/$collectionId/documents";
$swoole->server['path_info'] = "/v1/database/collections/$collectionId/documents";
2022-05-03 05:40:26 +00:00
2022-05-04 06:47:04 +00:00
self::resolve($utopia, $swoole, $response, $resolve, $reject);
}
);
}
2022-05-04 06:47:34 +00:00
private static function resolveDocumentMutate(
App $utopia,
Request $request,
2022-05-03 05:40:44 +00:00
Response $response,
Database $dbForProject,
string $collectionId,
string $method,
): callable {
2022-04-08 07:04:15 +00:00
return fn($type, $args, $context, $info) => new CoroutinePromise(
function (callable $resolve, callable $reject) use ($utopia, $request, $response, $dbForProject, $collectionId, $method, $type, $args) {
$swoole = $request->getSwoole();
2022-05-03 05:40:44 +00:00
$id = $args['id'] ?? 'unique()';
$read = $args['read'];
$write = $args['write'];
unset($args['id']);
unset($args['read']);
unset($args['write']);
2022-05-03 05:40:44 +00:00
// Order must be the same as the route params
$swoole->post = [
2022-05-03 05:40:44 +00:00
'documentId' => $id,
'collectionId' => $collectionId,
'data' => $args,
2022-05-03 05:40:44 +00:00
'read' => $read,
'write' => $write,
];
$swoole->server['request_method'] = $method;
$swoole->server['request_uri'] = "/v1/database/collections/$collectionId/documents";
$swoole->server['path_info'] = "/v1/database/collections/$collectionId/documents";
self::resolve($utopia, $swoole, $response, $resolve, $reject);
}
);
}
2022-05-04 06:47:34 +00:00
private static function resolveDocumentDelete(
App $utopia,
Request $request,
2022-05-03 05:41:13 +00:00
Response $response,
Database $dbForProject,
string $collectionId
): callable {
2022-04-08 07:04:15 +00:00
return fn($type, $args, $context, $info) => new CoroutinePromise(
2022-05-03 05:41:13 +00:00
function (callable $resolve, callable $reject) use ($utopia, $request, $response, $dbForProject, $collectionId, $type, $args) {
2022-05-04 06:45:37 +00:00
$swoole = $request->getSwoole();
$swoole->post = [
2022-05-03 05:41:13 +00:00
'collectionId' => $collectionId,
'documentId' => $args['id'],
];
2022-05-04 06:45:37 +00:00
$swoole->server['request_method'] = 'DELETE';
$swoole->server['request_uri'] = "/v1/database/collections/$collectionId/documents/{$args['id']}";
$swoole->server['path_info'] = "/v1/database/collections/$collectionId/documents/{$args['id']}";
2022-05-03 05:41:13 +00:00
2022-05-04 06:45:37 +00:00
self::resolve($utopia, $swoole, $response, $resolve, $reject);
}
);
}
/**
* @param App $utopia
2022-05-04 06:47:34 +00:00
* @param \Swoole\Http\Request $swoole
2022-04-07 06:41:36 +00:00
* @param Response $response
2022-05-04 06:47:34 +00:00
* @param callable $resolve
* @param callable $reject
* @return void
* @throws \Utopia\Exception
2022-04-05 13:48:51 +00:00
*/
2022-05-04 06:45:14 +00:00
private static function resolve(
App $utopia,
2022-05-04 06:45:14 +00:00
\Swoole\Http\Request $swoole,
Response $response,
callable $resolve,
callable $reject,
): void {
2022-05-04 06:45:14 +00:00
// Drop json content type so post args are used directly
if (
\array_key_exists('content-type', $swoole->header)
&& $swoole->header['content-type'] === 'application/json'
) {
2022-05-04 06:45:14 +00:00
unset($swoole->header['content-type']);
}
2021-03-18 19:26:45 +00:00
$gqlResponse = $response;
2022-05-04 06:45:14 +00:00
$request = new Request($swoole);
$apiResponse = new Response($response->getSwoole());
$apiResponse->setContentType(Response::CONTENT_TYPE_NULL);
2022-05-04 06:45:14 +00:00
$utopia->setResource('request', fn() => $request);
$utopia->setResource('response', fn() => $apiResponse);
2022-05-04 06:45:14 +00:00
try {
// Set route to null so match doesn't early return the GraphQL route
// Then get the route match the request so path matches are populated
$route = $utopia->setRoute(null)->match($request);
2022-04-12 06:47:40 +00:00
2022-05-04 06:45:14 +00:00
$utopia->execute($route, $request);
} catch (\Throwable $e) {
$gqlResponse->setStatusCode($apiResponse->getStatusCode());
2022-05-04 06:45:14 +00:00
$reject($e);
return;
}
2022-04-12 06:47:40 +00:00
$result = $apiResponse->getPayload();
2022-04-12 06:47:40 +00:00
$gqlResponse->setContentType($apiResponse->getContentType());
$gqlResponse->setStatusCode($apiResponse->getStatusCode());
if ($apiResponse->getStatusCode() < 200 || $apiResponse->getStatusCode() >= 400) {
$reject(new GQLException($result['message'], $apiResponse->getStatusCode()));
2022-05-04 06:45:14 +00:00
return;
2021-03-10 07:42:45 +00:00
}
2022-04-05 13:48:51 +00:00
// Add headers and cookies from inner to outer response
// TODO: Add setters to response to allow setting entire array at once
foreach ($apiResponse->getHeaders() as $key => $value) {
$gqlResponse->addHeader($key, $value);
}
foreach ($apiResponse->getCookies() as $name => $cookie) {
$gqlResponse->addCookie($name, $cookie['value'], $cookie['expire'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
}
2022-05-04 06:45:14 +00:00
$resolve($result);
2021-03-18 18:55:43 +00:00
}
2021-03-10 07:42:45 +00:00
}