Return specific Type on specific Mutation (#5893)

* Return specific Type on specific Mutation

* Add Optimization on Mutation

* Optimize SignUp
This commit is contained in:
Antoine Cormouls
2019-08-12 10:38:59 +02:00
committed by Antonio Davi Macedo Coelho de Castro
parent 6f6210387e
commit 9031961e86
5 changed files with 259 additions and 95 deletions

View File

@@ -1004,7 +1004,9 @@ describe('ParseGraphQLServer', () => {
query: gql` query: gql`
mutation DeleteCustomer($objectId: ID!) { mutation DeleteCustomer($objectId: ID!) {
objects { objects {
deleteCustomer(objectId: $objectId) deleteCustomer(objectId: $objectId) {
objectId
}
} }
} }
`, `,
@@ -1094,7 +1096,9 @@ describe('ParseGraphQLServer', () => {
query: gql` query: gql`
mutation DeleteSuperCar($objectId: ID!) { mutation DeleteSuperCar($objectId: ID!) {
objects { objects {
deleteSuperCar(objectId: $objectId) deleteSuperCar(objectId: $objectId) {
objectId
}
} }
} }
`, `,
@@ -3150,7 +3154,7 @@ describe('ParseGraphQLServer', () => {
expect(obj.get('someField')).toEqual('someValue'); expect(obj.get('someField')).toEqual('someValue');
}); });
it('should return CreateResult object using class specific mutation', async () => { it('should return specific type object using class specific mutation', async () => {
const customerSchema = new Parse.Schema('Customer'); const customerSchema = new Parse.Schema('Customer');
customerSchema.addString('someField'); customerSchema.addString('someField');
await customerSchema.save(); await customerSchema.save();
@@ -3164,6 +3168,7 @@ describe('ParseGraphQLServer', () => {
createCustomer(fields: $fields) { createCustomer(fields: $fields) {
objectId objectId
createdAt createdAt
someField
} }
} }
} }
@@ -3176,6 +3181,9 @@ describe('ParseGraphQLServer', () => {
}); });
expect(result.data.objects.createCustomer.objectId).toBeDefined(); expect(result.data.objects.createCustomer.objectId).toBeDefined();
expect(result.data.objects.createCustomer.someField).toEqual(
'someValue'
);
const customer = await new Parse.Query('Customer').get( const customer = await new Parse.Query('Customer').get(
result.data.objects.createCustomer.objectId result.data.objects.createCustomer.objectId
@@ -3313,7 +3321,7 @@ describe('ParseGraphQLServer', () => {
expect(obj.get('someField2')).toEqual('someField2Value1'); expect(obj.get('someField2')).toEqual('someField2Value1');
}); });
it('should return UpdateResult object using class specific mutation', async () => { it('should return specific type object using class specific mutation', async () => {
const obj = new Parse.Object('Customer'); const obj = new Parse.Object('Customer');
obj.set('someField1', 'someField1Value1'); obj.set('someField1', 'someField1Value1');
obj.set('someField2', 'someField2Value1'); obj.set('someField2', 'someField2Value1');
@@ -3330,6 +3338,8 @@ describe('ParseGraphQLServer', () => {
objects { objects {
updateCustomer(objectId: $objectId, fields: $fields) { updateCustomer(objectId: $objectId, fields: $fields) {
updatedAt updatedAt
someField1
someField2
} }
} }
} }
@@ -3343,6 +3353,12 @@ describe('ParseGraphQLServer', () => {
}); });
expect(result.data.objects.updateCustomer.updatedAt).toBeDefined(); expect(result.data.objects.updateCustomer.updatedAt).toBeDefined();
expect(result.data.objects.updateCustomer.someField1).toEqual(
'someField1Value2'
);
expect(result.data.objects.updateCustomer.someField2).toEqual(
'someField2Value1'
);
await obj.fetch(); await obj.fetch();
@@ -3737,8 +3753,10 @@ describe('ParseGraphQLServer', () => {
).toBeRejectedWith(jasmine.stringMatching('Object not found')); ).toBeRejectedWith(jasmine.stringMatching('Object not found'));
}); });
it('should return a boolean confirmation using class specific mutation', async () => { it('should return a specific type using class specific mutation', async () => {
const obj = new Parse.Object('Customer'); const obj = new Parse.Object('Customer');
obj.set('someField1', 'someField1Value1');
obj.set('someField2', 'someField2Value1');
await obj.save(); await obj.save();
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
@@ -3747,7 +3765,11 @@ describe('ParseGraphQLServer', () => {
mutation: gql` mutation: gql`
mutation DeleteCustomer($objectId: ID!) { mutation DeleteCustomer($objectId: ID!) {
objects { objects {
deleteCustomer(objectId: $objectId) deleteCustomer(objectId: $objectId) {
objectId
someField1
someField2
}
} }
} }
`, `,
@@ -3756,7 +3778,13 @@ describe('ParseGraphQLServer', () => {
}, },
}); });
expect(result.data.objects.deleteCustomer).toEqual(true); expect(result.data.objects.deleteCustomer.objectId).toEqual(obj.id);
expect(result.data.objects.deleteCustomer.someField1).toEqual(
'someField1Value1'
);
expect(result.data.objects.deleteCustomer.someField2).toEqual(
'someField2Value1'
);
await expectAsync( await expectAsync(
obj.fetch({ useMasterKey: true }) obj.fetch({ useMasterKey: true })
@@ -3855,7 +3883,9 @@ describe('ParseGraphQLServer', () => {
$objectId: ID! $objectId: ID!
) { ) {
objects { objects {
delete${className}(objectId: $objectId) delete${className}(objectId: $objectId) {
objectId
}
} }
} }
`, `,
@@ -3893,32 +3923,32 @@ describe('ParseGraphQLServer', () => {
expect( expect(
(await deleteObject(object4.className, object4.id)).data.objects[ (await deleteObject(object4.className, object4.id)).data.objects[
`delete${object4.className}` `delete${object4.className}`
] ].objectId
).toEqual(true); ).toEqual(object4.id);
await expectAsync( await expectAsync(
object4.fetch({ useMasterKey: true }) object4.fetch({ useMasterKey: true })
).toBeRejectedWith(jasmine.stringMatching('Object not found')); ).toBeRejectedWith(jasmine.stringMatching('Object not found'));
expect( expect(
(await deleteObject(object1.className, object1.id, { (await deleteObject(object1.className, object1.id, {
'X-Parse-Master-Key': 'test', 'X-Parse-Master-Key': 'test',
})).data.objects[`delete${object1.className}`] })).data.objects[`delete${object1.className}`].objectId
).toEqual(true); ).toEqual(object1.id);
await expectAsync( await expectAsync(
object1.fetch({ useMasterKey: true }) object1.fetch({ useMasterKey: true })
).toBeRejectedWith(jasmine.stringMatching('Object not found')); ).toBeRejectedWith(jasmine.stringMatching('Object not found'));
expect( expect(
(await deleteObject(object2.className, object2.id, { (await deleteObject(object2.className, object2.id, {
'X-Parse-Session-Token': user2.getSessionToken(), 'X-Parse-Session-Token': user2.getSessionToken(),
})).data.objects[`delete${object2.className}`] })).data.objects[`delete${object2.className}`].objectId
).toEqual(true); ).toEqual(object2.id);
await expectAsync( await expectAsync(
object2.fetch({ useMasterKey: true }) object2.fetch({ useMasterKey: true })
).toBeRejectedWith(jasmine.stringMatching('Object not found')); ).toBeRejectedWith(jasmine.stringMatching('Object not found'));
expect( expect(
(await deleteObject(object3.className, object3.id, { (await deleteObject(object3.className, object3.id, {
'X-Parse-Session-Token': user5.getSessionToken(), 'X-Parse-Session-Token': user5.getSessionToken(),
})).data.objects[`delete${object3.className}`] })).data.objects[`delete${object3.className}`].objectId
).toEqual(true); ).toEqual(object3.id);
await expectAsync( await expectAsync(
object3.fetch({ useMasterKey: true }) object3.fetch({ useMasterKey: true })
).toBeRejectedWith(jasmine.stringMatching('Object not found')); ).toBeRejectedWith(jasmine.stringMatching('Object not found'));
@@ -4078,12 +4108,17 @@ describe('ParseGraphQLServer', () => {
describe('Users Mutations', () => { describe('Users Mutations', () => {
it('should sign user up', async () => { it('should sign user up', async () => {
const userSchema = new Parse.Schema('_User');
userSchema.addString('someField');
await userSchema.update();
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
const result = await apolloClient.mutate({ const result = await apolloClient.mutate({
mutation: gql` mutation: gql`
mutation SignUp($fields: _UserSignUpFields) { mutation SignUp($fields: _UserSignUpFields) {
users { users {
signUp(fields: $fields) { signUp(fields: $fields) {
sessionToken sessionToken
someField
} }
} }
} }
@@ -4092,11 +4127,13 @@ describe('ParseGraphQLServer', () => {
fields: { fields: {
username: 'user1', username: 'user1',
password: 'user1', password: 'user1',
someField: 'someValue',
}, },
}, },
}); });
expect(result.data.users.signUp.sessionToken).toBeDefined(); expect(result.data.users.signUp.sessionToken).toBeDefined();
expect(result.data.users.signUp.someField).toEqual('someValue');
expect(typeof result.data.users.signUp.sessionToken).toBe('string'); expect(typeof result.data.users.signUp.sessionToken).toBe('string');
}); });
@@ -4104,26 +4141,31 @@ describe('ParseGraphQLServer', () => {
const user = new Parse.User(); const user = new Parse.User();
user.setUsername('user1'); user.setUsername('user1');
user.setPassword('user1'); user.setPassword('user1');
user.set('someField', 'someValue');
await user.signUp(); await user.signUp();
await Parse.User.logOut(); await Parse.User.logOut();
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
const result = await apolloClient.mutate({ const result = await apolloClient.mutate({
mutation: gql` mutation: gql`
mutation LogInUser($username: String!, $password: String!) { mutation LogInUser($input: _UserLoginFields) {
users { users {
logIn(username: $username, password: $password) { logIn(input: $input) {
sessionToken sessionToken
someField
} }
} }
} }
`, `,
variables: { variables: {
username: 'user1', input: {
password: 'user1', username: 'user1',
password: 'user1',
},
}, },
}); });
expect(result.data.users.logIn.sessionToken).toBeDefined(); expect(result.data.users.logIn.sessionToken).toBeDefined();
expect(result.data.users.logIn.someField).toEqual('someValue');
expect(typeof result.data.users.logIn.sessionToken).toBe('string'); expect(typeof result.data.users.logIn.sessionToken).toBe('string');
}); });
@@ -4136,17 +4178,19 @@ describe('ParseGraphQLServer', () => {
const logIn = await apolloClient.mutate({ const logIn = await apolloClient.mutate({
mutation: gql` mutation: gql`
mutation LogInUser($username: String!, $password: String!) { mutation LogInUser($input: _UserLoginFields) {
users { users {
logIn(username: $username, password: $password) { logIn(input: $input) {
sessionToken sessionToken
} }
} }
} }
`, `,
variables: { variables: {
username: 'user1', input: {
password: 'user1', username: 'user1',
password: 'user1',
},
}, },
}); });

View File

@@ -1,6 +1,9 @@
import { GraphQLNonNull, GraphQLBoolean } from 'graphql'; import { GraphQLNonNull } from 'graphql';
import getFieldNames from 'graphql-list-fields';
import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as parseClassTypes from './parseClassTypes';
import * as objectsMutations from './objectsMutations'; import * as objectsMutations from './objectsMutations';
import * as objectsQueries from './objectsQueries';
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
const getParseClassMutationConfig = function( const getParseClassMutationConfig = function(
@@ -9,6 +12,28 @@ const getParseClassMutationConfig = function(
return (parseClassConfig && parseClassConfig.mutation) || {}; return (parseClassConfig && parseClassConfig.mutation) || {};
}; };
const getOnlyRequiredFields = (
updatedFields,
selectedFieldsString,
includedFieldsString,
nativeObjectFields
) => {
const includedFields = includedFieldsString.split(',');
const selectedFields = selectedFieldsString.split(',');
const missingFields = selectedFields
.filter(
field =>
(!updatedFields[field] && !nativeObjectFields.includes(field)) ||
includedFields.includes(field)
)
.join(',');
if (!missingFields.length) {
return { needGet: false, keys: '' };
} else {
return { needGet: true, keys: missingFields };
}
};
const load = function( const load = function(
parseGraphQLSchema, parseGraphQLSchema,
parseClass, parseClass,
@@ -24,6 +49,7 @@ const load = function(
const { const {
classGraphQLCreateType, classGraphQLCreateType,
classGraphQLUpdateType, classGraphQLUpdateType,
classGraphQLOutputType,
} = parseGraphQLSchema.parseClassTypes[className]; } = parseGraphQLSchema.parseClassTypes[className];
const createFields = { const createFields = {
@@ -78,21 +104,50 @@ const load = function(
args: { args: {
fields: createFields, fields: createFields,
}, },
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT), type: new GraphQLNonNull(classGraphQLOutputType),
async resolve(_source, args, context) { async resolve(_source, args, context, mutationInfo) {
try { try {
const { fields } = args; let { fields } = args;
if (!fields) fields = {};
const { config, auth, info } = context; const { config, auth, info } = context;
transformTypes('create', fields); transformTypes('create', fields);
const createdObject = await objectsMutations.createObject(
return await objectsMutations.createObject(
className, className,
fields, fields,
config, config,
auth, auth,
info info
); );
const selectedFields = getFieldNames(mutationInfo);
const { keys, include } = parseClassTypes.extractKeysAndInclude(
selectedFields
);
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
fields,
keys,
include,
['objectId', 'createdAt', 'updatedAt']
);
let optimizedObject = {};
if (needGet) {
optimizedObject = await objectsQueries.getObject(
className,
createdObject.objectId,
requiredKeys,
include,
undefined,
undefined,
config,
auth,
info
);
}
return {
...createdObject,
updatedAt: createdObject.createdAt,
...fields,
...optimizedObject,
};
} catch (e) { } catch (e) {
parseGraphQLSchema.handleError(e); parseGraphQLSchema.handleError(e);
} }
@@ -108,15 +163,15 @@ const load = function(
objectId: defaultGraphQLTypes.OBJECT_ID_ATT, objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
fields: updateFields, fields: updateFields,
}, },
type: defaultGraphQLTypes.UPDATE_RESULT, type: new GraphQLNonNull(classGraphQLOutputType),
async resolve(_source, args, context) { async resolve(_source, args, context, mutationInfo) {
try { try {
const { objectId, fields } = args; const { objectId, fields } = args;
const { config, auth, info } = context; const { config, auth, info } = context;
transformTypes('update', fields); transformTypes('update', fields);
return await objectsMutations.updateObject( const updatedObject = await objectsMutations.updateObject(
className, className,
objectId, objectId,
fields, fields,
@@ -124,6 +179,32 @@ const load = function(
auth, auth,
info info
); );
const selectedFields = getFieldNames(mutationInfo);
const { keys, include } = parseClassTypes.extractKeysAndInclude(
selectedFields
);
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
fields,
keys,
include,
['objectId', 'updatedAt']
);
let optimizedObject = {};
if (needGet) {
optimizedObject = await objectsQueries.getObject(
className,
objectId,
requiredKeys,
include,
undefined,
undefined,
config,
auth,
info
);
}
return { ...updatedObject, ...fields, ...optimizedObject };
} catch (e) { } catch (e) {
parseGraphQLSchema.handleError(e); parseGraphQLSchema.handleError(e);
} }
@@ -138,19 +219,39 @@ const load = function(
args: { args: {
objectId: defaultGraphQLTypes.OBJECT_ID_ATT, objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
}, },
type: new GraphQLNonNull(GraphQLBoolean), type: new GraphQLNonNull(classGraphQLOutputType),
async resolve(_source, args, context) { async resolve(_source, args, context, mutationInfo) {
try { try {
const { objectId } = args; const { objectId } = args;
const { config, auth, info } = context; const { config, auth, info } = context;
const selectedFields = getFieldNames(mutationInfo);
const { keys, include } = parseClassTypes.extractKeysAndInclude(
selectedFields
);
return await objectsMutations.deleteObject( let optimizedObject = {};
const splitedKeys = keys.split(',');
if (splitedKeys.length > 1 || splitedKeys[0] !== 'objectId') {
optimizedObject = await objectsQueries.getObject(
className,
objectId,
keys,
include,
undefined,
undefined,
config,
auth,
info
);
}
await objectsMutations.deleteObject(
className, className,
objectId, objectId,
config, config,
auth, auth,
info info
); );
return { objectId: objectId, ...optimizedObject };
} catch (e) { } catch (e) {
parseGraphQLSchema.handleError(e); parseGraphQLSchema.handleError(e);
} }

View File

@@ -715,9 +715,28 @@ const load = (
} }
}, {}), }, {}),
}); });
const userLogInInputTypeName = '_UserLoginFields';
const userLogInInputType = new GraphQLInputObjectType({
name: userLogInInputTypeName,
description: `The ${userLogInInputTypeName} input type is used to login.`,
fields: {
username: {
description: 'This is the username used to log the user in.',
type: new GraphQLNonNull(GraphQLString),
},
password: {
description: 'This is the password used to log the user in.',
type: new GraphQLNonNull(GraphQLString),
},
},
});
parseGraphQLSchema.parseClassTypes[ parseGraphQLSchema.parseClassTypes[
'_User' '_User'
].signUpInputType = userSignUpInputType; ].signUpInputType = userSignUpInputType;
parseGraphQLSchema.parseClassTypes[
'_User'
].logInInputType = userLogInInputType;
parseGraphQLSchema.graphQLTypes.push(userSignUpInputType); parseGraphQLSchema.graphQLTypes.push(userSignUpInputType);
} }
}; };

View File

@@ -1,12 +1,7 @@
import { import { GraphQLBoolean, GraphQLNonNull, GraphQLObjectType } from 'graphql';
GraphQLBoolean,
GraphQLNonNull,
GraphQLObjectType,
GraphQLString,
} from 'graphql';
import UsersRouter from '../../Routers/UsersRouter'; import UsersRouter from '../../Routers/UsersRouter';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as objectsMutations from './objectsMutations'; import * as objectsMutations from './objectsMutations';
import { getUserFromSessionToken } from './usersQueries';
const usersRouter = new UsersRouter(); const usersRouter = new UsersRouter();
@@ -24,19 +19,23 @@ const load = parseGraphQLSchema => {
type: parseGraphQLSchema.parseClassTypes['_User'].signUpInputType, type: parseGraphQLSchema.parseClassTypes['_User'].signUpInputType,
}, },
}, },
type: new GraphQLNonNull(defaultGraphQLTypes.SIGN_UP_RESULT), type: new GraphQLNonNull(parseGraphQLSchema.meType),
async resolve(_source, args, context) { async resolve(_source, args, context, mutationInfo) {
try { try {
const { fields } = args; const { fields } = args;
const { config, auth, info } = context; const { config, auth, info } = context;
return await objectsMutations.createObject( const { sessionToken } = await objectsMutations.createObject(
'_User', '_User',
fields, fields,
config, config,
auth, auth,
info info
); );
info.sessionToken = sessionToken;
return await getUserFromSessionToken(config, info, mutationInfo);
} catch (e) { } catch (e) {
parseGraphQLSchema.handleError(e); parseGraphQLSchema.handleError(e);
} }
@@ -46,19 +45,17 @@ const load = parseGraphQLSchema => {
fields.logIn = { fields.logIn = {
description: 'The logIn mutation can be used to log the user in.', description: 'The logIn mutation can be used to log the user in.',
args: { args: {
username: { input: {
description: 'This is the username used to log the user in.', description: 'This is data needed to login',
type: new GraphQLNonNull(GraphQLString), type: parseGraphQLSchema.parseClassTypes['_User'].logInInputType,
},
password: {
description: 'This is the password used to log the user in.',
type: new GraphQLNonNull(GraphQLString),
}, },
}, },
type: new GraphQLNonNull(parseGraphQLSchema.meType), type: new GraphQLNonNull(parseGraphQLSchema.meType),
async resolve(_source, args, context) { async resolve(_source, args, context) {
try { try {
const { username, password } = args; const {
input: { username, password },
} = args;
const { config, auth, info } = context; const { config, auth, info } = context;
return (await usersRouter.handleLogIn({ return (await usersRouter.handleLogIn({

View File

@@ -5,6 +5,46 @@ import rest from '../../rest';
import Auth from '../../Auth'; import Auth from '../../Auth';
import { extractKeysAndInclude } from './parseClassTypes'; import { extractKeysAndInclude } from './parseClassTypes';
const getUserFromSessionToken = async (config, info, queryInfo) => {
if (!info || !info.sessionToken) {
throw new Parse.Error(
Parse.Error.INVALID_SESSION_TOKEN,
'Invalid session token'
);
}
const sessionToken = info.sessionToken;
const selectedFields = getFieldNames(queryInfo);
const { include } = extractKeysAndInclude(selectedFields);
const response = await rest.find(
config,
Auth.master(config),
'_Session',
{ sessionToken },
{
include: include
.split(',')
.map(included => `user.${included}`)
.join(','),
},
info.clientVersion
);
if (
!response.results ||
response.results.length == 0 ||
!response.results[0].user
) {
throw new Parse.Error(
Parse.Error.INVALID_SESSION_TOKEN,
'Invalid session token'
);
} else {
const user = response.results[0].user;
user.sessionToken = sessionToken;
return user;
}
};
const load = parseGraphQLSchema => { const load = parseGraphQLSchema => {
if (parseGraphQLSchema.isUsersClassDisabled) { if (parseGraphQLSchema.isUsersClassDisabled) {
return; return;
@@ -17,44 +57,7 @@ const load = parseGraphQLSchema => {
async resolve(_source, _args, context, queryInfo) { async resolve(_source, _args, context, queryInfo) {
try { try {
const { config, info } = context; const { config, info } = context;
return await getUserFromSessionToken(config, info, queryInfo);
if (!info || !info.sessionToken) {
throw new Parse.Error(
Parse.Error.INVALID_SESSION_TOKEN,
'Invalid session token'
);
}
const sessionToken = info.sessionToken;
const selectedFields = getFieldNames(queryInfo);
const { include } = extractKeysAndInclude(selectedFields);
const response = await rest.find(
config,
Auth.master(config),
'_Session',
{ sessionToken },
{
include: include
.split(',')
.map(included => `user.${included}`)
.join(','),
},
info.clientVersion
);
if (
!response.results ||
response.results.length == 0 ||
!response.results[0].user
) {
throw new Parse.Error(
Parse.Error.INVALID_SESSION_TOKEN,
'Invalid session token'
);
} else {
const user = response.results[0].user;
user.sessionToken = sessionToken;
return user;
}
} catch (e) { } catch (e) {
parseGraphQLSchema.handleError(e); parseGraphQLSchema.handleError(e);
} }
@@ -75,4 +78,4 @@ const load = parseGraphQLSchema => {
}; };
}; };
export { load }; export { load, getUserFromSessionToken };