|
| 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; |
0 commit comments