diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index 80cd93e3a3..56683d69c3 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -270,13 +270,13 @@ describe('ParseGraphQLSchema', () => { warn: message => { logged = true; expect(message).toEqual( - 'Query get could not be added to the auto schema because it collided with an existing field.' + 'Query viewer could not be added to the auto schema because it collided with an existing field.' ); }, }, }); await parseGraphQLSchema.load(); - expect(parseGraphQLSchema.addGraphQLQuery('get', {})).toBeUndefined(); + expect(parseGraphQLSchema.addGraphQLQuery('viewer', {})).toBeUndefined(); expect(logged).toBeTruthy(); }); @@ -291,12 +291,12 @@ describe('ParseGraphQLSchema', () => { }, }); await parseGraphQLSchema.load(); - delete parseGraphQLSchema.graphQLQueries.get; + delete parseGraphQLSchema.graphQLQueries.viewer; const field = {}; - expect(parseGraphQLSchema.addGraphQLQuery('get', field, true, true)).toBe( - field - ); - expect(parseGraphQLSchema.graphQLQueries['get']).toBe(field); + expect( + parseGraphQLSchema.addGraphQLQuery('viewer', field, true, true) + ).toBe(field); + expect(parseGraphQLSchema.graphQLQueries['viewer']).toBe(field); }); }); @@ -363,14 +363,14 @@ describe('ParseGraphQLSchema', () => { warn: message => { logged = true; expect(message).toEqual( - 'Mutation create could not be added to the auto schema because it collided with an existing field.' + 'Mutation signUp could not be added to the auto schema because it collided with an existing field.' ); }, }, }); await parseGraphQLSchema.load(); expect( - parseGraphQLSchema.addGraphQLMutation('create', {}) + parseGraphQLSchema.addGraphQLMutation('signUp', {}) ).toBeUndefined(); expect(logged).toBeTruthy(); }); @@ -386,12 +386,12 @@ describe('ParseGraphQLSchema', () => { }, }); await parseGraphQLSchema.load(); - delete parseGraphQLSchema.graphQLMutations.create; + delete parseGraphQLSchema.graphQLMutations.signUp; const field = {}; expect( - parseGraphQLSchema.addGraphQLMutation('create', field, true, true) + parseGraphQLSchema.addGraphQLMutation('signUp', field, true, true) ).toBe(field); - expect(parseGraphQLSchema.graphQLMutations['create']).toBe(field); + expect(parseGraphQLSchema.graphQLMutations['signUp']).toBe(field); }); }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index e5448888d1..36fc3070c7 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -18,6 +18,19 @@ const { ParseServer } = require('../'); const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); const ReadPreference = require('mongodb').ReadPreference; +function handleError(e) { + if ( + e && + e.networkError && + e.networkError.result && + e.networkError.result.errors + ) { + fail(e.networkError.result.errors); + } else { + fail(e); + } +} + describe('ParseGraphQLServer', () => { let parseServer; let parseGraphQLServer; @@ -447,14 +460,18 @@ describe('ParseGraphQLServer', () => { describe('GraphQL', () => { it('should be healthy', async () => { - const health = (await apolloClient.query({ - query: gql` - query Health { - health - } - `, - })).data.health; - expect(health).toBeTruthy(); + try { + const health = (await apolloClient.query({ + query: gql` + query Health { + health + } + `, + })).data.health; + expect(health).toBeTruthy(); + } catch (e) { + handleError(e); + } }); it('should be cors enabled', async () => { @@ -629,7 +646,7 @@ describe('ParseGraphQLServer', () => { const classType = (await apolloClient.query({ query: gql` query ClassType { - __type(name: "Class") { + __type(name: "ParseObject") { kind fields { name @@ -722,7 +739,7 @@ describe('ParseGraphQLServer', () => { })).data['__schema'].types.map(type => type.name); const expectedTypes = [ - 'Class', + 'ParseObject', 'CreateResult', 'Date', 'FileInfo', @@ -1572,111 +1589,1162 @@ describe('ParseGraphQLServer', () => { }); }); - describe('Objects Queries', () => { - describe('Get', () => { - it('should return a class object using generic query', async () => { - const obj = new Parse.Object('SomeClass'); - obj.set('someField', 'someValue'); - await obj.save(); - - const result = (await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) + describe('Class Schema Mutations', () => { + it('should create a new class', async () => { + try { + const result = await apolloClient.mutate({ + mutation: gql` + mutation { + class1: createClass(name: "Class1") { + name + schemaFields { + name + __typename + } + } + class2: createClass(name: "Class2", schemaFields: null) { + name + schemaFields { + name + __typename + } + } + class3: createClass(name: "Class3", schemaFields: {}) { + name + schemaFields { + name + __typename + } + } + class4: createClass( + name: "Class4" + schemaFields: { + addStrings: null + addNumbers: null + addBooleans: null + addArrays: null + addObjects: null + addDates: null + addFiles: null + addGeoPoint: null + addPolygons: null + addBytes: null + addPointers: null + addRelations: null + } + ) { + name + schemaFields { + name + __typename + } + } + class5: createClass( + name: "Class5" + schemaFields: { + addStrings: [] + addNumbers: [] + addBooleans: [] + addArrays: [] + addObjects: [] + addDates: [] + addFiles: [] + addPolygons: [] + addBytes: [] + addPointers: [] + addRelations: [] + } + ) { + name + schemaFields { + name + __typename + } + } + class6: createClass( + name: "Class6" + schemaFields: { + addStrings: [ + { name: "stringField1" } + { name: "stringField2" } + { name: "stringField3" } + ] + addNumbers: [ + { name: "numberField1" } + { name: "numberField2" } + { name: "numberField3" } + ] + addBooleans: [ + { name: "booleanField1" } + { name: "booleanField2" } + { name: "booleanField3" } + ] + addArrays: [ + { name: "arrayField1" } + { name: "arrayField2" } + { name: "arrayField3" } + ] + addObjects: [ + { name: "objectField1" } + { name: "objectField2" } + { name: "objectField3" } + ] + addDates: [ + { name: "dateField1" } + { name: "dateField2" } + { name: "dateField3" } + ] + addFiles: [ + { name: "fileField1" } + { name: "fileField2" } + { name: "fileField3" } + ] + addGeoPoint: { name: "geoPointField" } + addPolygons: [ + { name: "polygonField1" } + { name: "polygonField2" } + { name: "polygonField3" } + ] + addBytes: [ + { name: "bytesField1" } + { name: "bytesField2" } + { name: "bytesField3" } + ] + addPointers: [ + { name: "pointerField1", targetClassName: "Class1" } + { name: "pointerField2", targetClassName: "Class6" } + { name: "pointerField3", targetClassName: "Class2" } + ] + addRelations: [ + { name: "relationField1", targetClassName: "Class1" } + { name: "relationField2", targetClassName: "Class6" } + { name: "relationField3", targetClassName: "Class2" } + ] + remove: [ + { name: "stringField3" } + { name: "numberField3" } + { name: "booleanField3" } + { name: "arrayField3" } + { name: "objectField3" } + { name: "dateField3" } + { name: "fileField3" } + { name: "polygonField3" } + { name: "bytesField3" } + { name: "pointerField3" } + { name: "relationField3" } + { name: "doesNotExist" } + ] + } + ) { + name + schemaFields { + name + __typename + ... on SchemaPointerField { + targetClassName + } + ... on SchemaRelationField { + targetClassName + } + } + } } `, - variables: { - id: obj.id, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - })).data.get; - - expect(result.id).toEqual(obj.id); - expect(result.someField).toEqual('someValue'); - expect(new Date(result.createdAt)).toEqual(obj.createdAt); - expect(new Date(result.updatedAt)).toEqual(obj.updatedAt); - }); - - it('should return a class object using class specific query', async () => { - const obj = new Parse.Object('Customer'); - obj.set('someField', 'someValue'); - await obj.save(); - - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + }); + const classes = Object.keys(result.data).map(fieldName => ({ + name: result.data[fieldName].name, + schemaFields: result.data[fieldName].schemaFields.sort((a, b) => + a.name > b.name ? 1 : -1 + ), + __typename: result.data[fieldName].__typename, + })); + expect(classes).toEqual([ + { + name: 'Class1', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class2', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class3', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class4', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class5', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class6', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'arrayField1', __typename: 'SchemaArrayField' }, + { name: 'arrayField2', __typename: 'SchemaArrayField' }, + { name: 'booleanField1', __typename: 'SchemaBooleanField' }, + { name: 'booleanField2', __typename: 'SchemaBooleanField' }, + { name: 'bytesField1', __typename: 'SchemaBytesField' }, + { name: 'bytesField2', __typename: 'SchemaBytesField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'dateField1', __typename: 'SchemaDateField' }, + { name: 'dateField2', __typename: 'SchemaDateField' }, + { name: 'fileField1', __typename: 'SchemaFileField' }, + { name: 'fileField2', __typename: 'SchemaFileField' }, + { + name: 'geoPointField', + __typename: 'SchemaGeoPointField', + }, + { name: 'numberField1', __typename: 'SchemaNumberField' }, + { name: 'numberField2', __typename: 'SchemaNumberField' }, + { name: 'objectField1', __typename: 'SchemaObjectField' }, + { name: 'objectField2', __typename: 'SchemaObjectField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { + name: 'pointerField1', + __typename: 'SchemaPointerField', + targetClassName: 'Class1', + }, + { + name: 'pointerField2', + __typename: 'SchemaPointerField', + targetClassName: 'Class6', + }, + { name: 'polygonField1', __typename: 'SchemaPolygonField' }, + { name: 'polygonField2', __typename: 'SchemaPolygonField' }, + { + name: 'relationField1', + __typename: 'SchemaRelationField', + targetClassName: 'Class1', + }, + { + name: 'relationField2', + __typename: 'SchemaRelationField', + targetClassName: 'Class6', + }, + { name: 'stringField1', __typename: 'SchemaStringField' }, + { name: 'stringField2', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + ]); - const result = (await apolloClient.query({ + const findResult = await apolloClient.query({ query: gql` - query GetCustomer($id: ID!) { - customer(id: $id) { - id - someField - createdAt - updatedAt + query { + classes { + name + schemaFields { + name + __typename + ... on SchemaPointerField { + targetClassName + } + ... on SchemaRelationField { + targetClassName + } + } } } `, - variables: { - id: obj.id, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - })).data.customer; - - expect(result.id).toEqual(obj.id); - expect(result.someField).toEqual('someValue'); - expect(new Date(result.createdAt)).toEqual(obj.createdAt); - expect(new Date(result.updatedAt)).toEqual(obj.updatedAt); - }); - - it_only_db('mongo')( - 'should return child objects in array fields', - async () => { - const obj1 = new Parse.Object('Customer'); - const obj2 = new Parse.Object('SomeClass'); - const obj3 = new Parse.Object('Customer'); - - obj1.set('someCustomerField', 'imCustomerOne'); - const arrayField = [42.42, 42, 'string', true]; - obj1.set('arrayField', arrayField); - await obj1.save(); - - obj2.set('someClassField', 'imSomeClassTwo'); - await obj2.save(); - - obj3.set('manyRelations', [obj1, obj2]); - await obj3.save(); + }); + findResult.data.classes = findResult.data.classes + .filter(schemaClass => !schemaClass.name.startsWith('_')) + .sort((a, b) => (a.name > b.name ? 1 : -1)); + findResult.data.classes.forEach(schemaClass => { + schemaClass.schemaFields = schemaClass.schemaFields.sort((a, b) => + a.name > b.name ? 1 : -1 + ); + }); + expect(findResult.data.classes).toEqual([ + { + name: 'Class1', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class2', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class3', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class4', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class5', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + { + name: 'Class6', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'arrayField1', __typename: 'SchemaArrayField' }, + { name: 'arrayField2', __typename: 'SchemaArrayField' }, + { name: 'booleanField1', __typename: 'SchemaBooleanField' }, + { name: 'booleanField2', __typename: 'SchemaBooleanField' }, + { name: 'bytesField1', __typename: 'SchemaBytesField' }, + { name: 'bytesField2', __typename: 'SchemaBytesField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'dateField1', __typename: 'SchemaDateField' }, + { name: 'dateField2', __typename: 'SchemaDateField' }, + { name: 'fileField1', __typename: 'SchemaFileField' }, + { name: 'fileField2', __typename: 'SchemaFileField' }, + { + name: 'geoPointField', + __typename: 'SchemaGeoPointField', + }, + { name: 'numberField1', __typename: 'SchemaNumberField' }, + { name: 'numberField2', __typename: 'SchemaNumberField' }, + { name: 'objectField1', __typename: 'SchemaObjectField' }, + { name: 'objectField2', __typename: 'SchemaObjectField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { + name: 'pointerField1', + __typename: 'SchemaPointerField', + targetClassName: 'Class1', + }, + { + name: 'pointerField2', + __typename: 'SchemaPointerField', + targetClassName: 'Class6', + }, + { name: 'polygonField1', __typename: 'SchemaPolygonField' }, + { name: 'polygonField2', __typename: 'SchemaPolygonField' }, + { + name: 'relationField1', + __typename: 'SchemaRelationField', + targetClassName: 'Class1', + }, + { + name: 'relationField2', + __typename: 'SchemaRelationField', + targetClassName: 'Class6', + }, + { name: 'stringField1', __typename: 'SchemaStringField' }, + { name: 'stringField2', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + ]); + } catch (e) { + handleError(e); + } + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + it('should require master key to create a new class', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + createClass(name: "SomeClass") { + name + } + } + `, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.OPERATION_FORBIDDEN + ); + expect(e.graphQLErrors[0].message).toEqual( + 'unauthorized: master key is required' + ); + } + }); - const result = (await apolloClient.query({ - query: gql` - query GetCustomer($id: ID!) { - customer(id: $id) { - id - manyRelations { - ... on Customer { - id - someCustomerField - arrayField { - ... on Element { - value - } - } - } - ... on SomeClass { - id - someClassField - } - } - createdAt - updatedAt + it('should not allow duplicated field names when creating', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + createClass( + name: "SomeClass" + schemaFields: { + addStrings: [{ name: "someField" }] + addNumbers: [{ name: "someField" }] } + ) { + name } - `, - variables: { - id: obj3.id, + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - })).data.customer; - - expect(result.id).toEqual(obj3.id); - expect(result.manyRelations.length).toEqual(2); + }, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.INVALID_KEY_NAME + ); + expect(e.graphQLErrors[0].message).toEqual( + 'Duplicated field name: someField' + ); + } + }); + + it('should update an existing class', async () => { + try { + const result = await apolloClient.mutate({ + mutation: gql` + mutation { + createClass( + name: "MyNewClass" + schemaFields: { addStrings: [{ name: "willBeRemoved" }] } + ) { + name + schemaFields { + name + __typename + } + } + updateClass( + name: "MyNewClass" + schemaFields: { + addStrings: [ + { name: "stringField1" } + { name: "stringField2" } + { name: "stringField3" } + ] + addNumbers: [ + { name: "numberField1" } + { name: "numberField2" } + { name: "numberField3" } + ] + addBooleans: [ + { name: "booleanField1" } + { name: "booleanField2" } + { name: "booleanField3" } + ] + addArrays: [ + { name: "arrayField1" } + { name: "arrayField2" } + { name: "arrayField3" } + ] + addObjects: [ + { name: "objectField1" } + { name: "objectField2" } + { name: "objectField3" } + ] + addDates: [ + { name: "dateField1" } + { name: "dateField2" } + { name: "dateField3" } + ] + addFiles: [ + { name: "fileField1" } + { name: "fileField2" } + { name: "fileField3" } + ] + addGeoPoint: { name: "geoPointField" } + addPolygons: [ + { name: "polygonField1" } + { name: "polygonField2" } + { name: "polygonField3" } + ] + addBytes: [ + { name: "bytesField1" } + { name: "bytesField2" } + { name: "bytesField3" } + ] + addPointers: [ + { name: "pointerField1", targetClassName: "Class1" } + { name: "pointerField2", targetClassName: "Class6" } + { name: "pointerField3", targetClassName: "Class2" } + ] + addRelations: [ + { name: "relationField1", targetClassName: "Class1" } + { name: "relationField2", targetClassName: "Class6" } + { name: "relationField3", targetClassName: "Class2" } + ] + remove: [ + { name: "willBeRemoved" } + { name: "stringField3" } + { name: "numberField3" } + { name: "booleanField3" } + { name: "arrayField3" } + { name: "objectField3" } + { name: "dateField3" } + { name: "fileField3" } + { name: "polygonField3" } + { name: "bytesField3" } + { name: "pointerField3" } + { name: "relationField3" } + { name: "doesNotExist" } + ] + } + ) { + name + schemaFields { + name + __typename + ... on SchemaPointerField { + targetClassName + } + ... on SchemaRelationField { + targetClassName + } + } + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + result.data.createClass.schemaFields = result.data.createClass.schemaFields.sort( + (a, b) => (a.name > b.name ? 1 : -1) + ); + result.data.updateClass.schemaFields = result.data.updateClass.schemaFields.sort( + (a, b) => (a.name > b.name ? 1 : -1) + ); + expect(result).toEqual({ + data: { + createClass: { + name: 'MyNewClass', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + { name: 'willBeRemoved', __typename: 'SchemaStringField' }, + ], + __typename: 'Class', + }, + updateClass: { + name: 'MyNewClass', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'arrayField1', __typename: 'SchemaArrayField' }, + { name: 'arrayField2', __typename: 'SchemaArrayField' }, + { name: 'booleanField1', __typename: 'SchemaBooleanField' }, + { name: 'booleanField2', __typename: 'SchemaBooleanField' }, + { name: 'bytesField1', __typename: 'SchemaBytesField' }, + { name: 'bytesField2', __typename: 'SchemaBytesField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'dateField1', __typename: 'SchemaDateField' }, + { name: 'dateField2', __typename: 'SchemaDateField' }, + { name: 'fileField1', __typename: 'SchemaFileField' }, + { name: 'fileField2', __typename: 'SchemaFileField' }, + { + name: 'geoPointField', + __typename: 'SchemaGeoPointField', + }, + { name: 'numberField1', __typename: 'SchemaNumberField' }, + { name: 'numberField2', __typename: 'SchemaNumberField' }, + { name: 'objectField1', __typename: 'SchemaObjectField' }, + { name: 'objectField2', __typename: 'SchemaObjectField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { + name: 'pointerField1', + __typename: 'SchemaPointerField', + targetClassName: 'Class1', + }, + { + name: 'pointerField2', + __typename: 'SchemaPointerField', + targetClassName: 'Class6', + }, + { name: 'polygonField1', __typename: 'SchemaPolygonField' }, + { name: 'polygonField2', __typename: 'SchemaPolygonField' }, + { + name: 'relationField1', + __typename: 'SchemaRelationField', + targetClassName: 'Class1', + }, + { + name: 'relationField2', + __typename: 'SchemaRelationField', + targetClassName: 'Class6', + }, + { name: 'stringField1', __typename: 'SchemaStringField' }, + { name: 'stringField2', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + }, + }); + + const getResult = await apolloClient.query({ + query: gql` + query { + class(name: "MyNewClass") { + name + schemaFields { + name + __typename + ... on SchemaPointerField { + targetClassName + } + ... on SchemaRelationField { + targetClassName + } + } + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + getResult.data.class.schemaFields = getResult.data.class.schemaFields.sort( + (a, b) => (a.name > b.name ? 1 : -1) + ); + expect(getResult.data).toEqual({ + class: { + name: 'MyNewClass', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'arrayField1', __typename: 'SchemaArrayField' }, + { name: 'arrayField2', __typename: 'SchemaArrayField' }, + { name: 'booleanField1', __typename: 'SchemaBooleanField' }, + { name: 'booleanField2', __typename: 'SchemaBooleanField' }, + { name: 'bytesField1', __typename: 'SchemaBytesField' }, + { name: 'bytesField2', __typename: 'SchemaBytesField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'dateField1', __typename: 'SchemaDateField' }, + { name: 'dateField2', __typename: 'SchemaDateField' }, + { name: 'fileField1', __typename: 'SchemaFileField' }, + { name: 'fileField2', __typename: 'SchemaFileField' }, + { + name: 'geoPointField', + __typename: 'SchemaGeoPointField', + }, + { name: 'numberField1', __typename: 'SchemaNumberField' }, + { name: 'numberField2', __typename: 'SchemaNumberField' }, + { name: 'objectField1', __typename: 'SchemaObjectField' }, + { name: 'objectField2', __typename: 'SchemaObjectField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { + name: 'pointerField1', + __typename: 'SchemaPointerField', + targetClassName: 'Class1', + }, + { + name: 'pointerField2', + __typename: 'SchemaPointerField', + targetClassName: 'Class6', + }, + { name: 'polygonField1', __typename: 'SchemaPolygonField' }, + { name: 'polygonField2', __typename: 'SchemaPolygonField' }, + { + name: 'relationField1', + __typename: 'SchemaRelationField', + targetClassName: 'Class1', + }, + { + name: 'relationField2', + __typename: 'SchemaRelationField', + targetClassName: 'Class6', + }, + { name: 'stringField1', __typename: 'SchemaStringField' }, + { name: 'stringField2', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + ], + __typename: 'Class', + }, + }); + } catch (e) { + handleError(e); + } + }); + + it('should require master key to update an existing class', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + createClass(name: "SomeClass") { + name + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + } catch (e) { + handleError(e); + } + + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + updateClass(name: "SomeClass") { + name + } + } + `, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.OPERATION_FORBIDDEN + ); + expect(e.graphQLErrors[0].message).toEqual( + 'unauthorized: master key is required' + ); + } + }); + + it('should not allow duplicated field names when updating', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + createClass( + name: "SomeClass" + schemaFields: { addStrings: [{ name: "someField" }] } + ) { + name + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + } catch (e) { + handleError(e); + } + + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + updateClass( + name: "SomeClass" + schemaFields: { addNumbers: [{ name: "someField" }] } + ) { + name + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.INVALID_KEY_NAME + ); + expect(e.graphQLErrors[0].message).toEqual( + 'Duplicated field name: someField' + ); + } + }); + + it('should fail if updating an inexistent class', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + updateClass( + name: "SomeInexistentClass" + schemaFields: { addNumbers: [{ name: "someField" }] } + ) { + name + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.INVALID_CLASS_NAME + ); + expect(e.graphQLErrors[0].message).toEqual( + 'Class SomeInexistentClass does not exist.' + ); + } + }); + + it('should delete an existing class', async () => { + try { + const result = await apolloClient.mutate({ + mutation: gql` + mutation { + createClass( + name: "MyNewClass" + schemaFields: { addStrings: [{ name: "willBeRemoved" }] } + ) { + name + schemaFields { + name + __typename + } + } + deleteClass(name: "MyNewClass") { + name + schemaFields { + name + } + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + result.data.createClass.schemaFields = result.data.createClass.schemaFields.sort( + (a, b) => (a.name > b.name ? 1 : -1) + ); + result.data.deleteClass.schemaFields = result.data.deleteClass.schemaFields.sort( + (a, b) => (a.name > b.name ? 1 : -1) + ); + expect(result).toEqual({ + data: { + createClass: { + name: 'MyNewClass', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + { name: 'willBeRemoved', __typename: 'SchemaStringField' }, + ], + __typename: 'Class', + }, + deleteClass: { + name: 'MyNewClass', + schemaFields: [ + { name: 'ACL', __typename: 'SchemaACLField' }, + { name: 'createdAt', __typename: 'SchemaDateField' }, + { name: 'objectId', __typename: 'SchemaStringField' }, + { name: 'updatedAt', __typename: 'SchemaDateField' }, + { name: 'willBeRemoved', __typename: 'SchemaStringField' }, + ], + __typename: 'Class', + }, + }, + }); + + try { + await apolloClient.query({ + query: gql` + query { + class(name: "MyNewClass") { + name + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.INVALID_CLASS_NAME + ); + expect(e.graphQLErrors[0].message).toEqual( + 'Class MyNewClass does not exist.' + ); + } + } catch (e) { + handleError(e); + } + }); + + it('should require master key to delete an existing class', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + createClass(name: "SomeClass") { + name + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + } catch (e) { + handleError(e); + } + + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + deleteClass(name: "SomeClass") { + name + } + } + `, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.OPERATION_FORBIDDEN + ); + expect(e.graphQLErrors[0].message).toEqual( + 'unauthorized: master key is required' + ); + } + }); + + it('should fail if deleting an inexistent class', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation { + deleteClass(name: "SomeInexistentClass") { + name + } + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.INVALID_CLASS_NAME + ); + expect(e.graphQLErrors[0].message).toEqual( + 'Class SomeInexistentClass does not exist.' + ); + } + }); + + it('should require master key to get an existing class', async () => { + try { + await apolloClient.query({ + query: gql` + query { + class(name: "_User") { + name + } + } + `, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.OPERATION_FORBIDDEN + ); + expect(e.graphQLErrors[0].message).toEqual( + 'unauthorized: master key is required' + ); + } + }); + + it('should require master key to find the existing classes', async () => { + try { + await apolloClient.query({ + query: gql` + query { + classes { + name + } + } + `, + }); + fail('should fail'); + } catch (e) { + expect(e.graphQLErrors[0].extensions.code).toEqual( + Parse.Error.OPERATION_FORBIDDEN + ); + expect(e.graphQLErrors[0].message).toEqual( + 'unauthorized: master key is required' + ); + } + }); + }); + + describe('Objects Queries', () => { + describe('Get', () => { + it('should return a class object using class specific query', async () => { + const obj = new Parse.Object('Customer'); + obj.set('someField', 'someValue'); + await obj.save(); + + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + + const result = (await apolloClient.query({ + query: gql` + query GetCustomer($id: ID!) { + customer(id: $id) { + id + someField + createdAt + updatedAt + } + } + `, + variables: { + id: obj.id, + }, + })).data.customer; + + expect(result.id).toEqual(obj.id); + expect(result.someField).toEqual('someValue'); + expect(new Date(result.createdAt)).toEqual(obj.createdAt); + expect(new Date(result.updatedAt)).toEqual(obj.updatedAt); + }); + + it_only_db('mongo')( + 'should return child objects in array fields', + async () => { + const obj1 = new Parse.Object('Customer'); + const obj2 = new Parse.Object('SomeClass'); + const obj3 = new Parse.Object('Customer'); + + obj1.set('someCustomerField', 'imCustomerOne'); + const arrayField = [42.42, 42, 'string', true]; + obj1.set('arrayField', arrayField); + await obj1.save(); + + obj2.set('someClassField', 'imSomeClassTwo'); + await obj2.save(); + + obj3.set('manyRelations', [obj1, obj2]); + await obj3.save(); + + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + + const result = (await apolloClient.query({ + query: gql` + query GetCustomer($id: ID!) { + customer(id: $id) { + id + manyRelations { + ... on Customer { + id + someCustomerField + arrayField { + ... on Element { + value + } + } + } + ... on SomeClass { + id + someClassField + } + } + createdAt + updatedAt + } + } + `, + variables: { + id: obj3.id, + }, + })).data.customer; + + expect(result.id).toEqual(obj3.id); + expect(result.manyRelations.length).toEqual(2); const customerSubObject = result.manyRelations.find( o => o.id === obj1.id @@ -1822,10 +2890,11 @@ describe('ParseGraphQLServer', () => { const specificQueryResult = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - ${className.charAt(0).toLowerCase() + + get: ${className.charAt(0).toLowerCase() + className.slice(1)}(id: $id) { - id + id createdAt + someField } } `, @@ -1837,26 +2906,7 @@ describe('ParseGraphQLServer', () => { }, }); - const genericQueryResult = await apolloClient.query({ - query: gql` - query GetSomeObject($className: String!, $id: ID!) { - get(className: $className, id: $id) - } - `, - variables: { - className, - id, - }, - context: { - headers, - }, - }); - - expect(genericQueryResult.id).toEqual(specificQueryResult.id); - expect(genericQueryResult.createdAt).toEqual( - specificQueryResult.createdAt - ); - return genericQueryResult; + return specificQueryResult; } await Promise.all( @@ -1948,55 +2998,17 @@ describe('ParseGraphQLServer', () => { ).toEqual('someValue4'); }); - it('should not bring session token of another user', async () => { - await prepareData(); - - const result = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "_User", id: $id) - } - `, - variables: { - id: user2.id, - }, - context: { - headers: { - 'X-Parse-Session-Token': user1.getSessionToken(), - }, - }, - }); - expect(result.data.get.sessionToken).toBeUndefined(); - }); - - it('should not bring session token of current user', async () => { - await prepareData(); - - const result = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "_User", id: $id) - } - `, - variables: { - id: user1.id, - }, - context: { - headers: { - 'X-Parse-Session-Token': user1.getSessionToken(), - }, - }, - }); - expect(result.data.get.sessionToken).toBeUndefined(); - }); - it('should support keys argument', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const result1 = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get(className: "GraphQLClass", id: $id, keys: "someField") + get: graphQLClass(id: $id) { + someField + } } `, variables: { @@ -2012,11 +3024,12 @@ describe('ParseGraphQLServer', () => { const result2 = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get( - className: "GraphQLClass" - id: $id - keys: "someField,pointerToUser" - ) + get: graphQLClass(id: $id) { + someField + pointerToUser { + id + } + } } `, variables: { @@ -2043,7 +3056,11 @@ describe('ParseGraphQLServer', () => { const result1 = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get(className: "GraphQLClass", id: $id) + get: graphQLClass(id: $id) { + pointerToUser { + id + } + } } `, variables: { @@ -2059,11 +3076,6 @@ describe('ParseGraphQLServer', () => { const result2 = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get( - className: "GraphQLClass" - id: $id - include: "pointerToUser" - ) graphQLClass(id: $id) { pointerToUser { username @@ -2082,7 +3094,6 @@ describe('ParseGraphQLServer', () => { }); expect(result1.data.get.pointerToUser.username).toBeUndefined(); - expect(result2.data.get.pointerToUser.username).toBeDefined(); expect( result2.data.graphQLClass.pointerToUser.username ).toBeDefined(); @@ -2092,6 +3103,8 @@ describe('ParseGraphQLServer', () => { it('should read from primary by default', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const databaseAdapter = parseServer.config.databaseController.adapter; spyOn( @@ -2102,11 +3115,11 @@ describe('ParseGraphQLServer', () => { await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get( - className: "GraphQLClass" - id: $id - include: "pointerToUser" - ) + graphQLClass(id: $id) { + pointerToUser { + username + } + } } `, variables: { @@ -2144,6 +3157,8 @@ describe('ParseGraphQLServer', () => { it('should support readPreference argument', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const databaseAdapter = parseServer.config.databaseController.adapter; spyOn( @@ -2154,12 +3169,11 @@ describe('ParseGraphQLServer', () => { await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get( - className: "GraphQLClass" - id: $id - include: "pointerToUser" - readPreference: SECONDARY - ) + graphQLClass(id: $id, readPreference: SECONDARY) { + pointerToUser { + username + } + } } `, variables: { @@ -2197,6 +3211,8 @@ describe('ParseGraphQLServer', () => { it('should support includeReadPreference argument', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const databaseAdapter = parseServer.config.databaseController.adapter; spyOn( @@ -2207,13 +3223,15 @@ describe('ParseGraphQLServer', () => { await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get( - className: "GraphQLClass" + graphQLClass( id: $id - include: "pointerToUser" readPreference: SECONDARY includeReadPreference: NEAREST - ) + ) { + pointerToUser { + username + } + } } `, variables: { @@ -2251,35 +3269,6 @@ describe('ParseGraphQLServer', () => { }); describe('Find', () => { - it('should return class objects using generic query', async () => { - const obj1 = new Parse.Object('SomeClass'); - obj1.set('someField', 'someValue1'); - await obj1.save(); - const obj2 = new Parse.Object('SomeClass'); - obj2.set('someField', 'someValue1'); - await obj2.save(); - - const result = await apolloClient.query({ - query: gql` - query FindSomeObjects { - find(className: "SomeClass") { - results - } - } - `, - }); - - expect(result.data.find.results.length).toEqual(2); - - result.data.find.results.forEach(resultObj => { - const obj = resultObj.id === obj1.id ? obj1 : obj2; - expect(resultObj.id).toEqual(obj.id); - expect(resultObj.someField).toEqual(obj.get('someField')); - expect(new Date(resultObj.createdAt)).toEqual(obj.createdAt); - expect(new Date(resultObj.updatedAt)).toEqual(obj.updatedAt); - }); - }); - it('should return class objects using class specific query', async () => { const obj1 = new Parse.Object('Customer'); obj1.set('someField', 'someValue1'); @@ -2320,17 +3309,15 @@ describe('ParseGraphQLServer', () => { await prepareData(); await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + async function findObjects(className, headers) { const graphqlClassName = pluralize( className.charAt(0).toLowerCase() + className.slice(1) ); const result = await apolloClient.query({ query: gql` - query FindSomeObjects($className: String!) { - find(className: $className) { - results - } - ${graphqlClassName} { + query FindSomeObjects { + find: ${graphqlClassName} { results { id someField @@ -2338,24 +3325,11 @@ describe('ParseGraphQLServer', () => { } } `, - variables: { - className, - }, context: { headers, }, }); - const genericFindResults = result.data.find.results; - const specificFindResults = result.data[graphqlClassName].results; - genericFindResults.forEach(({ id, someField }) => { - expect( - specificFindResults.some( - ({ id: specificid, someField: specificSomeField }) => - id === specificid && someField === specificSomeField - ) - ); - }); return result; } @@ -2419,48 +3393,6 @@ describe('ParseGraphQLServer', () => { ).toEqual(['someValue3']); }); - it('should support where argument using generic query', async () => { - await prepareData(); - - const result = await apolloClient.query({ - query: gql` - query FindSomeObjects($where: Object) { - find(className: "GraphQLClass", where: $where) { - results - } - } - `, - variables: { - where: { - someField: { - $in: ['someValue1', 'someValue2', 'someValue3'], - }, - $or: [ - { - pointerToUser: { - __type: 'Pointer', - className: '_User', - objectId: user5.id, - }, - }, - { - id: object1.id, - }, - ], - }, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, - }, - }); - - expect( - result.data.find.results.map(object => object.someField).sort() - ).toEqual(['someValue1', 'someValue3']); - }); - it('should support where argument using class specific query', async () => { await prepareData(); @@ -2564,28 +3496,16 @@ describe('ParseGraphQLServer', () => { const result = await apolloClient.query({ query: gql` query FindSomeObjects( - $className: String! - $where: Object - $whereCustom: SomeClassWhereInput - $order: String - $orderCustom: [SomeClassOrder!] + $where: SomeClassWhereInput + $order: [SomeClassOrder!] $skip: Int $limit: Int ) { - find( - className: $className + find: someClasses( where: $where order: $order skip: $skip limit: $limit - ) { - results - } - someClasses( - where: $whereCustom - order: $orderCustom - skip: $skip - limit: $limit ) { results { someField @@ -2594,19 +3514,12 @@ describe('ParseGraphQLServer', () => { } `, variables: { - className: 'SomeClass', where: { - someField: { - $regex: '^someValue', - }, - }, - whereCustom: { someField: { _regex: '^someValue', }, }, - order: '-numberField,someField', - orderCustom: ['numberField_DESC', 'someField_ASC'], + order: ['numberField_DESC', 'someField_ASC'], skip: 4, limit: 2, }, @@ -2616,9 +3529,6 @@ describe('ParseGraphQLServer', () => { 'someValue14', 'someValue17', ]); - expect( - result.data.someClasses.results.map(obj => obj.someField) - ).toEqual(['someValue14', 'someValue17']); }); it('should support count', async () => { @@ -2651,19 +3561,10 @@ describe('ParseGraphQLServer', () => { const result = await apolloClient.query({ query: gql` query FindSomeObjects( - $where1: Object - $where2: GraphQLClassWhereInput + $where: GraphQLClassWhereInput $limit: Int ) { - find( - className: "GraphQLClass" - where: $where1 - limit: $limit - ) { - results - count - } - graphQLClasses(where: $where2, limit: $limit) { + find: graphQLClasses(where: $where, limit: $limit) { results { id } @@ -2672,8 +3573,7 @@ describe('ParseGraphQLServer', () => { } `, variables: { - where1: where, - where2: where, + where, limit: 0, }, context: { @@ -2685,8 +3585,6 @@ describe('ParseGraphQLServer', () => { expect(result.data.find.results).toEqual([]); expect(result.data.find.count).toEqual(2); - expect(result.data.graphQLClasses.results).toEqual([]); - expect(result.data.graphQLClasses.count).toEqual(2); }); it('should only count', async () => { @@ -2718,21 +3616,14 @@ describe('ParseGraphQLServer', () => { const result = await apolloClient.query({ query: gql` - query FindSomeObjects( - $where1: Object - $where2: GraphQLClassWhereInput - ) { - find(className: "GraphQLClass", where: $where1) { - count - } - graphQLClasses(where: $where2) { + query FindSomeObjects($where: GraphQLClassWhereInput) { + find: graphQLClasses(where: $where) { count } } `, variables: { - where1: where, - where2: where, + where, }, context: { headers: { @@ -2743,8 +3634,6 @@ describe('ParseGraphQLServer', () => { expect(result.data.find.results).toBeUndefined(); expect(result.data.find.count).toEqual(2); - expect(result.data.graphQLClasses.results).toBeUndefined(); - expect(result.data.graphQLClasses.count).toEqual(2); }); it('should respect max limit', async () => { @@ -2764,15 +3653,10 @@ describe('ParseGraphQLServer', () => { const result = await apolloClient.query({ query: gql` query FindSomeObjects($limit: Int) { - find( - className: "SomeClass" + find: someClasses( where: { id: { _exists: true } } limit: $limit ) { - results - count - } - someClasses(where: { id: { _exists: true } }, limit: $limit) { results { id } @@ -2792,28 +3676,26 @@ describe('ParseGraphQLServer', () => { expect(result.data.find.results.length).toEqual(10); expect(result.data.find.count).toEqual(100); - expect(result.data.someClasses.results.length).toEqual(10); - expect(result.data.someClasses.count).toEqual(100); }); it('should support keys argument', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const result1 = await apolloClient.query({ query: gql` - query FindSomeObject($where: Object) { - find( - className: "GraphQLClass" - where: $where - keys: "someField" - ) { - results + query FindSomeObject($where: GraphQLClassWhereInput) { + find: graphQLClasses(where: $where) { + results { + someField + } } } `, variables: { where: { - id: object3.id, + id: { _eq: object3.id }, }, }, context: { @@ -2825,19 +3707,20 @@ describe('ParseGraphQLServer', () => { const result2 = await apolloClient.query({ query: gql` - query FindSomeObject($where: Object) { - find( - className: "GraphQLClass" - where: $where - keys: "someField,pointerToUser" - ) { - results + query FindSomeObject($where: GraphQLClassWhereInput) { + find: graphQLClasses(where: $where) { + results { + someField + pointerToUser { + username + } + } } } `, variables: { where: { - id: object3.id, + id: { _eq: object3.id }, }, }, context: { @@ -2858,18 +3741,26 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const where = { + id: { + _eq: object3.id, + }, + }; + const result1 = await apolloClient.query({ query: gql` - query FindSomeObject($where: Object) { - find(className: "GraphQLClass", where: $where) { - results + query FindSomeObject($where: GraphQLClassWhereInput) { + find: graphQLClasses(where: $where) { + results { + pointerToUser { + id + } + } } } `, variables: { - where: { - id: object3.id, - }, + where, }, context: { headers: { @@ -2878,26 +3769,10 @@ describe('ParseGraphQLServer', () => { }, }); - const where = { - id: { - _eq: object3.id, - }, - }; - const result2 = await apolloClient.query({ query: gql` - query FindSomeObject( - $where1: Object - $where2: GraphQLClassWhereInput - ) { - find( - className: "GraphQLClass" - where: $where1 - include: "pointerToUser" - ) { - results - } - graphQLClasses(where: $where2) { + query FindSomeObject($where: GraphQLClassWhereInput) { + find: graphQLClasses(where: $where) { results { pointerToUser { username @@ -2907,8 +3782,7 @@ describe('ParseGraphQLServer', () => { } `, variables: { - where1: where, - where2: where, + where, }, context: { headers: { @@ -2922,59 +3796,14 @@ describe('ParseGraphQLServer', () => { expect( result2.data.find.results[0].pointerToUser.username ).toBeDefined(); - expect( - result2.data.graphQLClasses.results[0].pointerToUser.username - ).toBeDefined(); - }); - - it('should support includeAll argument', async () => { - const obj1 = new Parse.Object('SomeClass1'); - obj1.set('someField1', 'someValue1'); - const obj2 = new Parse.Object('SomeClass2'); - obj2.set('someField2', 'someValue2'); - const obj3 = new Parse.Object('SomeClass3'); - obj3.set('obj1', obj1); - obj3.set('obj2', obj2); - await Promise.all([obj1.save(), obj2.save(), obj3.save()]); - - const result1 = await apolloClient.query({ - query: gql` - query FindSomeObject { - find(className: "SomeClass3") { - results - } - } - `, - }); - - const result2 = await apolloClient.query({ - query: gql` - query FindSomeObject { - find(className: "SomeClass3", includeAll: true) { - results - } - } - `, - }); - - expect( - result1.data.find.results[0].obj1.someField1 - ).toBeUndefined(); - expect( - result1.data.find.results[0].obj2.someField2 - ).toBeUndefined(); - expect(result2.data.find.results[0].obj1.someField1).toEqual( - 'someValue1' - ); - expect(result2.data.find.results[0].obj2.someField2).toEqual( - 'someValue2' - ); }); describe_only_db('mongo')('read preferences', () => { it('should read from primary by default', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const databaseAdapter = parseServer.config.databaseController.adapter; spyOn( @@ -2985,8 +3814,12 @@ describe('ParseGraphQLServer', () => { await apolloClient.query({ query: gql` query FindSomeObjects { - find(className: "GraphQLClass", include: "pointerToUser") { - results + find: graphQLClasses { + results { + pointerToUser { + username + } + } } } `, @@ -3022,6 +3855,8 @@ describe('ParseGraphQLServer', () => { it('should support readPreference argument', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const databaseAdapter = parseServer.config.databaseController.adapter; spyOn( @@ -3032,12 +3867,12 @@ describe('ParseGraphQLServer', () => { await apolloClient.query({ query: gql` query FindSomeObjects { - find( - className: "GraphQLClass" - include: "pointerToUser" - readPreference: SECONDARY - ) { - results + find: graphQLClasses(readPreference: SECONDARY) { + results { + pointerToUser { + username + } + } } } `, @@ -3073,6 +3908,8 @@ describe('ParseGraphQLServer', () => { it('should support includeReadPreference argument', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const databaseAdapter = parseServer.config.databaseController.adapter; spyOn( @@ -3083,13 +3920,15 @@ describe('ParseGraphQLServer', () => { await apolloClient.query({ query: gql` query FindSomeObjects { - find( - className: "GraphQLClass" - include: "pointerToUser" + graphQLClasses( readPreference: SECONDARY includeReadPreference: NEAREST ) { - results + results { + pointerToUser { + username + } + } } } `, @@ -3123,98 +3962,80 @@ describe('ParseGraphQLServer', () => { }); it('should support subqueryReadPreference argument', async () => { - await prepareData(); - - const databaseAdapter = - parseServer.config.databaseController.adapter; - spyOn( - databaseAdapter.database.serverConfig, - 'cursor' - ).and.callThrough(); - - await apolloClient.query({ - query: gql` - query FindSomeObjects($where: Object) { - find( - className: "GraphQLClass" - where: $where - readPreference: SECONDARY - subqueryReadPreference: NEAREST - ) { - results + try { + await prepareData(); + + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + + const databaseAdapter = + parseServer.config.databaseController.adapter; + spyOn( + databaseAdapter.database.serverConfig, + 'cursor' + ).and.callThrough(); + + await apolloClient.query({ + query: gql` + query FindSomeObjects($where: GraphQLClassWhereInput) { + find: graphQLClasses( + where: $where + readPreference: SECONDARY + subqueryReadPreference: NEAREST + ) { + results { + id + } + } } - } - `, - variables: { - where: { - pointerToUser: { - $inQuery: { where: {}, className: '_User' }, + `, + variables: { + where: { + pointerToUser: { + _inQuery: { where: {}, className: '_User' }, + }, }, }, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - }, - }); - - let foundGraphQLClassReadPreference = false; - let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls - .all() - .forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { - foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe( - ReadPreference.SECONDARY - ); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { - foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe( - ReadPreference.NEAREST - ); - } }); - expect(foundGraphQLClassReadPreference).toBe(true); - expect(foundUserClassReadPreference).toBe(true); - }); - }); - }); - }); - - describe('Objects Mutations', () => { - describe('Create', () => { - it('should return CreateResult object using generic mutation', async () => { - const result = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id - createdAt - } - } - `, - variables: { - fields: { - someField: 'someValue', - }, - }, - }); - - expect(result.data.create.id).toBeDefined(); - - const obj = await new Parse.Query('SomeClass').get( - result.data.create.id - ); + let foundGraphQLClassReadPreference = false; + let foundUserClassReadPreference = false; + databaseAdapter.database.serverConfig.cursor.calls + .all() + .forEach(call => { + if ( + call.args[0].ns.collection.indexOf('GraphQLClass') >= 0 + ) { + foundGraphQLClassReadPreference = true; + expect(call.args[0].options.readPreference.mode).toBe( + ReadPreference.SECONDARY + ); + } else if ( + call.args[0].ns.collection.indexOf('_User') >= 0 + ) { + foundUserClassReadPreference = true; + expect(call.args[0].options.readPreference.mode).toBe( + ReadPreference.NEAREST + ); + } + }); - expect(obj.createdAt).toEqual( - new Date(result.data.create.createdAt) - ); - expect(obj.get('someField')).toEqual('someValue'); + expect(foundGraphQLClassReadPreference).toBe(true); + expect(foundUserClassReadPreference).toBe(true); + } catch (e) { + handleError(e); + } + }); }); + }); + }); + describe('Objects Mutations', () => { + describe('Create', () => { it('should return specific type object using class specific mutation', async () => { const customerSchema = new Parse.Schema('Customer'); customerSchema.addString('someField'); @@ -3260,29 +4081,18 @@ describe('ParseGraphQLServer', () => { async function createObject(className, headers) { const result = await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($className: String!) { - create(className: $className) { - id - createdAt - } + mutation CreateSomeObject { create${className} { id createdAt } } `, - variables: { - className, - }, context: { headers, }, }); - const { create } = result.data; - expect(create.id).toBeDefined(); - expect(create.createdAt).toBeDefined(); - const specificCreate = result.data[`create${className}`]; expect(specificCreate.id).toBeDefined(); expect(specificCreate.createdAt).toBeDefined(); @@ -3340,36 +4150,6 @@ describe('ParseGraphQLServer', () => { }); describe('Update', () => { - it('should return UpdateResult object using generic mutation', async () => { - const obj = new Parse.Object('SomeClass'); - obj.set('someField1', 'someField1Value1'); - obj.set('someField2', 'someField2Value1'); - await obj.save(); - - const result = await apolloClient.mutate({ - mutation: gql` - mutation UpdateSomeObject($id: ID!, $fields: Object) { - update(className: "SomeClass", id: $id, fields: $fields) { - updatedAt - } - } - `, - variables: { - id: obj.id, - fields: { - someField1: 'someField1Value2', - }, - }, - }); - - expect(result.data.update.updatedAt).toBeDefined(); - - await obj.fetch(); - - expect(obj.get('someField1')).toEqual('someField1Value2'); - expect(obj.get('someField2')).toEqual('someField2Value1'); - }); - it('should return specific type object using class specific mutation', async () => { const obj = new Parse.Object('Customer'); obj.set('someField1', 'someField1Value1'); @@ -3451,21 +4231,24 @@ describe('ParseGraphQLServer', () => { it('should respect level permissions', async () => { await prepareData(); - function updateObject(className, id, fields, headers) { - return apolloClient.mutate({ + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + + async function updateObject(className, id, fields, headers) { + return await apolloClient.mutate({ mutation: gql` mutation UpdateSomeObject( - $className: String! $id: ID! - $fields: Object + $fields: Update${className}FieldsInput ) { - update(className: $className, id: $id, fields: $fields) { + update: update${className}( + id: $id + fields: $fields + ) { updatedAt } } `, variables: { - className, id, fields, }, @@ -3803,28 +4586,6 @@ describe('ParseGraphQLServer', () => { }); describe('Delete', () => { - it('should return a boolean confirmation using generic mutation', async () => { - const obj = new Parse.Object('SomeClass'); - await obj.save(); - - const result = await apolloClient.mutate({ - mutation: gql` - mutation DeleteSomeObject($id: ID!) { - delete(className: "SomeClass", id: $id) - } - `, - variables: { - id: obj.id, - }, - }); - - expect(result.data.delete).toEqual(true); - - await expectAsync( - obj.fetch({ useMasterKey: true }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); - }); - it('should return a specific type using class specific mutation', async () => { const obj = new Parse.Object('Customer'); obj.set('someField1', 'someField1Value1'); @@ -3864,15 +4625,20 @@ describe('ParseGraphQLServer', () => { it('should respect level permissions', async () => { await prepareData(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + function deleteObject(className, id, headers) { return apolloClient.mutate({ mutation: gql` - mutation DeleteSomeObject($className: String!, $id: ID!) { - delete(className: $className, id: $id) + mutation DeleteSomeObject( + $id: ID! + ) { + delete: delete${className}(id: $id) { + id + } } `, variables: { - className, id, }, context: { @@ -3905,7 +4671,7 @@ describe('ParseGraphQLServer', () => { ); expect( (await deleteObject(object4.className, object4.id)).data.delete - ).toEqual(true); + ).toEqual({ id: object4.id, __typename: 'PublicClass' }); await expectAsync( object4.fetch({ useMasterKey: true }) ).toBeRejectedWith(jasmine.stringMatching('Object not found')); @@ -3913,7 +4679,7 @@ describe('ParseGraphQLServer', () => { (await deleteObject(object1.className, object1.id, { 'X-Parse-Master-Key': 'test', })).data.delete - ).toEqual(true); + ).toEqual({ id: object1.id, __typename: 'GraphQLClass' }); await expectAsync( object1.fetch({ useMasterKey: true }) ).toBeRejectedWith(jasmine.stringMatching('Object not found')); @@ -3921,7 +4687,7 @@ describe('ParseGraphQLServer', () => { (await deleteObject(object2.className, object2.id, { 'X-Parse-Session-Token': user2.getSessionToken(), })).data.delete - ).toEqual(true); + ).toEqual({ id: object2.id, __typename: 'GraphQLClass' }); await expectAsync( object2.fetch({ useMasterKey: true }) ).toBeRejectedWith(jasmine.stringMatching('Object not found')); @@ -3929,7 +4695,7 @@ describe('ParseGraphQLServer', () => { (await deleteObject(object3.className, object3.id, { 'X-Parse-Session-Token': user5.getSessionToken(), })).data.delete - ).toEqual(true); + ).toEqual({ id: object3.id, __typename: 'GraphQLClass' }); await expectAsync( object3.fetch({ useMasterKey: true }) ).toBeRejectedWith(jasmine.stringMatching('Object not found')); @@ -4511,368 +5277,402 @@ describe('ParseGraphQLServer', () => { describe('Data Types', () => { it('should support String', async () => { - const someFieldValue = 'some string'; + try { + const someFieldValue = 'some string'; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addStrings: [{ name: 'someField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('String'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('String'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + fields: { + someField: someFieldValue, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!, $someFieldValue: String) { - get(className: "SomeClass", id: $id) - someClasses(where: { someField: { _eq: $someFieldValue } }) { - results { + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!, $someFieldValue: String) { + someClass(id: $id) { someField } + someClasses(where: { someField: { _eq: $someFieldValue } }) { + results { + someField + } + } } - } - `, - variables: { - id: createResult.data.create.id, - someFieldValue, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass.id, + someFieldValue, + }, + }); - expect(typeof getResult.data.get.someField).toEqual('string'); - expect(getResult.data.get.someField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(2); + expect(typeof getResult.data.someClass.someField).toEqual('string'); + expect(getResult.data.someClass.someField).toEqual(someFieldValue); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); it('should support Int numbers', async () => { - const someFieldValue = 123; + try { + const someFieldValue = 123; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addNumbers: [{ name: 'someField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + fields: { + someField: someFieldValue, + }, }, - }, - }); + }); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Number'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('Number'); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!, $someFieldValue: Float) { - get(className: "SomeClass", id: $id) - someClasses(where: { someField: { _eq: $someFieldValue } }) { - results { + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!, $someFieldValue: Float) { + someClass(id: $id) { someField } + someClasses(where: { someField: { _eq: $someFieldValue } }) { + results { + someField + } + } } - } - `, - variables: { - id: createResult.data.create.id, - someFieldValue, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass.id, + someFieldValue, + }, + }); - expect(typeof getResult.data.get.someField).toEqual('number'); - expect(getResult.data.get.someField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(2); + expect(typeof getResult.data.someClass.someField).toEqual('number'); + expect(getResult.data.someClass.someField).toEqual(someFieldValue); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); it('should support Float numbers', async () => { - const someFieldValue = 123.4; + try { + const someFieldValue = 123.4; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addNumbers: [{ name: 'someField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Number'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('Number'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + fields: { + someField: someFieldValue, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!, $someFieldValue: Float) { - get(className: "SomeClass", id: $id) - someClasses(where: { someField: { _eq: $someFieldValue } }) { - results { + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!, $someFieldValue: Float) { + someClass(id: $id) { someField } + someClasses(where: { someField: { _eq: $someFieldValue } }) { + results { + someField + } + } } - } - `, - variables: { - id: createResult.data.create.id, - someFieldValue, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass.id, + someFieldValue, + }, + }); - expect(typeof getResult.data.get.someField).toEqual('number'); - expect(getResult.data.get.someField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(2); + expect(typeof getResult.data.someClass.someField).toEqual('number'); + expect(getResult.data.someClass.someField).toEqual(someFieldValue); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); it('should support Boolean', async () => { - const someFieldValueTrue = true; - const someFieldValueFalse = false; + try { + const someFieldValueTrue = true; + const someFieldValueFalse = false; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someFieldTrue: someFieldValueTrue, - someFieldFalse: someFieldValueFalse, + `, + variables: { + schemaFields: { + addBooleans: [ + { name: 'someFieldTrue' }, + { name: 'someFieldFalse' }, + ], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someFieldTrue.type).toEqual('Boolean'); - expect(schema.fields.someFieldFalse.type).toEqual('Boolean'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someFieldTrue.type).toEqual('Boolean'); + expect(schema.fields.someFieldFalse.type).toEqual('Boolean'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someFieldTrue: someFieldValueTrue, - someFieldFalse: someFieldValueFalse, + `, + variables: { + fields: { + someFieldTrue: someFieldValueTrue, + someFieldFalse: someFieldValueFalse, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject( - $id: ID! - $someFieldValueTrue: Boolean - $someFieldValueFalse: Boolean - ) { - get(className: "SomeClass", id: $id) - someClasses( - where: { - someFieldTrue: { _eq: $someFieldValueTrue } - someFieldFalse: { _eq: $someFieldValueFalse } - } + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject( + $id: ID! + $someFieldValueTrue: Boolean + $someFieldValueFalse: Boolean ) { - results { - id + someClass(id: $id) { + someFieldTrue + someFieldFalse + } + someClasses( + where: { + someFieldTrue: { _eq: $someFieldValueTrue } + someFieldFalse: { _eq: $someFieldValueFalse } + } + ) { + results { + id + } } } - } - `, - variables: { - id: createResult.data.create.id, - someFieldValueTrue, - someFieldValueFalse, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass.id, + someFieldValueTrue, + someFieldValueFalse, + }, + }); - expect(typeof getResult.data.get.someFieldTrue).toEqual('boolean'); - expect(typeof getResult.data.get.someFieldFalse).toEqual('boolean'); - expect(getResult.data.get.someFieldTrue).toEqual(true); - expect(getResult.data.get.someFieldFalse).toEqual(false); - expect(getResult.data.someClasses.results.length).toEqual(2); + expect(typeof getResult.data.someClass.someFieldTrue).toEqual( + 'boolean' + ); + expect(typeof getResult.data.someClass.someFieldFalse).toEqual( + 'boolean' + ); + expect(getResult.data.someClass.someFieldTrue).toEqual(true); + expect(getResult.data.someClass.someFieldFalse).toEqual(false); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); it('should support Date', async () => { - const someFieldValue = { - __type: 'Date', - iso: new Date().toISOString(), - }; + try { + const someFieldValue = new Date(); - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addDates: [{ name: 'someField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Date'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('Date'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + fields: { + someField: someFieldValue, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) - someClasses(where: { someField: { _exists: true } }) { - results { - id + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!) { + someClass(id: $id) { + someField + } + someClasses(where: { someField: { _exists: true } }) { + results { + id + } } } - } - `, - variables: { - id: createResult.data.create.id, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass.id, + }, + }); - expect(typeof getResult.data.get.someField).toEqual('object'); - expect(getResult.data.get.someField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(2); + expect(new Date(getResult.data.someClass.someField)).toEqual( + someFieldValue + ); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); - it('should support createdAt', async () => { - const createResult = await apolloClient.mutate({ + it('should support createdAt and updatedAt', async () => { + await apolloClient.mutate({ mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - createdAt + mutation CreateClass { + createClass(name: "SomeClass") { + name } } `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, }); const schema = await new Parse.Schema('SomeClass').get(); expect(schema.fields.createdAt.type).toEqual('Date'); - - const { createdAt } = createResult.data.create; - expect(Date.parse(createdAt)).not.toEqual(NaN); - }); - - it('should support updatedAt', async () => { - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id - } - } - `, - }); - - const schema = await new Parse.Schema('SomeClass').get(); expect(schema.fields.updatedAt.type).toEqual('Date'); - - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) - } - `, - variables: { - id: createResult.data.create.id, - }, - }); - - expect(typeof getResult.data.get.updatedAt).toEqual('string'); - expect(Date.parse(getResult.data.get.updatedAt)).not.toEqual(NaN); }); it('should support pointer on create', async () => { @@ -5359,6 +6159,7 @@ describe('ParseGraphQLServer', () => { id name } + count } } } @@ -5407,407 +6208,451 @@ describe('ParseGraphQLServer', () => { }); it('should support files', async () => { - parseServer = await global.reconfigureServer({ - publicServerURL: 'http://localhost:13377/parse', - }); + try { + parseServer = await global.reconfigureServer({ + publicServerURL: 'http://localhost:13377/parse', + }); - const body = new FormData(); - body.append( - 'operations', - JSON.stringify({ - query: ` - mutation CreateFile($upload: Upload!) { - createFile(upload: $upload) { - name - url + const body = new FormData(); + body.append( + 'operations', + JSON.stringify({ + query: ` + mutation CreateFile($upload: Upload!) { + createFile(upload: $upload) { + name + url + } } - } - `, - variables: { - upload: null, - }, - }) - ); - body.append('map', JSON.stringify({ 1: ['variables.upload'] })); - body.append('1', 'My File Content', { - filename: 'myFileName.txt', - contentType: 'text/plain', - }); - - let res = await fetch('http://localhost:13377/graphql', { - method: 'POST', - headers, - body, - }); - - expect(res.status).toEqual(200); + `, + variables: { + upload: null, + }, + }) + ); + body.append('map', JSON.stringify({ 1: ['variables.upload'] })); + body.append('1', 'My File Content', { + filename: 'myFileName.txt', + contentType: 'text/plain', + }); - const result = JSON.parse(await res.text()); + let res = await fetch('http://localhost:13377/graphql', { + method: 'POST', + headers, + body, + }); - expect(result.data.createFile.name).toEqual( - jasmine.stringMatching(/_myFileName.txt$/) - ); - expect(result.data.createFile.url).toEqual( - jasmine.stringMatching(/_myFileName.txt$/) - ); + expect(res.status).toEqual(200); - const someFieldValue = { - __type: 'File', - name: result.data.createFile.name, - url: result.data.createFile.url, - }; + const result = JSON.parse(await res.text()); - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id - } - } - `, - variables: { - fields: { - someField: someFieldValue, - }, - }, - }); + expect(result.data.createFile.name).toEqual( + jasmine.stringMatching(/_myFileName.txt$/) + ); + expect(result.data.createFile.url).toEqual( + jasmine.stringMatching(/_myFileName.txt$/) + ); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const someFieldValue = result.data.createFile.name; - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject( - $fields1: CreateSomeClassFieldsInput - $fields2: CreateSomeClassFieldsInput - ) { - createSomeClass1: createSomeClass(fields: $fields1) { - id - } - createSomeClass2: createSomeClass(fields: $fields2) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreaClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields1: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addFiles: [{ name: 'someField' }], + }, }, - fields2: { - someField: someFieldValue.name, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - }, - }); + }); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('File'); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) - findSomeClass1: someClasses( - where: { someField: { _exists: true } } + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject( + $fields1: CreateSomeClassFieldsInput + $fields2: CreateSomeClassFieldsInput ) { - results { - someField { - name - url - } + createSomeClass1: createSomeClass(fields: $fields1) { + id + } + createSomeClass2: createSomeClass(fields: $fields2) { + id } } - findSomeClass2: someClasses( - where: { someField: { _exists: true } } - ) { - results { + `, + variables: { + fields1: { + someField: someFieldValue, + }, + fields2: { + someField: someFieldValue.name, + }, + }, + }); + + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('File'); + + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!) { + someClass(id: $id) { someField { name url } } + findSomeClass1: someClasses( + where: { someField: { _exists: true } } + ) { + results { + someField { + name + url + } + } + } + findSomeClass2: someClasses( + where: { someField: { _exists: true } } + ) { + results { + someField { + name + url + } + } + } } - } - `, - variables: { - id: createResult.data.create.id, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass1.id, + }, + }); - expect(typeof getResult.data.get.someField).toEqual('object'); - expect(getResult.data.get.someField).toEqual(someFieldValue); - expect(getResult.data.findSomeClass1.results.length).toEqual(3); - expect(getResult.data.findSomeClass2.results.length).toEqual(3); + expect(typeof getResult.data.someClass.someField).toEqual('object'); + expect(getResult.data.someClass.someField.name).toEqual( + result.data.createFile.name + ); + expect(getResult.data.someClass.someField.url).toEqual( + result.data.createFile.url + ); + expect(getResult.data.findSomeClass1.results.length).toEqual(1); + expect(getResult.data.findSomeClass2.results.length).toEqual(1); - res = await fetch(getResult.data.get.someField.url); + res = await fetch(getResult.data.someClass.someField.url); - expect(res.status).toEqual(200); - expect(await res.text()).toEqual('My File Content'); + expect(res.status).toEqual(200); + expect(await res.text()).toEqual('My File Content'); + } catch (e) { + handleError(e); + } }); it('should support object values', async () => { - const someFieldValue = { - foo: { bar: 'baz' }, - number: 10, - }; + try { + const someFieldValue = { + foo: { bar: 'baz' }, + number: 10, + }; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addObjects: [{ name: 'someField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Object'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('Object'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + fields: { + someField: someFieldValue, + }, }, - }, - }); + }); - const where = { - someField: { - _eq: { _key: 'foo.bar', _value: 'baz' }, - _ne: { _key: 'foo.bar', _value: 'bat' }, - _gt: { _key: 'number', _value: 9 }, - _lt: { _key: 'number', _value: 11 }, - }, - }; - const queryResult = await apolloClient.query({ - query: gql` - query GetSomeObject( - $id: ID! - $where: SomeClassWhereInput - $genericWhere: Object - ) { - get(className: "SomeClass", id: $id) - someClasses(where: $where) { - results { + const where = { + someField: { + _eq: { _key: 'foo.bar', _value: 'baz' }, + _ne: { _key: 'foo.bar', _value: 'bat' }, + _gt: { _key: 'number', _value: 9 }, + _lt: { _key: 'number', _value: 11 }, + }, + }; + const queryResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!, $where: SomeClassWhereInput) { + someClass(id: $id) { id someField } + someClasses(where: $where) { + results { + id + someField + } + } } - find(className: "SomeClass", where: $genericWhere) { - results - } - } - `, - variables: { - id: createResult.data.create.id, - where, - genericWhere: where, // where and genericWhere types are different - }, - }); - - const { get: getResult, someClasses, find } = queryResult.data; + `, + variables: { + id: createResult.data.createSomeClass.id, + where, + }, + }); - const { someField } = getResult; - expect(typeof someField).toEqual('object'); - expect(someField).toEqual(someFieldValue); + const { someClass: getResult, someClasses } = queryResult.data; - // Checks class query results - expect(someClasses.results.length).toEqual(2); - expect(someClasses.results[0].someField).toEqual(someFieldValue); - expect(someClasses.results[1].someField).toEqual(someFieldValue); + const { someField } = getResult; + expect(typeof someField).toEqual('object'); + expect(someField).toEqual(someFieldValue); - // Checks generic query results - expect(find.results.length).toEqual(2); - expect(find.results[0].someField).toEqual(someFieldValue); - expect(find.results[1].someField).toEqual(someFieldValue); + // Checks class query results + expect(someClasses.results.length).toEqual(1); + expect(someClasses.results[0].someField).toEqual(someFieldValue); + } catch (e) { + handleError(e); + } }); it('should support object composed queries', async () => { - const someFieldValue = { - lorem: 'ipsum', - number: 10, - }; - const someFieldValue2 = { - foo: { - test: 'bar', - }, - number: 10, - }; + try { + const someFieldValue = { + lorem: 'ipsum', + number: 10, + }; + const someFieldValue2 = { + foo: { + test: 'bar', + }, + number: 10, + }; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields1: Object, $fields2: Object) { - create1: create(className: "SomeClass", fields: $fields1) { - id - } - create2: create(className: "SomeClass", fields: $fields2) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass { + createClass( + name: "SomeClass" + schemaFields: { addObjects: [{ name: "someField" }] } + ) { + name + } } - } - `, - variables: { - fields1: { - someField: someFieldValue, - }, - fields2: { - someField: someFieldValue2, + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - }, - }); + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const where = { - _and: [ - { - someField: { - _gt: { _key: 'number', _value: 9 }, + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject( + $fields1: CreateSomeClassFieldsInput + $fields2: CreateSomeClassFieldsInput + ) { + create1: createSomeClass(fields: $fields1) { + id + } + create2: createSomeClass(fields: $fields2) { + id + } + } + `, + variables: { + fields1: { + someField: someFieldValue, }, - }, - { - someField: { - _lt: { _key: 'number', _value: 11 }, + fields2: { + someField: someFieldValue2, }, }, - { - _or: [ - { - someField: { - _eq: { _key: 'lorem', _value: 'ipsum' }, - }, + }); + + const where = { + _and: [ + { + someField: { + _gt: { _key: 'number', _value: 9 }, }, - { - someField: { - _eq: { _key: 'foo.test', _value: 'bar' }, - }, + }, + { + someField: { + _lt: { _key: 'number', _value: 11 }, }, - ], - }, - ], - }; - const findResult = await apolloClient.query({ - query: gql` - query FindSomeObject( - $where: SomeClassWhereInput - $genericWhere: Object - ) { - someClasses(where: $where) { - results { - id - someField + }, + { + _or: [ + { + someField: { + _eq: { _key: 'lorem', _value: 'ipsum' }, + }, + }, + { + someField: { + _eq: { _key: 'foo.test', _value: 'bar' }, + }, + }, + ], + }, + ], + }; + const findResult = await apolloClient.query({ + query: gql` + query FindSomeObject($where: SomeClassWhereInput) { + someClasses(where: $where) { + results { + id + someField + } } } - find(className: "SomeClass", where: $genericWhere) { - results - } - } - `, - variables: { - where, - genericWhere: where, // where and genericWhere types are different - }, - }); - - const { create1, create2 } = createResult.data; - const { someClasses, find } = findResult.data; + `, + variables: { + where, + }, + }); - // Checks class query results - const { results } = someClasses; - expect(results.length).toEqual(2); - expect( - results.find(result => result.id === create1.id).someField - ).toEqual(someFieldValue); - expect( - results.find(result => result.id === create2.id).someField - ).toEqual(someFieldValue2); + const { create1, create2 } = createResult.data; + const { someClasses } = findResult.data; - // Checks generic query results - const { results: genericResults } = find; - expect(genericResults.length).toEqual(2); - expect( - genericResults.find(result => result.id === create1.id).someField - ).toEqual(someFieldValue); - expect( - genericResults.find(result => result.id === create2.id).someField - ).toEqual(someFieldValue2); + // Checks class query results + const { results } = someClasses; + expect(results.length).toEqual(2); + expect( + results.find(result => result.id === create1.id).someField + ).toEqual(someFieldValue); + expect( + results.find(result => result.id === create2.id).someField + ).toEqual(someFieldValue2); + } catch (e) { + handleError(e); + } }); it('should support array values', async () => { - const someFieldValue = [1, 'foo', ['bar'], { lorem: 'ipsum' }, true]; + try { + const someFieldValue = [ + 1, + 'foo', + ['bar'], + { lorem: 'ipsum' }, + true, + ]; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addArrays: [{ name: 'someField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Array'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('Array'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + fields: { + someField: someFieldValue, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) - someClasses(where: { someField: { _exists: true } }) { - results { - id + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!) { + someClass(id: $id) { someField { ... on Element { value } } } + someClasses(where: { someField: { _exists: true } }) { + results { + id + someField { + ... on Element { + value + } + } + } + } } - } - `, - variables: { - id: createResult.data.create.id, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass.id, + }, + }); - const { someField } = getResult.data.get; - expect(Array.isArray(someField)).toBeTruthy(); - expect(someField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(2); + const { someField } = getResult.data.someClass; + expect(Array.isArray(someField)).toBeTruthy(); + expect(someField.map(element => element.value)).toEqual( + someFieldValue + ); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); it('should support undefined array', async () => { @@ -5841,325 +6686,361 @@ describe('ParseGraphQLServer', () => { }); it('should support null values', async () => { - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + try { + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass { + createClass( + name: "SomeClass" + schemaFields: { + addStrings: [ + { name: "someStringField" } + { name: "someNullField" } + ] + addNumbers: [{ name: "someNumberField" }] + addBooleans: [{ name: "someBooleanField" }] + addObjects: [{ name: "someObjectField" }] + } + ) { + name + } } - } - `, - variables: { - fields: { - someStringField: 'some string', - someNumberField: 123, - someBooleanField: true, - someObjectField: { someField: 'some value' }, - someNullField: null, + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - }, - }); + }); - await apolloClient.mutate({ - mutation: gql` - mutation UpdateSomeObject($id: ID!, $fields: Object) { - update(className: "SomeClass", id: $id, fields: $fields) { - updatedAt + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - id: createResult.data.create.id, - fields: { - someStringField: null, - someNumberField: null, - someBooleanField: null, - someObjectField: null, - someNullField: 'now it has a string', + `, + variables: { + fields: { + someStringField: 'some string', + someNumberField: 123, + someBooleanField: true, + someObjectField: { someField: 'some value' }, + someNullField: null, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) - } - `, - variables: { - id: createResult.data.create.id, - }, - }); + await apolloClient.mutate({ + mutation: gql` + mutation UpdateSomeObject( + $id: ID! + $fields: UpdateSomeClassFieldsInput + ) { + updateSomeClass(id: $id, fields: $fields) { + updatedAt + } + } + `, + variables: { + id: createResult.data.createSomeClass.id, + fields: { + someStringField: null, + someNumberField: null, + someBooleanField: null, + someObjectField: null, + someNullField: 'now it has a string', + }, + }, + }); - expect(getResult.data.get.someStringField).toBeFalsy(); - expect(getResult.data.get.someNumberField).toBeFalsy(); - expect(getResult.data.get.someBooleanField).toBeFalsy(); - expect(getResult.data.get.someObjectField).toBeFalsy(); - expect(getResult.data.get.someNullField).toEqual( - 'now it has a string' - ); + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!) { + someClass(id: $id) { + someStringField + someNumberField + someBooleanField + someObjectField + someNullField + } + } + `, + variables: { + id: createResult.data.createSomeClass.id, + }, + }); + + expect(getResult.data.someClass.someStringField).toBeFalsy(); + expect(getResult.data.someClass.someNumberField).toBeFalsy(); + expect(getResult.data.someClass.someBooleanField).toBeFalsy(); + expect(getResult.data.someClass.someObjectField).toBeFalsy(); + expect(getResult.data.someClass.someNullField).toEqual( + 'now it has a string' + ); + } catch (e) { + handleError(e); + } }); it('should support Bytes', async () => { - const someFieldValue = { - __type: 'Bytes', - base64: 'aGVsbG8gd29ybGQ=', - }; + try { + const someFieldValue = 'aGVsbG8gd29ybGQ='; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addBytes: [{ name: 'someField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Bytes'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('Bytes'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject( - $fields1: CreateSomeClassFieldsInput - $fields2: CreateSomeClassFieldsInput - ) { - createSomeClass1: createSomeClass(fields: $fields1) { - id - } - createSomeClass2: createSomeClass(fields: $fields2) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject( + $fields1: CreateSomeClassFieldsInput + $fields2: CreateSomeClassFieldsInput + ) { + createSomeClass1: createSomeClass(fields: $fields1) { + id + } + createSomeClass2: createSomeClass(fields: $fields2) { + id + } } - } - `, - variables: { - fields1: { - someField: someFieldValue, - }, - fields2: { - someField: someFieldValue.base64, + `, + variables: { + fields1: { + someField: someFieldValue, + }, + fields2: { + someField: someFieldValue, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!, $someFieldValue: Bytes) { - get(className: "SomeClass", id: $id) - someClasses(where: { someField: { _eq: $someFieldValue } }) { - results { - id + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!, $someFieldValue: Bytes) { + someClass(id: $id) { someField } + someClasses(where: { someField: { _eq: $someFieldValue } }) { + results { + id + someField + } + } } - } - `, - variables: { - id: createResult.data.create.id, - someFieldValue, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass1.id, + someFieldValue, + }, + }); - expect(typeof getResult.data.get.someField).toEqual('object'); - expect(getResult.data.get.someField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(3); + expect(typeof getResult.data.someClass.someField).toEqual('string'); + expect(getResult.data.someClass.someField).toEqual(someFieldValue); + expect(getResult.data.someClasses.results.length).toEqual(2); + } catch (e) { + handleError(e); + } }); it('should support Geo Points', async () => { - const someFieldValue = { - __type: 'GeoPoint', - latitude: 45, - longitude: 45, - }; + try { + const someFieldValue = { + __typename: 'GeoPoint', + latitude: 45, + longitude: 45, + }; - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - someField: someFieldValue, + `, + variables: { + schemaFields: { + addGeoPoint: { name: 'someField' }, + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, }, - }, - }); + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('GeoPoint'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.someField.type).toEqual('GeoPoint'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - someField: { - latitude: someFieldValue.latitude, - longitude: someFieldValue.longitude, + `, + variables: { + fields: { + someField: { + latitude: someFieldValue.latitude, + longitude: someFieldValue.longitude, + }, }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) - someClasses(where: { someField: { _exists: true } }) { - results { - id + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!) { + someClass(id: $id) { someField { latitude longitude } } + someClasses(where: { someField: { _exists: true } }) { + results { + id + someField { + latitude + longitude + } + } + } } - } - `, - variables: { - id: createResult.data.create.id, - }, - }); + `, + variables: { + id: createResult.data.createSomeClass.id, + }, + }); - expect(typeof getResult.data.get.someField).toEqual('object'); - expect(getResult.data.get.someField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(2); + expect(typeof getResult.data.someClass.someField).toEqual('object'); + expect(getResult.data.someClass.someField).toEqual(someFieldValue); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); it('should support Polygons', async () => { - const someFieldValue = { - __type: 'Polygon', - coordinates: [[44, 45], [46, 47], [48, 49], [44, 45]], - }; + try { + const somePolygonFieldValue = [ + [44, 45], + [46, 47], + [48, 49], + [44, 45], + ].map(point => ({ + latitude: point[0], + longitude: point[1], + })); - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id + await apolloClient.mutate({ + mutation: gql` + mutation CreateClass($schemaFields: SchemaFieldsInput) { + createClass(name: "SomeClass", schemaFields: $schemaFields) { + name + } } - } - `, - variables: { - fields: { - somePolygonField: someFieldValue, + `, + variables: { + schemaFields: { + addPolygons: [{ name: 'somePolygonField' }], + }, }, - }, - }); + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.somePolygonField.type).toEqual('Polygon'); + const schema = await new Parse.Schema('SomeClass').get(); + expect(schema.fields.somePolygonField.type).toEqual('Polygon'); - await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { - createSomeClass(fields: $fields) { - id + const createResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSomeObject($fields: CreateSomeClassFieldsInput) { + createSomeClass(fields: $fields) { + id + } } - } - `, - variables: { - fields: { - somePolygonField: someFieldValue.coordinates.map(point => ({ - latitude: point[0], - longitude: point[1], - })), + `, + variables: { + fields: { + somePolygonField: somePolygonFieldValue, + }, }, - }, - }); + }); - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "SomeClass", id: $id) - someClasses(where: { somePolygonField: { _exists: true } }) { - results { - id + const getResult = await apolloClient.query({ + query: gql` + query GetSomeObject($id: ID!) { + someClass(id: $id) { somePolygonField { latitude longitude } } - } - } - `, - variables: { - id: createResult.data.create.id, - }, - }); - - expect(typeof getResult.data.get.somePolygonField).toEqual('object'); - expect(getResult.data.get.somePolygonField).toEqual(someFieldValue); - expect(getResult.data.someClasses.results.length).toEqual(2); - }); - - it('should support polygon values', async () => { - const someFieldValue = { - __type: 'Polygon', - coordinates: [[1.0, 2.1], [3.2, 4.3], [5.4, 6.5], [1.0, 2.1]], - }; - - const createResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSomeObject($fields: Object) { - create(className: "SomeClass", fields: $fields) { - id - } - } - `, - variables: { - fields: { - somePolygonField: someFieldValue, - }, - }, - }); - - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); - - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - someClass(id: $id) { - somePolygonField { - latitude - longitude + someClasses(where: { somePolygonField: { _exists: true } }) { + results { + id + somePolygonField { + latitude + longitude + } + } } } - } - `, - variables: { - id: createResult.data.create.id, - }, - }); - - const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.somePolygonField.type).toEqual('Polygon'); + `, + variables: { + id: createResult.data.createSomeClass.id, + }, + }); - const { somePolygonField } = getResult.data.someClass; - expect(Array.isArray(somePolygonField)).toBeTruthy(); - somePolygonField.forEach((coord, i) => { - expect(coord.latitude).toEqual(someFieldValue.coordinates[i][0]); - expect(coord.longitude).toEqual(someFieldValue.coordinates[i][1]); - }); + expect(typeof getResult.data.someClass.somePolygonField).toEqual( + 'object' + ); + expect(getResult.data.someClass.somePolygonField).toEqual( + somePolygonFieldValue.map(geoPoint => ({ + ...geoPoint, + __typename: 'GeoPoint', + })) + ); + expect(getResult.data.someClasses.results.length).toEqual(1); + } catch (e) { + handleError(e); + } }); it_only_db('mongo')('should support bytes values', async () => { @@ -6270,10 +7151,14 @@ describe('ParseGraphQLServer', () => { user.setPassword('user1'); await user.signUp(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const getResult = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get(className: "_User", id: $id) + get: user(id: $id) { + id + } } `, variables: { @@ -6290,10 +7175,14 @@ describe('ParseGraphQLServer', () => { deviceType: 'foo', }); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const getResult = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get(className: "_Installation", id: $id) + get: installation(id: $id) { + id + } } `, variables: { @@ -6310,10 +7199,14 @@ describe('ParseGraphQLServer', () => { const role = new Parse.Role('MyRole', roleACL); await role.save(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const getResult = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get(className: "_Role", id: $id) + get: role(id: $id) { + id + } } `, variables: { @@ -6330,11 +7223,15 @@ describe('ParseGraphQLServer', () => { user.setPassword('user1'); await user.signUp(); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const session = await Parse.Session.current(); const getResult = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get(className: "_Session", id: $id) + get: session(id: $id) { + id + } } `, variables: { @@ -6364,10 +7261,14 @@ describe('ParseGraphQLServer', () => { { useMasterKey: true } ); + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + const getResult = await apolloClient.query({ query: gql` query GetSomeObject($id: ID!) { - get(className: "_Product", id: $id) + get: product(id: $id) { + id + } } `, variables: { @@ -6382,124 +7283,6 @@ describe('ParseGraphQLServer', () => { expect(getResult.data.get.id).toEqual(product.id); }); - - it('should support PushStatus class', async () => { - const PushStatus = Parse.Object.extend('_PushStatus'); - const pushStatus = new PushStatus(); - await pushStatus.save(undefined, { useMasterKey: true }); - - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "_PushStatus", id: $id) - } - `, - variables: { - id: pushStatus.id, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, - }, - }); - - expect(getResult.data.get.id).toEqual(pushStatus.id); - }); - - it('should support JobStatus class', async () => { - const JobStatus = Parse.Object.extend('_JobStatus'); - const jobStatus = new JobStatus(); - await jobStatus.save(undefined, { useMasterKey: true }); - - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "_JobStatus", id: $id) - } - `, - variables: { - id: jobStatus.id, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, - }, - }); - - expect(getResult.data.get.id).toEqual(jobStatus.id); - }); - - it('should support JobSchedule class', async () => { - const JobSchedule = Parse.Object.extend('_JobSchedule'); - const jobSchedule = new JobSchedule(); - await jobSchedule.save(undefined, { useMasterKey: true }); - - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "_JobSchedule", id: $id) - } - `, - variables: { - id: jobSchedule.id, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, - }, - }); - - expect(getResult.data.get.id).toEqual(jobSchedule.id); - }); - - it('should support Hooks class', async () => { - const functionName = 'fooHook'; - await parseServer.config.hooksController.saveHook({ - functionName, - url: 'http://foo.bar', - }); - - const getResult = await apolloClient.query({ - query: gql` - query FindSomeObject { - find(className: "_Hooks") { - results - } - } - `, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, - }, - }); - - const { results } = getResult.data.find; - expect(results.length).toEqual(1); - expect(results[0].functionName).toEqual(functionName); - }); - - it('should support Audience class', async () => { - const Audience = Parse.Object.extend('_Audience'); - const audience = new Audience(); - await audience.save(); - - const getResult = await apolloClient.query({ - query: gql` - query GetSomeObject($id: ID!) { - get(className: "_Audience", id: $id) - } - `, - variables: { - id: audience.id, - }, - }); - - expect(getResult.data.get.id).toEqual(audience.id); - }); }); }); }); diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index b257c001bf..d68e3d5915 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -431,7 +431,7 @@ describe('schemas', () => { defaultValue: false, }, defaultZero: { type: 'Number', defaultValue: 0 }, - relation: { type: 'Relation', targetClass: 'SomeClass' } + relation: { type: 'Relation', targetClass: 'SomeClass' }, }, }, }).then(async response => { @@ -458,7 +458,7 @@ describe('schemas', () => { defaultValue: false, }, defaultZero: { type: 'Number', defaultValue: 0 }, - relation: { type: 'Relation', targetClass: 'SomeClass' } + relation: { type: 'Relation', targetClass: 'SomeClass' }, }, classLevelPermissions: defaultClassLevelPermissions, }); @@ -486,7 +486,7 @@ describe('schemas', () => { }); }); - it('try to set a relation field as a required field', async (done) => { + it('try to set a relation field as a required field', async done => { try { await request({ url: 'http://localhost:8378/1/schemas', @@ -497,7 +497,11 @@ describe('schemas', () => { className: 'NewClassWithRelationRequired', fields: { foo: { type: 'String' }, - relation: { type: 'Relation', targetClass: 'SomeClass', required: true } + relation: { + type: 'Relation', + targetClass: 'SomeClass', + required: true, + }, }, }, }); @@ -508,7 +512,7 @@ describe('schemas', () => { done(); }); - it('try to set a relation field with a default value', async (done) => { + it('try to set a relation field with a default value', async done => { try { await request({ url: 'http://localhost:8378/1/schemas', @@ -519,7 +523,11 @@ describe('schemas', () => { className: 'NewClassRelationWithOptions', fields: { foo: { type: 'String' }, - relation: { type: 'Relation', targetClass: 'SomeClass', defaultValue: { __type: 'Relation', className: '_User' } } + relation: { + type: 'Relation', + targetClass: 'SomeClass', + defaultValue: { __type: 'Relation', className: '_User' }, + }, }, }, }); @@ -530,7 +538,7 @@ describe('schemas', () => { done(); }); - it('try to update schemas with a relation field with options', async (done) => { + it('try to update schemas with a relation field with options', async done => { await request({ url: 'http://localhost:8378/1/schemas', method: 'POST', @@ -539,7 +547,7 @@ describe('schemas', () => { body: { className: 'NewClassRelationWithOptions', fields: { - foo: { type: 'String' } + foo: { type: 'String' }, }, }, }); @@ -552,10 +560,14 @@ describe('schemas', () => { body: { className: 'NewClassRelationWithOptions', fields: { - relation: { type: 'Relation', targetClass: 'SomeClass', required: true } + relation: { + type: 'Relation', + targetClass: 'SomeClass', + required: true, + }, }, - _method: "PUT" - } + _method: 'PUT', + }, }); fail('should fail'); } catch (e) { @@ -571,10 +583,14 @@ describe('schemas', () => { body: { className: 'NewClassRelationWithOptions', fields: { - relation: { type: 'Relation', targetClass: 'SomeClass', defaultValue: { __type: 'Relation', className: '_User' } } + relation: { + type: 'Relation', + targetClass: 'SomeClass', + defaultValue: { __type: 'Relation', className: '_User' }, + }, }, - _method: "PUT" - } + _method: 'PUT', + }, }); fail('should fail'); } catch (e) { diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index 60bf727aea..59efe9290d 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -14,6 +14,7 @@ import ParseGraphQLController, { import DatabaseController from '../Controllers/DatabaseController'; import { toGraphQLError } from './parseGraphQLUtils'; import * as schemaDirectives from './loaders/schemaDirectives'; +import * as schemaTypes from './loaders/schemaTypes'; const RESERVED_GRAPHQL_TYPE_NAMES = [ 'String', @@ -29,16 +30,16 @@ const RESERVED_GRAPHQL_TYPE_NAMES = [ 'SignUpFieldsInput', 'LogInFieldsInput', ]; -const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'get', 'find']; +const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'class', 'classes']; const RESERVED_GRAPHQL_MUTATION_NAMES = [ 'signUp', 'logIn', 'logOut', 'createFile', 'callCloudCode', - 'create', - 'update', - 'delete', + 'createClass', + 'updateClass', + 'deleteClass', ]; class ParseGraphQLSchema { @@ -97,6 +98,7 @@ class ParseGraphQLSchema { this.graphQLSchemaDirectives = {}; defaultGraphQLTypes.load(this); + schemaTypes.load(this); this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach( ([parseClass, parseClassConfig]) => { diff --git a/src/GraphQL/helpers/objectsMutations.js b/src/GraphQL/helpers/objectsMutations.js new file mode 100644 index 0000000000..aa74e1c8f8 --- /dev/null +++ b/src/GraphQL/helpers/objectsMutations.js @@ -0,0 +1,39 @@ +import rest from '../../rest'; + +const createObject = async (className, fields, config, auth, info) => { + if (!fields) { + fields = {}; + } + + return (await rest.create(config, auth, className, fields, info.clientSDK)) + .response; +}; + +const updateObject = async ( + className, + objectId, + fields, + config, + auth, + info +) => { + if (!fields) { + fields = {}; + } + + return (await rest.update( + config, + auth, + className, + { objectId }, + fields, + info.clientSDK + )).response; +}; + +const deleteObject = async (className, objectId, config, auth, info) => { + await rest.del(config, auth, className, objectId, info.clientSDK); + return true; +}; + +export { createObject, updateObject, deleteObject }; diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js new file mode 100644 index 0000000000..4eeb4c2bfb --- /dev/null +++ b/src/GraphQL/helpers/objectsQueries.js @@ -0,0 +1,127 @@ +import Parse from 'parse/node'; +import rest from '../../rest'; +import { transformQueryInputToParse } from '../transformers/query'; + +const getObject = async ( + className, + objectId, + keys, + include, + readPreference, + includeReadPreference, + config, + auth, + info +) => { + const options = {}; + if (keys) { + options.keys = keys; + } + if (include) { + options.include = include; + if (includeReadPreference) { + options.includeReadPreference = includeReadPreference; + } + } + if (readPreference) { + options.readPreference = readPreference; + } + + const response = await rest.get( + config, + auth, + className, + objectId, + options, + info.clientSDK + ); + + if (!response.results || response.results.length == 0) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } + + const object = response.results[0]; + if (className === '_User') { + delete object.sessionToken; + } + return object; +}; + +const findObjects = async ( + className, + where, + order, + skip, + limit, + keys, + include, + includeAll, + readPreference, + includeReadPreference, + subqueryReadPreference, + config, + auth, + info, + selectedFields +) => { + if (!where) { + where = {}; + } + transformQueryInputToParse(where); + + const options = {}; + + if (selectedFields.includes('results')) { + if (limit || limit === 0) { + options.limit = limit; + } + if (options.limit !== 0) { + if (order) { + options.order = order; + } + if (skip) { + options.skip = skip; + } + if (config.maxLimit && options.limit > config.maxLimit) { + // Silently replace the limit on the query with the max configured + options.limit = config.maxLimit; + } + if (keys) { + options.keys = keys; + } + if (includeAll === true) { + options.includeAll = includeAll; + } + if (!options.includeAll && include) { + options.include = include; + } + if ((options.includeAll || options.include) && includeReadPreference) { + options.includeReadPreference = includeReadPreference; + } + } + } else { + options.limit = 0; + } + + if (selectedFields.includes('count')) { + options.count = true; + } + + if (readPreference) { + options.readPreference = readPreference; + } + if (Object.keys(where).length > 0 && subqueryReadPreference) { + options.subqueryReadPreference = subqueryReadPreference; + } + + return await rest.find( + config, + auth, + className, + where, + options, + info.clientSDK + ); +}; + +export { getObject, findObjects }; diff --git a/src/GraphQL/loaders/defaultGraphQLMutations.js b/src/GraphQL/loaders/defaultGraphQLMutations.js index c7ebc6347d..4d997a4822 100644 --- a/src/GraphQL/loaders/defaultGraphQLMutations.js +++ b/src/GraphQL/loaders/defaultGraphQLMutations.js @@ -1,13 +1,13 @@ -import * as objectsMutations from './objectsMutations'; import * as filesMutations from './filesMutations'; import * as usersMutations from './usersMutations'; import * as functionsMutations from './functionsMutations'; +import * as schemaMutations from './schemaMutations'; const load = parseGraphQLSchema => { - objectsMutations.load(parseGraphQLSchema); filesMutations.load(parseGraphQLSchema); usersMutations.load(parseGraphQLSchema); functionsMutations.load(parseGraphQLSchema); + schemaMutations.load(parseGraphQLSchema); }; export { load }; diff --git a/src/GraphQL/loaders/defaultGraphQLQueries.js b/src/GraphQL/loaders/defaultGraphQLQueries.js index 07edc21f1e..8e8616ca5f 100644 --- a/src/GraphQL/loaders/defaultGraphQLQueries.js +++ b/src/GraphQL/loaders/defaultGraphQLQueries.js @@ -1,6 +1,6 @@ import { GraphQLNonNull, GraphQLBoolean } from 'graphql'; -import * as objectsQueries from './objectsQueries'; import * as usersQueries from './usersQueries'; +import * as schemaQueries from './schemaQueries'; const load = parseGraphQLSchema => { parseGraphQLSchema.addGraphQLQuery( @@ -15,8 +15,8 @@ const load = parseGraphQLSchema => { true ); - objectsQueries.load(parseGraphQLSchema); usersQueries.load(parseGraphQLSchema); + schemaQueries.load(parseGraphQLSchema); }; export { load }; diff --git a/src/GraphQL/loaders/defaultGraphQLTypes.js b/src/GraphQL/loaders/defaultGraphQLTypes.js index fb80aa7e78..2c57a2d065 100644 --- a/src/GraphQL/loaders/defaultGraphQLTypes.js +++ b/src/GraphQL/loaders/defaultGraphQLTypes.js @@ -455,17 +455,17 @@ const UPDATE_RESULT = new GraphQLObjectType({ fields: UPDATE_RESULT_FIELDS, }); -const CLASS_FIELDS = { +const PARSE_OBJECT_FIELDS = { ...CREATE_RESULT_FIELDS, ...UPDATE_RESULT_FIELDS, ...INPUT_FIELDS, }; -const CLASS = new GraphQLInterfaceType({ - name: 'Class', +const PARSE_OBJECT = new GraphQLInterfaceType({ + name: 'ParseObject', description: - 'The Class interface type is used as a base type for the auto generated class types.', - fields: CLASS_FIELDS, + 'The ParseObject interface type is used as a base type for the auto generated object types.', + fields: PARSE_OBJECT_FIELDS, }); const SESSION_TOKEN_ATT = { @@ -1074,7 +1074,7 @@ const load = parseGraphQLSchema => { parseGraphQLSchema.addGraphQLType(GEO_POINT, true); parseGraphQLSchema.addGraphQLType(CREATE_RESULT, true); parseGraphQLSchema.addGraphQLType(UPDATE_RESULT, true); - parseGraphQLSchema.addGraphQLType(CLASS, true); + parseGraphQLSchema.addGraphQLType(PARSE_OBJECT, true); parseGraphQLSchema.addGraphQLType(READ_PREFERENCE, true); parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true); parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true); @@ -1137,8 +1137,8 @@ export { CREATE_RESULT, UPDATE_RESULT_FIELDS, UPDATE_RESULT, - CLASS_FIELDS, - CLASS, + PARSE_OBJECT_FIELDS, + PARSE_OBJECT, SESSION_TOKEN_ATT, KEYS_ATT, INCLUDE_ATT, diff --git a/src/GraphQL/loaders/objectsMutations.js b/src/GraphQL/loaders/objectsMutations.js deleted file mode 100644 index 2780347f16..0000000000 --- a/src/GraphQL/loaders/objectsMutations.js +++ /dev/null @@ -1,127 +0,0 @@ -import { GraphQLNonNull, GraphQLBoolean } from 'graphql'; -import * as defaultGraphQLTypes from './defaultGraphQLTypes'; -import rest from '../../rest'; - -const createObject = async (className, fields, config, auth, info) => { - if (!fields) { - fields = {}; - } - - return (await rest.create(config, auth, className, fields, info.clientSDK)) - .response; -}; - -const updateObject = async ( - className, - objectId, - fields, - config, - auth, - info -) => { - if (!fields) { - fields = {}; - } - - return (await rest.update( - config, - auth, - className, - { objectId }, - fields, - info.clientSDK - )).response; -}; - -const deleteObject = async (className, objectId, config, auth, info) => { - await rest.del(config, auth, className, objectId, info.clientSDK); - return true; -}; - -const load = parseGraphQLSchema => { - parseGraphQLSchema.addGraphQLMutation( - 'create', - { - description: - 'The create mutation can be used to create a new object of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - fields: defaultGraphQLTypes.FIELDS_ATT, - }, - type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT), - async resolve(_source, args, context) { - try { - const { className, fields } = args; - const { config, auth, info } = context; - - const object = await createObject( - className, - fields, - config, - auth, - info - ); - - return object; - } catch (e) { - parseGraphQLSchema.handleError(e); - } - }, - }, - true, - true - ); - - parseGraphQLSchema.addGraphQLMutation( - 'update', - { - description: - 'The update mutation can be used to update an object of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - id: defaultGraphQLTypes.OBJECT_ID_ATT, - fields: defaultGraphQLTypes.FIELDS_ATT, - }, - type: new GraphQLNonNull(defaultGraphQLTypes.UPDATE_RESULT), - async resolve(_source, args, context) { - try { - const { className, id, fields } = args; - const { config, auth, info } = context; - - return await updateObject(className, id, fields, config, auth, info); - } catch (e) { - parseGraphQLSchema.handleError(e); - } - }, - }, - true, - true - ); - - parseGraphQLSchema.addGraphQLMutation( - 'delete', - { - description: - 'The delete mutation can be used to delete an object of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - id: defaultGraphQLTypes.OBJECT_ID_ATT, - }, - type: new GraphQLNonNull(GraphQLBoolean), - async resolve(_source, args, context) { - try { - const { className, id } = args; - const { config, auth, info } = context; - - return await deleteObject(className, id, config, auth, info); - } catch (e) { - parseGraphQLSchema.handleError(e); - } - }, - }, - true, - true - ); -}; - -export { createObject, updateObject, deleteObject, load }; diff --git a/src/GraphQL/loaders/objectsQueries.js b/src/GraphQL/loaders/objectsQueries.js deleted file mode 100644 index 7d292b4a4a..0000000000 --- a/src/GraphQL/loaders/objectsQueries.js +++ /dev/null @@ -1,260 +0,0 @@ -import { GraphQLNonNull, GraphQLBoolean, GraphQLString } from 'graphql'; -import getFieldNames from 'graphql-list-fields'; -import Parse from 'parse/node'; -import * as defaultGraphQLTypes from './defaultGraphQLTypes'; -import rest from '../../rest'; -import { transformQueryInputToParse } from '../transformers/query'; - -const getObject = async ( - className, - objectId, - keys, - include, - readPreference, - includeReadPreference, - config, - auth, - info -) => { - const options = {}; - if (keys) { - options.keys = keys; - } - if (include) { - options.include = include; - if (includeReadPreference) { - options.includeReadPreference = includeReadPreference; - } - } - if (readPreference) { - options.readPreference = readPreference; - } - - const response = await rest.get( - config, - auth, - className, - objectId, - options, - info.clientSDK - ); - - if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); - } - - const object = response.results[0]; - if (className === '_User') { - delete object.sessionToken; - } - return object; -}; - -const findObjects = async ( - className, - where, - order, - skip, - limit, - keys, - include, - includeAll, - readPreference, - includeReadPreference, - subqueryReadPreference, - config, - auth, - info, - selectedFields -) => { - if (!where) { - where = {}; - } - transformQueryInputToParse(where); - - const options = {}; - - if (selectedFields.includes('results')) { - if (limit || limit === 0) { - options.limit = limit; - } - if (options.limit !== 0) { - if (order) { - options.order = order; - } - if (skip) { - options.skip = skip; - } - if (config.maxLimit && options.limit > config.maxLimit) { - // Silently replace the limit on the query with the max configured - options.limit = config.maxLimit; - } - if (keys) { - options.keys = keys; - } - if (includeAll === true) { - options.includeAll = includeAll; - } - if (!options.includeAll && include) { - options.include = include; - } - if ((options.includeAll || options.include) && includeReadPreference) { - options.includeReadPreference = includeReadPreference; - } - } - } else { - options.limit = 0; - } - - if (selectedFields.includes('count')) { - options.count = true; - } - - if (readPreference) { - options.readPreference = readPreference; - } - if (Object.keys(where).length > 0 && subqueryReadPreference) { - options.subqueryReadPreference = subqueryReadPreference; - } - - return await rest.find( - config, - auth, - className, - where, - options, - info.clientSDK - ); -}; - -const load = parseGraphQLSchema => { - parseGraphQLSchema.addGraphQLQuery( - 'get', - { - description: - 'The get query can be used to get an object of a certain class by its objectId.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - id: defaultGraphQLTypes.OBJECT_ID_ATT, - keys: defaultGraphQLTypes.KEYS_ATT, - include: defaultGraphQLTypes.INCLUDE_ATT, - readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT, - includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT, - }, - type: new GraphQLNonNull(defaultGraphQLTypes.OBJECT), - async resolve(_source, args, context) { - try { - const { - className, - id, - keys, - include, - readPreference, - includeReadPreference, - } = args; - - const { config, auth, info } = context; - - const object = await getObject( - className, - id, - keys, - include, - readPreference, - includeReadPreference, - config, - auth, - info - ); - object.id = object.objectId; - delete object.objectId; - - return object; - } catch (e) { - parseGraphQLSchema.handleError(e); - } - }, - }, - true, - true - ); - - parseGraphQLSchema.addGraphQLQuery( - 'find', - { - description: - 'The find query can be used to find objects of a certain class.', - args: { - className: defaultGraphQLTypes.CLASS_NAME_ATT, - where: defaultGraphQLTypes.WHERE_ATT, - order: { - description: - 'This is the order in which the objects should be returned', - type: GraphQLString, - }, - skip: defaultGraphQLTypes.SKIP_ATT, - limit: defaultGraphQLTypes.LIMIT_ATT, - keys: defaultGraphQLTypes.KEYS_ATT, - include: defaultGraphQLTypes.INCLUDE_ATT, - includeAll: { - description: 'All pointers will be returned', - type: GraphQLBoolean, - }, - readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT, - includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT, - subqueryReadPreference: - defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT, - }, - type: new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT), - async resolve(_source, args, context, queryInfo) { - try { - const { - className, - where, - order, - skip, - limit, - keys, - include, - includeAll, - readPreference, - includeReadPreference, - subqueryReadPreference, - } = args; - - const { config, auth, info } = context; - const selectedFields = getFieldNames(queryInfo); - - const objects = await findObjects( - className, - where, - order, - skip, - limit, - keys, - include, - includeAll, - readPreference, - includeReadPreference, - subqueryReadPreference, - config, - auth, - info, - selectedFields - ); - objects.results.forEach(obj => { - obj.id = obj.objectId; - delete obj.objectId; - }); - return objects; - } catch (e) { - parseGraphQLSchema.handleError(e); - } - }, - }, - true, - true - ); -}; - -export { getObject, findObjects, load }; diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index c6343501cf..4a3b87f89e 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -5,8 +5,8 @@ import { extractKeysAndInclude, getParseClassMutationConfig, } from '../parseGraphQLUtils'; -import * as objectsMutations from './objectsMutations'; -import * as objectsQueries from './objectsQueries'; +import * as objectsMutations from '../helpers/objectsMutations'; +import * as objectsQueries from '../helpers/objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; import { transformClassNameToGraphQL } from '../transformers/className'; import { transformTypes } from '../transformers/mutation'; diff --git a/src/GraphQL/loaders/parseClassQueries.js b/src/GraphQL/loaders/parseClassQueries.js index cf98cc63c9..274dc13e70 100644 --- a/src/GraphQL/loaders/parseClassQueries.js +++ b/src/GraphQL/loaders/parseClassQueries.js @@ -2,7 +2,7 @@ import { GraphQLNonNull } from 'graphql'; import getFieldNames from 'graphql-list-fields'; import pluralize from 'pluralize'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; -import * as objectsQueries from './objectsQueries'; +import * as objectsQueries from '../helpers/objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; import { transformClassNameToGraphQL } from '../transformers/className'; import { extractKeysAndInclude } from '../parseGraphQLUtils'; diff --git a/src/GraphQL/loaders/parseClassTypes.js b/src/GraphQL/loaders/parseClassTypes.js index daa815de0c..69c529eb3a 100644 --- a/src/GraphQL/loaders/parseClassTypes.js +++ b/src/GraphQL/loaders/parseClassTypes.js @@ -3,8 +3,6 @@ import { GraphQLID, GraphQLObjectType, GraphQLString, - GraphQLFloat, - GraphQLBoolean, GraphQLList, GraphQLInputObjectType, GraphQLNonNull, @@ -13,149 +11,17 @@ import { } from 'graphql'; import getFieldNames from 'graphql-list-fields'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; -import * as objectsQueries from './objectsQueries'; +import * as objectsQueries from '../helpers/objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; import { transformClassNameToGraphQL } from '../transformers/className'; +import { transformInputTypeToGraphQL } from '../transformers/inputType'; +import { transformOutputTypeToGraphQL } from '../transformers/outputType'; +import { transformConstraintTypeToGraphQL } from '../transformers/constraintType'; import { extractKeysAndInclude, getParseClassMutationConfig, } from '../parseGraphQLUtils'; -const mapInputType = (parseType, targetClass, parseClassTypes) => { - switch (parseType) { - case 'String': - return GraphQLString; - case 'Number': - return GraphQLFloat; - case 'Boolean': - return GraphQLBoolean; - case 'Array': - return new GraphQLList(defaultGraphQLTypes.ANY); - case 'Object': - return defaultGraphQLTypes.OBJECT; - case 'Date': - return defaultGraphQLTypes.DATE; - case 'Pointer': - if ( - parseClassTypes[targetClass] && - parseClassTypes[targetClass].classGraphQLPointerType - ) { - return parseClassTypes[targetClass].classGraphQLPointerType; - } else { - return defaultGraphQLTypes.OBJECT; - } - case 'Relation': - if ( - parseClassTypes[targetClass] && - parseClassTypes[targetClass].classGraphQLRelationType - ) { - return parseClassTypes[targetClass].classGraphQLRelationType; - } else { - return defaultGraphQLTypes.OBJECT; - } - case 'File': - return defaultGraphQLTypes.FILE; - case 'GeoPoint': - return defaultGraphQLTypes.GEO_POINT_INPUT; - case 'Polygon': - return defaultGraphQLTypes.POLYGON_INPUT; - case 'Bytes': - return defaultGraphQLTypes.BYTES; - case 'ACL': - return defaultGraphQLTypes.OBJECT; - default: - return undefined; - } -}; - -const mapOutputType = (parseType, targetClass, parseClassTypes) => { - switch (parseType) { - case 'String': - return GraphQLString; - case 'Number': - return GraphQLFloat; - case 'Boolean': - return GraphQLBoolean; - case 'Array': - return new GraphQLList(defaultGraphQLTypes.ARRAY_RESULT); - case 'Object': - return defaultGraphQLTypes.OBJECT; - case 'Date': - return defaultGraphQLTypes.DATE; - case 'Pointer': - if ( - parseClassTypes[targetClass] && - parseClassTypes[targetClass].classGraphQLOutputType - ) { - return parseClassTypes[targetClass].classGraphQLOutputType; - } else { - return defaultGraphQLTypes.OBJECT; - } - case 'Relation': - if ( - parseClassTypes[targetClass] && - parseClassTypes[targetClass].classGraphQLFindResultType - ) { - return new GraphQLNonNull( - parseClassTypes[targetClass].classGraphQLFindResultType - ); - } else { - return new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT); - } - case 'File': - return defaultGraphQLTypes.FILE_INFO; - case 'GeoPoint': - return defaultGraphQLTypes.GEO_POINT; - case 'Polygon': - return defaultGraphQLTypes.POLYGON; - case 'Bytes': - return defaultGraphQLTypes.BYTES; - case 'ACL': - return defaultGraphQLTypes.OBJECT; - default: - return undefined; - } -}; - -const mapConstraintType = (parseType, targetClass, parseClassTypes) => { - switch (parseType) { - case 'String': - return defaultGraphQLTypes.STRING_WHERE_INPUT; - case 'Number': - return defaultGraphQLTypes.NUMBER_WHERE_INPUT; - case 'Boolean': - return defaultGraphQLTypes.BOOLEAN_WHERE_INPUT; - case 'Array': - return defaultGraphQLTypes.ARRAY_WHERE_INPUT; - case 'Object': - return defaultGraphQLTypes.OBJECT_WHERE_INPUT; - case 'Date': - return defaultGraphQLTypes.DATE_WHERE_INPUT; - case 'Pointer': - if ( - parseClassTypes[targetClass] && - parseClassTypes[targetClass].classGraphQLConstraintType - ) { - return parseClassTypes[targetClass].classGraphQLConstraintType; - } else { - return defaultGraphQLTypes.OBJECT; - } - case 'File': - return defaultGraphQLTypes.FILE_WHERE_INPUT; - case 'GeoPoint': - return defaultGraphQLTypes.GEO_POINT_WHERE_INPUT; - case 'Polygon': - return defaultGraphQLTypes.POLYGON_WHERE_INPUT; - case 'Bytes': - return defaultGraphQLTypes.BYTES_WHERE_INPUT; - case 'ACL': - return defaultGraphQLTypes.OBJECT_WHERE_INPUT; - case 'Relation': - default: - return undefined; - } -}; - const getParseClassTypeConfig = function( parseClassConfig: ?ParseGraphQLClassConfig ) { @@ -184,7 +50,9 @@ const getInputFieldsAndConstraints = function( // All allowed customs fields const classCustomFields = classFields.filter(field => { - return !Object.keys(defaultGraphQLTypes.CLASS_FIELDS).includes(field); + return !Object.keys(defaultGraphQLTypes.PARSE_OBJECT_FIELDS).includes( + field + ); }); if (allowedInputFields && allowedInputFields.create) { @@ -357,7 +225,7 @@ const load = ( fields: () => classCreateFields.reduce( (fields, field) => { - const type = mapInputType( + const type = transformInputTypeToGraphQL( parseClass.fields[field].type, parseClass.fields[field].targetClass, parseGraphQLSchema.parseClassTypes @@ -390,7 +258,7 @@ const load = ( fields: () => classUpdateFields.reduce( (fields, field) => { - const type = mapInputType( + const type = transformInputTypeToGraphQL( parseClass.fields[field].type, parseClass.fields[field].targetClass, parseGraphQLSchema.parseClassTypes @@ -503,7 +371,7 @@ const load = ( fields: () => ({ ...classConstraintFields.reduce((fields, field) => { const parseField = field === 'id' ? 'objectId' : field; - const type = mapConstraintType( + const type = transformConstraintTypeToGraphQL( parseClass.fields[parseField].type, parseClass.fields[parseField].targetClass, parseGraphQLSchema.parseClassTypes @@ -582,7 +450,7 @@ const load = ( const classGraphQLOutputTypeName = `${graphQLClassName}`; const outputFields = () => { return classOutputFields.reduce((fields, field) => { - const type = mapOutputType( + const type = transformOutputTypeToGraphQL( parseClass.fields[field].type, parseClass.fields[field].targetClass, parseGraphQLSchema.parseClassTypes @@ -704,12 +572,12 @@ const load = ( } else { return fields; } - }, defaultGraphQLTypes.CLASS_FIELDS); + }, defaultGraphQLTypes.PARSE_OBJECT_FIELDS); }; let classGraphQLOutputType = new GraphQLObjectType({ name: classGraphQLOutputTypeName, description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${graphQLClassName} class.`, - interfaces: [defaultGraphQLTypes.CLASS], + interfaces: [defaultGraphQLTypes.PARSE_OBJECT], fields: outputFields, }); classGraphQLOutputType = parseGraphQLSchema.addGraphQLType( @@ -760,7 +628,7 @@ const load = ( const viewerType = new GraphQLObjectType({ name: 'Viewer', description: `The Viewer object type is used in operations that involve outputting the current user data.`, - interfaces: [defaultGraphQLTypes.CLASS], + interfaces: [defaultGraphQLTypes.PARSE_OBJECT], fields: () => ({ ...outputFields(), sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT, @@ -775,7 +643,7 @@ const load = ( description: `The ${userSignUpInputTypeName} input type is used in operations that involve inputting objects of ${graphQLClassName} class when signing up.`, fields: () => classCreateFields.reduce((fields, field) => { - const type = mapInputType( + const type = transformInputTypeToGraphQL( parseClass.fields[field].type, parseClass.fields[field].targetClass, parseGraphQLSchema.parseClassTypes diff --git a/src/GraphQL/loaders/schemaMutations.js b/src/GraphQL/loaders/schemaMutations.js new file mode 100644 index 0000000000..71ac32d43d --- /dev/null +++ b/src/GraphQL/loaders/schemaMutations.js @@ -0,0 +1,146 @@ +import Parse from 'parse/node'; +import { GraphQLNonNull } from 'graphql'; +import * as schemaTypes from './schemaTypes'; +import { + transformToParse, + transformToGraphQL, +} from '../transformers/schemaFields'; +import { enforceMasterKeyAccess } from '../parseGraphQLUtils'; +import { getClass } from './schemaQueries'; + +const load = parseGraphQLSchema => { + parseGraphQLSchema.addGraphQLMutation( + 'createClass', + { + description: + 'The createClass mutation can be used to create the schema for a new object class.', + args: { + name: schemaTypes.CLASS_NAME_ATT, + schemaFields: { + description: "These are the schema's fields of the object class.", + type: schemaTypes.SCHEMA_FIELDS_INPUT, + }, + }, + type: new GraphQLNonNull(schemaTypes.CLASS), + resolve: async (_source, args, context) => { + try { + const { name, schemaFields } = args; + const { config, auth } = context; + + enforceMasterKeyAccess(auth); + + if (auth.isReadOnly) { + throw new Parse.Error( + Parse.Error.OPERATION_FORBIDDEN, + "read-only masterKey isn't allowed to create a schema." + ); + } + + const schema = await config.database.loadSchema({ clearCache: true }); + const parseClass = await schema.addClassIfNotExists( + name, + transformToParse(schemaFields) + ); + return { + name: parseClass.className, + schemaFields: transformToGraphQL(parseClass.fields), + }; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }, + true, + true + ); + + parseGraphQLSchema.addGraphQLMutation( + 'updateClass', + { + description: + 'The updateClass mutation can be used to update the schema for an existing object class.', + args: { + name: schemaTypes.CLASS_NAME_ATT, + schemaFields: { + description: "These are the schema's fields of the object class.", + type: schemaTypes.SCHEMA_FIELDS_INPUT, + }, + }, + type: new GraphQLNonNull(schemaTypes.CLASS), + resolve: async (_source, args, context) => { + try { + const { name, schemaFields } = args; + const { config, auth } = context; + + enforceMasterKeyAccess(auth); + + if (auth.isReadOnly) { + throw new Parse.Error( + Parse.Error.OPERATION_FORBIDDEN, + "read-only masterKey isn't allowed to update a schema." + ); + } + + const schema = await config.database.loadSchema({ clearCache: true }); + const existingParseClass = await getClass(name, schema); + const parseClass = await schema.updateClass( + name, + transformToParse(schemaFields, existingParseClass.fields), + undefined, + undefined, + config.database + ); + return { + name: parseClass.className, + schemaFields: transformToGraphQL(parseClass.fields), + }; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }, + true, + true + ); + + parseGraphQLSchema.addGraphQLMutation( + 'deleteClass', + { + description: + 'The deleteClass mutation can be used to delete an existing object class.', + args: { + name: schemaTypes.CLASS_NAME_ATT, + }, + type: new GraphQLNonNull(schemaTypes.CLASS), + resolve: async (_source, args, context) => { + try { + const { name } = args; + const { config, auth } = context; + + enforceMasterKeyAccess(auth); + + if (auth.isReadOnly) { + throw new Parse.Error( + Parse.Error.OPERATION_FORBIDDEN, + "read-only masterKey isn't allowed to delete a schema." + ); + } + + const schema = await config.database.loadSchema({ clearCache: true }); + const existingParseClass = await getClass(name, schema); + await config.database.deleteSchema(name); + return { + name: existingParseClass.className, + schemaFields: transformToGraphQL(existingParseClass.fields), + }; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }, + true, + true + ); +}; + +export { load }; diff --git a/src/GraphQL/loaders/schemaQueries.js b/src/GraphQL/loaders/schemaQueries.js new file mode 100644 index 0000000000..f5a166433a --- /dev/null +++ b/src/GraphQL/loaders/schemaQueries.js @@ -0,0 +1,86 @@ +import Parse from 'parse/node'; +import { GraphQLNonNull, GraphQLList } from 'graphql'; +import { transformToGraphQL } from '../transformers/schemaFields'; +import * as schemaTypes from './schemaTypes'; +import { enforceMasterKeyAccess } from '../parseGraphQLUtils'; + +const getClass = async (name, schema) => { + try { + return await schema.getOneSchema(name, true); + } catch (e) { + if (e === undefined) { + throw new Parse.Error( + Parse.Error.INVALID_CLASS_NAME, + `Class ${name} does not exist.` + ); + } else { + throw new Parse.Error( + Parse.Error.INTERNAL_SERVER_ERROR, + 'Database adapter error.' + ); + } + } +}; + +const load = parseGraphQLSchema => { + parseGraphQLSchema.addGraphQLQuery( + 'class', + { + description: + 'The class query can be used to retrieve an existing object class.', + args: { + name: schemaTypes.CLASS_NAME_ATT, + }, + type: new GraphQLNonNull(schemaTypes.CLASS), + resolve: async (_source, args, context) => { + try { + const { name } = args; + const { config, auth } = context; + + enforceMasterKeyAccess(auth); + + const schema = await config.database.loadSchema({ clearCache: true }); + const parseClass = await getClass(name, schema); + return { + name: parseClass.className, + schemaFields: transformToGraphQL(parseClass.fields), + }; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }, + true, + true + ); + + parseGraphQLSchema.addGraphQLQuery( + 'classes', + { + description: + 'The classes query can be used to retrieve the existing object classes.', + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(schemaTypes.CLASS)) + ), + resolve: async (_source, _args, context) => { + try { + const { config, auth } = context; + + enforceMasterKeyAccess(auth); + + const schema = await config.database.loadSchema({ clearCache: true }); + return (await schema.getAllClasses(true)).map(parseClass => ({ + name: parseClass.className, + schemaFields: transformToGraphQL(parseClass.fields), + })); + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }, + true, + true + ); +}; + +export { getClass, load }; diff --git a/src/GraphQL/loaders/schemaTypes.js b/src/GraphQL/loaders/schemaTypes.js new file mode 100644 index 0000000000..6572969787 --- /dev/null +++ b/src/GraphQL/loaders/schemaTypes.js @@ -0,0 +1,448 @@ +import { + GraphQLNonNull, + GraphQLString, + GraphQLInputObjectType, + GraphQLList, + GraphQLObjectType, + GraphQLInterfaceType, +} from 'graphql'; + +const SCHEMA_FIELD_NAME_ATT = { + description: 'This is the field name.', + type: new GraphQLNonNull(GraphQLString), +}; + +const SCHEMA_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaFieldInput', + description: + 'The SchemaFieldInput is used to specify a field of an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_FIELD = new GraphQLInterfaceType({ + name: 'SchemaField', + description: + 'The SchemaField interface type is used as a base type for the different supported fields of an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, + resolveType: value => + ({ + String: SCHEMA_STRING_FIELD, + Number: SCHEMA_NUMBER_FIELD, + Boolean: SCHEMA_BOOLEAN_FIELD, + Array: SCHEMA_ARRAY_FIELD, + Object: SCHEMA_OBJECT_FIELD, + Date: SCHEMA_DATE_FIELD, + File: SCHEMA_FILE_FIELD, + GeoPoint: SCHEMA_GEO_POINT_FIELD, + Polygon: SCHEMA_POLYGON_FIELD, + Bytes: SCHEMA_BYTES_FIELD, + Pointer: SCHEMA_POINTER_FIELD, + Relation: SCHEMA_RELATION_FIELD, + ACL: SCHEMA_ACL_FIELD, + }[value.type]), +}); + +const SCHEMA_STRING_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaStringFieldInput', + description: + 'The SchemaStringFieldInput is used to specify a field of type string for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_STRING_FIELD = new GraphQLObjectType({ + name: 'SchemaStringField', + description: + 'The SchemaStringField is used to return information of a String field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_NUMBER_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaNumberFieldInput', + description: + 'The SchemaNumberFieldInput is used to specify a field of type number for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_NUMBER_FIELD = new GraphQLObjectType({ + name: 'SchemaNumberField', + description: + 'The SchemaNumberField is used to return information of a Number field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_BOOLEAN_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaBooleanFieldInput', + description: + 'The SchemaBooleanFieldInput is used to specify a field of type boolean for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_BOOLEAN_FIELD = new GraphQLObjectType({ + name: 'SchemaBooleanField', + description: + 'The SchemaBooleanField is used to return information of a Boolean field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_ARRAY_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaArrayFieldInput', + description: + 'The SchemaArrayFieldInput is used to specify a field of type array for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_ARRAY_FIELD = new GraphQLObjectType({ + name: 'SchemaArrayField', + description: + 'The SchemaArrayField is used to return information of an Array field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_OBJECT_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaObjectFieldInput', + description: + 'The SchemaObjectFieldInput is used to specify a field of type object for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_OBJECT_FIELD = new GraphQLObjectType({ + name: 'SchemaObjectField', + description: + 'The SchemaObjectField is used to return information of an Object field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_DATE_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaDateFieldInput', + description: + 'The SchemaDateFieldInput is used to specify a field of type date for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_DATE_FIELD = new GraphQLObjectType({ + name: 'SchemaDateField', + description: + 'The SchemaDateField is used to return information of a Date field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_FILE_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaFileFieldInput', + description: + 'The SchemaFileFieldInput is used to specify a field of type file for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_FILE_FIELD = new GraphQLObjectType({ + name: 'SchemaFileField', + description: + 'The SchemaFileField is used to return information of a File field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_GEO_POINT_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaGeoPointFieldInput', + description: + 'The SchemaGeoPointFieldInput is used to specify a field of type geo point for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_GEO_POINT_FIELD = new GraphQLObjectType({ + name: 'SchemaGeoPointField', + description: + 'The SchemaGeoPointField is used to return information of a Geo Point field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_POLYGON_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaPolygonFieldInput', + description: + 'The SchemaPolygonFieldInput is used to specify a field of type polygon for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_POLYGON_FIELD = new GraphQLObjectType({ + name: 'SchemaPolygonField', + description: + 'The SchemaPolygonField is used to return information of a Polygon field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_BYTES_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'SchemaBytesFieldInput', + description: + 'The SchemaBytesFieldInput is used to specify a field of type bytes for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_BYTES_FIELD = new GraphQLObjectType({ + name: 'SchemaBytesField', + description: + 'The SchemaBytesField is used to return information of a Bytes field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const TARGET_CLASS_ATT = { + description: 'This is the name of the target class for the field.', + type: new GraphQLNonNull(GraphQLString), +}; + +const SCHEMA_POINTER_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'PointerFieldInput', + description: + 'The PointerFieldInput is used to specify a field of type pointer for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + targetClassName: TARGET_CLASS_ATT, + }, +}); + +const SCHEMA_POINTER_FIELD = new GraphQLObjectType({ + name: 'SchemaPointerField', + description: + 'The SchemaPointerField is used to return information of a Pointer field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + targetClassName: TARGET_CLASS_ATT, + }, +}); + +const SCHEMA_RELATION_FIELD_INPUT = new GraphQLInputObjectType({ + name: 'RelationFieldInput', + description: + 'The RelationFieldInput is used to specify a field of type relation for an object class schema.', + fields: { + name: SCHEMA_FIELD_NAME_ATT, + targetClassName: TARGET_CLASS_ATT, + }, +}); + +const SCHEMA_RELATION_FIELD = new GraphQLObjectType({ + name: 'SchemaRelationField', + description: + 'The SchemaRelationField is used to return information of a Relation field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + targetClassName: TARGET_CLASS_ATT, + }, +}); + +const SCHEMA_ACL_FIELD = new GraphQLObjectType({ + name: 'SchemaACLField', + description: + 'The SchemaACLField is used to return information of an ACL field.', + interfaces: [SCHEMA_FIELD], + fields: { + name: SCHEMA_FIELD_NAME_ATT, + }, +}); + +const SCHEMA_FIELDS_INPUT = new GraphQLInputObjectType({ + name: 'SchemaFieldsInput', + description: `The CreateClassSchemaInput type is used to specify the schema for a new object class to be created.`, + fields: { + addStrings: { + description: + 'These are the String fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_STRING_FIELD_INPUT)), + }, + addNumbers: { + description: + 'These are the Number fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_NUMBER_FIELD_INPUT)), + }, + addBooleans: { + description: + 'These are the Boolean fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_BOOLEAN_FIELD_INPUT)), + }, + addArrays: { + description: + 'These are the Array fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_ARRAY_FIELD_INPUT)), + }, + addObjects: { + description: + 'These are the Object fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_OBJECT_FIELD_INPUT)), + }, + addDates: { + description: 'These are the Date fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_DATE_FIELD_INPUT)), + }, + addFiles: { + description: 'These are the File fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_FILE_FIELD_INPUT)), + }, + addGeoPoint: { + description: + 'This is the Geo Point field to be added to the class schema. Currently it is supported only one GeoPoint field per Class.', + type: SCHEMA_GEO_POINT_FIELD_INPUT, + }, + addPolygons: { + description: + 'These are the Polygon fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_POLYGON_FIELD_INPUT)), + }, + addBytes: { + description: + 'These are the Bytes fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_BYTES_FIELD_INPUT)), + }, + addPointers: { + description: + 'These are the Pointer fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_POINTER_FIELD_INPUT)), + }, + addRelations: { + description: + 'These are the Relation fields to be added to the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_RELATION_FIELD_INPUT)), + }, + remove: { + description: 'These are the fields to be removed from the class schema.', + type: new GraphQLList(new GraphQLNonNull(SCHEMA_FIELD_INPUT)), + }, + }, +}); + +const CLASS_NAME_ATT = { + description: 'This is the name of the object class.', + type: new GraphQLNonNull(GraphQLString), +}; + +const CLASS = new GraphQLObjectType({ + name: 'Class', + description: `The Class type is used to return the information about an object class.`, + fields: { + name: CLASS_NAME_ATT, + schemaFields: { + description: "These are the schema's fields of the object class.", + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(SCHEMA_FIELD)) + ), + }, + }, +}); + +const load = parseGraphQLSchema => { + parseGraphQLSchema.addGraphQLType(SCHEMA_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_STRING_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_STRING_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_NUMBER_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_NUMBER_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_BOOLEAN_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_BOOLEAN_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_ARRAY_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_ARRAY_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_OBJECT_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_OBJECT_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_DATE_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_DATE_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_FILE_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_FILE_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_GEO_POINT_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_GEO_POINT_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_POLYGON_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_POLYGON_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_BYTES_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_BYTES_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_POINTER_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_POINTER_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_RELATION_FIELD_INPUT, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_RELATION_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_ACL_FIELD, true); + parseGraphQLSchema.addGraphQLType(SCHEMA_FIELDS_INPUT, true); + parseGraphQLSchema.addGraphQLType(CLASS, true); +}; + +export { + SCHEMA_FIELD_NAME_ATT, + SCHEMA_FIELD_INPUT, + SCHEMA_STRING_FIELD_INPUT, + SCHEMA_STRING_FIELD, + SCHEMA_NUMBER_FIELD_INPUT, + SCHEMA_NUMBER_FIELD, + SCHEMA_BOOLEAN_FIELD_INPUT, + SCHEMA_BOOLEAN_FIELD, + SCHEMA_ARRAY_FIELD_INPUT, + SCHEMA_ARRAY_FIELD, + SCHEMA_OBJECT_FIELD_INPUT, + SCHEMA_OBJECT_FIELD, + SCHEMA_DATE_FIELD_INPUT, + SCHEMA_DATE_FIELD, + SCHEMA_FILE_FIELD_INPUT, + SCHEMA_FILE_FIELD, + SCHEMA_GEO_POINT_FIELD_INPUT, + SCHEMA_GEO_POINT_FIELD, + SCHEMA_POLYGON_FIELD_INPUT, + SCHEMA_POLYGON_FIELD, + SCHEMA_BYTES_FIELD_INPUT, + SCHEMA_BYTES_FIELD, + TARGET_CLASS_ATT, + SCHEMA_POINTER_FIELD_INPUT, + SCHEMA_POINTER_FIELD, + SCHEMA_RELATION_FIELD_INPUT, + SCHEMA_RELATION_FIELD, + SCHEMA_ACL_FIELD, + SCHEMA_FIELDS_INPUT, + CLASS_NAME_ATT, + CLASS, + load, +}; diff --git a/src/GraphQL/loaders/usersMutations.js b/src/GraphQL/loaders/usersMutations.js index 98f3ea7a59..a4fb69faca 100644 --- a/src/GraphQL/loaders/usersMutations.js +++ b/src/GraphQL/loaders/usersMutations.js @@ -1,6 +1,6 @@ import { GraphQLNonNull } from 'graphql'; import UsersRouter from '../../Routers/UsersRouter'; -import * as objectsMutations from './objectsMutations'; +import * as objectsMutations from '../helpers/objectsMutations'; import { getUserFromSessionToken } from './usersQueries'; const usersRouter = new UsersRouter(); diff --git a/src/GraphQL/parseGraphQLUtils.js b/src/GraphQL/parseGraphQLUtils.js index 4fd5a91447..825f37ddd8 100644 --- a/src/GraphQL/parseGraphQLUtils.js +++ b/src/GraphQL/parseGraphQLUtils.js @@ -1,6 +1,15 @@ import Parse from 'parse/node'; import { ApolloError } from 'apollo-server-core'; +export function enforceMasterKeyAccess(auth) { + if (!auth.isMaster) { + throw new Parse.Error( + Parse.Error.OPERATION_FORBIDDEN, + 'unauthorized: master key is required' + ); + } +} + export function toGraphQLError(error) { let code, message; if (error instanceof Parse.Error) { diff --git a/src/GraphQL/transformers/constraintType.js b/src/GraphQL/transformers/constraintType.js new file mode 100644 index 0000000000..83c4e4b264 --- /dev/null +++ b/src/GraphQL/transformers/constraintType.js @@ -0,0 +1,46 @@ +import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes'; + +const transformConstraintTypeToGraphQL = ( + parseType, + targetClass, + parseClassTypes +) => { + switch (parseType) { + case 'String': + return defaultGraphQLTypes.STRING_WHERE_INPUT; + case 'Number': + return defaultGraphQLTypes.NUMBER_WHERE_INPUT; + case 'Boolean': + return defaultGraphQLTypes.BOOLEAN_WHERE_INPUT; + case 'Array': + return defaultGraphQLTypes.ARRAY_WHERE_INPUT; + case 'Object': + return defaultGraphQLTypes.OBJECT_WHERE_INPUT; + case 'Date': + return defaultGraphQLTypes.DATE_WHERE_INPUT; + case 'Pointer': + if ( + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLConstraintType + ) { + return parseClassTypes[targetClass].classGraphQLConstraintType; + } else { + return defaultGraphQLTypes.OBJECT; + } + case 'File': + return defaultGraphQLTypes.FILE_WHERE_INPUT; + case 'GeoPoint': + return defaultGraphQLTypes.GEO_POINT_WHERE_INPUT; + case 'Polygon': + return defaultGraphQLTypes.POLYGON_WHERE_INPUT; + case 'Bytes': + return defaultGraphQLTypes.BYTES_WHERE_INPUT; + case 'ACL': + return defaultGraphQLTypes.OBJECT_WHERE_INPUT; + case 'Relation': + default: + return undefined; + } +}; + +export { transformConstraintTypeToGraphQL }; diff --git a/src/GraphQL/transformers/inputType.js b/src/GraphQL/transformers/inputType.js new file mode 100644 index 0000000000..5ea1161261 --- /dev/null +++ b/src/GraphQL/transformers/inputType.js @@ -0,0 +1,62 @@ +import { + GraphQLString, + GraphQLFloat, + GraphQLBoolean, + GraphQLList, +} from 'graphql'; +import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes'; + +const transformInputTypeToGraphQL = ( + parseType, + targetClass, + parseClassTypes +) => { + switch (parseType) { + case 'String': + return GraphQLString; + case 'Number': + return GraphQLFloat; + case 'Boolean': + return GraphQLBoolean; + case 'Array': + return new GraphQLList(defaultGraphQLTypes.ANY); + case 'Object': + return defaultGraphQLTypes.OBJECT; + case 'Date': + return defaultGraphQLTypes.DATE; + case 'Pointer': + if ( + parseClassTypes && + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLPointerType + ) { + return parseClassTypes[targetClass].classGraphQLPointerType; + } else { + return defaultGraphQLTypes.OBJECT; + } + case 'Relation': + if ( + parseClassTypes && + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLRelationType + ) { + return parseClassTypes[targetClass].classGraphQLRelationType; + } else { + return defaultGraphQLTypes.OBJECT; + } + case 'File': + return defaultGraphQLTypes.FILE; + case 'GeoPoint': + return defaultGraphQLTypes.GEO_POINT_INPUT; + case 'Polygon': + return defaultGraphQLTypes.POLYGON_INPUT; + case 'Bytes': + return defaultGraphQLTypes.BYTES; + case 'ACL': + return defaultGraphQLTypes.OBJECT; + default: + return undefined; + } +}; + +export { transformInputTypeToGraphQL }; diff --git a/src/GraphQL/transformers/mutation.js b/src/GraphQL/transformers/mutation.js index eec564fe59..4fc5f6275c 100644 --- a/src/GraphQL/transformers/mutation.js +++ b/src/GraphQL/transformers/mutation.js @@ -1,5 +1,5 @@ import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes'; -import * as objectsMutations from '../loaders/objectsMutations'; +import * as objectsMutations from '../helpers/objectsMutations'; const transformTypes = async ( inputType: 'create' | 'update', diff --git a/src/GraphQL/transformers/outputType.js b/src/GraphQL/transformers/outputType.js new file mode 100644 index 0000000000..b8382cc1f2 --- /dev/null +++ b/src/GraphQL/transformers/outputType.js @@ -0,0 +1,65 @@ +import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes'; +import { + GraphQLString, + GraphQLFloat, + GraphQLBoolean, + GraphQLList, + GraphQLNonNull, +} from 'graphql'; + +const transformOutputTypeToGraphQL = ( + parseType, + targetClass, + parseClassTypes +) => { + switch (parseType) { + case 'String': + return GraphQLString; + case 'Number': + return GraphQLFloat; + case 'Boolean': + return GraphQLBoolean; + case 'Array': + return new GraphQLList(defaultGraphQLTypes.ARRAY_RESULT); + case 'Object': + return defaultGraphQLTypes.OBJECT; + case 'Date': + return defaultGraphQLTypes.DATE; + case 'Pointer': + if ( + parseClassTypes && + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLOutputType + ) { + return parseClassTypes[targetClass].classGraphQLOutputType; + } else { + return defaultGraphQLTypes.OBJECT; + } + case 'Relation': + if ( + parseClassTypes && + parseClassTypes[targetClass] && + parseClassTypes[targetClass].classGraphQLFindResultType + ) { + return new GraphQLNonNull( + parseClassTypes[targetClass].classGraphQLFindResultType + ); + } else { + return new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT); + } + case 'File': + return defaultGraphQLTypes.FILE_INFO; + case 'GeoPoint': + return defaultGraphQLTypes.GEO_POINT; + case 'Polygon': + return defaultGraphQLTypes.POLYGON; + case 'Bytes': + return defaultGraphQLTypes.BYTES; + case 'ACL': + return defaultGraphQLTypes.OBJECT; + default: + return undefined; + } +}; + +export { transformOutputTypeToGraphQL }; diff --git a/src/GraphQL/transformers/schemaFields.js b/src/GraphQL/transformers/schemaFields.js new file mode 100644 index 0000000000..9d94e6f80e --- /dev/null +++ b/src/GraphQL/transformers/schemaFields.js @@ -0,0 +1,147 @@ +import Parse from 'parse/node'; + +const transformToParse = (graphQLSchemaFields, existingFields) => { + if (!graphQLSchemaFields) { + return {}; + } + + let parseSchemaFields = {}; + + const reducerGenerator = type => (parseSchemaFields, field) => { + if (type === 'Remove') { + if (existingFields[field.name]) { + return { + ...parseSchemaFields, + [field.name]: { + __op: 'Delete', + }, + }; + } else { + return parseSchemaFields; + } + } + if ( + graphQLSchemaFields.remove && + graphQLSchemaFields.remove.find( + removeField => removeField.name === field.name + ) + ) { + return parseSchemaFields; + } + if ( + parseSchemaFields[field.name] || + (existingFields && existingFields[field.name]) + ) { + throw new Parse.Error( + Parse.Error.INVALID_KEY_NAME, + `Duplicated field name: ${field.name}` + ); + } + if (type === 'Relation' || type === 'Pointer') { + return { + ...parseSchemaFields, + [field.name]: { + type, + targetClass: field.targetClassName, + }, + }; + } + return { + ...parseSchemaFields, + [field.name]: { + type, + }, + }; + }; + + if (graphQLSchemaFields.addStrings) { + parseSchemaFields = graphQLSchemaFields.addStrings.reduce( + reducerGenerator('String'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addNumbers) { + parseSchemaFields = graphQLSchemaFields.addNumbers.reduce( + reducerGenerator('Number'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addBooleans) { + parseSchemaFields = graphQLSchemaFields.addBooleans.reduce( + reducerGenerator('Boolean'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addArrays) { + parseSchemaFields = graphQLSchemaFields.addArrays.reduce( + reducerGenerator('Array'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addObjects) { + parseSchemaFields = graphQLSchemaFields.addObjects.reduce( + reducerGenerator('Object'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addDates) { + parseSchemaFields = graphQLSchemaFields.addDates.reduce( + reducerGenerator('Date'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addFiles) { + parseSchemaFields = graphQLSchemaFields.addFiles.reduce( + reducerGenerator('File'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addGeoPoint) { + parseSchemaFields = [graphQLSchemaFields.addGeoPoint].reduce( + reducerGenerator('GeoPoint'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addPolygons) { + parseSchemaFields = graphQLSchemaFields.addPolygons.reduce( + reducerGenerator('Polygon'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addBytes) { + parseSchemaFields = graphQLSchemaFields.addBytes.reduce( + reducerGenerator('Bytes'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addPointers) { + parseSchemaFields = graphQLSchemaFields.addPointers.reduce( + reducerGenerator('Pointer'), + parseSchemaFields + ); + } + if (graphQLSchemaFields.addRelations) { + parseSchemaFields = graphQLSchemaFields.addRelations.reduce( + reducerGenerator('Relation'), + parseSchemaFields + ); + } + if (existingFields && graphQLSchemaFields.remove) { + parseSchemaFields = graphQLSchemaFields.remove.reduce( + reducerGenerator('Remove'), + parseSchemaFields + ); + } + + return parseSchemaFields; +}; + +const transformToGraphQL = parseSchemaFields => { + return Object.keys(parseSchemaFields).map(name => ({ + name, + type: parseSchemaFields[name].type, + targetClassName: parseSchemaFields[name].targetClass, + })); +}; + +export { transformToParse, transformToGraphQL };