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
973
spec/ParseGraphQLController.spec.js
Normal file
973
spec/ParseGraphQLController.spec.js
Normal file
@@ -0,0 +1,973 @@
|
|||||||
|
const {
|
||||||
|
default: ParseGraphQLController,
|
||||||
|
GraphQLConfigClassName,
|
||||||
|
GraphQLConfigId,
|
||||||
|
GraphQLConfigKey,
|
||||||
|
} = require('../lib/Controllers/ParseGraphQLController');
|
||||||
|
const { isEqual } = require('lodash');
|
||||||
|
|
||||||
|
describe('ParseGraphQLController', () => {
|
||||||
|
let parseServer;
|
||||||
|
let databaseController;
|
||||||
|
let cacheController;
|
||||||
|
let databaseUpdateArgs;
|
||||||
|
|
||||||
|
// Holds the graphQLConfig in memory instead of using the db
|
||||||
|
let graphQLConfigRecord;
|
||||||
|
|
||||||
|
const setConfigOnDb = graphQLConfigData => {
|
||||||
|
graphQLConfigRecord = {
|
||||||
|
objectId: GraphQLConfigId,
|
||||||
|
[GraphQLConfigKey]: graphQLConfigData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const removeConfigFromDb = () => {
|
||||||
|
graphQLConfigRecord = null;
|
||||||
|
};
|
||||||
|
const getConfigFromDb = () => {
|
||||||
|
return graphQLConfigRecord;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
parseServer = await global.reconfigureServer({
|
||||||
|
schemaCacheTTL: 100,
|
||||||
|
});
|
||||||
|
databaseController = parseServer.config.databaseController;
|
||||||
|
cacheController = parseServer.config.cacheController;
|
||||||
|
|
||||||
|
const defaultFind = databaseController.find.bind(databaseController);
|
||||||
|
databaseController.find = async (className, query, ...args) => {
|
||||||
|
if (
|
||||||
|
className === GraphQLConfigClassName &&
|
||||||
|
isEqual(query, { objectId: GraphQLConfigId })
|
||||||
|
) {
|
||||||
|
const graphQLConfigRecord = getConfigFromDb();
|
||||||
|
return graphQLConfigRecord ? [graphQLConfigRecord] : [];
|
||||||
|
} else {
|
||||||
|
return defaultFind(className, query, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultUpdate = databaseController.update.bind(databaseController);
|
||||||
|
databaseController.update = async (
|
||||||
|
className,
|
||||||
|
query,
|
||||||
|
update,
|
||||||
|
fullQueryOptions
|
||||||
|
) => {
|
||||||
|
databaseUpdateArgs = [className, query, update, fullQueryOptions];
|
||||||
|
if (
|
||||||
|
className === GraphQLConfigClassName &&
|
||||||
|
isEqual(query, { objectId: GraphQLConfigId }) &&
|
||||||
|
update &&
|
||||||
|
!!update[GraphQLConfigKey] &&
|
||||||
|
fullQueryOptions &&
|
||||||
|
isEqual(fullQueryOptions, { upsert: true })
|
||||||
|
) {
|
||||||
|
setConfigOnDb(update[GraphQLConfigKey]);
|
||||||
|
} else {
|
||||||
|
return defaultUpdate(...databaseUpdateArgs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
databaseUpdateArgs = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should require a databaseController', () => {
|
||||||
|
expect(() => new ParseGraphQLController()).toThrow(
|
||||||
|
'ParseGraphQLController requires a "databaseController" to be instantiated.'
|
||||||
|
);
|
||||||
|
expect(() => new ParseGraphQLController({ cacheController })).toThrow(
|
||||||
|
'ParseGraphQLController requires a "databaseController" to be instantiated.'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ParseGraphQLController({
|
||||||
|
cacheController,
|
||||||
|
mountGraphQL: false,
|
||||||
|
})
|
||||||
|
).toThrow(
|
||||||
|
'ParseGraphQLController requires a "databaseController" to be instantiated.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should construct without a cacheController', () => {
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
})
|
||||||
|
).not.toThrow();
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
mountGraphQL: true,
|
||||||
|
})
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
it('should set isMounted to true if config.mountGraphQL is true', () => {
|
||||||
|
const mountedController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
mountGraphQL: true,
|
||||||
|
});
|
||||||
|
expect(mountedController.isMounted).toBe(true);
|
||||||
|
const unmountedController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
mountGraphQL: false,
|
||||||
|
});
|
||||||
|
expect(unmountedController.isMounted).toBe(false);
|
||||||
|
const unmountedController2 = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expect(unmountedController2.isMounted).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getGraphQLConfig', () => {
|
||||||
|
it('should return an empty graphQLConfig if collection has none', async () => {
|
||||||
|
removeConfigFromDb();
|
||||||
|
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
mountGraphQL: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||||
|
expect(graphQLConfig).toEqual({});
|
||||||
|
});
|
||||||
|
it('should return an existing graphQLConfig', async () => {
|
||||||
|
setConfigOnDb({ enabledForClasses: ['_User'] });
|
||||||
|
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
mountGraphQL: false,
|
||||||
|
});
|
||||||
|
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||||
|
expect(graphQLConfig).toEqual({ enabledForClasses: ['_User'] });
|
||||||
|
});
|
||||||
|
it('should use the cache if mounted, and return the stored graphQLConfig', async () => {
|
||||||
|
removeConfigFromDb();
|
||||||
|
cacheController.graphQL.clear();
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
cacheController,
|
||||||
|
mountGraphQL: true,
|
||||||
|
});
|
||||||
|
cacheController.graphQL.put(parseGraphQLController.configCacheKey, {
|
||||||
|
enabledForClasses: ['SuperCar'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||||
|
expect(graphQLConfig).toEqual({ enabledForClasses: ['SuperCar'] });
|
||||||
|
});
|
||||||
|
it('should use the database when mounted and cache is empty', async () => {
|
||||||
|
setConfigOnDb({ disabledForClasses: ['SuperCar'] });
|
||||||
|
cacheController.graphQL.clear();
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
cacheController,
|
||||||
|
mountGraphQL: true,
|
||||||
|
});
|
||||||
|
const graphQLConfig = await parseGraphQLController.getGraphQLConfig();
|
||||||
|
expect(graphQLConfig).toEqual({ disabledForClasses: ['SuperCar'] });
|
||||||
|
});
|
||||||
|
it('should store the graphQLConfig in cache if mounted', async () => {
|
||||||
|
setConfigOnDb({ enabledForClasses: ['SuperCar'] });
|
||||||
|
cacheController.graphQL.clear();
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
cacheController,
|
||||||
|
mountGraphQL: true,
|
||||||
|
});
|
||||||
|
const cachedValueBefore = await cacheController.graphQL.get(
|
||||||
|
parseGraphQLController.configCacheKey
|
||||||
|
);
|
||||||
|
expect(cachedValueBefore).toBeNull();
|
||||||
|
await parseGraphQLController.getGraphQLConfig();
|
||||||
|
const cachedValueAfter = await cacheController.graphQL.get(
|
||||||
|
parseGraphQLController.configCacheKey
|
||||||
|
);
|
||||||
|
expect(cachedValueAfter).toEqual({ enabledForClasses: ['SuperCar'] });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateGraphQLConfig', () => {
|
||||||
|
const successfulUpdateResponse = { response: { result: true } };
|
||||||
|
|
||||||
|
it('should throw if graphQLConfig is not provided', async function() {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig()
|
||||||
|
).toBeRejectedWith('You must provide a graphQLConfig!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correct update the graphQLConfig object using the databaseController', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
const graphQLConfig = {
|
||||||
|
enabledForClasses: ['ClassA', 'ClassB'],
|
||||||
|
disabledForClasses: [],
|
||||||
|
classConfigs: [
|
||||||
|
{ className: 'ClassA', query: { get: false } },
|
||||||
|
{ className: 'ClassB', mutation: { destroy: false }, type: {} },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await parseGraphQLController.updateGraphQLConfig(graphQLConfig);
|
||||||
|
|
||||||
|
expect(databaseUpdateArgs).toBeTruthy();
|
||||||
|
const [className, query, update, op] = databaseUpdateArgs;
|
||||||
|
expect(className).toBe(GraphQLConfigClassName);
|
||||||
|
expect(query).toEqual({ objectId: GraphQLConfigId });
|
||||||
|
expect(update).toEqual({
|
||||||
|
[GraphQLConfigKey]: graphQLConfig,
|
||||||
|
});
|
||||||
|
expect(op).toEqual({ upsert: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if graphQLConfig is not an object', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig([])
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig(function() {})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig(Promise.resolve({}))
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig('')
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if graphQLConfig has an invalid root key', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({ invalidKey: true })
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if graphQLConfig has invalid class filters', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({ enabledForClasses: {} })
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
enabledForClasses: [undefined],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
disabledForClasses: [null],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
enabledForClasses: ['_User', null],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({ disabledForClasses: [''] })
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
enabledForClasses: [],
|
||||||
|
disabledForClasses: ['_User'],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if classConfigs array is invalid', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({ classConfigs: {} })
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({ classConfigs: [null] })
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [undefined],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [{ className: 'ValidClass' }, null],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({ classConfigs: [] })
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if a classConfig has invalid type settings', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
invalidKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if a classConfig has invalid type.inputFields settings', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: 'SuperCar',
|
||||||
|
type: {
|
||||||
|
inputFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: 'SuperCar',
|
||||||
|
type: {
|
||||||
|
inputFields: {
|
||||||
|
invalidKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: 'SuperCar',
|
||||||
|
type: {
|
||||||
|
inputFields: {
|
||||||
|
create: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: 'SuperCar',
|
||||||
|
type: {
|
||||||
|
inputFields: {
|
||||||
|
update: [null],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: 'SuperCar',
|
||||||
|
type: {
|
||||||
|
inputFields: {
|
||||||
|
create: [],
|
||||||
|
update: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: 'SuperCar',
|
||||||
|
type: {
|
||||||
|
inputFields: {
|
||||||
|
create: ['make', 'model'],
|
||||||
|
update: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if a classConfig has invalid type.outputFields settings', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
outputFields: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
outputFields: [null],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
outputFields: ['name', undefined],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
outputFields: [''],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
outputFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
outputFields: ['name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if a classConfig has invalid type.constraintFields settings', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
constraintFields: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
constraintFields: [null],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
constraintFields: ['name', undefined],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
constraintFields: [''],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
constraintFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
constraintFields: ['name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if a classConfig has invalid type.sortFields settings', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: [null],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: [
|
||||||
|
{
|
||||||
|
field: undefined,
|
||||||
|
asc: true,
|
||||||
|
desc: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: [
|
||||||
|
{
|
||||||
|
field: '',
|
||||||
|
asc: true,
|
||||||
|
desc: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
asc: true,
|
||||||
|
desc: 'false',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
asc: true,
|
||||||
|
desc: true,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
sortFields: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
asc: true,
|
||||||
|
desc: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if a classConfig has invalid query params', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
query: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
query: {
|
||||||
|
invalidKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
query: {
|
||||||
|
get: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
query: {
|
||||||
|
find: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
query: {
|
||||||
|
get: false,
|
||||||
|
find: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
query: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should throw if a classConfig has invalid mutation params', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
mutation: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
mutation: {
|
||||||
|
invalidKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
mutation: {
|
||||||
|
destroy: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
mutation: {
|
||||||
|
update: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
mutation: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
mutation: {
|
||||||
|
create: true,
|
||||||
|
update: true,
|
||||||
|
destroy: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolvedTo(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if _User create fields is missing username or password', async () => {
|
||||||
|
const parseGraphQLController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
});
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
inputFields: {
|
||||||
|
create: ['username', 'no-password'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeRejected();
|
||||||
|
expectAsync(
|
||||||
|
parseGraphQLController.updateGraphQLConfig({
|
||||||
|
classConfigs: [
|
||||||
|
{
|
||||||
|
className: '_User',
|
||||||
|
type: {
|
||||||
|
inputFields: {
|
||||||
|
create: ['username', 'password'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBeResolved(successfulUpdateResponse);
|
||||||
|
});
|
||||||
|
it('should update the cache if mounted', async () => {
|
||||||
|
removeConfigFromDb();
|
||||||
|
cacheController.graphQL.clear();
|
||||||
|
const mountedController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
cacheController,
|
||||||
|
mountGraphQL: true,
|
||||||
|
});
|
||||||
|
const unmountedController = new ParseGraphQLController({
|
||||||
|
databaseController,
|
||||||
|
cacheController,
|
||||||
|
mountGraphQL: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let cacheBeforeValue;
|
||||||
|
let cacheAfterValue;
|
||||||
|
|
||||||
|
cacheBeforeValue = await cacheController.graphQL.get(
|
||||||
|
mountedController.configCacheKey
|
||||||
|
);
|
||||||
|
expect(cacheBeforeValue).toBeNull();
|
||||||
|
|
||||||
|
await mountedController.updateGraphQLConfig({
|
||||||
|
enabledForClasses: ['SuperCar'],
|
||||||
|
});
|
||||||
|
cacheAfterValue = await cacheController.graphQL.get(
|
||||||
|
mountedController.configCacheKey
|
||||||
|
);
|
||||||
|
expect(cacheAfterValue).toEqual({ enabledForClasses: ['SuperCar'] });
|
||||||
|
|
||||||
|
// reset
|
||||||
|
removeConfigFromDb();
|
||||||
|
cacheController.graphQL.clear();
|
||||||
|
|
||||||
|
cacheBeforeValue = await cacheController.graphQL.get(
|
||||||
|
unmountedController.configCacheKey
|
||||||
|
);
|
||||||
|
expect(cacheBeforeValue).toBeNull();
|
||||||
|
|
||||||
|
await unmountedController.updateGraphQLConfig({
|
||||||
|
enabledForClasses: ['SuperCar'],
|
||||||
|
});
|
||||||
|
cacheAfterValue = await cacheController.graphQL.get(
|
||||||
|
unmountedController.configCacheKey
|
||||||
|
);
|
||||||
|
expect(cacheAfterValue).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@ const { ParseGraphQLSchema } = require('../lib/GraphQL/ParseGraphQLSchema');
|
|||||||
describe('ParseGraphQLSchema', () => {
|
describe('ParseGraphQLSchema', () => {
|
||||||
let parseServer;
|
let parseServer;
|
||||||
let databaseController;
|
let databaseController;
|
||||||
|
let parseGraphQLController;
|
||||||
let parseGraphQLSchema;
|
let parseGraphQLSchema;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -11,28 +12,37 @@ describe('ParseGraphQLSchema', () => {
|
|||||||
schemaCacheTTL: 100,
|
schemaCacheTTL: 100,
|
||||||
});
|
});
|
||||||
databaseController = parseServer.config.databaseController;
|
databaseController = parseServer.config.databaseController;
|
||||||
parseGraphQLSchema = new ParseGraphQLSchema(
|
parseGraphQLController = parseServer.config.parseGraphQLController;
|
||||||
|
parseGraphQLSchema = new ParseGraphQLSchema({
|
||||||
databaseController,
|
databaseController,
|
||||||
defaultLogger
|
parseGraphQLController,
|
||||||
);
|
log: defaultLogger,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
it('should require a databaseController and a log instance', () => {
|
it('should require a parseGraphQLController, databaseController and a log instance', () => {
|
||||||
expect(() => new ParseGraphQLSchema()).toThrow(
|
expect(() => new ParseGraphQLSchema()).toThrow(
|
||||||
'You must provide a databaseController instance!'
|
'You must provide a parseGraphQLController instance!'
|
||||||
);
|
);
|
||||||
expect(() => new ParseGraphQLSchema({})).toThrow(
|
expect(
|
||||||
'You must provide a log instance!'
|
() => new ParseGraphQLSchema({ parseGraphQLController: {} })
|
||||||
);
|
).toThrow('You must provide a databaseController instance!');
|
||||||
expect(() => new ParseGraphQLSchema({}, {})).not.toThrow();
|
expect(
|
||||||
|
() =>
|
||||||
|
new ParseGraphQLSchema({
|
||||||
|
parseGraphQLController: {},
|
||||||
|
databaseController: {},
|
||||||
|
})
|
||||||
|
).toThrow('You must provide a log instance!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('load', () => {
|
describe('load', () => {
|
||||||
it('should cache schema', async () => {
|
it('should cache schema', async () => {
|
||||||
const graphQLSchema = await parseGraphQLSchema.load();
|
const graphQLSchema = await parseGraphQLSchema.load();
|
||||||
expect(graphQLSchema).toBe(await parseGraphQLSchema.load());
|
const updatedGraphQLSchema = await parseGraphQLSchema.load();
|
||||||
|
expect(graphQLSchema).toBe(updatedGraphQLSchema);
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
expect(graphQLSchema).toBe(await parseGraphQLSchema.load());
|
expect(graphQLSchema).toBe(await parseGraphQLSchema.load());
|
||||||
});
|
});
|
||||||
@@ -40,26 +50,72 @@ describe('ParseGraphQLSchema', () => {
|
|||||||
it('should load a brand new GraphQL Schema if Parse Schema changes', async () => {
|
it('should load a brand new GraphQL Schema if Parse Schema changes', async () => {
|
||||||
await parseGraphQLSchema.load();
|
await parseGraphQLSchema.load();
|
||||||
const parseClasses = parseGraphQLSchema.parseClasses;
|
const parseClasses = parseGraphQLSchema.parseClasses;
|
||||||
const parseClassesString = parseGraphQLSchema.parseClasses;
|
const parseClassesString = parseGraphQLSchema.parseClassesString;
|
||||||
const parseClassTypes = parseGraphQLSchema.parseClasses;
|
const parseClassTypes = parseGraphQLSchema.parseClassTypes;
|
||||||
const graphQLSchema = parseGraphQLSchema.parseClasses;
|
const graphQLSchema = parseGraphQLSchema.graphQLSchema;
|
||||||
const graphQLTypes = parseGraphQLSchema.parseClasses;
|
const graphQLTypes = parseGraphQLSchema.graphQLTypes;
|
||||||
const graphQLQueries = parseGraphQLSchema.parseClasses;
|
const graphQLQueries = parseGraphQLSchema.graphQLQueries;
|
||||||
const graphQLMutations = parseGraphQLSchema.parseClasses;
|
const graphQLMutations = parseGraphQLSchema.graphQLMutations;
|
||||||
const graphQLSubscriptions = parseGraphQLSchema.parseClasses;
|
const graphQLSubscriptions = parseGraphQLSchema.graphQLSubscriptions;
|
||||||
const newClassObject = new Parse.Object('NewClass');
|
const newClassObject = new Parse.Object('NewClass');
|
||||||
await newClassObject.save();
|
await newClassObject.save();
|
||||||
await databaseController.schemaCache.clear();
|
await databaseController.schemaCache.clear();
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
await parseGraphQLSchema.load();
|
await parseGraphQLSchema.load();
|
||||||
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
|
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
|
||||||
expect(parseClassesString).not.toBe(parseGraphQLSchema.parseClasses);
|
expect(parseClassesString).not.toBe(
|
||||||
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClasses);
|
parseGraphQLSchema.parseClassesString
|
||||||
expect(graphQLSchema).not.toBe(parseGraphQLSchema.parseClasses);
|
);
|
||||||
expect(graphQLTypes).not.toBe(parseGraphQLSchema.parseClasses);
|
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
|
||||||
expect(graphQLQueries).not.toBe(parseGraphQLSchema.parseClasses);
|
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
|
||||||
expect(graphQLMutations).not.toBe(parseGraphQLSchema.parseClasses);
|
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);
|
||||||
expect(graphQLSubscriptions).not.toBe(parseGraphQLSchema.parseClasses);
|
expect(graphQLQueries).not.toBe(parseGraphQLSchema.graphQLQueries);
|
||||||
|
expect(graphQLMutations).not.toBe(parseGraphQLSchema.graphQLMutations);
|
||||||
|
expect(graphQLSubscriptions).not.toBe(
|
||||||
|
parseGraphQLSchema.graphQLSubscriptions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load a brand new GraphQL Schema if graphQLConfig changes', async () => {
|
||||||
|
const parseGraphQLController = {
|
||||||
|
graphQLConfig: { enabledForClasses: [] },
|
||||||
|
getGraphQLConfig() {
|
||||||
|
return this.graphQLConfig;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const parseGraphQLSchema = new ParseGraphQLSchema({
|
||||||
|
databaseController,
|
||||||
|
parseGraphQLController,
|
||||||
|
log: defaultLogger,
|
||||||
|
});
|
||||||
|
await parseGraphQLSchema.load();
|
||||||
|
const parseClasses = parseGraphQLSchema.parseClasses;
|
||||||
|
const parseClassesString = parseGraphQLSchema.parseClassesString;
|
||||||
|
const parseClassTypes = parseGraphQLSchema.parseClassTypes;
|
||||||
|
const graphQLSchema = parseGraphQLSchema.graphQLSchema;
|
||||||
|
const graphQLTypes = parseGraphQLSchema.graphQLTypes;
|
||||||
|
const graphQLQueries = parseGraphQLSchema.graphQLQueries;
|
||||||
|
const graphQLMutations = parseGraphQLSchema.graphQLMutations;
|
||||||
|
const graphQLSubscriptions = parseGraphQLSchema.graphQLSubscriptions;
|
||||||
|
|
||||||
|
parseGraphQLController.graphQLConfig = {
|
||||||
|
enabledForClasses: ['_User'],
|
||||||
|
};
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
await parseGraphQLSchema.load();
|
||||||
|
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
|
||||||
|
expect(parseClassesString).not.toBe(
|
||||||
|
parseGraphQLSchema.parseClassesString
|
||||||
|
);
|
||||||
|
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClassTypes);
|
||||||
|
expect(graphQLSchema).not.toBe(parseGraphQLSchema.graphQLSchema);
|
||||||
|
expect(graphQLTypes).not.toBe(parseGraphQLSchema.graphQLTypes);
|
||||||
|
expect(graphQLQueries).not.toBe(parseGraphQLSchema.graphQLQueries);
|
||||||
|
expect(graphQLMutations).not.toBe(parseGraphQLSchema.graphQLMutations);
|
||||||
|
expect(graphQLSubscriptions).not.toBe(
|
||||||
|
parseGraphQLSchema.graphQLSubscriptions
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,7 @@ const transformKeyValueForUpdate = (
|
|||||||
switch (key) {
|
switch (key) {
|
||||||
case 'objectId':
|
case 'objectId':
|
||||||
case '_id':
|
case '_id':
|
||||||
if (className === '_GlobalConfig') {
|
if (['_GlobalConfig', '_GraphQLConfig'].includes(className)) {
|
||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
value: parseInt(restValue),
|
value: parseInt(restValue),
|
||||||
@@ -252,7 +252,7 @@ function transformQueryKeyValue(className, key, value, schema, count = false) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'objectId': {
|
case 'objectId': {
|
||||||
if (className === '_GlobalConfig') {
|
if (['_GlobalConfig', '_GraphQLConfig'].includes(className)) {
|
||||||
value = parseInt(value);
|
value = parseInt(value);
|
||||||
}
|
}
|
||||||
return { key: '_id', value };
|
return { key: '_id', value };
|
||||||
|
|||||||
@@ -1133,6 +1133,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
'_JobSchedule',
|
'_JobSchedule',
|
||||||
'_Hooks',
|
'_Hooks',
|
||||||
'_GlobalConfig',
|
'_GlobalConfig',
|
||||||
|
'_GraphQLConfig',
|
||||||
'_Audience',
|
'_Audience',
|
||||||
...results.map(result => result.className),
|
...results.map(result => result.className),
|
||||||
...joins,
|
...joins,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export class CacheController extends AdaptableController {
|
|||||||
|
|
||||||
this.role = new SubCache('role', this);
|
this.role = new SubCache('role', this);
|
||||||
this.user = new SubCache('user', this);
|
this.user = new SubCache('user', this);
|
||||||
|
this.graphQL = new SubCache('graphQL', this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
|
|||||||
375
src/Controllers/ParseGraphQLController.js
Normal file
375
src/Controllers/ParseGraphQLController.js
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
import requiredParameter from '../../lib/requiredParameter';
|
||||||
|
import DatabaseController from './DatabaseController';
|
||||||
|
import CacheController from './CacheController';
|
||||||
|
|
||||||
|
const GraphQLConfigClassName = '_GraphQLConfig';
|
||||||
|
const GraphQLConfigId = '1';
|
||||||
|
const GraphQLConfigKey = 'config';
|
||||||
|
|
||||||
|
class ParseGraphQLController {
|
||||||
|
databaseController: DatabaseController;
|
||||||
|
cacheController: CacheController;
|
||||||
|
isMounted: boolean;
|
||||||
|
configCacheKey: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
params: {
|
||||||
|
databaseController: DatabaseController,
|
||||||
|
cacheController: CacheController,
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
this.databaseController =
|
||||||
|
params.databaseController ||
|
||||||
|
requiredParameter(
|
||||||
|
`ParseGraphQLController requires a "databaseController" to be instantiated.`
|
||||||
|
);
|
||||||
|
this.cacheController = params.cacheController;
|
||||||
|
this.isMounted = !!params.mountGraphQL;
|
||||||
|
this.configCacheKey = GraphQLConfigKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGraphQLConfig(): Promise<ParseGraphQLConfig> {
|
||||||
|
if (this.isMounted) {
|
||||||
|
const _cachedConfig = await this._getCachedGraphQLConfig();
|
||||||
|
if (_cachedConfig) {
|
||||||
|
return _cachedConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await this.databaseController.find(
|
||||||
|
GraphQLConfigClassName,
|
||||||
|
{ objectId: GraphQLConfigId },
|
||||||
|
{ limit: 1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
let graphQLConfig;
|
||||||
|
if (results.length != 1) {
|
||||||
|
// If there is no config in the database - return empty config.
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
graphQLConfig = results[0][GraphQLConfigKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isMounted) {
|
||||||
|
this._putCachedGraphQLConfig(graphQLConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return graphQLConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateGraphQLConfig(
|
||||||
|
graphQLConfig: ParseGraphQLConfig
|
||||||
|
): Promise<ParseGraphQLConfig> {
|
||||||
|
// throws if invalid
|
||||||
|
this._validateGraphQLConfig(
|
||||||
|
graphQLConfig || requiredParameter('You must provide a graphQLConfig!')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transform in dot notation to make sure it works
|
||||||
|
const update = Object.keys(graphQLConfig).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
return {
|
||||||
|
[GraphQLConfigKey]: {
|
||||||
|
...acc[GraphQLConfigKey],
|
||||||
|
[key]: graphQLConfig[key],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ [GraphQLConfigKey]: {} }
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.databaseController.update(
|
||||||
|
GraphQLConfigClassName,
|
||||||
|
{ objectId: GraphQLConfigId },
|
||||||
|
update,
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isMounted) {
|
||||||
|
this._putCachedGraphQLConfig(graphQLConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { response: { result: true } };
|
||||||
|
}
|
||||||
|
|
||||||
|
_getCachedGraphQLConfig() {
|
||||||
|
return this.cacheController.graphQL.get(this.configCacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
_putCachedGraphQLConfig(graphQLConfig: ParseGraphQLConfig) {
|
||||||
|
return this.cacheController.graphQL.put(
|
||||||
|
this.configCacheKey,
|
||||||
|
graphQLConfig,
|
||||||
|
60000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateGraphQLConfig(graphQLConfig: ?ParseGraphQLConfig): void {
|
||||||
|
const errorMessages: string = [];
|
||||||
|
if (!graphQLConfig) {
|
||||||
|
errorMessages.push('cannot be undefined, null or empty');
|
||||||
|
} else if (!isValidSimpleObject(graphQLConfig)) {
|
||||||
|
errorMessages.push('must be a valid object');
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
enabledForClasses = null,
|
||||||
|
disabledForClasses = null,
|
||||||
|
classConfigs = null,
|
||||||
|
...invalidKeys
|
||||||
|
} = graphQLConfig;
|
||||||
|
|
||||||
|
if (Object.keys(invalidKeys).length) {
|
||||||
|
errorMessages.push(
|
||||||
|
`encountered invalid keys: [${Object.keys(invalidKeys)}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
enabledForClasses !== null &&
|
||||||
|
!isValidStringArray(enabledForClasses)
|
||||||
|
) {
|
||||||
|
errorMessages.push(`"enabledForClasses" is not a valid array`);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
disabledForClasses !== null &&
|
||||||
|
!isValidStringArray(disabledForClasses)
|
||||||
|
) {
|
||||||
|
errorMessages.push(`"disabledForClasses" is not a valid array`);
|
||||||
|
}
|
||||||
|
if (classConfigs !== null) {
|
||||||
|
if (Array.isArray(classConfigs)) {
|
||||||
|
classConfigs.forEach(classConfig => {
|
||||||
|
const errorMessage = this._validateClassConfig(classConfig);
|
||||||
|
if (errorMessage) {
|
||||||
|
errorMessages.push(
|
||||||
|
`classConfig:${classConfig.className} is invalid because ${errorMessage}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorMessages.push(`"classConfigs" is not a valid array`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorMessages.length) {
|
||||||
|
throw new Error(`Invalid graphQLConfig: ${errorMessages.join('; ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateClassConfig(classConfig: ?ParseGraphQLClassConfig): string | void {
|
||||||
|
if (!isValidSimpleObject(classConfig)) {
|
||||||
|
return 'it must be a valid object';
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
type = null,
|
||||||
|
query = null,
|
||||||
|
mutation = null,
|
||||||
|
...invalidKeys
|
||||||
|
} = classConfig;
|
||||||
|
if (Object.keys(invalidKeys).length) {
|
||||||
|
return `"invalidKeys" [${Object.keys(
|
||||||
|
invalidKeys
|
||||||
|
)}] should not be present`;
|
||||||
|
}
|
||||||
|
if (typeof className !== 'string' || !className.trim().length) {
|
||||||
|
// TODO consider checking class exists in schema?
|
||||||
|
return `"className" must be a valid string`;
|
||||||
|
}
|
||||||
|
if (type !== null) {
|
||||||
|
if (!isValidSimpleObject(type)) {
|
||||||
|
return `"type" must be a valid object`;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
inputFields = null,
|
||||||
|
outputFields = null,
|
||||||
|
constraintFields = null,
|
||||||
|
sortFields = null,
|
||||||
|
...invalidKeys
|
||||||
|
} = type;
|
||||||
|
if (Object.keys(invalidKeys).length) {
|
||||||
|
return `"type" contains invalid keys, [${Object.keys(invalidKeys)}]`;
|
||||||
|
} else if (outputFields !== null && !isValidStringArray(outputFields)) {
|
||||||
|
return `"outputFields" must be a valid string array`;
|
||||||
|
} else if (
|
||||||
|
constraintFields !== null &&
|
||||||
|
!isValidStringArray(constraintFields)
|
||||||
|
) {
|
||||||
|
return `"constraintFields" must be a valid string array`;
|
||||||
|
}
|
||||||
|
if (sortFields !== null) {
|
||||||
|
if (Array.isArray(sortFields)) {
|
||||||
|
let errorMessage;
|
||||||
|
sortFields.every((sortField, index) => {
|
||||||
|
if (!isValidSimpleObject(sortField)) {
|
||||||
|
errorMessage = `"sortField" at index ${index} is not a valid object`;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const { field, asc, desc, ...invalidKeys } = sortField;
|
||||||
|
if (Object.keys(invalidKeys).length) {
|
||||||
|
errorMessage = `"sortField" at index ${index} contains invalid keys, [${Object.keys(
|
||||||
|
invalidKeys
|
||||||
|
)}]`;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (typeof field !== 'string' || field.trim().length === 0) {
|
||||||
|
errorMessage = `"sortField" at index ${index} did not provide the "field" as a string`;
|
||||||
|
return false;
|
||||||
|
} else if (
|
||||||
|
typeof asc !== 'boolean' ||
|
||||||
|
typeof desc !== 'boolean'
|
||||||
|
) {
|
||||||
|
errorMessage = `"sortField" at index ${index} did not provide "asc" or "desc" as booleans`;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (errorMessage) {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `"sortFields" must be a valid array.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inputFields !== null) {
|
||||||
|
if (isValidSimpleObject(inputFields)) {
|
||||||
|
const {
|
||||||
|
create = null,
|
||||||
|
update = null,
|
||||||
|
...invalidKeys
|
||||||
|
} = inputFields;
|
||||||
|
if (Object.keys(invalidKeys).length) {
|
||||||
|
return `"inputFields" contains invalid keys: [${Object.keys(
|
||||||
|
invalidKeys
|
||||||
|
)}]`;
|
||||||
|
} else {
|
||||||
|
if (update !== null && !isValidStringArray(update)) {
|
||||||
|
return `"inputFields.update" must be a valid string array`;
|
||||||
|
} else if (create !== null) {
|
||||||
|
if (!isValidStringArray(create)) {
|
||||||
|
return `"inputFields.create" must be a valid string array`;
|
||||||
|
} else if (className === '_User') {
|
||||||
|
if (
|
||||||
|
!create.includes('username') ||
|
||||||
|
!create.includes('password')
|
||||||
|
) {
|
||||||
|
return `"inputFields.create" must include required fields, username and password`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `"inputFields" must be a valid object`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query !== null) {
|
||||||
|
if (isValidSimpleObject(query)) {
|
||||||
|
const { find = null, get = null, ...invalidKeys } = query;
|
||||||
|
if (Object.keys(invalidKeys).length) {
|
||||||
|
return `"query" contains invalid keys, [${Object.keys(
|
||||||
|
invalidKeys
|
||||||
|
)}]`;
|
||||||
|
} else if (find !== null && typeof find !== 'boolean') {
|
||||||
|
return `"query.find" must be a boolean`;
|
||||||
|
} else if (get !== null && typeof get !== 'boolean') {
|
||||||
|
return `"query.get" must be a boolean`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `"query" must be a valid object`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mutation !== null) {
|
||||||
|
if (isValidSimpleObject(mutation)) {
|
||||||
|
const {
|
||||||
|
create = null,
|
||||||
|
update = null,
|
||||||
|
destroy = null,
|
||||||
|
...invalidKeys
|
||||||
|
} = mutation;
|
||||||
|
if (Object.keys(invalidKeys).length) {
|
||||||
|
return `"mutation" contains invalid keys, [${Object.keys(
|
||||||
|
invalidKeys
|
||||||
|
)}]`;
|
||||||
|
}
|
||||||
|
if (create !== null && typeof create !== 'boolean') {
|
||||||
|
return `"mutation.create" must be a boolean`;
|
||||||
|
}
|
||||||
|
if (update !== null && typeof update !== 'boolean') {
|
||||||
|
return `"mutation.update" must be a boolean`;
|
||||||
|
}
|
||||||
|
if (destroy !== null && typeof destroy !== 'boolean') {
|
||||||
|
return `"mutation.destroy" must be a boolean`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `"mutation" must be a valid object`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidStringArray = function(array): boolean {
|
||||||
|
return Array.isArray(array)
|
||||||
|
? !array.some(s => typeof s !== 'string' || s.trim().length < 1)
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Ensures the obj is a simple JSON/{}
|
||||||
|
* object, i.e. not an array, null, date
|
||||||
|
* etc.
|
||||||
|
*/
|
||||||
|
const isValidSimpleObject = function(obj): boolean {
|
||||||
|
return (
|
||||||
|
typeof obj === 'object' &&
|
||||||
|
!Array.isArray(obj) &&
|
||||||
|
obj !== null &&
|
||||||
|
obj instanceof Date !== true &&
|
||||||
|
obj instanceof Promise !== true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ParseGraphQLConfig {
|
||||||
|
enabledForClasses?: string[];
|
||||||
|
disabledForClasses?: string[];
|
||||||
|
classConfigs?: ParseGraphQLClassConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParseGraphQLClassConfig {
|
||||||
|
className: string;
|
||||||
|
/* The `type` object contains options for how the class types are generated */
|
||||||
|
type: ?{
|
||||||
|
/* Fields that are allowed when creating or updating an object. */
|
||||||
|
inputFields: ?{
|
||||||
|
/* Leave blank to allow all available fields in the schema. */
|
||||||
|
create?: string[],
|
||||||
|
update?: string[],
|
||||||
|
},
|
||||||
|
/* Fields on the edges that can be resolved from a query, i.e. the Result Type. */
|
||||||
|
outputFields: ?(string[]),
|
||||||
|
/* Fields by which a query can be filtered, i.e. the `where` object. */
|
||||||
|
constraintFields: ?(string[]),
|
||||||
|
/* Fields by which a query can be sorted; */
|
||||||
|
sortFields: ?({
|
||||||
|
field: string,
|
||||||
|
asc: boolean,
|
||||||
|
desc: boolean,
|
||||||
|
}[]),
|
||||||
|
};
|
||||||
|
/* The `query` object contains options for which class queries are generated */
|
||||||
|
query: ?{
|
||||||
|
get: ?boolean,
|
||||||
|
find: ?boolean,
|
||||||
|
};
|
||||||
|
/* The `mutation` object contains options for which class mutations are generated */
|
||||||
|
mutation: ?{
|
||||||
|
create: ?boolean,
|
||||||
|
update: ?boolean,
|
||||||
|
// delete is a reserved key word in js
|
||||||
|
destroy: ?boolean,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseGraphQLController;
|
||||||
|
export { GraphQLConfigClassName, GraphQLConfigId, GraphQLConfigKey };
|
||||||
@@ -132,6 +132,10 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({
|
|||||||
objectId: { type: 'String' },
|
objectId: { type: 'String' },
|
||||||
params: { type: 'Object' },
|
params: { type: 'Object' },
|
||||||
},
|
},
|
||||||
|
_GraphQLConfig: {
|
||||||
|
objectId: { type: 'String' },
|
||||||
|
config: { type: 'Object' },
|
||||||
|
},
|
||||||
_Audience: {
|
_Audience: {
|
||||||
objectId: { type: 'String' },
|
objectId: { type: 'String' },
|
||||||
name: { type: 'String' },
|
name: { type: 'String' },
|
||||||
@@ -163,6 +167,7 @@ const volatileClasses = Object.freeze([
|
|||||||
'_PushStatus',
|
'_PushStatus',
|
||||||
'_Hooks',
|
'_Hooks',
|
||||||
'_GlobalConfig',
|
'_GlobalConfig',
|
||||||
|
'_GraphQLConfig',
|
||||||
'_JobSchedule',
|
'_JobSchedule',
|
||||||
'_Audience',
|
'_Audience',
|
||||||
]);
|
]);
|
||||||
@@ -475,6 +480,10 @@ const _GlobalConfigSchema = {
|
|||||||
className: '_GlobalConfig',
|
className: '_GlobalConfig',
|
||||||
fields: defaultColumns._GlobalConfig,
|
fields: defaultColumns._GlobalConfig,
|
||||||
};
|
};
|
||||||
|
const _GraphQLConfigSchema = {
|
||||||
|
className: '_GraphQLConfig',
|
||||||
|
fields: defaultColumns._GraphQLConfig,
|
||||||
|
};
|
||||||
const _PushStatusSchema = convertSchemaToAdapterSchema(
|
const _PushStatusSchema = convertSchemaToAdapterSchema(
|
||||||
injectDefaultSchema({
|
injectDefaultSchema({
|
||||||
className: '_PushStatus',
|
className: '_PushStatus',
|
||||||
@@ -509,6 +518,7 @@ const VolatileClassesSchemas = [
|
|||||||
_JobScheduleSchema,
|
_JobScheduleSchema,
|
||||||
_PushStatusSchema,
|
_PushStatusSchema,
|
||||||
_GlobalConfigSchema,
|
_GlobalConfigSchema,
|
||||||
|
_GraphQLConfigSchema,
|
||||||
_AudienceSchema,
|
_AudienceSchema,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
|
|||||||
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
|
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||||
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
|
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
|
||||||
import ParsePushAdapter from '@parse/push-adapter';
|
import ParsePushAdapter from '@parse/push-adapter';
|
||||||
|
import ParseGraphQLController from './ParseGraphQLController';
|
||||||
|
|
||||||
export function getControllers(options: ParseServerOptions) {
|
export function getControllers(options: ParseServerOptions) {
|
||||||
const loggerController = getLoggerController(options);
|
const loggerController = getLoggerController(options);
|
||||||
@@ -43,6 +44,10 @@ export function getControllers(options: ParseServerOptions) {
|
|||||||
const databaseController = getDatabaseController(options, cacheController);
|
const databaseController = getDatabaseController(options, cacheController);
|
||||||
const hooksController = getHooksController(options, databaseController);
|
const hooksController = getHooksController(options, databaseController);
|
||||||
const authDataManager = getAuthDataManager(options);
|
const authDataManager = getAuthDataManager(options);
|
||||||
|
const parseGraphQLController = getParseGraphQLController(options, {
|
||||||
|
databaseController,
|
||||||
|
cacheController,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
loggerController,
|
loggerController,
|
||||||
filesController,
|
filesController,
|
||||||
@@ -54,6 +59,7 @@ export function getControllers(options: ParseServerOptions) {
|
|||||||
pushControllerQueue,
|
pushControllerQueue,
|
||||||
analyticsController,
|
analyticsController,
|
||||||
cacheController,
|
cacheController,
|
||||||
|
parseGraphQLController,
|
||||||
liveQueryController,
|
liveQueryController,
|
||||||
databaseController,
|
databaseController,
|
||||||
hooksController,
|
hooksController,
|
||||||
@@ -123,6 +129,16 @@ export function getCacheController(
|
|||||||
return new CacheController(cacheControllerAdapter, appId);
|
return new CacheController(cacheControllerAdapter, appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getParseGraphQLController(
|
||||||
|
options: ParseServerOptions,
|
||||||
|
controllerDeps
|
||||||
|
): ParseGraphQLController {
|
||||||
|
return new ParseGraphQLController({
|
||||||
|
mountGraphQL: options.mountGraphQL,
|
||||||
|
...controllerDeps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function getAnalyticsController(
|
export function getAnalyticsController(
|
||||||
options: ParseServerOptions
|
options: ParseServerOptions
|
||||||
): AnalyticsController {
|
): AnalyticsController {
|
||||||
|
|||||||
@@ -8,36 +8,57 @@ import * as parseClassQueries from './loaders/parseClassQueries';
|
|||||||
import * as parseClassMutations from './loaders/parseClassMutations';
|
import * as parseClassMutations from './loaders/parseClassMutations';
|
||||||
import * as defaultGraphQLQueries from './loaders/defaultGraphQLQueries';
|
import * as defaultGraphQLQueries from './loaders/defaultGraphQLQueries';
|
||||||
import * as defaultGraphQLMutations from './loaders/defaultGraphQLMutations';
|
import * as defaultGraphQLMutations from './loaders/defaultGraphQLMutations';
|
||||||
|
import ParseGraphQLController, {
|
||||||
|
ParseGraphQLConfig,
|
||||||
|
} from '../Controllers/ParseGraphQLController';
|
||||||
|
import DatabaseController from '../Controllers/DatabaseController';
|
||||||
import { toGraphQLError } from './parseGraphQLUtils';
|
import { toGraphQLError } from './parseGraphQLUtils';
|
||||||
import * as schemaDirectives from './loaders/schemaDirectives';
|
import * as schemaDirectives from './loaders/schemaDirectives';
|
||||||
|
|
||||||
class ParseGraphQLSchema {
|
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 =
|
this.databaseController =
|
||||||
databaseController ||
|
params.databaseController ||
|
||||||
requiredParameter('You must provide a databaseController instance!');
|
requiredParameter('You must provide a databaseController instance!');
|
||||||
this.log = log || requiredParameter('You must provide a log instance!');
|
this.log =
|
||||||
this.graphQLCustomTypeDefs = graphQLCustomTypeDefs;
|
params.log || requiredParameter('You must provide a log instance!');
|
||||||
|
this.graphQLCustomTypeDefs = params.graphQLCustomTypeDefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
const schemaController = await this.databaseController.loadSchema();
|
const { parseGraphQLConfig } = await this._initializeSchemaAndConfig();
|
||||||
const parseClasses = await schemaController.getAllClasses();
|
|
||||||
|
const parseClasses = await this._getClassesForSchema(parseGraphQLConfig);
|
||||||
const parseClassesString = JSON.stringify(parseClasses);
|
const parseClassesString = JSON.stringify(parseClasses);
|
||||||
|
|
||||||
if (this.graphQLSchema) {
|
if (
|
||||||
if (this.parseClasses === parseClasses) {
|
this.graphQLSchema &&
|
||||||
return this.graphQLSchema;
|
!this._hasSchemaInputChanged({
|
||||||
}
|
parseClasses,
|
||||||
|
parseClassesString,
|
||||||
if (this.parseClassesString === parseClassesString) {
|
parseGraphQLConfig,
|
||||||
this.parseClasses = parseClasses;
|
})
|
||||||
return this.graphQLSchema;
|
) {
|
||||||
}
|
return this.graphQLSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.parseClasses = parseClasses;
|
this.parseClasses = parseClasses;
|
||||||
this.parseClassesString = parseClassesString;
|
this.parseClassesString = parseClassesString;
|
||||||
|
this.parseGraphQLConfig = parseGraphQLConfig;
|
||||||
this.parseClassTypes = {};
|
this.parseClassTypes = {};
|
||||||
this.meType = null;
|
this.meType = null;
|
||||||
this.graphQLAutoSchema = null;
|
this.graphQLAutoSchema = null;
|
||||||
@@ -53,16 +74,15 @@ class ParseGraphQLSchema {
|
|||||||
|
|
||||||
defaultGraphQLTypes.load(this);
|
defaultGraphQLTypes.load(this);
|
||||||
|
|
||||||
parseClasses.forEach(parseClass => {
|
this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach(
|
||||||
parseClassTypes.load(this, parseClass);
|
([parseClass, parseClassConfig]) => {
|
||||||
|
parseClassTypes.load(this, parseClass, parseClassConfig);
|
||||||
parseClassQueries.load(this, parseClass);
|
parseClassQueries.load(this, parseClass, parseClassConfig);
|
||||||
|
parseClassMutations.load(this, parseClass, parseClassConfig);
|
||||||
parseClassMutations.load(this, parseClass);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
defaultGraphQLQueries.load(this);
|
defaultGraphQLQueries.load(this);
|
||||||
|
|
||||||
defaultGraphQLMutations.load(this);
|
defaultGraphQLMutations.load(this);
|
||||||
|
|
||||||
let graphQLQuery = undefined;
|
let graphQLQuery = undefined;
|
||||||
@@ -160,6 +180,104 @@ class ParseGraphQLSchema {
|
|||||||
}
|
}
|
||||||
throw toGraphQLError(error);
|
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 };
|
export { ParseGraphQLSchema };
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ import { handleParseErrors, handleParseHeaders } from '../middlewares';
|
|||||||
import requiredParameter from '../requiredParameter';
|
import requiredParameter from '../requiredParameter';
|
||||||
import defaultLogger from '../logger';
|
import defaultLogger from '../logger';
|
||||||
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
|
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
|
||||||
|
import ParseGraphQLController, {
|
||||||
|
ParseGraphQLConfig,
|
||||||
|
} from '../Controllers/ParseGraphQLController';
|
||||||
|
|
||||||
class ParseGraphQLServer {
|
class ParseGraphQLServer {
|
||||||
|
parseGraphQLController: ParseGraphQLController;
|
||||||
|
|
||||||
constructor(parseServer, config) {
|
constructor(parseServer, config) {
|
||||||
this.parseServer =
|
this.parseServer =
|
||||||
parseServer ||
|
parseServer ||
|
||||||
@@ -19,12 +24,15 @@ class ParseGraphQLServer {
|
|||||||
requiredParameter('You must provide a config.graphQLPath!');
|
requiredParameter('You must provide a config.graphQLPath!');
|
||||||
}
|
}
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.parseGraphQLSchema = new ParseGraphQLSchema(
|
this.parseGraphQLController = this.parseServer.config.parseGraphQLController;
|
||||||
this.parseServer.config.databaseController,
|
this.parseGraphQLSchema = new ParseGraphQLSchema({
|
||||||
(this.parseServer.config && this.parseServer.config.loggerController) ||
|
parseGraphQLController: this.parseGraphQLController,
|
||||||
|
databaseController: this.parseServer.config.databaseController,
|
||||||
|
log:
|
||||||
|
(this.parseServer.config && this.parseServer.config.loggerController) ||
|
||||||
defaultLogger,
|
defaultLogger,
|
||||||
this.config.graphQLCustomTypeDefs
|
graphQLCustomTypeDefs: this.config.graphQLCustomTypeDefs,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getGraphQLOptions(req) {
|
async _getGraphQLOptions(req) {
|
||||||
@@ -111,6 +119,10 @@ class ParseGraphQLServer {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setGraphQLConfig(graphQLConfig: ParseGraphQLConfig): Promise {
|
||||||
|
return this.parseGraphQLController.updateGraphQLConfig(graphQLConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ParseGraphQLServer };
|
export { ParseGraphQLServer };
|
||||||
|
|||||||
@@ -1,23 +1,58 @@
|
|||||||
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import * as objectsMutations from './objectsMutations';
|
import * as objectsMutations from './objectsMutations';
|
||||||
|
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||||
|
|
||||||
const load = (parseGraphQLSchema, parseClass) => {
|
const getParseClassMutationConfig = function(
|
||||||
const className = parseClass.className;
|
parseClassConfig: ?ParseGraphQLClassConfig
|
||||||
|
) {
|
||||||
|
return (parseClassConfig && parseClassConfig.mutation) || {};
|
||||||
|
};
|
||||||
|
|
||||||
const classGraphQLInputType =
|
const load = function(
|
||||||
parseGraphQLSchema.parseClassTypes[className].classGraphQLInputType;
|
parseGraphQLSchema,
|
||||||
const fields = {
|
parseClass,
|
||||||
description: 'These are the fields of the object.',
|
parseClassConfig: ?ParseGraphQLClassConfig
|
||||||
type: classGraphQLInputType,
|
) {
|
||||||
|
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) {
|
if (fields) {
|
||||||
Object.keys(fields).forEach(field => {
|
Object.keys(fields).forEach(field => {
|
||||||
if (classGraphQLInputTypeFields[field]) {
|
let inputTypeField;
|
||||||
switch (classGraphQLInputTypeFields[field].type) {
|
if (inputType === 'create') {
|
||||||
|
inputTypeField = classGraphQLCreateTypeFields[field];
|
||||||
|
} else {
|
||||||
|
inputTypeField = classGraphQLUpdateTypeFields[field];
|
||||||
|
}
|
||||||
|
if (inputTypeField) {
|
||||||
|
switch (inputTypeField.type) {
|
||||||
case defaultGraphQLTypes.GEO_POINT:
|
case defaultGraphQLTypes.GEO_POINT:
|
||||||
fields[field].__type = 'GeoPoint';
|
fields[field].__type = 'GeoPoint';
|
||||||
break;
|
break;
|
||||||
@@ -36,86 +71,92 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createGraphQLMutationName = `create${className}`;
|
if (isCreateEnabled) {
|
||||||
parseGraphQLSchema.graphQLObjectsMutations[createGraphQLMutationName] = {
|
const createGraphQLMutationName = `create${className}`;
|
||||||
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${className} class.`,
|
parseGraphQLSchema.graphQLObjectsMutations[createGraphQLMutationName] = {
|
||||||
args: {
|
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${className} class.`,
|
||||||
fields,
|
args: {
|
||||||
},
|
fields: createFields,
|
||||||
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
|
},
|
||||||
async resolve(_source, args, context) {
|
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
|
||||||
try {
|
async resolve(_source, args, context) {
|
||||||
const { fields } = args;
|
try {
|
||||||
const { config, auth, info } = context;
|
const { fields } = args;
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
transformTypes(fields);
|
transformTypes('create', fields);
|
||||||
|
|
||||||
return await objectsMutations.createObject(
|
return await objectsMutations.createObject(
|
||||||
className,
|
className,
|
||||||
fields,
|
fields,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const updateGraphQLMutationName = `update${className}`;
|
if (isUpdateEnabled) {
|
||||||
parseGraphQLSchema.graphQLObjectsMutations[updateGraphQLMutationName] = {
|
const updateGraphQLMutationName = `update${className}`;
|
||||||
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${className} class.`,
|
parseGraphQLSchema.graphQLObjectsMutations[updateGraphQLMutationName] = {
|
||||||
args: {
|
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${className} class.`,
|
||||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
args: {
|
||||||
fields,
|
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||||
},
|
fields: updateFields,
|
||||||
type: defaultGraphQLTypes.UPDATE_RESULT,
|
},
|
||||||
async resolve(_source, args, context) {
|
type: defaultGraphQLTypes.UPDATE_RESULT,
|
||||||
try {
|
async resolve(_source, args, context) {
|
||||||
const { objectId, fields } = args;
|
try {
|
||||||
const { config, auth, info } = context;
|
const { objectId, fields } = args;
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
transformTypes(fields);
|
transformTypes('update', fields);
|
||||||
|
|
||||||
return await objectsMutations.updateObject(
|
return await objectsMutations.updateObject(
|
||||||
className,
|
className,
|
||||||
objectId,
|
objectId,
|
||||||
fields,
|
fields,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const deleteGraphQLMutationName = `delete${className}`;
|
if (isDestroyEnabled) {
|
||||||
parseGraphQLSchema.graphQLObjectsMutations[deleteGraphQLMutationName] = {
|
const deleteGraphQLMutationName = `delete${className}`;
|
||||||
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${className} class.`,
|
parseGraphQLSchema.graphQLObjectsMutations[deleteGraphQLMutationName] = {
|
||||||
args: {
|
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${className} class.`,
|
||||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
args: {
|
||||||
},
|
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||||
type: new GraphQLNonNull(GraphQLBoolean),
|
},
|
||||||
async resolve(_source, args, context) {
|
type: new GraphQLNonNull(GraphQLBoolean),
|
||||||
try {
|
async resolve(_source, args, context) {
|
||||||
const { objectId } = args;
|
try {
|
||||||
const { config, auth, info } = context;
|
const { objectId } = args;
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
return await objectsMutations.deleteObject(
|
return await objectsMutations.deleteObject(
|
||||||
className,
|
className,
|
||||||
objectId,
|
objectId,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { load };
|
export { load };
|
||||||
|
|||||||
@@ -3,9 +3,24 @@ import getFieldNames from 'graphql-list-fields';
|
|||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import * as objectsQueries from './objectsQueries';
|
import * as objectsQueries from './objectsQueries';
|
||||||
import * as parseClassTypes from './parseClassTypes';
|
import * as parseClassTypes from './parseClassTypes';
|
||||||
|
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||||
|
|
||||||
const load = (parseGraphQLSchema, parseClass) => {
|
const getParseClassQueryConfig = function(
|
||||||
const className = parseClass.className;
|
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 {
|
const {
|
||||||
classGraphQLOutputType,
|
classGraphQLOutputType,
|
||||||
@@ -13,90 +28,94 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
classGraphQLFindResultType,
|
classGraphQLFindResultType,
|
||||||
} = parseGraphQLSchema.parseClassTypes[className];
|
} = parseGraphQLSchema.parseClassTypes[className];
|
||||||
|
|
||||||
const getGraphQLQueryName = `get${className}`;
|
if (isGetEnabled) {
|
||||||
parseGraphQLSchema.graphQLObjectsQueries[getGraphQLQueryName] = {
|
const getGraphQLQueryName = `get${className}`;
|
||||||
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${className} class by its id.`,
|
parseGraphQLSchema.graphQLObjectsQueries[getGraphQLQueryName] = {
|
||||||
args: {
|
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${className} class by its id.`,
|
||||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
args: {
|
||||||
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||||
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
||||||
},
|
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
||||||
type: new GraphQLNonNull(classGraphQLOutputType),
|
},
|
||||||
async resolve(_source, args, context, queryInfo) {
|
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||||
try {
|
async resolve(_source, args, context, queryInfo) {
|
||||||
const { objectId, readPreference, includeReadPreference } = args;
|
try {
|
||||||
const { config, auth, info } = context;
|
const { objectId, readPreference, includeReadPreference } = args;
|
||||||
const selectedFields = getFieldNames(queryInfo);
|
const { config, auth, info } = context;
|
||||||
|
const selectedFields = getFieldNames(queryInfo);
|
||||||
|
|
||||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||||
selectedFields
|
selectedFields
|
||||||
);
|
);
|
||||||
|
|
||||||
return await objectsQueries.getObject(
|
return await objectsQueries.getObject(
|
||||||
className,
|
className,
|
||||||
objectId,
|
objectId,
|
||||||
keys,
|
keys,
|
||||||
include,
|
include,
|
||||||
readPreference,
|
readPreference,
|
||||||
includeReadPreference,
|
includeReadPreference,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const findGraphQLQueryName = `find${className}`;
|
if (isFindEnabled) {
|
||||||
parseGraphQLSchema.graphQLObjectsQueries[findGraphQLQueryName] = {
|
const findGraphQLQueryName = `find${className}`;
|
||||||
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${className} class.`,
|
parseGraphQLSchema.graphQLObjectsQueries[findGraphQLQueryName] = {
|
||||||
args: classGraphQLFindArgs,
|
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${className} class.`,
|
||||||
type: new GraphQLNonNull(classGraphQLFindResultType),
|
args: classGraphQLFindArgs,
|
||||||
async resolve(_source, args, context, queryInfo) {
|
type: new GraphQLNonNull(classGraphQLFindResultType),
|
||||||
try {
|
async resolve(_source, args, context, queryInfo) {
|
||||||
const {
|
try {
|
||||||
where,
|
const {
|
||||||
order,
|
where,
|
||||||
skip,
|
order,
|
||||||
limit,
|
skip,
|
||||||
readPreference,
|
limit,
|
||||||
includeReadPreference,
|
readPreference,
|
||||||
subqueryReadPreference,
|
includeReadPreference,
|
||||||
} = args;
|
subqueryReadPreference,
|
||||||
const { config, auth, info } = context;
|
} = args;
|
||||||
const selectedFields = getFieldNames(queryInfo);
|
const { config, auth, info } = context;
|
||||||
|
const selectedFields = getFieldNames(queryInfo);
|
||||||
|
|
||||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||||
selectedFields
|
selectedFields
|
||||||
.filter(field => field.includes('.'))
|
.filter(field => field.includes('.'))
|
||||||
.map(field => field.slice(field.indexOf('.') + 1))
|
.map(field => field.slice(field.indexOf('.') + 1))
|
||||||
);
|
);
|
||||||
const parseOrder = order && order.join(',');
|
const parseOrder = order && order.join(',');
|
||||||
|
|
||||||
return await objectsQueries.findObjects(
|
return await objectsQueries.findObjects(
|
||||||
className,
|
className,
|
||||||
where,
|
where,
|
||||||
parseOrder,
|
parseOrder,
|
||||||
skip,
|
skip,
|
||||||
limit,
|
limit,
|
||||||
keys,
|
keys,
|
||||||
include,
|
include,
|
||||||
false,
|
false,
|
||||||
readPreference,
|
readPreference,
|
||||||
includeReadPreference,
|
includeReadPreference,
|
||||||
subqueryReadPreference,
|
subqueryReadPreference,
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info,
|
info,
|
||||||
selectedFields.map(field => field.split('.', 1)[0])
|
selectedFields.map(field => field.split('.', 1)[0])
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { load };
|
export { load };
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import getFieldNames from 'graphql-list-fields';
|
import getFieldNames from 'graphql-list-fields';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import * as objectsQueries from './objectsQueries';
|
import * as objectsQueries from './objectsQueries';
|
||||||
|
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
|
||||||
|
|
||||||
const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
||||||
switch (parseType) {
|
switch (parseType) {
|
||||||
@@ -161,14 +162,105 @@ const extractKeysAndInclude = selectedFields => {
|
|||||||
return { keys, include };
|
return { keys, include };
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = (parseGraphQLSchema, parseClass) => {
|
const getParseClassTypeConfig = function(
|
||||||
const className = parseClass.className;
|
parseClassConfig: ?ParseGraphQLClassConfig
|
||||||
|
) {
|
||||||
|
return (parseClassConfig && parseClassConfig.type) || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInputFieldsAndConstraints = function(
|
||||||
|
parseClass,
|
||||||
|
parseClassConfig: ?ParseGraphQLClassConfig
|
||||||
|
) {
|
||||||
const classFields = Object.keys(parseClass.fields);
|
const classFields = Object.keys(parseClass.fields);
|
||||||
|
const {
|
||||||
|
inputFields: allowedInputFields,
|
||||||
|
outputFields: allowedOutputFields,
|
||||||
|
constraintFields: allowedConstraintFields,
|
||||||
|
sortFields: allowedSortFields,
|
||||||
|
} = getParseClassTypeConfig(parseClassConfig);
|
||||||
|
|
||||||
const classCustomFields = classFields.filter(
|
let classOutputFields;
|
||||||
field => !Object.keys(defaultGraphQLTypes.CLASS_FIELDS).includes(field)
|
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 classGraphQLScalarTypeName = `${className}Pointer`;
|
||||||
const parseScalarValue = value => {
|
const parseScalarValue = value => {
|
||||||
@@ -271,12 +363,12 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
});
|
});
|
||||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLRelationOpType);
|
parseGraphQLSchema.graphQLTypes.push(classGraphQLRelationOpType);
|
||||||
|
|
||||||
const classGraphQLInputTypeName = `${className}Fields`;
|
const classGraphQLCreateTypeName = `${className}CreateFields`;
|
||||||
const classGraphQLInputType = new GraphQLInputObjectType({
|
const classGraphQLCreateType = new GraphQLInputObjectType({
|
||||||
name: classGraphQLInputTypeName,
|
name: classGraphQLCreateTypeName,
|
||||||
description: `The ${classGraphQLInputTypeName} input type is used in operations that involve inputting objects of ${className} class.`,
|
description: `The ${classGraphQLCreateTypeName} input type is used in operations that involve creation of objects in the ${className} class.`,
|
||||||
fields: () =>
|
fields: () =>
|
||||||
classCustomFields.reduce(
|
classCreateFields.reduce(
|
||||||
(fields, field) => {
|
(fields, field) => {
|
||||||
const type = mapInputType(
|
const type = mapInputType(
|
||||||
parseClass.fields[field].type,
|
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 classGraphQLConstraintTypeName = `${className}PointerConstraint`;
|
||||||
const classGraphQLConstraintType = new GraphQLInputObjectType({
|
const classGraphQLConstraintType = new GraphQLInputObjectType({
|
||||||
@@ -333,7 +456,7 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
name: classGraphQLConstraintsTypeName,
|
name: classGraphQLConstraintsTypeName,
|
||||||
description: `The ${classGraphQLConstraintsTypeName} input type is used in operations that involve filtering objects of ${className} class.`,
|
description: `The ${classGraphQLConstraintsTypeName} input type is used in operations that involve filtering objects of ${className} class.`,
|
||||||
fields: () => ({
|
fields: () => ({
|
||||||
...classFields.reduce((fields, field) => {
|
...classConstraintFields.reduce((fields, field) => {
|
||||||
const type = mapConstraintType(
|
const type = mapConstraintType(
|
||||||
parseClass.fields[field].type,
|
parseClass.fields[field].type,
|
||||||
parseClass.fields[field].targetClass,
|
parseClass.fields[field].targetClass,
|
||||||
@@ -371,12 +494,18 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
const classGraphQLOrderType = new GraphQLEnumType({
|
const classGraphQLOrderType = new GraphQLEnumType({
|
||||||
name: classGraphQLOrderTypeName,
|
name: classGraphQLOrderTypeName,
|
||||||
description: `The ${classGraphQLOrderTypeName} input type is used when sorting objects of the ${className} class.`,
|
description: `The ${classGraphQLOrderTypeName} input type is used when sorting objects of the ${className} class.`,
|
||||||
values: classFields.reduce((orderFields, field) => {
|
values: classSortFields.reduce((sortFields, fieldConfig) => {
|
||||||
return {
|
const { field, asc, desc } = fieldConfig;
|
||||||
...orderFields,
|
const updatedSortFields = {
|
||||||
[`${field}_ASC`]: { value: field },
|
...sortFields,
|
||||||
[`${field}_DESC`]: { value: `-${field}` },
|
|
||||||
};
|
};
|
||||||
|
if (asc) {
|
||||||
|
updatedSortFields[`${field}_ASC`] = { value: field };
|
||||||
|
}
|
||||||
|
if (desc) {
|
||||||
|
updatedSortFields[`${field}_DESC`] = { value: `-${field}` };
|
||||||
|
}
|
||||||
|
return updatedSortFields;
|
||||||
}, {}),
|
}, {}),
|
||||||
});
|
});
|
||||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLOrderType);
|
parseGraphQLSchema.graphQLTypes.push(classGraphQLOrderType);
|
||||||
@@ -400,7 +529,7 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
|
|
||||||
const classGraphQLOutputTypeName = `${className}Class`;
|
const classGraphQLOutputTypeName = `${className}Class`;
|
||||||
const outputFields = () => {
|
const outputFields = () => {
|
||||||
return classCustomFields.reduce((fields, field) => {
|
return classOutputFields.reduce((fields, field) => {
|
||||||
const type = mapOutputType(
|
const type = mapOutputType(
|
||||||
parseClass.fields[field].type,
|
parseClass.fields[field].type,
|
||||||
parseClass.fields[field].targetClass,
|
parseClass.fields[field].targetClass,
|
||||||
@@ -531,7 +660,8 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
parseGraphQLSchema.parseClassTypes[className] = {
|
parseGraphQLSchema.parseClassTypes[className] = {
|
||||||
classGraphQLScalarType,
|
classGraphQLScalarType,
|
||||||
classGraphQLRelationOpType,
|
classGraphQLRelationOpType,
|
||||||
classGraphQLInputType,
|
classGraphQLCreateType,
|
||||||
|
classGraphQLUpdateType,
|
||||||
classGraphQLConstraintType,
|
classGraphQLConstraintType,
|
||||||
classGraphQLConstraintsType,
|
classGraphQLConstraintsType,
|
||||||
classGraphQLFindArgs,
|
classGraphQLFindArgs,
|
||||||
@@ -552,37 +682,32 @@ const load = (parseGraphQLSchema, parseClass) => {
|
|||||||
parseGraphQLSchema.meType = meType;
|
parseGraphQLSchema.meType = meType;
|
||||||
parseGraphQLSchema.graphQLTypes.push(meType);
|
parseGraphQLSchema.graphQLTypes.push(meType);
|
||||||
|
|
||||||
const userSignUpInputTypeName = `_UserSignUpFields`;
|
const userSignUpInputTypeName = '_UserSignUpFields';
|
||||||
const userSignUpInputType = new GraphQLInputObjectType({
|
const userSignUpInputType = new GraphQLInputObjectType({
|
||||||
name: userSignUpInputTypeName,
|
name: userSignUpInputTypeName,
|
||||||
description: `The ${userSignUpInputTypeName} input type is used in operations that involve inputting objects of ${className} class when signing up.`,
|
description: `The ${userSignUpInputTypeName} input type is used in operations that involve inputting objects of ${className} class when signing up.`,
|
||||||
fields: () =>
|
fields: () =>
|
||||||
classCustomFields.reduce(
|
classCreateFields.reduce((fields, field) => {
|
||||||
(fields, field) => {
|
const type = mapInputType(
|
||||||
const type = mapInputType(
|
parseClass.fields[field].type,
|
||||||
parseClass.fields[field].type,
|
parseClass.fields[field].targetClass,
|
||||||
parseClass.fields[field].targetClass,
|
parseGraphQLSchema.parseClassTypes
|
||||||
parseGraphQLSchema.parseClassTypes
|
);
|
||||||
);
|
if (type) {
|
||||||
if (type) {
|
return {
|
||||||
return {
|
...fields,
|
||||||
...fields,
|
[field]: {
|
||||||
[field]: {
|
description: `This is the object ${field}.`,
|
||||||
description: `This is the object ${field}.`,
|
type:
|
||||||
type:
|
field === 'username' || field === 'password'
|
||||||
field === 'username' || field === 'password'
|
? new GraphQLNonNull(type)
|
||||||
? new GraphQLNonNull(type)
|
: type,
|
||||||
: type,
|
},
|
||||||
},
|
};
|
||||||
};
|
} else {
|
||||||
} else {
|
return fields;
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ACL: defaultGraphQLTypes.ACL_ATT,
|
|
||||||
}
|
}
|
||||||
),
|
}, {}),
|
||||||
});
|
});
|
||||||
parseGraphQLSchema.parseClassTypes[
|
parseGraphQLSchema.parseClassTypes[
|
||||||
'_User'
|
'_User'
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import * as objectsMutations from './objectsMutations';
|
|||||||
const usersRouter = new UsersRouter();
|
const usersRouter = new UsersRouter();
|
||||||
|
|
||||||
const load = parseGraphQLSchema => {
|
const load = parseGraphQLSchema => {
|
||||||
|
if (parseGraphQLSchema.isUsersClassDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fields = {};
|
const fields = {};
|
||||||
|
|
||||||
fields.signUp = {
|
fields.signUp = {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import Auth from '../../Auth';
|
|||||||
import { extractKeysAndInclude } from './parseClassTypes';
|
import { extractKeysAndInclude } from './parseClassTypes';
|
||||||
|
|
||||||
const load = parseGraphQLSchema => {
|
const load = parseGraphQLSchema => {
|
||||||
|
if (parseGraphQLSchema.isUsersClassDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fields = {};
|
const fields = {};
|
||||||
|
|
||||||
fields.me = {
|
fields.me = {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { FeaturesRouter } from './Routers/FeaturesRouter';
|
|||||||
import { FilesRouter } from './Routers/FilesRouter';
|
import { FilesRouter } from './Routers/FilesRouter';
|
||||||
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
||||||
import { GlobalConfigRouter } from './Routers/GlobalConfigRouter';
|
import { GlobalConfigRouter } from './Routers/GlobalConfigRouter';
|
||||||
|
import { GraphQLRouter } from './Routers/GraphQLRouter';
|
||||||
import { HooksRouter } from './Routers/HooksRouter';
|
import { HooksRouter } from './Routers/HooksRouter';
|
||||||
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
||||||
import { InstallationsRouter } from './Routers/InstallationsRouter';
|
import { InstallationsRouter } from './Routers/InstallationsRouter';
|
||||||
@@ -231,6 +232,7 @@ class ParseServer {
|
|||||||
new IAPValidationRouter(),
|
new IAPValidationRouter(),
|
||||||
new FeaturesRouter(),
|
new FeaturesRouter(),
|
||||||
new GlobalConfigRouter(),
|
new GlobalConfigRouter(),
|
||||||
|
new GraphQLRouter(),
|
||||||
new PurgeRouter(),
|
new PurgeRouter(),
|
||||||
new HooksRouter(),
|
new HooksRouter(),
|
||||||
new CloudCodeRouter(),
|
new CloudCodeRouter(),
|
||||||
|
|||||||
50
src/Routers/GraphQLRouter.js
Normal file
50
src/Routers/GraphQLRouter.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import Parse from 'parse/node';
|
||||||
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
import * as middleware from '../middlewares';
|
||||||
|
|
||||||
|
const GraphQLConfigPath = '/graphql-config';
|
||||||
|
|
||||||
|
export class GraphQLRouter extends PromiseRouter {
|
||||||
|
async getGraphQLConfig(req) {
|
||||||
|
const result = await req.config.parseGraphQLController.getGraphQLConfig();
|
||||||
|
return {
|
||||||
|
response: result,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateGraphQLConfig(req) {
|
||||||
|
if (req.auth.isReadOnly) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OPERATION_FORBIDDEN,
|
||||||
|
"read-only masterKey isn't allowed to update the GraphQL config."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const data = await req.config.parseGraphQLController.updateGraphQLConfig(
|
||||||
|
req.body.params
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
response: data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mountRoutes() {
|
||||||
|
this.route(
|
||||||
|
'GET',
|
||||||
|
GraphQLConfigPath,
|
||||||
|
middleware.promiseEnforceMasterKeyAccess,
|
||||||
|
req => {
|
||||||
|
return this.getGraphQLConfig(req);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.route(
|
||||||
|
'PUT',
|
||||||
|
GraphQLConfigPath,
|
||||||
|
middleware.promiseEnforceMasterKeyAccess,
|
||||||
|
req => {
|
||||||
|
return this.updateGraphQLConfig(req);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphQLRouter;
|
||||||
Reference in New Issue
Block a user