GraphQL DX: Relation/Pointer (#5946)
* Add a test on deep complex GraphQL Query * Relation/Pointer new DX + deep nested mutations * Fix lint * Review * Remove unnecessary code * Fix objectId on update
This commit is contained in:
committed by
Antonio Davi Macedo Coelho de Castro
parent
89e8868a85
commit
5b3a492965
@@ -1643,7 +1643,6 @@ describe('ParseGraphQLServer', () => {
|
|||||||
obj2.set('someClassField', 'imSomeClassTwo');
|
obj2.set('someClassField', 'imSomeClassTwo');
|
||||||
await obj2.save();
|
await obj2.save();
|
||||||
|
|
||||||
//const obj3Relation = obj3.relation('manyRelations')
|
|
||||||
obj3.set('manyRelations', [obj1, obj2]);
|
obj3.set('manyRelations', [obj1, obj2]);
|
||||||
await obj3.save();
|
await obj3.save();
|
||||||
|
|
||||||
@@ -1704,6 +1703,119 @@ describe('ParseGraphQLServer', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it_only_db('mongo')(
|
||||||
|
'should return many child objects in allow cyclic query',
|
||||||
|
async () => {
|
||||||
|
const obj1 = new Parse.Object('Employee');
|
||||||
|
const obj2 = new Parse.Object('Team');
|
||||||
|
const obj3 = new Parse.Object('Company');
|
||||||
|
const obj4 = new Parse.Object('Country');
|
||||||
|
|
||||||
|
obj1.set('name', 'imAnEmployee');
|
||||||
|
await obj1.save();
|
||||||
|
|
||||||
|
obj2.set('name', 'imATeam');
|
||||||
|
obj2.set('employees', [obj1]);
|
||||||
|
await obj2.save();
|
||||||
|
|
||||||
|
obj3.set('name', 'imACompany');
|
||||||
|
obj3.set('teams', [obj2]);
|
||||||
|
obj3.set('employees', [obj1]);
|
||||||
|
await obj3.save();
|
||||||
|
|
||||||
|
obj4.set('name', 'imACountry');
|
||||||
|
obj4.set('companies', [obj3]);
|
||||||
|
await obj4.save();
|
||||||
|
|
||||||
|
obj1.set('country', obj4);
|
||||||
|
await obj1.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const result = (await apolloClient.query({
|
||||||
|
query: gql`
|
||||||
|
query DeepComplexGraphQLQuery($objectId: ID!) {
|
||||||
|
country(objectId: $objectId) {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
companies {
|
||||||
|
... on Company {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
employees {
|
||||||
|
... on Employee {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
teams {
|
||||||
|
... on Team {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
employees {
|
||||||
|
... on Employee {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
country {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
objectId: obj4.id,
|
||||||
|
},
|
||||||
|
})).data.country;
|
||||||
|
|
||||||
|
const expectedResult = {
|
||||||
|
objectId: obj4.id,
|
||||||
|
name: 'imACountry',
|
||||||
|
__typename: 'Country',
|
||||||
|
companies: [
|
||||||
|
{
|
||||||
|
objectId: obj3.id,
|
||||||
|
name: 'imACompany',
|
||||||
|
__typename: 'Company',
|
||||||
|
employees: [
|
||||||
|
{
|
||||||
|
objectId: obj1.id,
|
||||||
|
name: 'imAnEmployee',
|
||||||
|
__typename: 'Employee',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
teams: [
|
||||||
|
{
|
||||||
|
objectId: obj2.id,
|
||||||
|
name: 'imATeam',
|
||||||
|
__typename: 'Team',
|
||||||
|
employees: [
|
||||||
|
{
|
||||||
|
objectId: obj1.id,
|
||||||
|
name: 'imAnEmployee',
|
||||||
|
__typename: 'Employee',
|
||||||
|
country: {
|
||||||
|
objectId: obj4.id,
|
||||||
|
name: 'imACountry',
|
||||||
|
__typename: 'Country',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
it('should respect level permissions', async () => {
|
it('should respect level permissions', async () => {
|
||||||
await prepareData();
|
await prepareData();
|
||||||
|
|
||||||
@@ -3321,6 +3433,41 @@ describe('ParseGraphQLServer', () => {
|
|||||||
expect(obj.get('someField2')).toEqual('someField2Value1');
|
expect(obj.get('someField2')).toEqual('someField2Value1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return only objectId using class specific mutation', async () => {
|
||||||
|
const obj = new Parse.Object('Customer');
|
||||||
|
obj.set('someField1', 'someField1Value1');
|
||||||
|
obj.set('someField2', 'someField2Value1');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const result = await apolloClient.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation UpdateCustomer(
|
||||||
|
$objectId: ID!
|
||||||
|
$fields: UpdateCustomerFieldsInput
|
||||||
|
) {
|
||||||
|
updateCustomer(objectId: $objectId, fields: $fields) {
|
||||||
|
objectId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
objectId: obj.id,
|
||||||
|
fields: {
|
||||||
|
someField1: 'someField1Value2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.data.updateCustomer.objectId).toEqual(obj.id);
|
||||||
|
|
||||||
|
await obj.fetch();
|
||||||
|
|
||||||
|
expect(obj.get('someField1')).toEqual('someField1Value2');
|
||||||
|
expect(obj.get('someField2')).toEqual('someField2Value1');
|
||||||
|
});
|
||||||
|
|
||||||
it('should respect level permissions', async () => {
|
it('should respect level permissions', async () => {
|
||||||
await prepareData();
|
await prepareData();
|
||||||
|
|
||||||
@@ -4759,255 +4906,541 @@ describe('ParseGraphQLServer', () => {
|
|||||||
expect(Date.parse(getResult.data.get.updatedAt)).not.toEqual(NaN);
|
expect(Date.parse(getResult.data.get.updatedAt)).not.toEqual(NaN);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support pointer values', async () => {
|
it('should support pointer on create', async () => {
|
||||||
const parent = new Parse.Object('ParentClass');
|
const company = new Parse.Object('Company');
|
||||||
await parent.save();
|
company.set('name', 'imACompany1');
|
||||||
|
await company.save();
|
||||||
|
|
||||||
const pointerFieldValue = {
|
const country = new Parse.Object('Country');
|
||||||
__type: 'Pointer',
|
country.set('name', 'imACountry');
|
||||||
className: 'ParentClass',
|
country.set('company', company);
|
||||||
objectId: parent.id,
|
await country.save();
|
||||||
};
|
|
||||||
|
|
||||||
const createResult = await apolloClient.mutate({
|
const company2 = new Parse.Object('Company');
|
||||||
|
company2.set('name', 'imACompany2');
|
||||||
|
await company2.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { createCountry: result },
|
||||||
|
} = await apolloClient.mutate({
|
||||||
mutation: gql`
|
mutation: gql`
|
||||||
mutation CreateChildObject($fields: Object) {
|
mutation Create($fields: CreateCountryFieldsInput) {
|
||||||
create(className: "ChildClass", fields: $fields) {
|
createCountry(fields: $fields) {
|
||||||
objectId
|
objectId
|
||||||
|
company {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
fields: {
|
fields: {
|
||||||
pointerField: pointerFieldValue,
|
name: 'imCountry2',
|
||||||
|
company: { link: { objectId: company2.id } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
expect(result.objectId).toBeDefined();
|
||||||
|
expect(result.company.objectId).toEqual(company2.id);
|
||||||
const schema = await new Parse.Schema('ChildClass').get();
|
expect(result.company.name).toEqual('imACompany2');
|
||||||
expect(schema.fields.pointerField.type).toEqual('Pointer');
|
|
||||||
expect(schema.fields.pointerField.targetClass).toEqual('ParentClass');
|
|
||||||
|
|
||||||
await apolloClient.mutate({
|
|
||||||
mutation: gql`
|
|
||||||
mutation CreateChildObject(
|
|
||||||
$fields1: CreateChildClassFieldsInput
|
|
||||||
$fields2: CreateChildClassFieldsInput
|
|
||||||
) {
|
|
||||||
createChildClass1: createChildClass(fields: $fields1) {
|
|
||||||
objectId
|
|
||||||
}
|
|
||||||
createChildClass2: createChildClass(fields: $fields2) {
|
|
||||||
objectId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
fields1: {
|
|
||||||
pointerField: pointerFieldValue,
|
|
||||||
},
|
|
||||||
fields2: {
|
|
||||||
pointerField: pointerFieldValue.objectId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getResult = await apolloClient.query({
|
|
||||||
query: gql`
|
|
||||||
query GetChildObject(
|
|
||||||
$objectId: ID!
|
|
||||||
$pointerFieldValue1: ParentClassPointer
|
|
||||||
$pointerFieldValue2: ParentClassPointer
|
|
||||||
) {
|
|
||||||
get(className: "ChildClass", objectId: $objectId)
|
|
||||||
findChildClass1: childClasses(
|
|
||||||
where: { pointerField: { _eq: $pointerFieldValue1 } }
|
|
||||||
) {
|
|
||||||
results {
|
|
||||||
pointerField {
|
|
||||||
objectId
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
findChildClass2: childClasses(
|
|
||||||
where: { pointerField: { _eq: $pointerFieldValue2 } }
|
|
||||||
) {
|
|
||||||
results {
|
|
||||||
pointerField {
|
|
||||||
objectId
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
objectId: createResult.data.create.objectId,
|
|
||||||
pointerFieldValue1: pointerFieldValue,
|
|
||||||
pointerFieldValue2: pointerFieldValue.objectId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(typeof getResult.data.get.pointerField).toEqual('object');
|
|
||||||
expect(getResult.data.get.pointerField).toEqual(pointerFieldValue);
|
|
||||||
expect(getResult.data.findChildClass1.results.length).toEqual(3);
|
|
||||||
expect(getResult.data.findChildClass2.results.length).toEqual(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it_only_db('mongo')('should support relation', async () => {
|
it('should support nested pointer on create', async () => {
|
||||||
const someObject1 = new Parse.Object('SomeClass');
|
const company = new Parse.Object('Company');
|
||||||
await someObject1.save();
|
company.set('name', 'imACompany1');
|
||||||
const someObject2 = new Parse.Object('SomeClass');
|
await company.save();
|
||||||
await someObject2.save();
|
|
||||||
|
|
||||||
const pointerValue1 = {
|
const country = new Parse.Object('Country');
|
||||||
__type: 'Pointer',
|
country.set('name', 'imACountry');
|
||||||
className: 'SomeClass',
|
country.set('company', company);
|
||||||
objectId: someObject1.id,
|
await country.save();
|
||||||
};
|
|
||||||
const pointerValue2 = {
|
|
||||||
__type: 'Pointer',
|
|
||||||
className: 'SomeClass',
|
|
||||||
objectId: someObject2.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const createResult = await apolloClient.mutate({
|
|
||||||
mutation: gql`
|
|
||||||
mutation CreateMainObject($fields: Object) {
|
|
||||||
create(className: "MainClass", fields: $fields) {
|
|
||||||
objectId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
fields: {
|
|
||||||
relationField: {
|
|
||||||
__op: 'Batch',
|
|
||||||
ops: [
|
|
||||||
{
|
|
||||||
__op: 'AddRelation',
|
|
||||||
objects: [pointerValue1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__op: 'AddRelation',
|
|
||||||
objects: [pointerValue2],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
const schema = await new Parse.Schema('MainClass').get();
|
const {
|
||||||
expect(schema.fields.relationField.type).toEqual('Relation');
|
data: { createCountry: result },
|
||||||
expect(schema.fields.relationField.targetClass).toEqual('SomeClass');
|
} = await apolloClient.mutate({
|
||||||
|
|
||||||
await apolloClient.mutate({
|
|
||||||
mutation: gql`
|
mutation: gql`
|
||||||
mutation CreateMainObject($fields: CreateMainClassFieldsInput) {
|
mutation Create($fields: CreateCountryFieldsInput) {
|
||||||
createMainClass(fields: $fields) {
|
createCountry(fields: $fields) {
|
||||||
objectId
|
objectId
|
||||||
}
|
company {
|
||||||
}
|
objectId
|
||||||
`,
|
name
|
||||||
variables: {
|
|
||||||
fields: {
|
|
||||||
relationField: {
|
|
||||||
_op: 'Batch',
|
|
||||||
ops: [
|
|
||||||
{
|
|
||||||
_op: 'AddRelation',
|
|
||||||
objects: [pointerValue1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_op: 'RemoveRelation',
|
|
||||||
objects: [pointerValue1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_op: 'AddRelation',
|
|
||||||
objects: [pointerValue2],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getResult = await apolloClient.query({
|
|
||||||
query: gql`
|
|
||||||
query GetMainObject($objectId: ID!) {
|
|
||||||
get(className: "MainClass", objectId: $objectId)
|
|
||||||
mainClass(objectId: $objectId) {
|
|
||||||
relationField {
|
|
||||||
results {
|
|
||||||
objectId
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
count
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
objectId: createResult.data.create.objectId,
|
fields: {
|
||||||
},
|
name: 'imCountry2',
|
||||||
});
|
company: {
|
||||||
|
createAndLink: {
|
||||||
expect(typeof getResult.data.get.relationField).toEqual('object');
|
name: 'imACompany2',
|
||||||
expect(getResult.data.get.relationField).toEqual({
|
|
||||||
__type: 'Relation',
|
|
||||||
className: 'SomeClass',
|
|
||||||
});
|
|
||||||
expect(getResult.data.mainClass.relationField.results.length).toEqual(
|
|
||||||
2
|
|
||||||
);
|
|
||||||
expect(getResult.data.mainClass.relationField.count).toEqual(2);
|
|
||||||
|
|
||||||
const findResult = await apolloClient.query({
|
|
||||||
query: gql`
|
|
||||||
query FindSomeObjects($where: Object) {
|
|
||||||
find(className: "SomeClass", where: $where) {
|
|
||||||
results
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
where: {
|
|
||||||
$relatedTo: {
|
|
||||||
object: {
|
|
||||||
__type: 'Pointer',
|
|
||||||
className: 'MainClass',
|
|
||||||
objectId: createResult.data.create.objectId,
|
|
||||||
},
|
},
|
||||||
key: 'relationField',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const compare = (obj1, obj2) =>
|
expect(result.objectId).toBeDefined();
|
||||||
obj1.createdAt > obj2.createdAt ? 1 : -1;
|
expect(result.company.objectId).toBeDefined();
|
||||||
|
expect(result.company.name).toEqual('imACompany2');
|
||||||
|
});
|
||||||
|
|
||||||
expect(findResult.data.find.results).toEqual(jasmine.any(Array));
|
it('should support pointer on update', async () => {
|
||||||
expect(findResult.data.find.results.sort(compare)).toEqual(
|
const company = new Parse.Object('Company');
|
||||||
[
|
company.set('name', 'imACompany1');
|
||||||
{
|
await company.save();
|
||||||
objectId: someObject1.id,
|
|
||||||
createdAt: someObject1.createdAt.toISOString(),
|
const country = new Parse.Object('Country');
|
||||||
updatedAt: someObject1.updatedAt.toISOString(),
|
country.set('name', 'imACountry');
|
||||||
|
country.set('company', company);
|
||||||
|
await country.save();
|
||||||
|
|
||||||
|
const company2 = new Parse.Object('Company');
|
||||||
|
company2.set('name', 'imACompany2');
|
||||||
|
await company2.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { updateCountry: result },
|
||||||
|
} = await apolloClient.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation Update(
|
||||||
|
$objectId: ID!
|
||||||
|
$fields: UpdateCountryFieldsInput
|
||||||
|
) {
|
||||||
|
updateCountry(objectId: $objectId, fields: $fields) {
|
||||||
|
objectId
|
||||||
|
company {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
objectId: country.id,
|
||||||
|
fields: {
|
||||||
|
company: { link: { objectId: company2.id } },
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
objectId: someObject2.id,
|
});
|
||||||
createdAt: someObject2.createdAt.toISOString(),
|
|
||||||
updatedAt: someObject2.updatedAt.toISOString(),
|
expect(result.objectId).toBeDefined();
|
||||||
|
expect(result.company.objectId).toEqual(company2.id);
|
||||||
|
expect(result.company.name).toEqual('imACompany2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support nested pointer on update', async () => {
|
||||||
|
const company = new Parse.Object('Company');
|
||||||
|
company.set('name', 'imACompany1');
|
||||||
|
await company.save();
|
||||||
|
|
||||||
|
const country = new Parse.Object('Country');
|
||||||
|
country.set('name', 'imACountry');
|
||||||
|
country.set('company', company);
|
||||||
|
await country.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { updateCountry: result },
|
||||||
|
} = await apolloClient.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation Update(
|
||||||
|
$objectId: ID!
|
||||||
|
$fields: UpdateCountryFieldsInput
|
||||||
|
) {
|
||||||
|
updateCountry(objectId: $objectId, fields: $fields) {
|
||||||
|
objectId
|
||||||
|
company {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
objectId: country.id,
|
||||||
|
fields: {
|
||||||
|
company: {
|
||||||
|
createAndLink: {
|
||||||
|
name: 'imACompany2',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
].sort(compare)
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
|
expect(result.objectId).toBeDefined();
|
||||||
|
expect(result.company.objectId).toBeDefined();
|
||||||
|
expect(result.company.name).toEqual('imACompany2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it_only_db('mongo')(
|
||||||
|
'should support relation and nested relation on create',
|
||||||
|
async () => {
|
||||||
|
const company = new Parse.Object('Company');
|
||||||
|
company.set('name', 'imACompany1');
|
||||||
|
await company.save();
|
||||||
|
|
||||||
|
const country = new Parse.Object('Country');
|
||||||
|
country.set('name', 'imACountry');
|
||||||
|
country.relation('companies').add(company);
|
||||||
|
await country.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { createCountry: result },
|
||||||
|
} = await apolloClient.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation CreateCountry($fields: CreateCountryFieldsInput) {
|
||||||
|
createCountry(fields: $fields) {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
companies {
|
||||||
|
results {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
fields: {
|
||||||
|
name: 'imACountry2',
|
||||||
|
companies: {
|
||||||
|
add: [{ objectId: company.id }],
|
||||||
|
createAndAdd: [
|
||||||
|
{
|
||||||
|
name: 'imACompany2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'imACompany3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.objectId).toBeDefined();
|
||||||
|
expect(result.name).toEqual('imACountry2');
|
||||||
|
expect(result.companies.results.length).toEqual(3);
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(o => o.objectId === company.id)
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(o => o.name === 'imACompany2')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(o => o.name === 'imACompany3')
|
||||||
|
).toBeTruthy();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it_only_db('mongo')('should support deep nested creation', async () => {
|
||||||
|
const team = new Parse.Object('Team');
|
||||||
|
team.set('name', 'imATeam1');
|
||||||
|
await team.save();
|
||||||
|
|
||||||
|
const company = new Parse.Object('Company');
|
||||||
|
company.set('name', 'imACompany1');
|
||||||
|
company.relation('teams').add(team);
|
||||||
|
await company.save();
|
||||||
|
|
||||||
|
const country = new Parse.Object('Country');
|
||||||
|
country.set('name', 'imACountry');
|
||||||
|
country.relation('companies').add(company);
|
||||||
|
await country.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { createCountry: result },
|
||||||
|
} = await apolloClient.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation CreateCountry($fields: CreateCountryFieldsInput) {
|
||||||
|
createCountry(fields: $fields) {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
companies {
|
||||||
|
results {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
teams {
|
||||||
|
results {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
fields: {
|
||||||
|
name: 'imACountry2',
|
||||||
|
companies: {
|
||||||
|
createAndAdd: [
|
||||||
|
{
|
||||||
|
name: 'imACompany2',
|
||||||
|
teams: {
|
||||||
|
createAndAdd: {
|
||||||
|
name: 'imATeam2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'imACompany3',
|
||||||
|
teams: {
|
||||||
|
createAndAdd: {
|
||||||
|
name: 'imATeam3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.objectId).toBeDefined();
|
||||||
|
expect(result.name).toEqual('imACountry2');
|
||||||
|
expect(result.companies.results.length).toEqual(2);
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(
|
||||||
|
c =>
|
||||||
|
c.name === 'imACompany2' &&
|
||||||
|
c.teams.results.some(t => t.name === 'imATeam2')
|
||||||
|
)
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(
|
||||||
|
c =>
|
||||||
|
c.name === 'imACompany3' &&
|
||||||
|
c.teams.results.some(t => t.name === 'imATeam3')
|
||||||
|
)
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it_only_db('mongo')(
|
||||||
|
'should support relation and nested relation on update',
|
||||||
|
async () => {
|
||||||
|
const company1 = new Parse.Object('Company');
|
||||||
|
company1.set('name', 'imACompany1');
|
||||||
|
await company1.save();
|
||||||
|
|
||||||
|
const company2 = new Parse.Object('Company');
|
||||||
|
company2.set('name', 'imACompany2');
|
||||||
|
await company2.save();
|
||||||
|
|
||||||
|
const country = new Parse.Object('Country');
|
||||||
|
country.set('name', 'imACountry');
|
||||||
|
country.relation('companies').add(company1);
|
||||||
|
await country.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { updateCountry: result },
|
||||||
|
} = await apolloClient.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation UpdateCountry(
|
||||||
|
$objectId: ID!
|
||||||
|
$fields: UpdateCountryFieldsInput
|
||||||
|
) {
|
||||||
|
updateCountry(objectId: $objectId, fields: $fields) {
|
||||||
|
objectId
|
||||||
|
companies {
|
||||||
|
results {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
objectId: country.id,
|
||||||
|
fields: {
|
||||||
|
companies: {
|
||||||
|
add: [{ objectId: company2.id }],
|
||||||
|
remove: [{ objectId: company1.id }],
|
||||||
|
createAndAdd: [
|
||||||
|
{
|
||||||
|
name: 'imACompany3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.objectId).toEqual(country.id);
|
||||||
|
expect(result.companies.results.length).toEqual(2);
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(o => o.objectId === company2.id)
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(o => o.name === 'imACompany3')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(o => o.objectId === company1.id)
|
||||||
|
).toBeFalsy();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it_only_db('mongo')(
|
||||||
|
'should support nested relation on create with filter',
|
||||||
|
async () => {
|
||||||
|
const company = new Parse.Object('Company');
|
||||||
|
company.set('name', 'imACompany1');
|
||||||
|
await company.save();
|
||||||
|
|
||||||
|
const country = new Parse.Object('Country');
|
||||||
|
country.set('name', 'imACountry');
|
||||||
|
country.relation('companies').add(company);
|
||||||
|
await country.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { createCountry: result },
|
||||||
|
} = await apolloClient.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation CreateCountry(
|
||||||
|
$fields: CreateCountryFieldsInput
|
||||||
|
$where: CompanyWhereInput
|
||||||
|
) {
|
||||||
|
createCountry(fields: $fields) {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
companies(where: $where) {
|
||||||
|
results {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
where: {
|
||||||
|
name: {
|
||||||
|
_eq: 'imACompany2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
name: 'imACountry2',
|
||||||
|
companies: {
|
||||||
|
add: [{ objectId: company.id }],
|
||||||
|
createAndAdd: [
|
||||||
|
{
|
||||||
|
name: 'imACompany2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'imACompany3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.objectId).toBeDefined();
|
||||||
|
expect(result.name).toEqual('imACountry2');
|
||||||
|
expect(result.companies.results.length).toEqual(1);
|
||||||
|
expect(
|
||||||
|
result.companies.results.some(o => o.name === 'imACompany2')
|
||||||
|
).toBeTruthy();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it_only_db('mongo')('should support relation on query', async () => {
|
||||||
|
const company1 = new Parse.Object('Company');
|
||||||
|
company1.set('name', 'imACompany1');
|
||||||
|
await company1.save();
|
||||||
|
|
||||||
|
const company2 = new Parse.Object('Company');
|
||||||
|
company2.set('name', 'imACompany2');
|
||||||
|
await company2.save();
|
||||||
|
|
||||||
|
const country = new Parse.Object('Country');
|
||||||
|
country.set('name', 'imACountry');
|
||||||
|
country.relation('companies').add([company1, company2]);
|
||||||
|
await country.save();
|
||||||
|
|
||||||
|
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||||
|
|
||||||
|
// Without where
|
||||||
|
const {
|
||||||
|
data: { country: result1 },
|
||||||
|
} = await apolloClient.query({
|
||||||
|
query: gql`
|
||||||
|
query getCountry($objectId: ID!) {
|
||||||
|
country(objectId: $objectId) {
|
||||||
|
objectId
|
||||||
|
companies {
|
||||||
|
results {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
objectId: country.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result1.objectId).toEqual(country.id);
|
||||||
|
expect(result1.companies.results.length).toEqual(2);
|
||||||
|
expect(
|
||||||
|
result1.companies.results.some(o => o.objectId === company1.id)
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
result1.companies.results.some(o => o.objectId === company2.id)
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
// With where
|
||||||
|
const {
|
||||||
|
data: { country: result2 },
|
||||||
|
} = await apolloClient.query({
|
||||||
|
query: gql`
|
||||||
|
query getCountry($objectId: ID!, $where: CompanyWhereInput) {
|
||||||
|
country(objectId: $objectId) {
|
||||||
|
objectId
|
||||||
|
companies(where: $where) {
|
||||||
|
results {
|
||||||
|
objectId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
objectId: country.id,
|
||||||
|
where: {
|
||||||
|
name: { _eq: 'imACompany1' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result2.objectId).toEqual(country.id);
|
||||||
|
expect(result2.companies.results.length).toEqual(1);
|
||||||
|
expect(result2.companies.results[0].objectId).toEqual(company1.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support files', async () => {
|
it('should support files', async () => {
|
||||||
|
|||||||
@@ -395,14 +395,14 @@ const POLYGON_INPUT = new GraphQLList(new GraphQLNonNull(GEO_POINT_INPUT));
|
|||||||
|
|
||||||
const POLYGON = new GraphQLList(new GraphQLNonNull(GEO_POINT));
|
const POLYGON = new GraphQLList(new GraphQLNonNull(GEO_POINT));
|
||||||
|
|
||||||
const RELATION_OP = new GraphQLEnumType({
|
const RELATION_INPUT = new GraphQLInputObjectType({
|
||||||
name: 'RelationOp',
|
name: 'RelationInput',
|
||||||
description:
|
description: 'Object involved into a relation',
|
||||||
'The RelationOp enum type is used to specify which kind of operation should be executed to a relation.',
|
fields: {
|
||||||
values: {
|
objectId: {
|
||||||
Batch: { value: 'Batch' },
|
description: 'Id of the object involved.',
|
||||||
AddRelation: { value: 'AddRelation' },
|
type: new GraphQLNonNull(GraphQLID),
|
||||||
RemoveRelation: { value: 'RemoveRelation' },
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -491,6 +491,17 @@ const INCLUDE_ATT = {
|
|||||||
type: GraphQLString,
|
type: GraphQLString,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const POINTER_INPUT = new GraphQLInputObjectType({
|
||||||
|
name: 'PointerInput',
|
||||||
|
description: 'Allow to link an object to another object',
|
||||||
|
fields: {
|
||||||
|
objectId: {
|
||||||
|
description: 'Id of the object involved.',
|
||||||
|
type: new GraphQLNonNull(GraphQLID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const READ_PREFERENCE = new GraphQLEnumType({
|
const READ_PREFERENCE = new GraphQLEnumType({
|
||||||
name: 'ReadPreference',
|
name: 'ReadPreference',
|
||||||
description:
|
description:
|
||||||
@@ -1080,7 +1091,6 @@ const load = parseGraphQLSchema => {
|
|||||||
parseGraphQLSchema.addGraphQLType(FILE_INFO, true);
|
parseGraphQLSchema.addGraphQLType(FILE_INFO, true);
|
||||||
parseGraphQLSchema.addGraphQLType(GEO_POINT_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(GEO_POINT_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(GEO_POINT, true);
|
parseGraphQLSchema.addGraphQLType(GEO_POINT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(RELATION_OP, true);
|
|
||||||
parseGraphQLSchema.addGraphQLType(CREATE_RESULT, true);
|
parseGraphQLSchema.addGraphQLType(CREATE_RESULT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(UPDATE_RESULT, true);
|
parseGraphQLSchema.addGraphQLType(UPDATE_RESULT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(CLASS, true);
|
parseGraphQLSchema.addGraphQLType(CLASS, true);
|
||||||
@@ -1108,6 +1118,8 @@ const load = parseGraphQLSchema => {
|
|||||||
parseGraphQLSchema.addGraphQLType(FIND_RESULT, true);
|
parseGraphQLSchema.addGraphQLType(FIND_RESULT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(SIGN_UP_RESULT, true);
|
parseGraphQLSchema.addGraphQLType(SIGN_UP_RESULT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(ELEMENT, true);
|
parseGraphQLSchema.addGraphQLType(ELEMENT, true);
|
||||||
|
parseGraphQLSchema.addGraphQLType(RELATION_INPUT, true);
|
||||||
|
parseGraphQLSchema.addGraphQLType(POINTER_INPUT, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -1133,7 +1145,6 @@ export {
|
|||||||
GEO_POINT,
|
GEO_POINT,
|
||||||
POLYGON_INPUT,
|
POLYGON_INPUT,
|
||||||
POLYGON,
|
POLYGON,
|
||||||
RELATION_OP,
|
|
||||||
CLASS_NAME_ATT,
|
CLASS_NAME_ATT,
|
||||||
FIELDS_ATT,
|
FIELDS_ATT,
|
||||||
OBJECT_ID_ATT,
|
OBJECT_ID_ATT,
|
||||||
@@ -1195,6 +1206,8 @@ export {
|
|||||||
SIGN_UP_RESULT,
|
SIGN_UP_RESULT,
|
||||||
ARRAY_RESULT,
|
ARRAY_RESULT,
|
||||||
ELEMENT,
|
ELEMENT,
|
||||||
|
POINTER_INPUT,
|
||||||
|
RELATION_INPUT,
|
||||||
load,
|
load,
|
||||||
loadArrayResult,
|
loadArrayResult,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import rest from '../../rest';
|
import rest from '../../rest';
|
||||||
import { transformMutationInputToParse } from '../transformers/mutation';
|
|
||||||
|
|
||||||
const createObject = async (className, fields, config, auth, info) => {
|
const createObject = async (className, fields, config, auth, info) => {
|
||||||
if (!fields) {
|
if (!fields) {
|
||||||
fields = {};
|
fields = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
transformMutationInputToParse(fields);
|
|
||||||
|
|
||||||
return (await rest.create(config, auth, className, fields, info.clientSDK))
|
return (await rest.create(config, auth, className, fields, info.clientSDK))
|
||||||
.response;
|
.response;
|
||||||
};
|
};
|
||||||
@@ -26,8 +23,6 @@ const updateObject = async (
|
|||||||
fields = {};
|
fields = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
transformMutationInputToParse(fields);
|
|
||||||
|
|
||||||
return (await rest.update(
|
return (await rest.update(
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull } from 'graphql';
|
||||||
import getFieldNames from 'graphql-list-fields';
|
import getFieldNames from 'graphql-list-fields';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import { extractKeysAndInclude } from '../parseGraphQLUtils';
|
import {
|
||||||
|
extractKeysAndInclude,
|
||||||
|
getParseClassMutationConfig,
|
||||||
|
} from '../parseGraphQLUtils';
|
||||||
import * as objectsMutations from './objectsMutations';
|
import * as objectsMutations from './objectsMutations';
|
||||||
import * as objectsQueries from './objectsQueries';
|
import * as objectsQueries from './objectsQueries';
|
||||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||||
import { transformClassNameToGraphQL } from '../transformers/className';
|
import { transformClassNameToGraphQL } from '../transformers/className';
|
||||||
|
import { transformTypes } from '../transformers/mutation';
|
||||||
const getParseClassMutationConfig = function(
|
|
||||||
parseClassConfig: ?ParseGraphQLClassConfig
|
|
||||||
) {
|
|
||||||
return (parseClassConfig && parseClassConfig.mutation) || {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOnlyRequiredFields = (
|
const getOnlyRequiredFields = (
|
||||||
updatedFields,
|
updatedFields,
|
||||||
@@ -55,43 +53,6 @@ const load = function(
|
|||||||
classGraphQLOutputType,
|
classGraphQLOutputType,
|
||||||
} = parseGraphQLSchema.parseClassTypes[className];
|
} = parseGraphQLSchema.parseClassTypes[className];
|
||||||
|
|
||||||
const transformTypes = (inputType: 'create' | 'update', fields) => {
|
|
||||||
if (fields) {
|
|
||||||
const classGraphQLCreateTypeFields =
|
|
||||||
isCreateEnabled && classGraphQLCreateType
|
|
||||||
? classGraphQLCreateType.getFields()
|
|
||||||
: null;
|
|
||||||
const classGraphQLUpdateTypeFields =
|
|
||||||
isUpdateEnabled && classGraphQLUpdateType
|
|
||||||
? classGraphQLUpdateType.getFields()
|
|
||||||
: null;
|
|
||||||
Object.keys(fields).forEach(field => {
|
|
||||||
let inputTypeField;
|
|
||||||
if (inputType === 'create' && classGraphQLCreateTypeFields) {
|
|
||||||
inputTypeField = classGraphQLCreateTypeFields[field];
|
|
||||||
} else if (classGraphQLUpdateTypeFields) {
|
|
||||||
inputTypeField = classGraphQLUpdateTypeFields[field];
|
|
||||||
}
|
|
||||||
if (inputTypeField) {
|
|
||||||
switch (inputTypeField.type) {
|
|
||||||
case defaultGraphQLTypes.GEO_POINT_INPUT:
|
|
||||||
fields[field].__type = 'GeoPoint';
|
|
||||||
break;
|
|
||||||
case defaultGraphQLTypes.POLYGON_INPUT:
|
|
||||||
fields[field] = {
|
|
||||||
__type: 'Polygon',
|
|
||||||
coordinates: fields[field].map(geoPoint => [
|
|
||||||
geoPoint.latitude,
|
|
||||||
geoPoint.longitude,
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isCreateEnabled) {
|
if (isCreateEnabled) {
|
||||||
const createGraphQLMutationName = `create${graphQLClassName}`;
|
const createGraphQLMutationName = `create${graphQLClassName}`;
|
||||||
parseGraphQLSchema.addGraphQLMutation(createGraphQLMutationName, {
|
parseGraphQLSchema.addGraphQLMutation(createGraphQLMutationName, {
|
||||||
@@ -110,10 +71,16 @@ const load = function(
|
|||||||
let { fields } = args;
|
let { fields } = args;
|
||||||
if (!fields) fields = {};
|
if (!fields) fields = {};
|
||||||
const { config, auth, info } = context;
|
const { config, auth, info } = context;
|
||||||
transformTypes('create', fields);
|
|
||||||
|
const parseFields = await transformTypes('create', fields, {
|
||||||
|
className,
|
||||||
|
parseGraphQLSchema,
|
||||||
|
req: { config, auth, info },
|
||||||
|
});
|
||||||
|
|
||||||
const createdObject = await objectsMutations.createObject(
|
const createdObject = await objectsMutations.createObject(
|
||||||
className,
|
className,
|
||||||
fields,
|
parseFields,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
@@ -172,12 +139,16 @@ const load = function(
|
|||||||
const { objectId, fields } = args;
|
const { objectId, fields } = args;
|
||||||
const { config, auth, info } = context;
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
transformTypes('update', fields);
|
const parseFields = await transformTypes('update', fields, {
|
||||||
|
className,
|
||||||
|
parseGraphQLSchema,
|
||||||
|
req: { config, auth, info },
|
||||||
|
});
|
||||||
|
|
||||||
const updatedObject = await objectsMutations.updateObject(
|
const updatedObject = await objectsMutations.updateObject(
|
||||||
className,
|
className,
|
||||||
objectId,
|
objectId,
|
||||||
fields,
|
parseFields,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
@@ -205,7 +176,12 @@ const load = function(
|
|||||||
info
|
info
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { ...updatedObject, ...fields, ...optimizedObject };
|
return {
|
||||||
|
objectId: objectId,
|
||||||
|
...updatedObject,
|
||||||
|
...fields,
|
||||||
|
...optimizedObject,
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
|||||||
import * as objectsQueries from './objectsQueries';
|
import * as objectsQueries from './objectsQueries';
|
||||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||||
import { transformClassNameToGraphQL } from '../transformers/className';
|
import { transformClassNameToGraphQL } from '../transformers/className';
|
||||||
import { extractKeysAndInclude } from '../parseGraphQLUtils';
|
import {
|
||||||
|
extractKeysAndInclude,
|
||||||
|
getParseClassMutationConfig,
|
||||||
|
} from '../parseGraphQLUtils';
|
||||||
|
|
||||||
const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
||||||
switch (parseType) {
|
switch (parseType) {
|
||||||
@@ -34,18 +37,18 @@ const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
|||||||
case 'Pointer':
|
case 'Pointer':
|
||||||
if (
|
if (
|
||||||
parseClassTypes[targetClass] &&
|
parseClassTypes[targetClass] &&
|
||||||
parseClassTypes[targetClass].classGraphQLScalarType
|
parseClassTypes[targetClass].classGraphQLPointerType
|
||||||
) {
|
) {
|
||||||
return parseClassTypes[targetClass].classGraphQLScalarType;
|
return parseClassTypes[targetClass].classGraphQLPointerType;
|
||||||
} else {
|
} else {
|
||||||
return defaultGraphQLTypes.OBJECT;
|
return defaultGraphQLTypes.OBJECT;
|
||||||
}
|
}
|
||||||
case 'Relation':
|
case 'Relation':
|
||||||
if (
|
if (
|
||||||
parseClassTypes[targetClass] &&
|
parseClassTypes[targetClass] &&
|
||||||
parseClassTypes[targetClass].classGraphQLRelationOpType
|
parseClassTypes[targetClass].classGraphQLRelationType
|
||||||
) {
|
) {
|
||||||
return parseClassTypes[targetClass].classGraphQLRelationOpType;
|
return parseClassTypes[targetClass].classGraphQLRelationType;
|
||||||
} else {
|
} else {
|
||||||
return defaultGraphQLTypes.OBJECT;
|
return defaultGraphQLTypes.OBJECT;
|
||||||
}
|
}
|
||||||
@@ -259,6 +262,11 @@ const load = (
|
|||||||
classSortFields,
|
classSortFields,
|
||||||
} = getInputFieldsAndConstraints(parseClass, parseClassConfig);
|
} = getInputFieldsAndConstraints(parseClass, parseClassConfig);
|
||||||
|
|
||||||
|
const {
|
||||||
|
create: isCreateEnabled = true,
|
||||||
|
update: isUpdateEnabled = true,
|
||||||
|
} = getParseClassMutationConfig(parseClassConfig);
|
||||||
|
|
||||||
const classGraphQLScalarTypeName = `${graphQLClassName}Pointer`;
|
const classGraphQLScalarTypeName = `${graphQLClassName}Pointer`;
|
||||||
const parseScalarValue = value => {
|
const parseScalarValue = value => {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
@@ -339,31 +347,6 @@ const load = (
|
|||||||
parseGraphQLSchema.addGraphQLType(classGraphQLScalarType) ||
|
parseGraphQLSchema.addGraphQLType(classGraphQLScalarType) ||
|
||||||
defaultGraphQLTypes.OBJECT;
|
defaultGraphQLTypes.OBJECT;
|
||||||
|
|
||||||
const classGraphQLRelationOpTypeName = `${graphQLClassName}RelationOpInput`;
|
|
||||||
let classGraphQLRelationOpType = new GraphQLInputObjectType({
|
|
||||||
name: classGraphQLRelationOpTypeName,
|
|
||||||
description: `The ${classGraphQLRelationOpTypeName} type is used in operations that involve relations with the ${graphQLClassName} class.`,
|
|
||||||
fields: () => ({
|
|
||||||
_op: {
|
|
||||||
description: 'This is the operation to be executed.',
|
|
||||||
type: new GraphQLNonNull(defaultGraphQLTypes.RELATION_OP),
|
|
||||||
},
|
|
||||||
ops: {
|
|
||||||
description:
|
|
||||||
'In the case of a Batch operation, this is the list of operations to be executed.',
|
|
||||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLRelationOpType)),
|
|
||||||
},
|
|
||||||
objects: {
|
|
||||||
description:
|
|
||||||
'In the case of a AddRelation or RemoveRelation operation, this is the list of objects to be added/removed.',
|
|
||||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLScalarType)),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
classGraphQLRelationOpType =
|
|
||||||
parseGraphQLSchema.addGraphQLType(classGraphQLRelationOpType) ||
|
|
||||||
defaultGraphQLTypes.OBJECT;
|
|
||||||
|
|
||||||
const classGraphQLCreateTypeName = `Create${graphQLClassName}FieldsInput`;
|
const classGraphQLCreateTypeName = `Create${graphQLClassName}FieldsInput`;
|
||||||
let classGraphQLCreateType = new GraphQLInputObjectType({
|
let classGraphQLCreateType = new GraphQLInputObjectType({
|
||||||
name: classGraphQLCreateTypeName,
|
name: classGraphQLCreateTypeName,
|
||||||
@@ -430,6 +413,62 @@ const load = (
|
|||||||
classGraphQLUpdateType
|
classGraphQLUpdateType
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const classGraphQLPointerTypeName = `${graphQLClassName}PointerInput`;
|
||||||
|
let classGraphQLPointerType = new GraphQLInputObjectType({
|
||||||
|
name: classGraphQLPointerTypeName,
|
||||||
|
description: `Allow to link OR add and link an object of the ${graphQLClassName} class.`,
|
||||||
|
fields: () => {
|
||||||
|
const fields = {
|
||||||
|
link: {
|
||||||
|
description: `Link an existing object from ${graphQLClassName} class.`,
|
||||||
|
type: defaultGraphQLTypes.POINTER_INPUT,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (isCreateEnabled) {
|
||||||
|
fields['createAndLink'] = {
|
||||||
|
description: `Create and link an object from ${graphQLClassName} class.`,
|
||||||
|
type: classGraphQLCreateType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
classGraphQLPointerType =
|
||||||
|
parseGraphQLSchema.addGraphQLType(classGraphQLPointerType) ||
|
||||||
|
defaultGraphQLTypes.OBJECT;
|
||||||
|
|
||||||
|
const classGraphQLRelationTypeName = `${graphQLClassName}RelationInput`;
|
||||||
|
let classGraphQLRelationType = new GraphQLInputObjectType({
|
||||||
|
name: classGraphQLRelationTypeName,
|
||||||
|
description: `Allow to add, remove, createAndAdd objects of the ${graphQLClassName} class into a relation field.`,
|
||||||
|
fields: () => {
|
||||||
|
const fields = {
|
||||||
|
add: {
|
||||||
|
description: `Add an existing object from the ${graphQLClassName} class into the relation.`,
|
||||||
|
type: new GraphQLList(
|
||||||
|
new GraphQLNonNull(defaultGraphQLTypes.RELATION_INPUT)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
description: `Remove an existing object from the ${graphQLClassName} class out of the relation.`,
|
||||||
|
type: new GraphQLList(
|
||||||
|
new GraphQLNonNull(defaultGraphQLTypes.RELATION_INPUT)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (isCreateEnabled) {
|
||||||
|
fields['createAndAdd'] = {
|
||||||
|
description: `Create and add an object of the ${graphQLClassName} class into the relation.`,
|
||||||
|
type: new GraphQLList(new GraphQLNonNull(classGraphQLCreateType)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
classGraphQLRelationType =
|
||||||
|
parseGraphQLSchema.addGraphQLType(classGraphQLRelationType) ||
|
||||||
|
defaultGraphQLTypes.OBJECT;
|
||||||
|
|
||||||
const classGraphQLConstraintTypeName = `${graphQLClassName}PointerWhereInput`;
|
const classGraphQLConstraintTypeName = `${graphQLClassName}PointerWhereInput`;
|
||||||
let classGraphQLConstraintType = new GraphQLInputObjectType({
|
let classGraphQLConstraintType = new GraphQLInputObjectType({
|
||||||
name: classGraphQLConstraintTypeName,
|
name: classGraphQLConstraintTypeName,
|
||||||
@@ -700,8 +739,9 @@ const load = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
parseGraphQLSchema.parseClassTypes[className] = {
|
parseGraphQLSchema.parseClassTypes[className] = {
|
||||||
|
classGraphQLPointerType,
|
||||||
|
classGraphQLRelationType,
|
||||||
classGraphQLScalarType,
|
classGraphQLScalarType,
|
||||||
classGraphQLRelationOpType,
|
|
||||||
classGraphQLCreateType,
|
classGraphQLCreateType,
|
||||||
classGraphQLUpdateType,
|
classGraphQLUpdateType,
|
||||||
classGraphQLConstraintType,
|
classGraphQLConstraintType,
|
||||||
@@ -709,6 +749,11 @@ const load = (
|
|||||||
classGraphQLFindArgs,
|
classGraphQLFindArgs,
|
||||||
classGraphQLOutputType,
|
classGraphQLOutputType,
|
||||||
classGraphQLFindResultType,
|
classGraphQLFindResultType,
|
||||||
|
config: {
|
||||||
|
parseClassConfig,
|
||||||
|
isCreateEnabled,
|
||||||
|
isUpdateEnabled,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (className === '_User') {
|
if (className === '_User') {
|
||||||
|
|||||||
@@ -39,3 +39,7 @@ export const extractKeysAndInclude = selectedFields => {
|
|||||||
}
|
}
|
||||||
return { keys, include };
|
return { keys, include };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getParseClassMutationConfig = function(parseClassConfig) {
|
||||||
|
return (parseClassConfig && parseClassConfig.mutation) || {};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,21 +1,184 @@
|
|||||||
const parseMap = {
|
import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes';
|
||||||
_op: '__op',
|
import * as objectsMutations from '../loaders/objectsMutations';
|
||||||
};
|
|
||||||
|
|
||||||
const transformMutationInputToParse = fields => {
|
const transformTypes = async (
|
||||||
if (!fields || typeof fields !== 'object') {
|
inputType: 'create' | 'update',
|
||||||
return;
|
fields,
|
||||||
|
{ className, parseGraphQLSchema, req }
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
classGraphQLCreateType,
|
||||||
|
classGraphQLUpdateType,
|
||||||
|
config: { isCreateEnabled, isUpdateEnabled },
|
||||||
|
} = parseGraphQLSchema.parseClassTypes[className];
|
||||||
|
const parseClass = parseGraphQLSchema.parseClasses.find(
|
||||||
|
clazz => clazz.className === className
|
||||||
|
);
|
||||||
|
if (fields) {
|
||||||
|
const classGraphQLCreateTypeFields =
|
||||||
|
isCreateEnabled && classGraphQLCreateType
|
||||||
|
? classGraphQLCreateType.getFields()
|
||||||
|
: null;
|
||||||
|
const classGraphQLUpdateTypeFields =
|
||||||
|
isUpdateEnabled && classGraphQLUpdateType
|
||||||
|
? classGraphQLUpdateType.getFields()
|
||||||
|
: null;
|
||||||
|
const promises = Object.keys(fields).map(async field => {
|
||||||
|
let inputTypeField;
|
||||||
|
if (inputType === 'create' && classGraphQLCreateTypeFields) {
|
||||||
|
inputTypeField = classGraphQLCreateTypeFields[field];
|
||||||
|
} else if (classGraphQLUpdateTypeFields) {
|
||||||
|
inputTypeField = classGraphQLUpdateTypeFields[field];
|
||||||
|
}
|
||||||
|
if (inputTypeField) {
|
||||||
|
switch (true) {
|
||||||
|
case inputTypeField.type === defaultGraphQLTypes.GEO_POINT_INPUT:
|
||||||
|
fields[field] = transformers.geoPoint(fields[field]);
|
||||||
|
break;
|
||||||
|
case inputTypeField.type === defaultGraphQLTypes.POLYGON_INPUT:
|
||||||
|
fields[field] = transformers.polygon(fields[field]);
|
||||||
|
break;
|
||||||
|
case parseClass.fields[field].type === 'Relation':
|
||||||
|
fields[field] = await transformers.relation(
|
||||||
|
parseClass.fields[field].targetClass,
|
||||||
|
field,
|
||||||
|
fields[field],
|
||||||
|
parseGraphQLSchema,
|
||||||
|
req
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case parseClass.fields[field].type === 'Pointer':
|
||||||
|
fields[field] = await transformers.pointer(
|
||||||
|
parseClass.fields[field].targetClass,
|
||||||
|
field,
|
||||||
|
fields[field],
|
||||||
|
parseGraphQLSchema,
|
||||||
|
req
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
Object.keys(fields).forEach(fieldName => {
|
return fields;
|
||||||
const fieldValue = fields[fieldName];
|
|
||||||
if (parseMap[fieldName]) {
|
|
||||||
delete fields[fieldName];
|
|
||||||
fields[parseMap[fieldName]] = fieldValue;
|
|
||||||
}
|
|
||||||
if (typeof fieldValue === 'object') {
|
|
||||||
transformMutationInputToParse(fieldValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { transformMutationInputToParse };
|
const transformers = {
|
||||||
|
polygon: value => ({
|
||||||
|
__type: 'Polygon',
|
||||||
|
coordinates: value.map(geoPoint => [geoPoint.latitude, geoPoint.longitude]),
|
||||||
|
}),
|
||||||
|
geoPoint: value => ({
|
||||||
|
...value,
|
||||||
|
__type: 'GeoPoint',
|
||||||
|
}),
|
||||||
|
relation: async (
|
||||||
|
targetClass,
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
parseGraphQLSchema,
|
||||||
|
{ config, auth, info }
|
||||||
|
) => {
|
||||||
|
if (Object.keys(value) === 0)
|
||||||
|
throw new Error(
|
||||||
|
`You need to provide atleast one operation on the relation mutation of field ${field}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const op = {
|
||||||
|
__op: 'Batch',
|
||||||
|
ops: [],
|
||||||
|
};
|
||||||
|
let nestedObjectsToAdd = [];
|
||||||
|
|
||||||
|
if (value.createAndAdd) {
|
||||||
|
nestedObjectsToAdd = (await Promise.all(
|
||||||
|
value.createAndAdd.map(async input => {
|
||||||
|
const parseFields = await transformTypes('create', input, {
|
||||||
|
className: targetClass,
|
||||||
|
parseGraphQLSchema,
|
||||||
|
req: { config, auth, info },
|
||||||
|
});
|
||||||
|
return objectsMutations.createObject(
|
||||||
|
targetClass,
|
||||||
|
parseFields,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
info
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)).map(object => ({
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: targetClass,
|
||||||
|
objectId: object.objectId,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.add || nestedObjectsToAdd.length > 0) {
|
||||||
|
if (!value.add) value.add = [];
|
||||||
|
value.add = value.add.map(input => ({
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: targetClass,
|
||||||
|
objectId: input.objectId,
|
||||||
|
}));
|
||||||
|
op.ops.push({
|
||||||
|
__op: 'AddRelation',
|
||||||
|
objects: [...value.add, ...nestedObjectsToAdd],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.remove) {
|
||||||
|
op.ops.push({
|
||||||
|
__op: 'RemoveRelation',
|
||||||
|
objects: value.remove.map(input => ({
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: targetClass,
|
||||||
|
objectId: input.objectId,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return op;
|
||||||
|
},
|
||||||
|
pointer: async (
|
||||||
|
targetClass,
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
parseGraphQLSchema,
|
||||||
|
{ config, auth, info }
|
||||||
|
) => {
|
||||||
|
if (Object.keys(value) > 1 || Object.keys(value) === 0)
|
||||||
|
throw new Error(
|
||||||
|
`You need to provide link OR createLink on the pointer mutation of field ${field}`
|
||||||
|
);
|
||||||
|
|
||||||
|
let nestedObjectToAdd;
|
||||||
|
if (value.createAndLink) {
|
||||||
|
const parseFields = await transformTypes('create', value.createAndLink, {
|
||||||
|
className: targetClass,
|
||||||
|
parseGraphQLSchema,
|
||||||
|
req: { config, auth, info },
|
||||||
|
});
|
||||||
|
nestedObjectToAdd = await objectsMutations.createObject(
|
||||||
|
targetClass,
|
||||||
|
parseFields,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
info
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: targetClass,
|
||||||
|
objectId: nestedObjectToAdd.objectId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (value.link && value.link.objectId) {
|
||||||
|
return {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: targetClass,
|
||||||
|
objectId: value.link.objectId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { transformTypes };
|
||||||
|
|||||||
Reference in New Issue
Block a user