GraphQL Configuration Options (#5782)
* add parse-graph-ql configuration for class schema customisation Not yet tested - essentially an RFC * refactor and add graphql router, controller and config cache * fix(GraphQLController): add missing check isEnabled * chore(GraphQLController): remove awaits from cache put * chore(GraphQLController): remove check for if its enabled * refactor(GraphQLController): only use cache if mounted * chore(GraphQLController): group all validation errors and throw at once * chore(GraphQLSchema): move transformations into controller validation * refactor(GraphQL): improve ctrl validation and fix schema usage of config * refactor(GraphQLSchema): remove code related to additional schema This code has been moved into a separate feature branch. * fix(GraphQLSchema): fix incorrect default return type for class configs * refactor(GraphQLSchema): update staleness check code to account for config * fix(GraphQLServer): fix regressed tests due to internal schema changes This will be followed up with a backwards compatability fix for the `ClassFields` issue to avoid breakages for our users * refactor: rename to ParseGraphQLController for consistency * fix(ParseGraphQLCtrl): numerous fixes for validity checking Also includes some minor code refactoring * chore(GraphQL): minor syntax cleanup * fix(SchemaController): add _GraphQLConfig to volatile classes * refactor(ParseGraphQLServer): return update config value in setGraphQLConfig * testing(ParseGraphQL): add test cases for new graphQLConfig * fix(GraphQLController): fix issue where config with multiple items was not being mapped to the db * fix(postgres): add _GraphQLConfig default schema on load fixes failing postgres tests * GraphQL @mock directive (#5836) * Add mock directive * Include tests for @mock directive * Fix existing tests due to the change from ClassFields to ClassCreateFields * fix(parseClassMutations): safer type transformation based on input type * fix(parseClassMutations): only define necessary input fields * fix(GraphQL): fix incorrect import paths
This commit is contained in:
committed by
Antonio Davi Macedo Coelho de Castro
parent
bbcc20fd60
commit
d3810c2eba
@@ -8,36 +8,57 @@ import * as parseClassQueries from './loaders/parseClassQueries';
|
||||
import * as parseClassMutations from './loaders/parseClassMutations';
|
||||
import * as defaultGraphQLQueries from './loaders/defaultGraphQLQueries';
|
||||
import * as defaultGraphQLMutations from './loaders/defaultGraphQLMutations';
|
||||
import ParseGraphQLController, {
|
||||
ParseGraphQLConfig,
|
||||
} from '../Controllers/ParseGraphQLController';
|
||||
import DatabaseController from '../Controllers/DatabaseController';
|
||||
import { toGraphQLError } from './parseGraphQLUtils';
|
||||
import * as schemaDirectives from './loaders/schemaDirectives';
|
||||
|
||||
class ParseGraphQLSchema {
|
||||
constructor(databaseController, log, graphQLCustomTypeDefs) {
|
||||
databaseController: DatabaseController;
|
||||
parseGraphQLController: ParseGraphQLController;
|
||||
parseGraphQLConfig: ParseGraphQLConfig;
|
||||
graphQLCustomTypeDefs: any;
|
||||
|
||||
constructor(
|
||||
params: {
|
||||
databaseController: DatabaseController,
|
||||
parseGraphQLController: ParseGraphQLController,
|
||||
log: any,
|
||||
} = {}
|
||||
) {
|
||||
this.parseGraphQLController =
|
||||
params.parseGraphQLController ||
|
||||
requiredParameter('You must provide a parseGraphQLController instance!');
|
||||
this.databaseController =
|
||||
databaseController ||
|
||||
params.databaseController ||
|
||||
requiredParameter('You must provide a databaseController instance!');
|
||||
this.log = log || requiredParameter('You must provide a log instance!');
|
||||
this.graphQLCustomTypeDefs = graphQLCustomTypeDefs;
|
||||
this.log =
|
||||
params.log || requiredParameter('You must provide a log instance!');
|
||||
this.graphQLCustomTypeDefs = params.graphQLCustomTypeDefs;
|
||||
}
|
||||
|
||||
async load() {
|
||||
const schemaController = await this.databaseController.loadSchema();
|
||||
const parseClasses = await schemaController.getAllClasses();
|
||||
const { parseGraphQLConfig } = await this._initializeSchemaAndConfig();
|
||||
|
||||
const parseClasses = await this._getClassesForSchema(parseGraphQLConfig);
|
||||
const parseClassesString = JSON.stringify(parseClasses);
|
||||
|
||||
if (this.graphQLSchema) {
|
||||
if (this.parseClasses === parseClasses) {
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
|
||||
if (this.parseClassesString === parseClassesString) {
|
||||
this.parseClasses = parseClasses;
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
if (
|
||||
this.graphQLSchema &&
|
||||
!this._hasSchemaInputChanged({
|
||||
parseClasses,
|
||||
parseClassesString,
|
||||
parseGraphQLConfig,
|
||||
})
|
||||
) {
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
|
||||
this.parseClasses = parseClasses;
|
||||
this.parseClassesString = parseClassesString;
|
||||
this.parseGraphQLConfig = parseGraphQLConfig;
|
||||
this.parseClassTypes = {};
|
||||
this.meType = null;
|
||||
this.graphQLAutoSchema = null;
|
||||
@@ -53,16 +74,15 @@ class ParseGraphQLSchema {
|
||||
|
||||
defaultGraphQLTypes.load(this);
|
||||
|
||||
parseClasses.forEach(parseClass => {
|
||||
parseClassTypes.load(this, parseClass);
|
||||
|
||||
parseClassQueries.load(this, parseClass);
|
||||
|
||||
parseClassMutations.load(this, parseClass);
|
||||
});
|
||||
this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach(
|
||||
([parseClass, parseClassConfig]) => {
|
||||
parseClassTypes.load(this, parseClass, parseClassConfig);
|
||||
parseClassQueries.load(this, parseClass, parseClassConfig);
|
||||
parseClassMutations.load(this, parseClass, parseClassConfig);
|
||||
}
|
||||
);
|
||||
|
||||
defaultGraphQLQueries.load(this);
|
||||
|
||||
defaultGraphQLMutations.load(this);
|
||||
|
||||
let graphQLQuery = undefined;
|
||||
@@ -160,6 +180,104 @@ class ParseGraphQLSchema {
|
||||
}
|
||||
throw toGraphQLError(error);
|
||||
}
|
||||
|
||||
async _initializeSchemaAndConfig() {
|
||||
const [schemaController, parseGraphQLConfig] = await Promise.all([
|
||||
this.databaseController.loadSchema(),
|
||||
this.parseGraphQLController.getGraphQLConfig(),
|
||||
]);
|
||||
|
||||
this.schemaController = schemaController;
|
||||
|
||||
return {
|
||||
parseGraphQLConfig,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all classes found by the `schemaController`
|
||||
* minus those filtered out by the app's parseGraphQLConfig.
|
||||
*/
|
||||
async _getClassesForSchema(parseGraphQLConfig: ParseGraphQLConfig) {
|
||||
const { enabledForClasses, disabledForClasses } = parseGraphQLConfig;
|
||||
const allClasses = await this.schemaController.getAllClasses();
|
||||
|
||||
if (Array.isArray(enabledForClasses) || Array.isArray(disabledForClasses)) {
|
||||
let includedClasses = allClasses;
|
||||
if (enabledForClasses) {
|
||||
includedClasses = allClasses.filter(clazz => {
|
||||
return enabledForClasses.includes(clazz.className);
|
||||
});
|
||||
}
|
||||
if (disabledForClasses) {
|
||||
// Classes included in `enabledForClasses` that
|
||||
// are also present in `disabledForClasses` will
|
||||
// still be filtered out
|
||||
includedClasses = includedClasses.filter(clazz => {
|
||||
return !disabledForClasses.includes(clazz.className);
|
||||
});
|
||||
}
|
||||
|
||||
this.isUsersClassDisabled = !includedClasses.some(clazz => {
|
||||
return clazz.className === '_User';
|
||||
});
|
||||
|
||||
return includedClasses;
|
||||
} else {
|
||||
return allClasses;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of tuples
|
||||
* that provide the parseClass along with
|
||||
* its parseClassConfig where provided.
|
||||
*/
|
||||
_getParseClassesWithConfig(
|
||||
parseClasses,
|
||||
parseGraphQLConfig: ParseGraphQLConfig
|
||||
) {
|
||||
const { classConfigs } = parseGraphQLConfig;
|
||||
return parseClasses.map(parseClass => {
|
||||
let parseClassConfig;
|
||||
if (classConfigs) {
|
||||
parseClassConfig = classConfigs.find(
|
||||
c => c.className === parseClass.className
|
||||
);
|
||||
}
|
||||
return [parseClass, parseClassConfig];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for changes to the parseClasses
|
||||
* objects (i.e. database schema) or to
|
||||
* the parseGraphQLConfig object. If no
|
||||
* changes are found, return true;
|
||||
*/
|
||||
_hasSchemaInputChanged(params: {
|
||||
parseClasses: any,
|
||||
parseClassesString: string,
|
||||
parseGraphQLConfig: ?ParseGraphQLConfig,
|
||||
}): boolean {
|
||||
const { parseClasses, parseClassesString, parseGraphQLConfig } = params;
|
||||
|
||||
if (
|
||||
JSON.stringify(this.parseGraphQLConfig) ===
|
||||
JSON.stringify(parseGraphQLConfig)
|
||||
) {
|
||||
if (this.parseClasses === parseClasses) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.parseClassesString === parseClassesString) {
|
||||
this.parseClasses = parseClasses;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { ParseGraphQLSchema };
|
||||
|
||||
@@ -9,8 +9,13 @@ import { handleParseErrors, handleParseHeaders } from '../middlewares';
|
||||
import requiredParameter from '../requiredParameter';
|
||||
import defaultLogger from '../logger';
|
||||
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
|
||||
import ParseGraphQLController, {
|
||||
ParseGraphQLConfig,
|
||||
} from '../Controllers/ParseGraphQLController';
|
||||
|
||||
class ParseGraphQLServer {
|
||||
parseGraphQLController: ParseGraphQLController;
|
||||
|
||||
constructor(parseServer, config) {
|
||||
this.parseServer =
|
||||
parseServer ||
|
||||
@@ -19,12 +24,15 @@ class ParseGraphQLServer {
|
||||
requiredParameter('You must provide a config.graphQLPath!');
|
||||
}
|
||||
this.config = config;
|
||||
this.parseGraphQLSchema = new ParseGraphQLSchema(
|
||||
this.parseServer.config.databaseController,
|
||||
(this.parseServer.config && this.parseServer.config.loggerController) ||
|
||||
this.parseGraphQLController = this.parseServer.config.parseGraphQLController;
|
||||
this.parseGraphQLSchema = new ParseGraphQLSchema({
|
||||
parseGraphQLController: this.parseGraphQLController,
|
||||
databaseController: this.parseServer.config.databaseController,
|
||||
log:
|
||||
(this.parseServer.config && this.parseServer.config.loggerController) ||
|
||||
defaultLogger,
|
||||
this.config.graphQLCustomTypeDefs
|
||||
);
|
||||
graphQLCustomTypeDefs: this.config.graphQLCustomTypeDefs,
|
||||
});
|
||||
}
|
||||
|
||||
async _getGraphQLOptions(req) {
|
||||
@@ -111,6 +119,10 @@ class ParseGraphQLServer {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setGraphQLConfig(graphQLConfig: ParseGraphQLConfig): Promise {
|
||||
return this.parseGraphQLController.updateGraphQLConfig(graphQLConfig);
|
||||
}
|
||||
}
|
||||
|
||||
export { ParseGraphQLServer };
|
||||
|
||||
@@ -1,23 +1,58 @@
|
||||
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsMutations from './objectsMutations';
|
||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||
|
||||
const load = (parseGraphQLSchema, parseClass) => {
|
||||
const className = parseClass.className;
|
||||
const getParseClassMutationConfig = function(
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
return (parseClassConfig && parseClassConfig.mutation) || {};
|
||||
};
|
||||
|
||||
const classGraphQLInputType =
|
||||
parseGraphQLSchema.parseClassTypes[className].classGraphQLInputType;
|
||||
const fields = {
|
||||
description: 'These are the fields of the object.',
|
||||
type: classGraphQLInputType,
|
||||
const load = function(
|
||||
parseGraphQLSchema,
|
||||
parseClass,
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
const { className } = parseClass;
|
||||
const {
|
||||
create: isCreateEnabled = true,
|
||||
update: isUpdateEnabled = true,
|
||||
destroy: isDestroyEnabled = true,
|
||||
} = getParseClassMutationConfig(parseClassConfig);
|
||||
|
||||
const {
|
||||
classGraphQLCreateType,
|
||||
classGraphQLUpdateType,
|
||||
} = parseGraphQLSchema.parseClassTypes[className];
|
||||
|
||||
const createFields = {
|
||||
description: 'These are the fields used to create the object.',
|
||||
type: classGraphQLCreateType,
|
||||
};
|
||||
const updateFields = {
|
||||
description: 'These are the fields used to update the object.',
|
||||
type: classGraphQLUpdateType,
|
||||
};
|
||||
const classGraphQLInputTypeFields = classGraphQLInputType.getFields();
|
||||
|
||||
const transformTypes = fields => {
|
||||
const classGraphQLCreateTypeFields = isCreateEnabled
|
||||
? classGraphQLCreateType.getFields()
|
||||
: null;
|
||||
const classGraphQLUpdateTypeFields = isUpdateEnabled
|
||||
? classGraphQLUpdateType.getFields()
|
||||
: null;
|
||||
|
||||
const transformTypes = (inputType: 'create' | 'update', fields) => {
|
||||
if (fields) {
|
||||
Object.keys(fields).forEach(field => {
|
||||
if (classGraphQLInputTypeFields[field]) {
|
||||
switch (classGraphQLInputTypeFields[field].type) {
|
||||
let inputTypeField;
|
||||
if (inputType === 'create') {
|
||||
inputTypeField = classGraphQLCreateTypeFields[field];
|
||||
} else {
|
||||
inputTypeField = classGraphQLUpdateTypeFields[field];
|
||||
}
|
||||
if (inputTypeField) {
|
||||
switch (inputTypeField.type) {
|
||||
case defaultGraphQLTypes.GEO_POINT:
|
||||
fields[field].__type = 'GeoPoint';
|
||||
break;
|
||||
@@ -36,86 +71,92 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
}
|
||||
};
|
||||
|
||||
const createGraphQLMutationName = `create${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[createGraphQLMutationName] = {
|
||||
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${className} class.`,
|
||||
args: {
|
||||
fields,
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
if (isCreateEnabled) {
|
||||
const createGraphQLMutationName = `create${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[createGraphQLMutationName] = {
|
||||
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${className} class.`,
|
||||
args: {
|
||||
fields: createFields,
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
transformTypes(fields);
|
||||
transformTypes('create', fields);
|
||||
|
||||
return await objectsMutations.createObject(
|
||||
className,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
return await objectsMutations.createObject(
|
||||
className,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const updateGraphQLMutationName = `update${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[updateGraphQLMutationName] = {
|
||||
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${className} class.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
fields,
|
||||
},
|
||||
type: defaultGraphQLTypes.UPDATE_RESULT,
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { objectId, fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
if (isUpdateEnabled) {
|
||||
const updateGraphQLMutationName = `update${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[updateGraphQLMutationName] = {
|
||||
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${className} class.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
fields: updateFields,
|
||||
},
|
||||
type: defaultGraphQLTypes.UPDATE_RESULT,
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { objectId, fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
transformTypes(fields);
|
||||
transformTypes('update', fields);
|
||||
|
||||
return await objectsMutations.updateObject(
|
||||
className,
|
||||
objectId,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
return await objectsMutations.updateObject(
|
||||
className,
|
||||
objectId,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const deleteGraphQLMutationName = `delete${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[deleteGraphQLMutationName] = {
|
||||
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${className} class.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { objectId } = args;
|
||||
const { config, auth, info } = context;
|
||||
if (isDestroyEnabled) {
|
||||
const deleteGraphQLMutationName = `delete${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[deleteGraphQLMutationName] = {
|
||||
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${className} class.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { objectId } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return await objectsMutations.deleteObject(
|
||||
className,
|
||||
objectId,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
return await objectsMutations.deleteObject(
|
||||
className,
|
||||
objectId,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { load };
|
||||
|
||||
@@ -3,9 +3,24 @@ import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
import * as parseClassTypes from './parseClassTypes';
|
||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||
|
||||
const load = (parseGraphQLSchema, parseClass) => {
|
||||
const className = parseClass.className;
|
||||
const getParseClassQueryConfig = function(
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
return (parseClassConfig && parseClassConfig.query) || {};
|
||||
};
|
||||
|
||||
const load = function(
|
||||
parseGraphQLSchema,
|
||||
parseClass,
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
const { className } = parseClass;
|
||||
const {
|
||||
get: isGetEnabled = true,
|
||||
find: isFindEnabled = true,
|
||||
} = getParseClassQueryConfig(parseClassConfig);
|
||||
|
||||
const {
|
||||
classGraphQLOutputType,
|
||||
@@ -13,90 +28,94 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
classGraphQLFindResultType,
|
||||
} = parseGraphQLSchema.parseClassTypes[className];
|
||||
|
||||
const getGraphQLQueryName = `get${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsQueries[getGraphQLQueryName] = {
|
||||
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${className} class by its id.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
||||
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const { objectId, readPreference, includeReadPreference } = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
if (isGetEnabled) {
|
||||
const getGraphQLQueryName = `get${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsQueries[getGraphQLQueryName] = {
|
||||
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${className} class by its id.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
||||
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const { objectId, readPreference, includeReadPreference } = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
);
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
);
|
||||
|
||||
return await objectsQueries.getObject(
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
return await objectsQueries.getObject(
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const findGraphQLQueryName = `find${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsQueries[findGraphQLQueryName] = {
|
||||
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${className} class.`,
|
||||
args: classGraphQLFindArgs,
|
||||
type: new GraphQLNonNull(classGraphQLFindResultType),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
} = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
if (isFindEnabled) {
|
||||
const findGraphQLQueryName = `find${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsQueries[findGraphQLQueryName] = {
|
||||
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${className} class.`,
|
||||
args: classGraphQLFindArgs,
|
||||
type: new GraphQLNonNull(classGraphQLFindResultType),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
} = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
);
|
||||
const parseOrder = order && order.join(',');
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
);
|
||||
const parseOrder = order && order.join(',');
|
||||
|
||||
return await objectsQueries.findObjects(
|
||||
className,
|
||||
where,
|
||||
parseOrder,
|
||||
skip,
|
||||
limit,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0])
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
return await objectsQueries.findObjects(
|
||||
className,
|
||||
where,
|
||||
parseOrder,
|
||||
skip,
|
||||
limit,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0])
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { load };
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||
|
||||
const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
||||
switch (parseType) {
|
||||
@@ -161,14 +162,105 @@ const extractKeysAndInclude = selectedFields => {
|
||||
return { keys, include };
|
||||
};
|
||||
|
||||
const load = (parseGraphQLSchema, parseClass) => {
|
||||
const className = parseClass.className;
|
||||
const getParseClassTypeConfig = function(
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
return (parseClassConfig && parseClassConfig.type) || {};
|
||||
};
|
||||
|
||||
const getInputFieldsAndConstraints = function(
|
||||
parseClass,
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
const classFields = Object.keys(parseClass.fields);
|
||||
const {
|
||||
inputFields: allowedInputFields,
|
||||
outputFields: allowedOutputFields,
|
||||
constraintFields: allowedConstraintFields,
|
||||
sortFields: allowedSortFields,
|
||||
} = getParseClassTypeConfig(parseClassConfig);
|
||||
|
||||
const classCustomFields = classFields.filter(
|
||||
field => !Object.keys(defaultGraphQLTypes.CLASS_FIELDS).includes(field)
|
||||
);
|
||||
let classOutputFields;
|
||||
let classCreateFields;
|
||||
let classUpdateFields;
|
||||
let classConstraintFields;
|
||||
let classSortFields;
|
||||
|
||||
// All allowed customs fields
|
||||
const classCustomFields = classFields.filter(field => {
|
||||
return !Object.keys(defaultGraphQLTypes.CLASS_FIELDS).includes(field);
|
||||
});
|
||||
|
||||
if (allowedInputFields && allowedInputFields.create) {
|
||||
classCreateFields = classCustomFields.filter(field => {
|
||||
return allowedInputFields.create.includes(field);
|
||||
});
|
||||
} else {
|
||||
classCreateFields = classCustomFields;
|
||||
}
|
||||
if (allowedInputFields && allowedInputFields.update) {
|
||||
classUpdateFields = classCustomFields.filter(field => {
|
||||
return allowedInputFields.update.includes(field);
|
||||
});
|
||||
} else {
|
||||
classUpdateFields = classCustomFields;
|
||||
}
|
||||
|
||||
if (allowedOutputFields) {
|
||||
classOutputFields = classCustomFields.filter(field => {
|
||||
return allowedOutputFields.includes(field);
|
||||
});
|
||||
} else {
|
||||
classOutputFields = classCustomFields;
|
||||
}
|
||||
|
||||
if (allowedConstraintFields) {
|
||||
classConstraintFields = classCustomFields.filter(field => {
|
||||
return allowedConstraintFields.includes(field);
|
||||
});
|
||||
} else {
|
||||
classConstraintFields = classFields;
|
||||
}
|
||||
|
||||
if (allowedSortFields) {
|
||||
classSortFields = allowedSortFields;
|
||||
if (!classSortFields.length) {
|
||||
// must have at least 1 order field
|
||||
// otherwise the FindArgs Input Type will throw.
|
||||
classSortFields.push({
|
||||
field: 'objectId',
|
||||
asc: true,
|
||||
desc: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
classSortFields = classFields.map(field => {
|
||||
return { field, asc: true, desc: true };
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
classCreateFields,
|
||||
classUpdateFields,
|
||||
classConstraintFields,
|
||||
classOutputFields,
|
||||
classSortFields,
|
||||
};
|
||||
};
|
||||
|
||||
const load = (
|
||||
parseGraphQLSchema,
|
||||
parseClass,
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) => {
|
||||
const { className } = parseClass;
|
||||
const {
|
||||
classCreateFields,
|
||||
classUpdateFields,
|
||||
classOutputFields,
|
||||
classConstraintFields,
|
||||
classSortFields,
|
||||
} = getInputFieldsAndConstraints(parseClass, parseClassConfig);
|
||||
|
||||
const classGraphQLScalarTypeName = `${className}Pointer`;
|
||||
const parseScalarValue = value => {
|
||||
@@ -271,12 +363,12 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLRelationOpType);
|
||||
|
||||
const classGraphQLInputTypeName = `${className}Fields`;
|
||||
const classGraphQLInputType = new GraphQLInputObjectType({
|
||||
name: classGraphQLInputTypeName,
|
||||
description: `The ${classGraphQLInputTypeName} input type is used in operations that involve inputting objects of ${className} class.`,
|
||||
const classGraphQLCreateTypeName = `${className}CreateFields`;
|
||||
const classGraphQLCreateType = new GraphQLInputObjectType({
|
||||
name: classGraphQLCreateTypeName,
|
||||
description: `The ${classGraphQLCreateTypeName} input type is used in operations that involve creation of objects in the ${className} class.`,
|
||||
fields: () =>
|
||||
classCustomFields.reduce(
|
||||
classCreateFields.reduce(
|
||||
(fields, field) => {
|
||||
const type = mapInputType(
|
||||
parseClass.fields[field].type,
|
||||
@@ -300,7 +392,38 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
}
|
||||
),
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLInputType);
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLCreateType);
|
||||
|
||||
const classGraphQLUpdateTypeName = `${className}UpdateFields`;
|
||||
const classGraphQLUpdateType = new GraphQLInputObjectType({
|
||||
name: classGraphQLUpdateTypeName,
|
||||
description: `The ${classGraphQLUpdateTypeName} input type is used in operations that involve creation of objects in the ${className} class.`,
|
||||
fields: () =>
|
||||
classUpdateFields.reduce(
|
||||
(fields, field) => {
|
||||
const type = mapInputType(
|
||||
parseClass.fields[field].type,
|
||||
parseClass.fields[field].targetClass,
|
||||
parseGraphQLSchema.parseClassTypes
|
||||
);
|
||||
if (type) {
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
},
|
||||
{
|
||||
ACL: defaultGraphQLTypes.ACL_ATT,
|
||||
}
|
||||
),
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLUpdateType);
|
||||
|
||||
const classGraphQLConstraintTypeName = `${className}PointerConstraint`;
|
||||
const classGraphQLConstraintType = new GraphQLInputObjectType({
|
||||
@@ -333,7 +456,7 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
name: classGraphQLConstraintsTypeName,
|
||||
description: `The ${classGraphQLConstraintsTypeName} input type is used in operations that involve filtering objects of ${className} class.`,
|
||||
fields: () => ({
|
||||
...classFields.reduce((fields, field) => {
|
||||
...classConstraintFields.reduce((fields, field) => {
|
||||
const type = mapConstraintType(
|
||||
parseClass.fields[field].type,
|
||||
parseClass.fields[field].targetClass,
|
||||
@@ -371,12 +494,18 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
const classGraphQLOrderType = new GraphQLEnumType({
|
||||
name: classGraphQLOrderTypeName,
|
||||
description: `The ${classGraphQLOrderTypeName} input type is used when sorting objects of the ${className} class.`,
|
||||
values: classFields.reduce((orderFields, field) => {
|
||||
return {
|
||||
...orderFields,
|
||||
[`${field}_ASC`]: { value: field },
|
||||
[`${field}_DESC`]: { value: `-${field}` },
|
||||
values: classSortFields.reduce((sortFields, fieldConfig) => {
|
||||
const { field, asc, desc } = fieldConfig;
|
||||
const updatedSortFields = {
|
||||
...sortFields,
|
||||
};
|
||||
if (asc) {
|
||||
updatedSortFields[`${field}_ASC`] = { value: field };
|
||||
}
|
||||
if (desc) {
|
||||
updatedSortFields[`${field}_DESC`] = { value: `-${field}` };
|
||||
}
|
||||
return updatedSortFields;
|
||||
}, {}),
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLOrderType);
|
||||
@@ -400,7 +529,7 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
|
||||
const classGraphQLOutputTypeName = `${className}Class`;
|
||||
const outputFields = () => {
|
||||
return classCustomFields.reduce((fields, field) => {
|
||||
return classOutputFields.reduce((fields, field) => {
|
||||
const type = mapOutputType(
|
||||
parseClass.fields[field].type,
|
||||
parseClass.fields[field].targetClass,
|
||||
@@ -531,7 +660,8 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
parseGraphQLSchema.parseClassTypes[className] = {
|
||||
classGraphQLScalarType,
|
||||
classGraphQLRelationOpType,
|
||||
classGraphQLInputType,
|
||||
classGraphQLCreateType,
|
||||
classGraphQLUpdateType,
|
||||
classGraphQLConstraintType,
|
||||
classGraphQLConstraintsType,
|
||||
classGraphQLFindArgs,
|
||||
@@ -552,37 +682,32 @@ const load = (parseGraphQLSchema, parseClass) => {
|
||||
parseGraphQLSchema.meType = meType;
|
||||
parseGraphQLSchema.graphQLTypes.push(meType);
|
||||
|
||||
const userSignUpInputTypeName = `_UserSignUpFields`;
|
||||
const userSignUpInputTypeName = '_UserSignUpFields';
|
||||
const userSignUpInputType = new GraphQLInputObjectType({
|
||||
name: userSignUpInputTypeName,
|
||||
description: `The ${userSignUpInputTypeName} input type is used in operations that involve inputting objects of ${className} class when signing up.`,
|
||||
fields: () =>
|
||||
classCustomFields.reduce(
|
||||
(fields, field) => {
|
||||
const type = mapInputType(
|
||||
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;
|
||||
}
|
||||
},
|
||||
{
|
||||
ACL: defaultGraphQLTypes.ACL_ATT,
|
||||
classCreateFields.reduce((fields, field) => {
|
||||
const type = mapInputType(
|
||||
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.parseClassTypes[
|
||||
'_User'
|
||||
|
||||
@@ -11,6 +11,9 @@ import * as objectsMutations from './objectsMutations';
|
||||
const usersRouter = new UsersRouter();
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
if (parseGraphQLSchema.isUsersClassDisabled) {
|
||||
return;
|
||||
}
|
||||
const fields = {};
|
||||
|
||||
fields.signUp = {
|
||||
|
||||
@@ -6,6 +6,9 @@ import Auth from '../../Auth';
|
||||
import { extractKeysAndInclude } from './parseClassTypes';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
if (parseGraphQLSchema.isUsersClassDisabled) {
|
||||
return;
|
||||
}
|
||||
const fields = {};
|
||||
|
||||
fields.me = {
|
||||
|
||||
Reference in New Issue
Block a user