Relay Spec (#6089)
* Install graphql-relay * Add relayNodeInterface to ParseGraphQLSchema * Add support to global id * Add support to global id in other operations * Fix sort by glboal id * Fix where by global id * Introduce IdWhereInput * Add Relay object identification tests * Client mutation id on createFile mutation * Client mutation id on callCloudCode mutation * Client mutation id on signUp mutation * Client mutation id on logIn mutation * Client mutation id on logOut mutation * Client mutation id on createClass mutation * Client mutation id on updateClass mutation * Client mutation id on deleteClass mutation * Client mutation id on create object mutation * Improve Viewer type * Client mutation id on update object mutation * Client mutation id on delete object mutation * Introducing connections * Fix tests * Add pagination test * Fix file location * Fix postgres tests * Add comments * Tests to calculateSkipAndLimit
This commit is contained in:
committed by
GitHub
parent
67e3c33ffe
commit
a9066e20dc
@@ -588,10 +588,15 @@ const CLASS_NAME_ATT = {
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
};
|
||||
|
||||
const GLOBAL_OR_OBJECT_ID_ATT = {
|
||||
description:
|
||||
'This is the object id. You can use either the global or the object id.',
|
||||
type: OBJECT_ID,
|
||||
};
|
||||
|
||||
const OBJECT_ID_ATT = {
|
||||
description: 'This is the object id.',
|
||||
type: OBJECT_ID,
|
||||
resolve: ({ objectId }) => objectId,
|
||||
};
|
||||
|
||||
const CREATED_AT_ATT = {
|
||||
@@ -611,7 +616,7 @@ const INPUT_FIELDS = {
|
||||
};
|
||||
|
||||
const CREATE_RESULT_FIELDS = {
|
||||
id: OBJECT_ID_ATT,
|
||||
objectId: OBJECT_ID_ATT,
|
||||
createdAt: CREATED_AT_ATT,
|
||||
};
|
||||
|
||||
@@ -637,7 +642,7 @@ const PARSE_OBJECT = new GraphQLInterfaceType({
|
||||
});
|
||||
|
||||
const SESSION_TOKEN_ATT = {
|
||||
description: 'The user session token',
|
||||
description: 'The current user session token.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
};
|
||||
|
||||
@@ -926,6 +931,25 @@ const options = {
|
||||
type: GraphQLString,
|
||||
};
|
||||
|
||||
const ID_WHERE_INPUT = new GraphQLInputObjectType({
|
||||
name: 'IdWhereInput',
|
||||
description:
|
||||
'The IdWhereInput input type is used in operations that involve filtering objects by an id.',
|
||||
fields: {
|
||||
equalTo: equalTo(GraphQLID),
|
||||
notEqualTo: notEqualTo(GraphQLID),
|
||||
lessThan: lessThan(GraphQLID),
|
||||
lessThanOrEqualTo: lessThanOrEqualTo(GraphQLID),
|
||||
greaterThan: greaterThan(GraphQLID),
|
||||
greaterThanOrEqualTo: greaterThanOrEqualTo(GraphQLID),
|
||||
in: inOp(GraphQLID),
|
||||
notIn: notIn(GraphQLID),
|
||||
exists,
|
||||
inQueryKey,
|
||||
notInQueryKey,
|
||||
},
|
||||
});
|
||||
|
||||
const STRING_WHERE_INPUT = new GraphQLInputObjectType({
|
||||
name: 'StringWhereInput',
|
||||
description:
|
||||
@@ -1164,19 +1188,6 @@ const POLYGON_WHERE_INPUT = new GraphQLInputObjectType({
|
||||
},
|
||||
});
|
||||
|
||||
const FIND_RESULT = new GraphQLObjectType({
|
||||
name: 'FindResult',
|
||||
description:
|
||||
'The FindResult object type is used in the find queries to return the data of the matched objects.',
|
||||
fields: {
|
||||
results: {
|
||||
description: 'This is the objects returned by the query',
|
||||
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(OBJECT))),
|
||||
},
|
||||
count: COUNT_ATT,
|
||||
},
|
||||
});
|
||||
|
||||
const ELEMENT = new GraphQLObjectType({
|
||||
name: 'Element',
|
||||
description: "The Element object type is used to return array items' value.",
|
||||
@@ -1247,6 +1258,7 @@ const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.addGraphQLType(CENTER_SPHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(GEO_WITHIN_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(GEO_INTERSECTS_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(ID_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(STRING_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(NUMBER_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(BOOLEAN_WHERE_INPUT, true);
|
||||
@@ -1258,9 +1270,7 @@ const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.addGraphQLType(FILE_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(GEO_POINT_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(POLYGON_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(FIND_RESULT, true);
|
||||
parseGraphQLSchema.addGraphQLType(ELEMENT, true);
|
||||
parseGraphQLSchema.addGraphQLType(OBJECT_ID, true);
|
||||
parseGraphQLSchema.addGraphQLType(ACL_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(USER_ACL_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(ROLE_ACL_INPUT, true);
|
||||
@@ -1296,6 +1306,7 @@ export {
|
||||
POLYGON,
|
||||
OBJECT_ID,
|
||||
CLASS_NAME_ATT,
|
||||
GLOBAL_OR_OBJECT_ID_ATT,
|
||||
OBJECT_ID_ATT,
|
||||
UPDATED_AT_ATT,
|
||||
CREATED_AT_ATT,
|
||||
@@ -1337,6 +1348,7 @@ export {
|
||||
notInQueryKey,
|
||||
matchesRegex,
|
||||
options,
|
||||
ID_WHERE_INPUT,
|
||||
STRING_WHERE_INPUT,
|
||||
NUMBER_WHERE_INPUT,
|
||||
BOOLEAN_WHERE_INPUT,
|
||||
@@ -1348,7 +1360,6 @@ export {
|
||||
FILE_WHERE_INPUT,
|
||||
GEO_POINT_WHERE_INPUT,
|
||||
POLYGON_WHERE_INPUT,
|
||||
FIND_RESULT,
|
||||
ARRAY_RESULT,
|
||||
ELEMENT,
|
||||
ACL_INPUT,
|
||||
|
||||
51
src/GraphQL/loaders/defaultRelaySchema.js
Normal file
51
src/GraphQL/loaders/defaultRelaySchema.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { nodeDefinitions, fromGlobalId } from 'graphql-relay';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from '../helpers/objectsQueries';
|
||||
import { extractKeysAndInclude } from './parseClassTypes';
|
||||
|
||||
const GLOBAL_ID_ATT = {
|
||||
description: 'This is the global id.',
|
||||
type: defaultGraphQLTypes.OBJECT_ID,
|
||||
};
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
const { nodeInterface, nodeField } = nodeDefinitions(
|
||||
async (globalId, context, queryInfo) => {
|
||||
try {
|
||||
const { type, id } = fromGlobalId(globalId);
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
return {
|
||||
className: type,
|
||||
...(await objectsQueries.getObject(
|
||||
type,
|
||||
id,
|
||||
keys,
|
||||
include,
|
||||
undefined,
|
||||
undefined,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
)),
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
obj => {
|
||||
return parseGraphQLSchema.parseClassTypes[obj.className]
|
||||
.classGraphQLOutputType;
|
||||
}
|
||||
);
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(nodeInterface, true);
|
||||
parseGraphQLSchema.relayNodeInterface = nodeInterface;
|
||||
parseGraphQLSchema.addGraphQLQuery('node', nodeField, true);
|
||||
};
|
||||
|
||||
export { GLOBAL_ID_ATT, load };
|
||||
@@ -1,80 +1,97 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import { GraphQLUpload } from 'graphql-upload';
|
||||
import Parse from 'parse/node';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import logger from '../../logger';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'createFile',
|
||||
{
|
||||
description:
|
||||
'The create mutation can be used to create and upload a new file.',
|
||||
args: {
|
||||
upload: {
|
||||
description: 'This is the new file to be created and uploaded',
|
||||
type: new GraphQLNonNull(GraphQLUpload),
|
||||
},
|
||||
const createMutation = mutationWithClientMutationId({
|
||||
name: 'CreateFile',
|
||||
description:
|
||||
'The createFile mutation can be used to create and upload a new file.',
|
||||
inputFields: {
|
||||
upload: {
|
||||
description: 'This is the new file to be created and uploaded.',
|
||||
type: new GraphQLNonNull(GraphQLUpload),
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
|
||||
async resolve(_source, args, context) {
|
||||
},
|
||||
outputFields: {
|
||||
fileInfo: {
|
||||
description: 'This is the created file info.',
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context) => {
|
||||
try {
|
||||
const { upload } = args;
|
||||
const { config } = context;
|
||||
|
||||
const { createReadStream, filename, mimetype } = await upload;
|
||||
let data = null;
|
||||
if (createReadStream) {
|
||||
const stream = createReadStream();
|
||||
data = await new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
stream
|
||||
.on('error', reject)
|
||||
.on('data', chunk => chunks.push(chunk))
|
||||
.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
}
|
||||
|
||||
if (!data || !data.length) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
'Invalid file upload.'
|
||||
);
|
||||
}
|
||||
|
||||
if (filename.length > 128) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_FILE_NAME,
|
||||
'Filename too long.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_FILE_NAME,
|
||||
'Filename contains invalid characters.'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { upload } = args;
|
||||
const { config } = context;
|
||||
|
||||
const { createReadStream, filename, mimetype } = await upload;
|
||||
let data = null;
|
||||
if (createReadStream) {
|
||||
const stream = createReadStream();
|
||||
data = await new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
stream
|
||||
.on('error', reject)
|
||||
.on('data', chunk => chunks.push(chunk))
|
||||
.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
}
|
||||
|
||||
if (!data || !data.length) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
'Invalid file upload.'
|
||||
);
|
||||
}
|
||||
|
||||
if (filename.length > 128) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_FILE_NAME,
|
||||
'Filename too long.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_FILE_NAME,
|
||||
'Filename contains invalid characters.'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return await config.filesController.createFile(
|
||||
return {
|
||||
fileInfo: await config.filesController.createFile(
|
||||
config,
|
||||
filename,
|
||||
data,
|
||||
mimetype
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error('Error creating a file: ', e);
|
||||
throw new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
`Could not store file: ${filename}.`
|
||||
);
|
||||
}
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
logger.error('Error creating a file: ', e);
|
||||
throw new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
`Could not store file: ${filename}.`
|
||||
);
|
||||
}
|
||||
},
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
createMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(createMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'createFile',
|
||||
createMutation,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLNonNull, GraphQLEnumType } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import { FunctionsRouter } from '../../Routers/FunctionsRouter';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
|
||||
@@ -21,28 +22,34 @@ const load = parseGraphQLSchema => {
|
||||
true
|
||||
);
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'callCloudCode',
|
||||
{
|
||||
description:
|
||||
'The call mutation can be used to invoke a cloud code function.',
|
||||
args: {
|
||||
functionName: {
|
||||
description: 'This is the function to be called.',
|
||||
type: new GraphQLNonNull(cloudCodeFunctionEnum),
|
||||
},
|
||||
params: {
|
||||
description: 'These are the params to be passed to the function.',
|
||||
type: defaultGraphQLTypes.OBJECT,
|
||||
},
|
||||
const callCloudCodeMutation = mutationWithClientMutationId({
|
||||
name: 'CallCloudCode',
|
||||
description:
|
||||
'The callCloudCode mutation can be used to invoke a cloud code function.',
|
||||
inputFields: {
|
||||
functionName: {
|
||||
description: 'This is the function to be called.',
|
||||
type: new GraphQLNonNull(cloudCodeFunctionEnum),
|
||||
},
|
||||
type: defaultGraphQLTypes.ANY,
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { functionName, params } = args;
|
||||
const { config, auth, info } = context;
|
||||
params: {
|
||||
description: 'These are the params to be passed to the function.',
|
||||
type: defaultGraphQLTypes.OBJECT,
|
||||
},
|
||||
},
|
||||
outputFields: {
|
||||
result: {
|
||||
description:
|
||||
'This is the result value of the cloud code function execution.',
|
||||
type: defaultGraphQLTypes.ANY,
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context) => {
|
||||
try {
|
||||
const { functionName, params } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return (await FunctionsRouter.handleCloudFunction({
|
||||
return {
|
||||
result: (await FunctionsRouter.handleCloudFunction({
|
||||
params: {
|
||||
functionName,
|
||||
},
|
||||
@@ -50,12 +57,23 @@ const load = parseGraphQLSchema => {
|
||||
auth,
|
||||
info,
|
||||
body: params,
|
||||
})).response.result;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
})).response.result,
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
callCloudCodeMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(callCloudCodeMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'callCloudCode',
|
||||
callCloudCodeMutation,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import {
|
||||
@@ -17,13 +18,16 @@ const getOnlyRequiredFields = (
|
||||
includedFieldsString,
|
||||
nativeObjectFields
|
||||
) => {
|
||||
const includedFields = includedFieldsString.split(',');
|
||||
const selectedFields = selectedFieldsString.split(',');
|
||||
const includedFields = includedFieldsString
|
||||
? includedFieldsString.split(',')
|
||||
: [];
|
||||
const selectedFields = selectedFieldsString
|
||||
? selectedFieldsString.split(',')
|
||||
: [];
|
||||
const missingFields = selectedFields
|
||||
.filter(
|
||||
field =>
|
||||
!nativeObjectFields.includes(field) ||
|
||||
includedFields.includes(field)
|
||||
!nativeObjectFields.includes(field) || includedFields.includes(field)
|
||||
)
|
||||
.join(',');
|
||||
if (!missingFields.length) {
|
||||
@@ -40,6 +44,8 @@ const load = function(
|
||||
) {
|
||||
const className = parseClass.className;
|
||||
const graphQLClassName = transformClassNameToGraphQL(className);
|
||||
const getGraphQLQueryName =
|
||||
graphQLClassName.charAt(0).toLowerCase() + graphQLClassName.slice(1);
|
||||
|
||||
const {
|
||||
create: isCreateEnabled = true,
|
||||
@@ -55,18 +61,25 @@ const load = function(
|
||||
|
||||
if (isCreateEnabled) {
|
||||
const createGraphQLMutationName = `create${graphQLClassName}`;
|
||||
parseGraphQLSchema.addGraphQLMutation(createGraphQLMutationName, {
|
||||
const createGraphQLMutation = mutationWithClientMutationId({
|
||||
name: `Create${graphQLClassName}`,
|
||||
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${graphQLClassName} class.`,
|
||||
args: {
|
||||
inputFields: {
|
||||
fields: {
|
||||
description: 'These are the fields used to create the object.',
|
||||
description:
|
||||
'These are the fields that will be used to create the new object.',
|
||||
type: classGraphQLCreateType || defaultGraphQLTypes.OBJECT,
|
||||
},
|
||||
},
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, mutationInfo) {
|
||||
outputFields: {
|
||||
[getGraphQLQueryName]: {
|
||||
description: 'This is the created object.',
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
let { fields } = args;
|
||||
if (!fields) fields = {};
|
||||
@@ -85,13 +98,15 @@ const load = function(
|
||||
auth,
|
||||
info
|
||||
);
|
||||
const selectedFields = getFieldNames(mutationInfo);
|
||||
const selectedFields = getFieldNames(mutationInfo)
|
||||
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
|
||||
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||
fields,
|
||||
keys,
|
||||
include,
|
||||
['id', 'createdAt', 'updatedAt']
|
||||
['id', 'objectId', 'createdAt', 'updatedAt']
|
||||
);
|
||||
let optimizedObject = {};
|
||||
if (needGet) {
|
||||
@@ -108,37 +123,65 @@ const load = function(
|
||||
);
|
||||
}
|
||||
return {
|
||||
...createdObject,
|
||||
updatedAt: createdObject.createdAt,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
[getGraphQLQueryName]: {
|
||||
...createdObject,
|
||||
updatedAt: createdObject.createdAt,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
createGraphQLMutation.args.input.type.ofType
|
||||
) &&
|
||||
parseGraphQLSchema.addGraphQLType(createGraphQLMutation.type)
|
||||
) {
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
createGraphQLMutationName,
|
||||
createGraphQLMutation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isUpdateEnabled) {
|
||||
const updateGraphQLMutationName = `update${graphQLClassName}`;
|
||||
parseGraphQLSchema.addGraphQLMutation(updateGraphQLMutationName, {
|
||||
const updateGraphQLMutation = mutationWithClientMutationId({
|
||||
name: `Update${graphQLClassName}`,
|
||||
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${graphQLClassName} class.`,
|
||||
args: {
|
||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
inputFields: {
|
||||
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||
fields: {
|
||||
description: 'These are the fields used to update the object.',
|
||||
description:
|
||||
'These are the fields that will be used to update the object.',
|
||||
type: classGraphQLUpdateType || defaultGraphQLTypes.OBJECT,
|
||||
},
|
||||
},
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, mutationInfo) {
|
||||
outputFields: {
|
||||
[getGraphQLQueryName]: {
|
||||
description: 'This is the updated object.',
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
const { id, fields } = args;
|
||||
let { id, fields } = args;
|
||||
if (!fields) fields = {};
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const globalIdObject = fromGlobalId(id);
|
||||
|
||||
if (globalIdObject.type === className) {
|
||||
id = globalIdObject.id;
|
||||
}
|
||||
|
||||
const parseFields = await transformTypes('update', fields, {
|
||||
className,
|
||||
parseGraphQLSchema,
|
||||
@@ -154,15 +197,17 @@ const load = function(
|
||||
info
|
||||
);
|
||||
|
||||
const selectedFields = getFieldNames(mutationInfo);
|
||||
const selectedFields = getFieldNames(mutationInfo)
|
||||
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
|
||||
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||
fields,
|
||||
keys,
|
||||
include,
|
||||
['id', 'updatedAt']
|
||||
['id', 'objectId', 'updatedAt']
|
||||
);
|
||||
|
||||
let optimizedObject = {};
|
||||
if (needGet) {
|
||||
optimizedObject = await objectsQueries.getObject(
|
||||
@@ -178,38 +223,69 @@ const load = function(
|
||||
);
|
||||
}
|
||||
return {
|
||||
id,
|
||||
...updatedObject,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
[getGraphQLQueryName]: {
|
||||
objectId: id,
|
||||
...updatedObject,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
updateGraphQLMutation.args.input.type.ofType
|
||||
) &&
|
||||
parseGraphQLSchema.addGraphQLType(updateGraphQLMutation.type)
|
||||
) {
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
updateGraphQLMutationName,
|
||||
updateGraphQLMutation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDestroyEnabled) {
|
||||
const deleteGraphQLMutationName = `delete${graphQLClassName}`;
|
||||
parseGraphQLSchema.addGraphQLMutation(deleteGraphQLMutationName, {
|
||||
const deleteGraphQLMutation = mutationWithClientMutationId({
|
||||
name: `Delete${graphQLClassName}`,
|
||||
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${graphQLClassName} class.`,
|
||||
args: {
|
||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
inputFields: {
|
||||
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, mutationInfo) {
|
||||
outputFields: {
|
||||
[getGraphQLQueryName]: {
|
||||
description: 'This is the deleted object.',
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
const { id } = args;
|
||||
let { id } = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(mutationInfo);
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
const globalIdObject = fromGlobalId(id);
|
||||
|
||||
if (globalIdObject.type === className) {
|
||||
id = globalIdObject.id;
|
||||
}
|
||||
|
||||
const selectedFields = getFieldNames(mutationInfo)
|
||||
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
|
||||
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
let optimizedObject = {};
|
||||
const splitedKeys = keys.split(',');
|
||||
if (splitedKeys.length > 1 || splitedKeys[0] !== 'id') {
|
||||
if (
|
||||
keys &&
|
||||
keys.split(',').filter(key => !['id', 'objectId'].includes(key))
|
||||
.length > 0
|
||||
) {
|
||||
optimizedObject = await objectsQueries.getObject(
|
||||
className,
|
||||
id,
|
||||
@@ -229,12 +305,29 @@ const load = function(
|
||||
auth,
|
||||
info
|
||||
);
|
||||
return { id, ...optimizedObject };
|
||||
return {
|
||||
[getGraphQLQueryName]: {
|
||||
objectId: id,
|
||||
...optimizedObject,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
deleteGraphQLMutation.args.input.type.ofType
|
||||
) &&
|
||||
parseGraphQLSchema.addGraphQLType(deleteGraphQLMutation.type)
|
||||
) {
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
deleteGraphQLMutationName,
|
||||
deleteGraphQLMutation
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { fromGlobalId } from 'graphql-relay';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import pluralize from 'pluralize';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
@@ -14,11 +15,18 @@ const getParseClassQueryConfig = function(
|
||||
};
|
||||
|
||||
const getQuery = async (className, _source, args, context, queryInfo) => {
|
||||
const { id, options } = args;
|
||||
let { id } = args;
|
||||
const { options } = args;
|
||||
const { readPreference, includeReadPreference } = options || {};
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const globalIdObject = fromGlobalId(id);
|
||||
|
||||
if (globalIdObject.type === className) {
|
||||
id = globalIdObject.id;
|
||||
}
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
return await objectsQueries.getObject(
|
||||
@@ -58,7 +66,7 @@ const load = function(
|
||||
parseGraphQLSchema.addGraphQLQuery(getGraphQLQueryName, {
|
||||
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${graphQLClassName} class by its id.`,
|
||||
args: {
|
||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(
|
||||
@@ -82,11 +90,20 @@ const load = function(
|
||||
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${graphQLClassName} class.`,
|
||||
args: classGraphQLFindArgs,
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLFindResultType || defaultGraphQLTypes.FIND_RESULT
|
||||
classGraphQLFindResultType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const { where, order, skip, limit, options } = args;
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
options,
|
||||
} = args;
|
||||
const {
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
@@ -97,8 +114,8 @@ const load = function(
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
.filter(field => field.startsWith('edges.node.'))
|
||||
.map(field => field.replace('edges.node.', ''))
|
||||
);
|
||||
const parseOrder = order && order.join(',');
|
||||
|
||||
@@ -107,7 +124,10 @@ const load = function(
|
||||
where,
|
||||
parseOrder,
|
||||
skip,
|
||||
limit,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
@@ -117,7 +137,7 @@ const load = function(
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0]),
|
||||
selectedFields,
|
||||
parseClass.fields
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
GraphQLNonNull,
|
||||
GraphQLEnumType,
|
||||
} from 'graphql';
|
||||
import {
|
||||
globalIdField,
|
||||
connectionArgs,
|
||||
connectionDefinitions,
|
||||
} from 'graphql-relay';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from '../helpers/objectsQueries';
|
||||
@@ -30,9 +35,7 @@ const getInputFieldsAndConstraints = function(
|
||||
parseClass,
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
const classFields = Object.keys(parseClass.fields)
|
||||
.filter(field => field !== 'objectId')
|
||||
.concat('id');
|
||||
const classFields = Object.keys(parseClass.fields).concat('id');
|
||||
const {
|
||||
inputFields: allowedInputFields,
|
||||
outputFields: allowedOutputFields,
|
||||
@@ -48,8 +51,9 @@ const getInputFieldsAndConstraints = function(
|
||||
|
||||
// All allowed customs fields
|
||||
const classCustomFields = classFields.filter(field => {
|
||||
return !Object.keys(defaultGraphQLTypes.PARSE_OBJECT_FIELDS).includes(
|
||||
field
|
||||
return (
|
||||
!Object.keys(defaultGraphQLTypes.PARSE_OBJECT_FIELDS).includes(field) &&
|
||||
field !== 'id'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -153,7 +157,11 @@ const load = (
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type,
|
||||
type:
|
||||
className === '_User' &&
|
||||
(field === 'username' || field === 'password')
|
||||
? new GraphQLNonNull(type)
|
||||
: type,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
@@ -209,7 +217,7 @@ const load = (
|
||||
fields: () => {
|
||||
const fields = {
|
||||
link: {
|
||||
description: `Link an existing object from ${graphQLClassName} class.`,
|
||||
description: `Link an existing object from ${graphQLClassName} class. You can use either the global or the object id.`,
|
||||
type: GraphQLID,
|
||||
},
|
||||
};
|
||||
@@ -233,17 +241,17 @@ const load = (
|
||||
fields: () => {
|
||||
const fields = {
|
||||
add: {
|
||||
description: `Add an existing object from the ${graphQLClassName} class into the relation.`,
|
||||
description: `Add existing objects from the ${graphQLClassName} class into the relation. You can use either the global or the object ids.`,
|
||||
type: new GraphQLList(defaultGraphQLTypes.OBJECT_ID),
|
||||
},
|
||||
remove: {
|
||||
description: `Remove an existing object from the ${graphQLClassName} class out of the relation.`,
|
||||
description: `Remove existing objects from the ${graphQLClassName} class out of the relation. You can use either the global or the object ids.`,
|
||||
type: new GraphQLList(defaultGraphQLTypes.OBJECT_ID),
|
||||
},
|
||||
};
|
||||
if (isCreateEnabled) {
|
||||
fields['createAndAdd'] = {
|
||||
description: `Create and add an object of the ${graphQLClassName} class into the relation.`,
|
||||
description: `Create and add objects of the ${graphQLClassName} class into the relation.`,
|
||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLCreateType)),
|
||||
};
|
||||
}
|
||||
@@ -268,12 +276,12 @@ const load = (
|
||||
notInQueryKey: defaultGraphQLTypes.notInQueryKey,
|
||||
inQuery: {
|
||||
description:
|
||||
'This is the inQuery operator to specify a constraint to select the objects where a field equals to any of the ids in the result of a different query.',
|
||||
'This is the inQuery operator to specify a constraint to select the objects where a field equals to any of the object ids in the result of a different query.',
|
||||
type: defaultGraphQLTypes.SUBQUERY_INPUT,
|
||||
},
|
||||
notInQuery: {
|
||||
description:
|
||||
'This is the notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the ids in the result of a different query.',
|
||||
'This is the notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the object ids in the result of a different query.',
|
||||
type: defaultGraphQLTypes.SUBQUERY_INPUT,
|
||||
},
|
||||
},
|
||||
@@ -298,7 +306,8 @@ const load = (
|
||||
const type = transformConstraintTypeToGraphQL(
|
||||
parseClass.fields[parseField].type,
|
||||
parseClass.fields[parseField].targetClass,
|
||||
parseGraphQLSchema.parseClassTypes
|
||||
parseGraphQLSchema.parseClassTypes,
|
||||
field
|
||||
);
|
||||
if (type) {
|
||||
return {
|
||||
@@ -339,11 +348,12 @@ const load = (
|
||||
const updatedSortFields = {
|
||||
...sortFields,
|
||||
};
|
||||
const value = field === 'id' ? 'objectId' : field;
|
||||
if (asc) {
|
||||
updatedSortFields[`${field}_ASC`] = { value: field };
|
||||
updatedSortFields[`${field}_ASC`] = { value };
|
||||
}
|
||||
if (desc) {
|
||||
updatedSortFields[`${field}_DESC`] = { value: `-${field}` };
|
||||
updatedSortFields[`${field}_DESC`] = { value: `-${value}` };
|
||||
}
|
||||
return updatedSortFields;
|
||||
}, {}),
|
||||
@@ -365,10 +375,18 @@ const load = (
|
||||
: GraphQLString,
|
||||
},
|
||||
skip: defaultGraphQLTypes.SKIP_ATT,
|
||||
limit: defaultGraphQLTypes.LIMIT_ATT,
|
||||
...connectionArgs,
|
||||
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
||||
};
|
||||
const classGraphQLOutputTypeName = `${graphQLClassName}`;
|
||||
const interfaces = [
|
||||
defaultGraphQLTypes.PARSE_OBJECT,
|
||||
parseGraphQLSchema.relayNodeInterface,
|
||||
];
|
||||
const parseObjectFields = {
|
||||
id: globalIdField(className, obj => obj.objectId),
|
||||
...defaultGraphQLTypes.PARSE_OBJECT_FIELDS,
|
||||
};
|
||||
const outputFields = () => {
|
||||
return classOutputFields.reduce((fields, field) => {
|
||||
const type = transformOutputTypeToGraphQL(
|
||||
@@ -392,7 +410,16 @@ const load = (
|
||||
type,
|
||||
async resolve(source, args, context, queryInfo) {
|
||||
try {
|
||||
const { where, order, skip, limit, options } = args;
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
options,
|
||||
} = args;
|
||||
const {
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
@@ -403,9 +430,11 @@ const load = (
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
.filter(field => field.startsWith('edges.node.'))
|
||||
.map(field => field.replace('edges.node.', ''))
|
||||
);
|
||||
const parseOrder = order && order.join(',');
|
||||
|
||||
return await objectsQueries.findObjects(
|
||||
source[field].className,
|
||||
{
|
||||
@@ -419,9 +448,12 @@ const load = (
|
||||
},
|
||||
...(where || {}),
|
||||
},
|
||||
order,
|
||||
parseOrder,
|
||||
skip,
|
||||
limit,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
@@ -431,8 +463,11 @@ const load = (
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0]),
|
||||
parseClass.fields
|
||||
selectedFields,
|
||||
parseGraphQLSchema.parseClasses.find(
|
||||
parseClass =>
|
||||
parseClass.className === source[field].className
|
||||
).fields
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
@@ -491,39 +526,32 @@ const load = (
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
}, defaultGraphQLTypes.PARSE_OBJECT_FIELDS);
|
||||
}, parseObjectFields);
|
||||
};
|
||||
let classGraphQLOutputType = new GraphQLObjectType({
|
||||
name: classGraphQLOutputTypeName,
|
||||
description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${graphQLClassName} class.`,
|
||||
interfaces: [defaultGraphQLTypes.PARSE_OBJECT],
|
||||
interfaces,
|
||||
fields: outputFields,
|
||||
});
|
||||
classGraphQLOutputType = parseGraphQLSchema.addGraphQLType(
|
||||
classGraphQLOutputType
|
||||
);
|
||||
|
||||
const classGraphQLFindResultTypeName = `${graphQLClassName}FindResult`;
|
||||
let classGraphQLFindResultType = new GraphQLObjectType({
|
||||
name: classGraphQLFindResultTypeName,
|
||||
description: `The ${classGraphQLFindResultTypeName} object type is used in the ${graphQLClassName} find query to return the data of the matched objects.`,
|
||||
fields: {
|
||||
results: {
|
||||
description: 'This is the objects returned by the query',
|
||||
type: new GraphQLNonNull(
|
||||
new GraphQLList(
|
||||
new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
)
|
||||
)
|
||||
),
|
||||
},
|
||||
const { connectionType, edgeType } = connectionDefinitions({
|
||||
name: graphQLClassName,
|
||||
connectionFields: {
|
||||
count: defaultGraphQLTypes.COUNT_ATT,
|
||||
},
|
||||
nodeType: classGraphQLOutputType || defaultGraphQLTypes.OBJECT,
|
||||
});
|
||||
classGraphQLFindResultType = parseGraphQLSchema.addGraphQLType(
|
||||
classGraphQLFindResultType
|
||||
);
|
||||
let classGraphQLFindResultType = undefined;
|
||||
if (
|
||||
parseGraphQLSchema.addGraphQLType(edgeType) &&
|
||||
parseGraphQLSchema.addGraphQLType(connectionType, false, false, true)
|
||||
) {
|
||||
classGraphQLFindResultType = connectionType;
|
||||
}
|
||||
|
||||
parseGraphQLSchema.parseClassTypes[className] = {
|
||||
classGraphQLPointerType,
|
||||
@@ -546,67 +574,16 @@ 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.PARSE_OBJECT],
|
||||
fields: () => ({
|
||||
...outputFields(),
|
||||
sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT,
|
||||
user: {
|
||||
description: 'This is the current user.',
|
||||
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||
},
|
||||
}),
|
||||
});
|
||||
parseGraphQLSchema.viewerType = viewerType;
|
||||
parseGraphQLSchema.addGraphQLType(viewerType, true, true);
|
||||
|
||||
const userSignUpInputTypeName = 'SignUpFieldsInput';
|
||||
const userSignUpInputType = new GraphQLInputObjectType({
|
||||
name: userSignUpInputTypeName,
|
||||
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 = transformInputTypeToGraphQL(
|
||||
parseClass.fields[field].type,
|
||||
parseClass.fields[field].targetClass,
|
||||
parseGraphQLSchema.parseClassTypes
|
||||
);
|
||||
if (type) {
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type:
|
||||
field === 'username' || field === 'password'
|
||||
? new GraphQLNonNull(type)
|
||||
: type,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
}, {}),
|
||||
});
|
||||
parseGraphQLSchema.addGraphQLType(userSignUpInputType, true, true);
|
||||
|
||||
const userLogInInputTypeName = 'LogInFieldsInput';
|
||||
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.addGraphQLType(userLogInInputType, true, true);
|
||||
|
||||
parseGraphQLSchema.parseClassTypes[
|
||||
className
|
||||
].signUpInputType = userSignUpInputType;
|
||||
parseGraphQLSchema.parseClassTypes[
|
||||
className
|
||||
].logInInputType = userLogInInputType;
|
||||
parseGraphQLSchema.viewerType = viewerType;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Parse from 'parse/node';
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import * as schemaTypes from './schemaTypes';
|
||||
import {
|
||||
transformToParse,
|
||||
@@ -9,135 +10,183 @@ import { enforceMasterKeyAccess } from '../parseGraphQLUtils';
|
||||
import { getClass } from './schemaQueries';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
const createClassMutation = mutationWithClientMutationId({
|
||||
name: 'CreateClass',
|
||||
description:
|
||||
'The createClass mutation can be used to create the schema for a new object class.',
|
||||
inputFields: {
|
||||
name: schemaTypes.CLASS_NAME_ATT,
|
||||
schemaFields: {
|
||||
description: "These are the schema's fields of the object class.",
|
||||
type: schemaTypes.SCHEMA_FIELDS_INPUT,
|
||||
},
|
||||
},
|
||||
outputFields: {
|
||||
class: {
|
||||
description: 'This is the created class.',
|
||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (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 {
|
||||
class: {
|
||||
name: parseClass.className,
|
||||
schemaFields: transformToGraphQL(parseClass.fields),
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
createClassMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(createClassMutation.type, true, true);
|
||||
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);
|
||||
}
|
||||
},
|
||||
},
|
||||
createClassMutation,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
const updateClassMutation = mutationWithClientMutationId({
|
||||
name: 'UpdateClass',
|
||||
description:
|
||||
'The updateClass mutation can be used to update the schema for an existing object class.',
|
||||
inputFields: {
|
||||
name: schemaTypes.CLASS_NAME_ATT,
|
||||
schemaFields: {
|
||||
description: "These are the schema's fields of the object class.",
|
||||
type: schemaTypes.SCHEMA_FIELDS_INPUT,
|
||||
},
|
||||
},
|
||||
outputFields: {
|
||||
class: {
|
||||
description: 'This is the updated class.',
|
||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (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 {
|
||||
class: {
|
||||
name: parseClass.className,
|
||||
schemaFields: transformToGraphQL(parseClass.fields),
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
updateClassMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(updateClassMutation.type, 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);
|
||||
}
|
||||
},
|
||||
},
|
||||
updateClassMutation,
|
||||
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);
|
||||
}
|
||||
const deleteClassMutation = mutationWithClientMutationId({
|
||||
name: 'DeleteClass',
|
||||
description:
|
||||
'The deleteClass mutation can be used to delete an existing object class.',
|
||||
inputFields: {
|
||||
name: schemaTypes.CLASS_NAME_ATT,
|
||||
},
|
||||
outputFields: {
|
||||
class: {
|
||||
description: 'This is the deleted class.',
|
||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (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 {
|
||||
class: {
|
||||
name: existingParseClass.className,
|
||||
schemaFields: transformToGraphQL(existingParseClass.fields),
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
deleteClassMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(deleteClassMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'deleteClass',
|
||||
deleteClassMutation,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { GraphQLNonNull, GraphQLString } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import UsersRouter from '../../Routers/UsersRouter';
|
||||
import * as objectsMutations from '../helpers/objectsMutations';
|
||||
import { getUserFromSessionToken } from './usersQueries';
|
||||
@@ -10,110 +11,166 @@ const load = parseGraphQLSchema => {
|
||||
return;
|
||||
}
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'signUp',
|
||||
{
|
||||
description: 'The signUp mutation can be used to sign the user up.',
|
||||
args: {
|
||||
fields: {
|
||||
descriptions: 'These are the fields of the user.',
|
||||
type: parseGraphQLSchema.parseClassTypes['_User'].signUpInputType,
|
||||
},
|
||||
},
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
async resolve(_source, args, context, mutationInfo) {
|
||||
try {
|
||||
const { fields } = args;
|
||||
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const { sessionToken } = await objectsMutations.createObject(
|
||||
'_User',
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
|
||||
info.sessionToken = sessionToken;
|
||||
|
||||
return await getUserFromSessionToken(config, info, mutationInfo);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
const signUpMutation = mutationWithClientMutationId({
|
||||
name: 'SignUp',
|
||||
description:
|
||||
'The signUp mutation can be used to create and sign up a new user.',
|
||||
inputFields: {
|
||||
userFields: {
|
||||
descriptions:
|
||||
'These are the fields of the new user to be created and signed up.',
|
||||
type:
|
||||
parseGraphQLSchema.parseClassTypes['_User'].classGraphQLCreateType,
|
||||
},
|
||||
},
|
||||
outputFields: {
|
||||
viewer: {
|
||||
description:
|
||||
'This is the new user that was created, signed up and returned as a viewer.',
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
const { userFields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const { sessionToken } = await objectsMutations.createObject(
|
||||
'_User',
|
||||
userFields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
|
||||
info.sessionToken = sessionToken;
|
||||
|
||||
return {
|
||||
viewer: await getUserFromSessionToken(
|
||||
config,
|
||||
info,
|
||||
mutationInfo,
|
||||
'viewer.user.',
|
||||
true
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
signUpMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(signUpMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation('signUp', signUpMutation, true, true);
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'logIn',
|
||||
{
|
||||
description: 'The logIn mutation can be used to log the user in.',
|
||||
args: {
|
||||
fields: {
|
||||
description: 'This is data needed to login',
|
||||
type: parseGraphQLSchema.parseClassTypes['_User'].logInInputType,
|
||||
},
|
||||
const logInMutation = mutationWithClientMutationId({
|
||||
name: 'LogIn',
|
||||
description: 'The logIn mutation can be used to log in an existing user.',
|
||||
inputFields: {
|
||||
username: {
|
||||
description: 'This is the username used to log in the user.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const {
|
||||
fields: { username, password },
|
||||
} = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return (await usersRouter.handleLogIn({
|
||||
body: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
query: {},
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
})).response;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
password: {
|
||||
description: 'This is the password used to log in the user.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
},
|
||||
outputFields: {
|
||||
viewer: {
|
||||
description:
|
||||
'This is the existing user that was logged in and returned as a viewer.',
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
const { username, password } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const { sessionToken } = (await usersRouter.handleLogIn({
|
||||
body: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
query: {},
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
})).response;
|
||||
|
||||
info.sessionToken = sessionToken;
|
||||
|
||||
return {
|
||||
viewer: await getUserFromSessionToken(
|
||||
config,
|
||||
info,
|
||||
mutationInfo,
|
||||
'viewer.user.',
|
||||
true
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
logInMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(logInMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation('logIn', logInMutation, true, true);
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'logOut',
|
||||
{
|
||||
description: 'The logOut mutation can be used to log the user out.',
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
async resolve(_source, _args, context, mutationInfo) {
|
||||
try {
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const viewer = await getUserFromSessionToken(
|
||||
config,
|
||||
info,
|
||||
mutationInfo
|
||||
);
|
||||
|
||||
await usersRouter.handleLogOut({
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
});
|
||||
|
||||
return viewer;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
const logOutMutation = mutationWithClientMutationId({
|
||||
name: 'LogOut',
|
||||
description: 'The logOut mutation can be used to log out an existing user.',
|
||||
outputFields: {
|
||||
viewer: {
|
||||
description:
|
||||
'This is the existing user that was logged out and returned as a viewer.',
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (_args, context, mutationInfo) => {
|
||||
try {
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const viewer = await getUserFromSessionToken(
|
||||
config,
|
||||
info,
|
||||
mutationInfo,
|
||||
'viewer.user.',
|
||||
true
|
||||
);
|
||||
|
||||
await usersRouter.handleLogOut({
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
});
|
||||
|
||||
return { viewer };
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
logOutMutation.args.input.type.ofType,
|
||||
true,
|
||||
true
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(logOutMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation('logOut', logOutMutation, true, true);
|
||||
};
|
||||
|
||||
export { load };
|
||||
|
||||
@@ -5,7 +5,13 @@ import rest from '../../rest';
|
||||
import Auth from '../../Auth';
|
||||
import { extractKeysAndInclude } from './parseClassTypes';
|
||||
|
||||
const getUserFromSessionToken = async (config, info, queryInfo) => {
|
||||
const getUserFromSessionToken = async (
|
||||
config,
|
||||
info,
|
||||
queryInfo,
|
||||
keysPrefix,
|
||||
validatedToken
|
||||
) => {
|
||||
if (!info || !info.sessionToken) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
@@ -13,20 +19,42 @@ const getUserFromSessionToken = async (config, info, queryInfo) => {
|
||||
);
|
||||
}
|
||||
const sessionToken = info.sessionToken;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
const selectedFields = getFieldNames(queryInfo)
|
||||
.filter(field => field.startsWith(keysPrefix))
|
||||
.map(field => field.replace(keysPrefix, ''));
|
||||
|
||||
const keysAndInclude = extractKeysAndInclude(selectedFields);
|
||||
const { keys } = keysAndInclude;
|
||||
let { include } = keysAndInclude;
|
||||
|
||||
if (validatedToken && !keys && !include) {
|
||||
return {
|
||||
sessionToken,
|
||||
};
|
||||
} else if (keys && !include) {
|
||||
include = 'user';
|
||||
}
|
||||
|
||||
const options = {};
|
||||
if (keys) {
|
||||
options.keys = keys
|
||||
.split(',')
|
||||
.map(key => `user.${key}`)
|
||||
.join(',');
|
||||
}
|
||||
if (include) {
|
||||
options.include = include
|
||||
.split(',')
|
||||
.map(included => `user.${included}`)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
const { include } = extractKeysAndInclude(selectedFields);
|
||||
const response = await rest.find(
|
||||
config,
|
||||
Auth.master(config),
|
||||
'_Session',
|
||||
{ sessionToken },
|
||||
{
|
||||
include: include
|
||||
.split(',')
|
||||
.map(included => `user.${included}`)
|
||||
.join(','),
|
||||
},
|
||||
options,
|
||||
info.clientVersion
|
||||
);
|
||||
if (
|
||||
@@ -40,8 +68,10 @@ const getUserFromSessionToken = async (config, info, queryInfo) => {
|
||||
);
|
||||
} else {
|
||||
const user = response.results[0].user;
|
||||
user.sessionToken = sessionToken;
|
||||
return user;
|
||||
return {
|
||||
sessionToken,
|
||||
user,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,7 +89,13 @@ const load = parseGraphQLSchema => {
|
||||
async resolve(_source, _args, context, queryInfo) {
|
||||
try {
|
||||
const { config, info } = context;
|
||||
return await getUserFromSessionToken(config, info, queryInfo);
|
||||
return await getUserFromSessionToken(
|
||||
config,
|
||||
info,
|
||||
queryInfo,
|
||||
'user.',
|
||||
false
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user