Skip to content

Commit 51b74a4

Browse files
authored
Merge pull request #1 from omairvaiyani/graphql-config-modularisation
refactor and add graphql router, controller and config cache
2 parents a22dfe6 + 5121073 commit 51b74a4

File tree

9 files changed

+363
-70
lines changed

9 files changed

+363
-70
lines changed

src/Controllers/CacheController.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export class CacheController extends AdaptableController {
4545

4646
this.role = new SubCache('role', this);
4747
this.user = new SubCache('user', this);
48+
this.graphQL = new SubCache('graphQL', this);
4849
}
4950

5051
get(key) {

src/Controllers/GraphQLController.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import requiredParameter from '../../lib/requiredParameter';
2+
import DatabaseController from './DatabaseController';
3+
import CacheController from './CacheController';
4+
5+
const GraphQLConfigClass = '_GraphQLConfig';
6+
const GraphQLConfigId = '1';
7+
const GraphQLConfigKey = 'config';
8+
9+
class GraphQLController {
10+
databaseController: DatabaseController;
11+
cacheController: CacheController;
12+
isEnabled: boolean;
13+
14+
constructor(params: {
15+
databaseController: DatabaseController,
16+
cacheController: CacheController,
17+
mountGraphQL: boolean,
18+
}) {
19+
this.databaseController =
20+
params.databaseController ||
21+
requiredParameter(
22+
`GraphQLController requires a "databaseController" to be instantiated.`
23+
);
24+
this.cacheController =
25+
params.cacheController ||
26+
requiredParameter(
27+
`GraphQLController requires a "cacheController" to be instantiated.`
28+
);
29+
this.isEnabled = !!params.mountGraphQL;
30+
}
31+
32+
async getGraphQLConfig(): Promise<ParseGraphQLConfig> {
33+
const _cachedConfig = await this._getCachedGraphQLConfig();
34+
if (_cachedConfig) {
35+
return _cachedConfig;
36+
}
37+
38+
const results = await this.databaseController.find(
39+
GraphQLConfigClass,
40+
{ objectId: GraphQLConfigId },
41+
{ limit: 1 }
42+
);
43+
44+
let graphQLConfig;
45+
if (results.length != 1) {
46+
// If there is no config in the database - return empty config.
47+
return {};
48+
} else {
49+
graphQLConfig = results[0][GraphQLConfigKey];
50+
}
51+
52+
await this._putCachedGraphQLConfig(graphQLConfig);
53+
54+
return graphQLConfig;
55+
}
56+
57+
async updateGraphQLConfig(
58+
graphQLConfig: ParseGraphQLConfig
59+
): Promise<ParseGraphQLConfig> {
60+
// throws if invalid
61+
this._validateGraphQLConfig(graphQLConfig);
62+
63+
// Transform in dot notation to make sure it works
64+
const update = Object.keys(graphQLConfig).reduce((acc, key) => {
65+
acc[`${GraphQLConfigKey}.${key}`] = graphQLConfig[key];
66+
return acc;
67+
}, {});
68+
69+
await this.databaseController.update(
70+
GraphQLConfigClass,
71+
{ objectId: GraphQLConfigId },
72+
update,
73+
{ upsert: true }
74+
);
75+
76+
await this._putCachedGraphQLConfig(graphQLConfig);
77+
78+
return { response: { result: true } };
79+
}
80+
81+
async _getCachedGraphQLConfig() {
82+
return this.cacheController.graphQL.get(GraphQLConfigKey);
83+
}
84+
85+
async _putCachedGraphQLConfig(graphQLConfig: ParseGraphQLConfig) {
86+
return this.cacheController.graphQL.put(
87+
GraphQLConfigKey,
88+
graphQLConfig,
89+
60000
90+
);
91+
}
92+
93+
_validateGraphQLConfig(graphQLConfig: ?ParseGraphQLConfig): void {
94+
let errorMessage: string;
95+
if (!graphQLConfig) {
96+
errorMessage = 'cannot be undefined, null or empty.';
97+
} else if (typeof graphQLConfig !== 'object') {
98+
errorMessage = 'must be a valid object.';
99+
} else {
100+
const {
101+
enabledForClasses,
102+
disabledForClasses,
103+
classConfigs,
104+
...invalidKeys
105+
} = graphQLConfig;
106+
107+
if (invalidKeys.length) {
108+
errorMessage = `encountered invalid keys: ${invalidKeys}`;
109+
}
110+
// TODO use more rigirous structural validations
111+
if (enabledForClasses && !Array.isArray(enabledForClasses)) {
112+
errorMessage = `"enabledForClasses" is not a valid array.`;
113+
}
114+
if (disabledForClasses && !Array.isArray(disabledForClasses)) {
115+
errorMessage = `"disabledForClasses" is not a valid array.`;
116+
}
117+
if (classConfigs && !Array.isArray(classConfigs)) {
118+
errorMessage = `"classConfigs" is not a valid array.`;
119+
}
120+
}
121+
if (errorMessage) {
122+
throw new Error(`Invalid graphQLConfig: ${errorMessage}`);
123+
}
124+
}
125+
}
126+
127+
export interface ParseGraphQLConfig {
128+
enabledForClasses?: string[];
129+
disabledForClasses?: string[];
130+
classConfigs?: ParseGraphQLClassConfig[];
131+
}
132+
133+
export interface ParseGraphQLClassConfig {
134+
className: string;
135+
/* The `type` object contains options for how the class types are generated */
136+
type: ?{
137+
/* Fields that are allowed when creating or updating an object. */
138+
inputFields:
139+
| ?(string[])
140+
| ?{
141+
/* Leave blank to allow all available fields in the schema. */
142+
create?: string[],
143+
update?: string[],
144+
},
145+
/* Fields on the edges that can be resolved from a query, i.e. the Result Type. */
146+
outputFields: ?(string[]),
147+
/* Fields by which a query can be filtered, i.e. the `where` object. */
148+
constraintFields: ?(string[]),
149+
/* Fields by which a query can be sorted; suffix with _ASC or _DESC to enforce direction. */
150+
sortFields: ?(string[]),
151+
};
152+
/* The `query` object contains options for which class queries are generated */
153+
query: ?{
154+
get: ?boolean,
155+
find: ?boolean,
156+
};
157+
/* The `mutation` object contains options for which class mutations are generated */
158+
mutation: ?{
159+
create: ?boolean,
160+
update: ?boolean,
161+
delete: ?boolean,
162+
};
163+
}
164+
165+
export default GraphQLController;

src/Controllers/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
2525
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
2626
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
2727
import ParsePushAdapter from '@parse/push-adapter';
28+
import GraphQLController from './GraphQLController';
2829

2930
export function getControllers(options: ParseServerOptions) {
3031
const loggerController = getLoggerController(options);
@@ -43,6 +44,10 @@ export function getControllers(options: ParseServerOptions) {
4344
const databaseController = getDatabaseController(options, cacheController);
4445
const hooksController = getHooksController(options, databaseController);
4546
const authDataManager = getAuthDataManager(options);
47+
const graphQLController = getGraphQLController(options, {
48+
databaseController,
49+
cacheController,
50+
});
4651
return {
4752
loggerController,
4853
filesController,
@@ -54,6 +59,7 @@ export function getControllers(options: ParseServerOptions) {
5459
pushControllerQueue,
5560
analyticsController,
5661
cacheController,
62+
graphQLController,
5763
liveQueryController,
5864
databaseController,
5965
hooksController,
@@ -123,6 +129,16 @@ export function getCacheController(
123129
return new CacheController(cacheControllerAdapter, appId);
124130
}
125131

132+
export function getGraphQLController(
133+
options: ParseServerOptions,
134+
controllerDeps
135+
): GraphQLController {
136+
return new GraphQLController({
137+
mountGraphQL: options.mountGraphQL,
138+
...controllerDeps,
139+
});
140+
}
141+
126142
export function getAnalyticsController(
127143
options: ParseServerOptions
128144
): AnalyticsController {

0 commit comments

Comments
 (0)