Skip to content

Commit e92b88f

Browse files
committed
feat: add query and mutation to use Parse.Config
1 parent 46f8ea9 commit e92b88f

File tree

5 files changed

+367
-24
lines changed

5 files changed

+367
-24
lines changed

spec/ParseGraphQLServer.spec.js

Lines changed: 256 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7081,12 +7081,21 @@ describe('ParseGraphQLServer', () => {
70817081
});
70827082

70837083
describe("Config Queries", () => {
7084-
fit("should return the config value for a specific parameter", async () => {
7084+
beforeEach(async () => {
7085+
// Setup initial config data
7086+
await Parse.Config.save(
7087+
{ publicParam: 'publicValue', privateParam: 'privateValue' },
7088+
{ privateParam: true },
7089+
{ useMasterKey: true }
7090+
);
7091+
});
7092+
7093+
it("should return the config value for a specific parameter", async () => {
70857094
const query = gql`
7086-
query ConfigValue($paramName: String!) {
7095+
query configValue($paramName: String!) {
70877096
configValue(paramName: $paramName) {
70887097
value
7089-
source
7098+
isMasterKeyOnly
70907099
}
70917100
}
70927101
`;
@@ -7103,8 +7112,250 @@ describe('ParseGraphQLServer', () => {
71037112

71047113
expect(result.errors).toBeUndefined();
71057114
expect(result.data.configValue.value).toEqual('publicValue');
7106-
expect(result.data.configValue.source).toEqual('params');
7107-
})
7115+
expect(result.data.configValue.isMasterKeyOnly).toEqual(false);
7116+
});
7117+
7118+
it("should return null for non-existent parameter", async () => {
7119+
const query = gql`
7120+
query configValue($paramName: String!) {
7121+
configValue(paramName: $paramName) {
7122+
value
7123+
isMasterKeyOnly
7124+
}
7125+
}
7126+
`;
7127+
7128+
const result = await apolloClient.query({
7129+
query,
7130+
variables: { paramName: 'nonExistentParam' },
7131+
context: {
7132+
headers: {
7133+
'X-Parse-Master-Key': 'test',
7134+
},
7135+
},
7136+
});
7137+
7138+
expect(result.errors).toBeUndefined();
7139+
expect(result.data.configValue.value).toBeNull();
7140+
expect(result.data.configValue.isMasterKeyOnly).toBeNull();
7141+
});
7142+
});
7143+
7144+
describe("Config Mutations", () => {
7145+
it("should update a config value using mutation and retrieve it with query", async () => {
7146+
const mutation = gql`
7147+
mutation updateConfigValue($input: UpdateConfigValueInput!) {
7148+
updateConfigValue(input: $input) {
7149+
clientMutationId
7150+
configValue {
7151+
value
7152+
isMasterKeyOnly
7153+
}
7154+
}
7155+
}
7156+
`;
7157+
7158+
const query = gql`
7159+
query configValue($paramName: String!) {
7160+
configValue(paramName: $paramName) {
7161+
value
7162+
isMasterKeyOnly
7163+
}
7164+
}
7165+
`;
7166+
7167+
const mutationResult = await apolloClient.mutate({
7168+
mutation,
7169+
variables: {
7170+
input: {
7171+
clientMutationId: 'test-mutation-id',
7172+
paramName: 'testParam',
7173+
value: 'testValue',
7174+
isMasterKeyOnly: false,
7175+
},
7176+
},
7177+
context: {
7178+
headers: {
7179+
'X-Parse-Master-Key': 'test',
7180+
},
7181+
},
7182+
});
7183+
7184+
expect(mutationResult.errors).toBeUndefined();
7185+
expect(mutationResult.data.updateConfigValue.configValue.value).toEqual('testValue');
7186+
expect(mutationResult.data.updateConfigValue.configValue.isMasterKeyOnly).toEqual(false);
7187+
7188+
const queryResult = await apolloClient.query({
7189+
query,
7190+
variables: { paramName: 'testParam' },
7191+
context: {
7192+
headers: {
7193+
'X-Parse-Master-Key': 'test',
7194+
},
7195+
},
7196+
});
7197+
7198+
expect(queryResult.errors).toBeUndefined();
7199+
expect(queryResult.data.configValue.value).toEqual('testValue');
7200+
expect(queryResult.data.configValue.isMasterKeyOnly).toEqual(false);
7201+
});
7202+
7203+
it("should update a config value with isMasterKeyOnly set to true", async () => {
7204+
const mutation = gql`
7205+
mutation updateConfigValue($input: UpdateConfigValueInput!) {
7206+
updateConfigValue(input: $input) {
7207+
clientMutationId
7208+
configValue {
7209+
value
7210+
isMasterKeyOnly
7211+
}
7212+
}
7213+
}
7214+
`;
7215+
7216+
const query = gql`
7217+
query configValue($paramName: String!) {
7218+
configValue(paramName: $paramName) {
7219+
value
7220+
isMasterKeyOnly
7221+
}
7222+
}
7223+
`;
7224+
7225+
const mutationResult = await apolloClient.mutate({
7226+
mutation,
7227+
variables: {
7228+
input: {
7229+
clientMutationId: 'test-mutation-id-2',
7230+
paramName: 'privateTestParam',
7231+
value: 'privateValue',
7232+
isMasterKeyOnly: true,
7233+
},
7234+
},
7235+
context: {
7236+
headers: {
7237+
'X-Parse-Master-Key': 'test',
7238+
},
7239+
},
7240+
});
7241+
7242+
expect(mutationResult.errors).toBeUndefined();
7243+
expect(mutationResult.data.updateConfigValue.configValue.value).toEqual('privateValue');
7244+
expect(mutationResult.data.updateConfigValue.configValue.isMasterKeyOnly).toEqual(true);
7245+
7246+
const queryResult = await apolloClient.query({
7247+
query,
7248+
variables: { paramName: 'privateTestParam' },
7249+
context: {
7250+
headers: {
7251+
'X-Parse-Master-Key': 'test',
7252+
},
7253+
},
7254+
});
7255+
7256+
expect(queryResult.errors).toBeUndefined();
7257+
expect(queryResult.data.configValue.value).toEqual('privateValue');
7258+
expect(queryResult.data.configValue.isMasterKeyOnly).toEqual(true);
7259+
});
7260+
7261+
it("should update an existing config value", async () => {
7262+
await Parse.Config.save(
7263+
{ existingParam: 'initialValue' },
7264+
{},
7265+
{ useMasterKey: true }
7266+
);
7267+
7268+
const mutation = gql`
7269+
mutation updateConfigValue($input: UpdateConfigValueInput!) {
7270+
updateConfigValue(input: $input) {
7271+
clientMutationId
7272+
configValue {
7273+
value
7274+
isMasterKeyOnly
7275+
}
7276+
}
7277+
}
7278+
`;
7279+
7280+
const query = gql`
7281+
query configValue($paramName: String!) {
7282+
configValue(paramName: $paramName) {
7283+
value
7284+
isMasterKeyOnly
7285+
}
7286+
}
7287+
`;
7288+
7289+
const mutationResult = await apolloClient.mutate({
7290+
mutation,
7291+
variables: {
7292+
input: {
7293+
clientMutationId: 'test-mutation-id-3',
7294+
paramName: 'existingParam',
7295+
value: 'updatedValue',
7296+
isMasterKeyOnly: false,
7297+
},
7298+
},
7299+
context: {
7300+
headers: {
7301+
'X-Parse-Master-Key': 'test',
7302+
},
7303+
},
7304+
});
7305+
7306+
expect(mutationResult.errors).toBeUndefined();
7307+
expect(mutationResult.data.updateConfigValue.configValue.value).toEqual('updatedValue');
7308+
7309+
const queryResult = await apolloClient.query({
7310+
query,
7311+
variables: { paramName: 'existingParam' },
7312+
context: {
7313+
headers: {
7314+
'X-Parse-Master-Key': 'test',
7315+
},
7316+
},
7317+
});
7318+
7319+
expect(queryResult.errors).toBeUndefined();
7320+
expect(queryResult.data.configValue.value).toEqual('updatedValue');
7321+
});
7322+
7323+
it("should require master key to update config", async () => {
7324+
const mutation = gql`
7325+
mutation updateConfigValue($input: UpdateConfigValueInput!) {
7326+
updateConfigValue(input: $input) {
7327+
clientMutationId
7328+
configValue {
7329+
value
7330+
isMasterKeyOnly
7331+
}
7332+
}
7333+
}
7334+
`;
7335+
7336+
try {
7337+
await apolloClient.mutate({
7338+
mutation,
7339+
variables: {
7340+
input: {
7341+
clientMutationId: 'test-mutation-id-4',
7342+
paramName: 'testParam',
7343+
value: 'testValue',
7344+
isMasterKeyOnly: false,
7345+
},
7346+
},
7347+
context: {
7348+
headers: {
7349+
'X-Parse-Application-Id': 'test',
7350+
},
7351+
},
7352+
});
7353+
fail('Should have thrown an error');
7354+
} catch (error) {
7355+
expect(error.graphQLErrors).toBeDefined();
7356+
expect(error.graphQLErrors[0].message).toContain('Permission denied');
7357+
}
7358+
});
71087359
})
71097360

71107361
describe('Users Queries', () => {

src/GraphQL/ParseGraphQLSchema.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const RESERVED_GRAPHQL_TYPE_NAMES = [
4949
'DeleteClassPayload',
5050
'PageInfo',
5151
];
52-
const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'class', 'classes'];
52+
const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'class', 'classes', 'configValue'];
5353
const RESERVED_GRAPHQL_MUTATION_NAMES = [
5454
'signUp',
5555
'logIn',
@@ -59,6 +59,7 @@ const RESERVED_GRAPHQL_MUTATION_NAMES = [
5959
'createClass',
6060
'updateClass',
6161
'deleteClass',
62+
'updateConfigValue',
6263
];
6364

6465
class ParseGraphQLSchema {
@@ -118,6 +119,7 @@ class ParseGraphQLSchema {
118119
this.functionNamesString = functionNamesString;
119120
this.parseClassTypes = {};
120121
this.viewerType = null;
122+
this.configValueType = null;
121123
this.graphQLAutoSchema = null;
122124
this.graphQLSchema = null;
123125
this.graphQLTypes = [];
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { GraphQLNonNull, GraphQLString, GraphQLBoolean } from 'graphql';
2+
import { mutationWithClientMutationId } from 'graphql-relay';
3+
import Parse from 'parse/node';
4+
import { createSanitizedError } from '../../Error';
5+
import GlobalConfigRouter from '../../Routers/GlobalConfigRouter';
6+
7+
const globalConfigRouter = new GlobalConfigRouter();
8+
9+
const updateConfigValue = async (context, paramName, value, isMasterKeyOnly = false) => {
10+
const { config, auth } = context;
11+
12+
if (!auth.isMaster) {
13+
throw createSanitizedError(
14+
Parse.Error.OPERATION_FORBIDDEN,
15+
'Master Key is required to update GlobalConfig.'
16+
);
17+
}
18+
19+
await globalConfigRouter.updateGlobalConfig({
20+
body: {
21+
params: { [paramName]: value },
22+
masterKeyOnly: { [paramName]: isMasterKeyOnly },
23+
},
24+
config,
25+
auth,
26+
context,
27+
});
28+
29+
return { value, isMasterKeyOnly };
30+
};
31+
32+
const load = parseGraphQLSchema => {
33+
const updateConfigValueMutation = mutationWithClientMutationId({
34+
name: 'UpdateConfigValue',
35+
description: 'Updates the value of a specific parameter in GlobalConfig.',
36+
inputFields: {
37+
paramName: {
38+
description: 'The name of the parameter to set.',
39+
type: new GraphQLNonNull(GraphQLString),
40+
},
41+
value: {
42+
description: 'The value to set for the parameter.',
43+
type: new GraphQLNonNull(GraphQLString),
44+
},
45+
isMasterKeyOnly: {
46+
description: 'Whether this parameter should only be accessible with master key.',
47+
type: GraphQLBoolean,
48+
defaultValue: false,
49+
},
50+
},
51+
outputFields: {
52+
configValue: {
53+
description: 'The updated config value.',
54+
type: new GraphQLNonNull(parseGraphQLSchema.configValueType),
55+
},
56+
},
57+
mutateAndGetPayload: async (args, context) => {
58+
try {
59+
const { paramName, value, isMasterKeyOnly } = args;
60+
const result = await updateConfigValue(context, paramName, value, isMasterKeyOnly);
61+
return {
62+
configValue: result,
63+
};
64+
} catch (e) {
65+
parseGraphQLSchema.handleError(e);
66+
}
67+
},
68+
});
69+
70+
parseGraphQLSchema.addGraphQLType(updateConfigValueMutation.args.input.type.ofType, true, true);
71+
parseGraphQLSchema.addGraphQLType(updateConfigValueMutation.type, true, true);
72+
parseGraphQLSchema.addGraphQLMutation('updateConfigValue', updateConfigValueMutation, true, true);
73+
};
74+
75+
export { load, updateConfigValue };
76+

0 commit comments

Comments
 (0)