Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a22dfe6
add parse-graph-ql configuration for class schema customisation
omairvaiyani Jul 8, 2019
5121073
refactor and add graphql router, controller and config cache
omairvaiyani Jul 11, 2019
51b74a4
Merge pull request #1 from omairvaiyani/graphql-config-modularisation
omairvaiyani Jul 11, 2019
1714528
fix(GraphQLController): add missing check isEnabled
omairvaiyani Jul 12, 2019
078e4ad
chore(GraphQLController): remove awaits from cache put
omairvaiyani Jul 12, 2019
8dba72e
chore(GraphQLController): remove check for if its enabled
omairvaiyani Jul 12, 2019
03400f7
refactor(GraphQLController): only use cache if mounted
omairvaiyani Jul 12, 2019
7334bd7
chore(GraphQLController): group all validation errors and throw at once
omairvaiyani Jul 12, 2019
b10d09b
chore(GraphQLSchema): move transformations into controller validation
omairvaiyani Jul 12, 2019
eef0b2b
refactor(GraphQL): improve ctrl validation and fix schema usage of co…
omairvaiyani Jul 12, 2019
206f8bc
refactor(GraphQLSchema): remove code related to additional schema
omairvaiyani Jul 12, 2019
a2fb12e
fix(GraphQLSchema): fix incorrect default return type for class configs
omairvaiyani Jul 12, 2019
5a7004d
refactor(GraphQLSchema): update staleness check code to account for c…
omairvaiyani Jul 12, 2019
eb7356c
fix(GraphQLServer): fix regressed tests due to internal schema changes
omairvaiyani Jul 13, 2019
c986320
Merge branch 'master' into graphql-config
omairvaiyani Jul 13, 2019
df8aa90
refactor: rename to ParseGraphQLController for consistency
omairvaiyani Jul 14, 2019
8f87292
fix(ParseGraphQLCtrl): numerous fixes for validity checking
omairvaiyani Jul 16, 2019
5ef997c
chore(GraphQL): minor syntax cleanup
omairvaiyani Jul 16, 2019
1f9a7a6
fix(SchemaController): add _GraphQLConfig to volatile classes
omairvaiyani Jul 16, 2019
543b319
refactor(ParseGraphQLServer): return update config value in setGraphQ…
omairvaiyani Jul 16, 2019
091e92f
testing(ParseGraphQL): add test cases for new graphQLConfig
omairvaiyani Jul 16, 2019
704e2fb
Merge branch 'graphql-config' of https://github.com/omairvaiyani/pars…
omairvaiyani Jul 16, 2019
a77d2c2
fix(GraphQLController): fix issue where config with multiple items wa…
omairvaiyani Jul 16, 2019
4a139ad
fix(postgres): add _GraphQLConfig default schema on load
omairvaiyani Jul 22, 2019
687c9d6
Merge branch 'master' into graphql-config-merge-master
omairvaiyani Jul 22, 2019
848ea77
Merge pull request #2 from omairvaiyani/graphql-config-merge-master
omairvaiyani Jul 22, 2019
2e0940c
GraphQL @mock directive (#5836)
davimacedo Jul 22, 2019
e60e798
Merge branch 'graphql-config' of github.com:omairvaiyani/parse-server…
davimacedo Jul 22, 2019
c9595ec
Fix existing tests due to the change from ClassFields to ClassCreateF…
davimacedo Jul 22, 2019
aedd4bc
fix(parseClassMutations): safer type transformation based on input type
omairvaiyani Jul 25, 2019
17e4a5a
fix(parseClassMutations): only define necessary input fields
omairvaiyani Jul 25, 2019
441dead
fix(GraphQL): fix incorrect import paths
omairvaiyani Jul 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,12 +685,15 @@ describe('ParseGraphQLServer', () => {
const expectedTypes = [
'_RoleClass',
'_RoleConstraints',
'_RoleFields',
'_RoleCreateFields',
'_RoleUpdateFields',
'_RoleFindResult',
'_UserClass',
'_UserConstraints',
'_UserFindResult',
'_UserFields',
'_UserSignUpFields',
'_UserCreateFields',
'_UserUpdateFields',
];
expect(
expectedTypes.every(type => schemaTypes.indexOf(type) !== -1)
Expand Down
1 change: 1 addition & 0 deletions src/Controllers/CacheController.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class CacheController extends AdaptableController {

this.role = new SubCache('role', this);
this.user = new SubCache('user', this);
this.graphQL = new SubCache('graphQL', this);
}

get(key) {
Expand Down
168 changes: 168 additions & 0 deletions src/Controllers/GraphQLController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import requiredParameter from '../../lib/requiredParameter';
import DatabaseController from './DatabaseController';
import CacheController from './CacheController';

const GraphQLConfigClass = '_GraphQLConfig';
const GraphQLConfigId = '1';
const GraphQLConfigKey = 'config';

class GraphQLController {
databaseController: DatabaseController;
cacheController: CacheController;
isEnabled: boolean;

constructor(params: {
databaseController: DatabaseController,
cacheController: CacheController,
mountGraphQL: boolean,
}) {
this.databaseController =
params.databaseController ||
requiredParameter(
`GraphQLController requires a "databaseController" to be instantiated.`
);
this.cacheController =
params.cacheController ||
requiredParameter(
`GraphQLController requires a "cacheController" to be instantiated.`
);
this.isEnabled = !!params.mountGraphQL;
}

async getGraphQLConfig(): Promise<ParseGraphQLConfig> {
const _cachedConfig = await this._getCachedGraphQLConfig();
if (_cachedConfig) {
return _cachedConfig;
}

const results = await this.databaseController.find(
GraphQLConfigClass,
{ 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];
}

await this._putCachedGraphQLConfig(graphQLConfig);

return graphQLConfig;
}

async updateGraphQLConfig(
graphQLConfig: ParseGraphQLConfig
): Promise<ParseGraphQLConfig> {
if(!this.isEnabled) {
throw new Error('GraphQL is not enabled on this application.');
}
// throws if invalid
this._validateGraphQLConfig(graphQLConfig);

// Transform in dot notation to make sure it works
const update = Object.keys(graphQLConfig).reduce((acc, key) => {
acc[`${GraphQLConfigKey}.${key}`] = graphQLConfig[key];
return acc;
}, {});

await this.databaseController.update(
GraphQLConfigClass,
{ objectId: GraphQLConfigId },
update,
{ upsert: true }
);

await this._putCachedGraphQLConfig(graphQLConfig);

return { response: { result: true } };
}

async _getCachedGraphQLConfig() {
return this.cacheController.graphQL.get(GraphQLConfigKey);
}

async _putCachedGraphQLConfig(graphQLConfig: ParseGraphQLConfig) {
return this.cacheController.graphQL.put(
GraphQLConfigKey,
graphQLConfig,
60000
);
}

_validateGraphQLConfig(graphQLConfig: ?ParseGraphQLConfig): void {
let errorMessage: string;
if (!graphQLConfig) {
errorMessage = 'cannot be undefined, null or empty.';
} else if (typeof graphQLConfig !== 'object') {
errorMessage = 'must be a valid object.';
} else {
const {
enabledForClasses,
disabledForClasses,
classConfigs,
...invalidKeys
} = graphQLConfig;

if (invalidKeys.length) {
errorMessage = `encountered invalid keys: ${invalidKeys}`;
}
// TODO use more rigirous structural validations
if (enabledForClasses && !Array.isArray(enabledForClasses)) {
errorMessage = `"enabledForClasses" is not a valid array.`;
}
if (disabledForClasses && !Array.isArray(disabledForClasses)) {
errorMessage = `"disabledForClasses" is not a valid array.`;
}
if (classConfigs && !Array.isArray(classConfigs)) {
errorMessage = `"classConfigs" is not a valid array.`;
}
}
if (errorMessage) {
throw new Error(`Invalid graphQLConfig: ${errorMessage}`);
}
}
}

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:
| ?(string[])
| ?{
/* 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; suffix with _ASC or _DESC to enforce direction. */
sortFields: ?(string[]),
};
/* 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: ?boolean,
};
}

export default GraphQLController;
16 changes: 16 additions & 0 deletions src/Controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
import ParsePushAdapter from '@parse/push-adapter';
import GraphQLController from './GraphQLController';

export function getControllers(options: ParseServerOptions) {
const loggerController = getLoggerController(options);
Expand All @@ -43,6 +44,10 @@ export function getControllers(options: ParseServerOptions) {
const databaseController = getDatabaseController(options, cacheController);
const hooksController = getHooksController(options, databaseController);
const authDataManager = getAuthDataManager(options);
const graphQLController = getGraphQLController(options, {
databaseController,
cacheController,
});
return {
loggerController,
filesController,
Expand All @@ -54,6 +59,7 @@ export function getControllers(options: ParseServerOptions) {
pushControllerQueue,
analyticsController,
cacheController,
graphQLController,
liveQueryController,
databaseController,
hooksController,
Expand Down Expand Up @@ -123,6 +129,16 @@ export function getCacheController(
return new CacheController(cacheControllerAdapter, appId);
}

export function getGraphQLController(
options: ParseServerOptions,
controllerDeps
): GraphQLController {
return new GraphQLController({
mountGraphQL: options.mountGraphQL,
...controllerDeps,
});
}

export function getAnalyticsController(
options: ParseServerOptions
): AnalyticsController {
Expand Down
Loading