WIP input file handling
This commit is contained in:
parent
81265ab458
commit
67807f8c6f
9 changed files with 125 additions and 30 deletions
|
@ -11,8 +11,10 @@ use GraphQL\Validator\Rules\QueryComplexity;
|
|||
use GraphQL\Validator\Rules\QueryDepth;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Storage\Validator\File;
|
||||
|
||||
App::get('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
|
@ -55,6 +57,9 @@ App::post('/v1/graphql')
|
|||
->param('query', '', new Text(1024), 'The query to execute. Max 1024 chars.', true)
|
||||
->param('operationName', null, new Text(256), 'Name of the operation to execute', true)
|
||||
->param('variables', [], new JSON(), 'Variables to use in the operation', true)
|
||||
->param('operations', '', new Text(1024), 'Variables to use in the operation', true)
|
||||
->param('map', '', new Text(1024), 'Variables to use in the operation', true)
|
||||
->param('files', [], new ArrayList(new File()), 'Files to upload. Use a path that is relative to the current directory.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('promiseAdapter')
|
||||
|
@ -69,6 +74,9 @@ function graphqlRequest(
|
|||
string $query,
|
||||
?string $operationName,
|
||||
?array $variables,
|
||||
?string $operations,
|
||||
?string $map,
|
||||
?array $files,
|
||||
Appwrite\Utopia\Request $request,
|
||||
Appwrite\Utopia\Response $response,
|
||||
CoroutinePromiseAdapter $promiseAdapter,
|
||||
|
@ -97,6 +105,23 @@ function graphqlRequest(
|
|||
$debugFlags = DebugFlag::NONE;
|
||||
}
|
||||
|
||||
$map = \json_decode($map, true);
|
||||
$result = \json_decode($operations, true);
|
||||
if ($request->getHeader('content-type') === 'multipart/form-data') {
|
||||
foreach ($map as $fileKey => $locations) {
|
||||
foreach ($locations as $location) {
|
||||
$items = &$result;
|
||||
foreach (explode('.', $location) as $key) {
|
||||
if (!isset($items[$key]) || !is_array($items[$key])) {
|
||||
$items[$key] = [];
|
||||
}
|
||||
$items = &$items[$key];
|
||||
}
|
||||
$items = $request->getFiles($fileKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$promise = GraphQL::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$gqlSchema,
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Types\JsonType;
|
||||
use Appwrite\GraphQL\Types\InputFile;
|
||||
use Appwrite\GraphQL\Types\Json;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
@ -23,7 +24,8 @@ use function Co\go;
|
|||
|
||||
class Builder
|
||||
{
|
||||
protected static ?JsonType $jsonParser = null;
|
||||
protected static ?Json $jsonType = null;
|
||||
protected static ?InputFile $inputFile = null;
|
||||
|
||||
protected static array $typeMapping = [];
|
||||
protected static array $defaultDocumentArgs = [];
|
||||
|
@ -90,17 +92,20 @@ class Builder
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a singleton for $jsonParser
|
||||
*
|
||||
* @return JsonType
|
||||
*/
|
||||
public static function json(): JsonType
|
||||
public static function json(): Json
|
||||
{
|
||||
if (is_null(self::$jsonParser)) {
|
||||
self::$jsonParser = new JsonType();
|
||||
if (is_null(self::$jsonType)) {
|
||||
self::$jsonType = new Json();
|
||||
}
|
||||
return self::$jsonParser;
|
||||
return self::$jsonType;
|
||||
}
|
||||
|
||||
public static function inputFile(): InputFile
|
||||
{
|
||||
if (is_null(self::$inputFile)) {
|
||||
self::$inputFile = new InputFile();
|
||||
}
|
||||
return self::$inputFile;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,7 +204,6 @@ class Builder
|
|||
case 'Appwrite\Network\Validator\URL':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case 'Appwrite\Storage\Validator\File':
|
||||
case 'Utopia\Database\Validator\Key':
|
||||
case 'Utopia\Database\Validator\CustomId':
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
|
@ -238,6 +242,9 @@ class Builder
|
|||
case 'Utopia\Validator\JSON':
|
||||
$type = self::json();
|
||||
break;
|
||||
case 'Appwrite\Storage\Validator\File':
|
||||
$type = self::inputFile();
|
||||
break;
|
||||
default:
|
||||
$type = Type::string();
|
||||
break;
|
||||
|
|
61
src/Appwrite/GraphQL/Types/InputFile.php
Normal file
61
src/Appwrite/GraphQL/Types/InputFile.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Appwrite\GraphQL\Types;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Utils\Utils;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class InputFile extends ScalarType
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name = 'Upload';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $description
|
||||
= 'The `Upload` special type represents a file to be uploaded in the same HTTP request as specified by
|
||||
[graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec).';
|
||||
|
||||
/**
|
||||
* Serializes an internal value to include in a response.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function serialize($value)
|
||||
{
|
||||
throw new InvariantViolation('`Upload` cannot be serialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an externally provided value (query variable) to use as an input.
|
||||
*
|
||||
* @param mixed $value
|
||||
**/
|
||||
public function parseValue($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input.
|
||||
*
|
||||
* @param \GraphQL\Language\AST\Node $valueNode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function parseLiteral($valueNode, ?array $variables = null)
|
||||
{
|
||||
throw new Error('`Upload` cannot be hardcoded in query, be sure to conform to GraphQL multipart request specification. Instead got: ' . $valueNode->kind, $valueNode);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ use GraphQL\Language\AST\StringValueNode;
|
|||
use GraphQL\Type\Definition\ScalarType;
|
||||
|
||||
// https://github.com/webonyx/graphql-php/issues/129#issuecomment-309366803
|
||||
class JsonType extends ScalarType
|
||||
class Json extends ScalarType
|
||||
{
|
||||
public $name = 'Json';
|
||||
public $description =
|
||||
|
@ -40,25 +40,24 @@ class JsonType extends ScalarType
|
|||
public function parseLiteral(Node $valueNode, ?array $variables = null)
|
||||
{
|
||||
switch ($valueNode) {
|
||||
case ($valueNode instanceof StringValueNode):
|
||||
case ($valueNode instanceof BooleanValueNode):
|
||||
case $valueNode instanceof StringValueNode:
|
||||
case $valueNode instanceof BooleanValueNode:
|
||||
return $valueNode->value;
|
||||
case ($valueNode instanceof IntValueNode):
|
||||
case ($valueNode instanceof FloatValueNode):
|
||||
case $valueNode instanceof IntValueNode:
|
||||
case $valueNode instanceof FloatValueNode:
|
||||
return floatval($valueNode->value);
|
||||
case ($valueNode instanceof ObjectValueNode): {
|
||||
case $valueNode instanceof ObjectValueNode:
|
||||
$value = [];
|
||||
foreach ($valueNode->fields as $field) {
|
||||
$value[$field->name->value] = $this->parseLiteral($field->value);
|
||||
$value[$field->name->value] =
|
||||
$this->parseLiteral($field->value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
case ($valueNode instanceof ListValueNode):
|
||||
return array_map([$this, 'parseLiteral'], $valueNode->values);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function identity($value)
|
|
@ -550,9 +550,10 @@ trait GraphQLBase
|
|||
}
|
||||
}';
|
||||
case self::$UPDATE_USER_PHONE:
|
||||
return 'mutation updateUserPhone($userId: String!, number: String!){
|
||||
usersUpdatePhone(userId: $userId, number: number) {
|
||||
return 'mutation updateUserPhone($userId: String!, $number: String!){
|
||||
usersUpdatePhone(userId: $userId, number: $number) {
|
||||
name
|
||||
phone
|
||||
email
|
||||
}
|
||||
}';
|
||||
|
@ -747,7 +748,7 @@ trait GraphQLBase
|
|||
}
|
||||
}';
|
||||
case self::$UPDATE_ACCOUNT_PHONE:
|
||||
return 'mutation updateAccountPhone($number: Json!, $password: String!){
|
||||
return 'mutation updateAccountPhone($number: String!, $password: String!){
|
||||
accountUpdatePhone(number: $number, password: $password) {
|
||||
_id
|
||||
name
|
||||
|
@ -1099,7 +1100,7 @@ trait GraphQLBase
|
|||
functionsDelete(functionId: $functionId)
|
||||
}';
|
||||
case self::$CREATE_DEPLOYMENT:
|
||||
return 'mutation createDeployment($functionId: String!, $entrypoint: String!, $code: String!, $activate: Boolean!) {
|
||||
return 'mutation createDeployment($functionId: String!, $entrypoint: String!, $code: InputFile!, $activate: Boolean!) {
|
||||
functionsCreateDeployment(functionId: $functionId, entrypoint: $entrypoint, code: $code, activate: $activate) {
|
||||
_id
|
||||
entrypoint
|
||||
|
|
|
@ -447,8 +447,6 @@ class GraphQLDatabaseServerTest extends Scope
|
|||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
\var_dump($actor);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $actor['body']);
|
||||
$this->assertIsArray($actor['body']['data']);
|
||||
$this->assertIsArray($actor['body']['data']['actorsCreate']);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
|
@ -53,12 +54,13 @@ class GraphQLFunctionsClientTest extends Scope
|
|||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz";
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'entrypoint' => 'main.rb',
|
||||
'code' => realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz",
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
|
||||
'activate' => true,
|
||||
]
|
||||
];
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
|
@ -53,12 +54,13 @@ class GraphQLFunctionsServerTest extends Scope
|
|||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz";
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'entrypoint' => 'main.rb',
|
||||
'code' => realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz",
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
|
||||
'activate' => true,
|
||||
]
|
||||
];
|
||||
|
|
|
@ -257,6 +257,6 @@ class GraphQLTeamsServerTest extends Scope
|
|||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(204, $team['headers']['status-code']);
|
||||
$this->assertEquals(200, $team['headers']['status-code']);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue