1
0
Fork 0
mirror of synced 2024-07-01 04:30:59 +12:00

Fix custom entity id properties

This commit is contained in:
Jake Barnby 2022-07-07 19:39:42 +12:00
parent 8762bcba90
commit b5e8273839
4 changed files with 129 additions and 94 deletions

View file

@ -55,68 +55,14 @@ App::post('/v1/graphql')
->param('query', '', new Text(1024), 'The query to execute. Max 1024 chars.', true) ->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('operationName', null, new Text(256), 'Name of the operation to execute', true)
->param('variables', [], new JSON(), 'Variables to use in the operation', 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)
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('promiseAdapter') ->inject('promiseAdapter')
->inject('gqlSchema') ->inject('gqlSchema')
->action(Closure::fromCallable('graphqlRequest')); ->action(Closure::fromCallable('graphqlRequest'));
App::post('/v1/graphql')
->desc('GraphQL Endpoint')
->groups(['grapgql'])
->label('scope', 'graphql')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'graphql')
->label('sdk.method', 'upload')
->label('sdk.description', '/docs/references/graphql/upload.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ANY)
->label('abuse-limit', 60)
->label('abuse-time', 60)
->param('operations', '', new Text(1024), 'Query and variables for operation', true)
->param('map', '', new Text(1024), 'Map of form data keys to file replacement dot paths within the operations JSON. For example: "variables.code"', true)
->inject('request')
->inject('response')
->inject('promiseAdapter')
->inject('gqlSchema')
->action(Closure::fromCallable('graphqlUpload'));
/**
* @throws Exception
*/
function graphqlUpload(
?string $operations,
?string $map,
Appwrite\Utopia\Request $request,
Appwrite\Utopia\Response $response,
CoroutinePromiseAdapter $promiseAdapter,
Type\Schema $gqlSchema
): void {
$contentType = $request->getHeader('content-type');
if (!\str_starts_with($contentType, 'multipart/form-data')) {
throw new Exception('Invalid content type', 400);
}
$map = \json_decode($map, true);
$operations = \json_decode($operations, true);
foreach ($map as $fileKey => $locations) {
foreach ($locations as $location) {
$items = &$operations;
foreach (explode('.', $location) as $key) {
if (!isset($items[$key]) || !is_array($items[$key])) {
$items[$key] = [];
}
$items = &$items[$key];
}
$items = $request->getFiles($fileKey);
}
}
$query = $operations['query'];
$variables = $operations['variables'];
graphqlRequest($query, null, $variables, $request, $response, $promiseAdapter, $gqlSchema);
}
/** /**
* @throws Exception * @throws Exception
* @throws \Exception * @throws \Exception
@ -125,15 +71,38 @@ function graphqlRequest(
string $query, string $query,
?string $operationName, ?string $operationName,
?array $variables, ?array $variables,
?string $operations,
?string $map,
Appwrite\Utopia\Request $request, Appwrite\Utopia\Request $request,
Appwrite\Utopia\Response $response, Appwrite\Utopia\Response $response,
CoroutinePromiseAdapter $promiseAdapter, CoroutinePromiseAdapter $promiseAdapter,
Type\Schema $gqlSchema Type\Schema $gqlSchema
): void { ): void {
$contentType = $request->getHeader('content-type'); $contentType = $request->getHeader('content-type');
if ($contentType === 'application/graphql') { if ($contentType === 'application/graphql') {
$query = $request->getSwoole()->rawContent(); $query = $request->getSwoole()->rawContent();
} }
if (\str_starts_with($contentType, 'multipart/form-data')) {
$map = \json_decode($map, true);
$operations = \json_decode($operations, true);
foreach ($map as $fileKey => $locations) {
foreach ($locations as $location) {
$items = &$operations;
foreach (explode('.', $location) as $key) {
if (!isset($items[$key]) || !is_array($items[$key])) {
$items[$key] = [];
}
$items = &$items[$key];
}
$items = $request->getFiles($fileKey);
}
}
$query = $operations['query'];
$variables = $operations['variables'];
}
if (empty($query)) { if (empty($query)) {
throw new Exception('No query supplied.', 400, Exception::GRAPHQL_NO_QUERY); throw new Exception('No query supplied.', 400, Exception::GRAPHQL_NO_QUERY);
} }

View file

@ -16,7 +16,6 @@ use Utopia\CLI\Console;
use Utopia\Database\Database; use Utopia\Database\Database;
use Utopia\Database\Document; use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
use Utopia\Route; use Utopia\Route;
use Utopia\Validator; use Utopia\Validator;
@ -497,7 +496,7 @@ class Builder
foreach ($collections as $collectionId => $attributes) { foreach ($collections as $collectionId => $attributes) {
$objectType = new ObjectType([ $objectType = new ObjectType([
'name' => $collectionId, 'name' => $collectionId,
'fields' => $attributes 'fields' => \array_merge(["_id" => ['type' => Type::string()]], $attributes),
]); ]);
$attributes = \array_merge( $attributes = \array_merge(
@ -695,7 +694,6 @@ class Builder
} }
$gqlResponse = $response; $gqlResponse = $response;
$request = new Request($swoole); $request = new Request($swoole);
$apiResponse = new Response($response->getSwoole()); $apiResponse = new Response($response->getSwoole());
$apiResponse->setContentType(Response::CONTENT_TYPE_NULL); $apiResponse->setContentType(Response::CONTENT_TYPE_NULL);
@ -710,31 +708,42 @@ class Builder
$utopia->execute($route, $request); $utopia->execute($route, $request);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$gqlResponse->setStatusCode($apiResponse->getStatusCode()); self::reassign($gqlResponse, $apiResponse);
$reject($e); $reject($e);
return; return;
} }
self::reassign($gqlResponse, $apiResponse);
$result = $apiResponse->getPayload(); $result = $apiResponse->getPayload();
$gqlResponse->setContentType($apiResponse->getContentType()); if ($result['$id']) {
$gqlResponse->setStatusCode($apiResponse->getStatusCode()); $result['_id'] = $result['$id'];
}
if ($apiResponse->getStatusCode() < 200 || $apiResponse->getStatusCode() >= 400) { if ($apiResponse->getStatusCode() < 200 || $apiResponse->getStatusCode() >= 400) {
$reject(new GQLException($result['message'], $apiResponse->getStatusCode())); $reject(new GQLException($result['message'], $apiResponse->getStatusCode()));
return; return;
} }
// Add headers and cookies from inner to outer response $resolve($result);
// TODO: Add setters to response to allow setting entire array at once }
/**
* @param Response $gqlResponse
* @param Response $apiResponse
* @return void
* @throws \Utopia\Exception
*/
private static function reassign(Response $gqlResponse, Response $apiResponse): void
{
$gqlResponse->setContentType($apiResponse->getContentType());
$gqlResponse->setStatusCode($apiResponse->getStatusCode());
foreach ($apiResponse->getHeaders() as $key => $value) { foreach ($apiResponse->getHeaders() as $key => $value) {
$gqlResponse->addHeader($key, $value); $gqlResponse->addHeader($key, $value);
} }
foreach ($apiResponse->getCookies() as $name => $cookie) { foreach ($apiResponse->getCookies() as $name => $cookie) {
$gqlResponse->addCookie($name, $cookie['value'], $cookie['expire'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']); $gqlResponse->addCookie($name, $cookie['value'], $cookie['expire'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
} }
$resolve($result);
} }
} }

View file

@ -35,12 +35,18 @@ trait GraphQLBase
public static string $DELETE_INDEX = 'delete_index'; public static string $DELETE_INDEX = 'delete_index';
// Documents // Documents
public static string $CREATE_DOCUMENT = 'create_document_rest'; public static string $CREATE_DOCUMENT = 'create_document_rest';
public static string $CREATE_CUSTOM_ENTITY = 'create_document_hooks';
public static string $GET_DOCUMENTS = 'list_documents'; public static string $GET_DOCUMENTS = 'list_documents';
public static string $GET_DOCUMENT = 'get_document'; public static string $GET_DOCUMENT = 'get_document';
public static string $UPDATE_DOCUMENT = 'update_document'; public static string $UPDATE_DOCUMENT = 'update_document';
public static string $DELETE_DOCUMENT = 'delete_document'; public static string $DELETE_DOCUMENT = 'delete_document';
// Custom Entities
public static string $CREATE_CUSTOM_ENTITY = 'create_custom_entity';
public static string $GET_CUSTOM_ENTITIES = 'get_custom_entities';
public static string $GET_CUSTOM_ENTITY = 'get_custom_entity';
public static string $UPDATE_CUSTOM_ENTITY = 'update_custom_entity';
public static string $DELETE_CUSTOM_ENTITY = 'delete_custom_entity';
// Localization // Localization
public static string $GET_LOCALE = 'get_locale'; public static string $GET_LOCALE = 'get_locale';
public static string $LIST_COUNTRIES = 'list_countries'; public static string $LIST_COUNTRIES = 'list_countries';
@ -414,6 +420,7 @@ trait GraphQLBase
case self::$CREATE_CUSTOM_ENTITY: case self::$CREATE_CUSTOM_ENTITY:
return 'mutation createActor($name: String!, $age: Int!, $alive: Boolean!, $salary: Float, $email: String!, $role: String!, $ip: String, $url: String){ return 'mutation createActor($name: String!, $age: Int!, $alive: Boolean!, $salary: Float, $email: String!, $role: String!, $ip: String, $url: String){
actorsCreate(name: $name, age: $age, alive: $alive, salary: $salary, email: $email, role: $role, ip: $ip, url: $url) { actorsCreate(name: $name, age: $age, alive: $alive, salary: $salary, email: $email, role: $role, ip: $ip, url: $url) {
_id
name name
age age
alive alive
@ -422,6 +429,52 @@ trait GraphQLBase
role role
} }
}'; }';
case self::$GET_CUSTOM_ENTITIES:
return 'query getCustomEntities($name: String!){
actorsList(name: $name) {
total
actors {
name
age
alive
salary
email
role
ip
url
}
}
}';
case self::$GET_CUSTOM_ENTITY:
return 'query getCustomEntity($id: String!){
actorsGet(id: $id) {
name
age
alive
salary
email
role
ip
url
}
}';
case self::$UPDATE_CUSTOM_ENTITY:
return 'mutation updateCustomEntity($id: String!, $name: String!, $age: Int!, $alive: Boolean!, $salary: Float, $email: String!, $role: String!, $ip: String, $url: String){
actorsUpdate(id: $id, name: $name, age: $age, alive: $alive, salary: $salary, email: $email, role: $role, ip: $ip, url: $url) {
name
age
alive
salary
email
role
ip
url
}
}';
case self::$DELETE_CUSTOM_ENTITY:
return 'mutation deleteCustomEntity($id: String!){
actorsDelete(id: $id)
}';
case self::$UPDATE_DOCUMENT: case self::$UPDATE_DOCUMENT:
return 'mutation updateDocument($databaseId: String!, $collectionId: String!, $documentId: String!, $data: Json!, $read: [String!], $write: [String!]){ return 'mutation updateDocument($databaseId: String!, $collectionId: String!, $documentId: String!, $data: Json!, $read: [String!], $write: [String!]){
databasesUpdateDocument(databaseId: $databaseId, collectionId: $collectionId, documentId: $documentId, data: $data, read: $read, write: $write) { databasesUpdateDocument(databaseId: $databaseId, collectionId: $collectionId, documentId: $documentId, data: $data, read: $read, write: $write) {

View file

@ -426,7 +426,7 @@ class GraphQLDatabaseServerTest extends Scope
* @depends testCreateEnumAttribute * @depends testCreateEnumAttribute
* @throws Exception * @throws Exception
*/ */
public function testCreateCustomEntity(): void public function testCreateCustomEntity(): array
{ {
$projectId = $this->getProject()['$id']; $projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_CUSTOM_ENTITY); $query = $this->getQuery(self::$CREATE_CUSTOM_ENTITY);
@ -447,9 +447,13 @@ class GraphQLDatabaseServerTest extends Scope
'x-appwrite-project' => $projectId, 'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload); ], $this->getHeaders()), $gqlPayload);
$this->assertArrayNotHasKey('errors', $actor['body']); $this->assertArrayNotHasKey('errors', $actor['body']);
$this->assertIsArray($actor['body']['data']); $this->assertIsArray($actor['body']['data']);
$this->assertIsArray($actor['body']['data']['actorsCreate']); $actor = $actor['body']['data']['actorsCreate'];
$this->assertIsArray($actor);
return $actor;
} }
public function testGetDatabases(): void public function testGetDatabases(): void
@ -706,30 +710,30 @@ class GraphQLDatabaseServerTest extends Scope
$this->assertIsArray($document['body']['data']['databasesGetDocument']); $this->assertIsArray($document['body']['data']['databasesGetDocument']);
} }
// /** /**
// * @depends testCreateCustomEntity * @depends testCreateCustomEntity
// * @throws Exception * @throws Exception
// */ */
// public function testGetCustomEntity($data) public function testGetCustomEntity($data)
// { {
// $projectId = $this->getProject()['$id']; $projectId = $this->getProject()['$id'];
// $query = $this->getQuery(self::$GET_CUSTOM_ENTITY); $query = $this->getQuery(self::$GET_CUSTOM_ENTITY);
// $gqlPayload = [ $gqlPayload = [
// 'query' => $query, 'query' => $query,
// 'variables' => [ 'variables' => [
// 'id' => $data['entity']['_id'], 'id' => $data['_id'],
// ] ]
// ]; ];
//
// $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([ $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
// 'content-type' => 'application/json', 'content-type' => 'application/json',
// 'x-appwrite-project' => $projectId, 'x-appwrite-project' => $projectId,
// ], $this->getHeaders()), $gqlPayload); ], $this->getHeaders()), $gqlPayload);
//
// $this->assertArrayNotHasKey('errors', $entity['body']); $this->assertArrayNotHasKey('errors', $entity['body']);
// $this->assertIsArray($entity['body']['data']); $this->assertIsArray($entity['body']['data']);
// $this->assertIsArray($entity['body']['data']['actorGet']); $this->assertIsArray($entity['body']['data']['actorsGet']);
// } }
/** /**
* @depends testCreateDatabase * @depends testCreateDatabase