Skip to content

Commit dd0d451

Browse files
committed
Client mutation id on createFile mutation
1 parent b536f5b commit dd0d451

File tree

3 files changed

+153
-77
lines changed

3 files changed

+153
-77
lines changed

spec/ParseGraphQLServer.spec.js

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const gql = require('graphql-tag');
1717
const { ParseServer } = require('../');
1818
const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer');
1919
const ReadPreference = require('mongodb').ReadPreference;
20+
const uuidv4 = require('uuid/v4');
2021

2122
function handleError(e) {
2223
if (
@@ -780,6 +781,45 @@ describe('ParseGraphQLServer', () => {
780781
expect(userFields).toContain('id');
781782
expect(userFields).toContain('objectId');
782783
});
784+
785+
it('should have clientMutationId in create file input', async () => {
786+
const createFileInputFields = (await apolloClient.query({
787+
query: gql`
788+
query {
789+
__type(name: "CreateFileInput") {
790+
inputFields {
791+
name
792+
}
793+
}
794+
}
795+
`,
796+
})).data['__type'].inputFields
797+
.map(field => field.name)
798+
.sort();
799+
800+
expect(createFileInputFields).toEqual(['clientMutationId', 'upload']);
801+
});
802+
803+
it('should have clientMutationId in create file payload', async () => {
804+
const createFilePayloadFields = (await apolloClient.query({
805+
query: gql`
806+
query {
807+
__type(name: "CreateFilePayload") {
808+
fields {
809+
name
810+
}
811+
}
812+
}
813+
`,
814+
})).data['__type'].fields
815+
.map(field => field.name)
816+
.sort();
817+
818+
expect(createFilePayloadFields).toEqual([
819+
'clientMutationId',
820+
'fileInfo',
821+
]);
822+
});
783823
});
784824

785825
describe('Parse Class Types', () => {
@@ -5510,6 +5550,8 @@ describe('ParseGraphQLServer', () => {
55105550
describe('Files Mutations', () => {
55115551
describe('Create', () => {
55125552
it('should return File object', async () => {
5553+
const clientMutationId = uuidv4();
5554+
55135555
parseServer = await global.reconfigureServer({
55145556
publicServerURL: 'http://localhost:13377/parse',
55155557
});
@@ -5519,19 +5561,28 @@ describe('ParseGraphQLServer', () => {
55195561
'operations',
55205562
JSON.stringify({
55215563
query: `
5522-
mutation CreateFile($upload: Upload!) {
5523-
createFile(upload: $upload) {
5524-
name
5525-
url
5564+
mutation CreateFile($input: CreateFileInput!) {
5565+
createFile(input: $input) {
5566+
clientMutationId
5567+
fileInfo {
5568+
name
5569+
url
5570+
}
55265571
}
55275572
}
55285573
`,
55295574
variables: {
5530-
upload: null,
5575+
input: {
5576+
clientMutationId,
5577+
upload: null,
5578+
},
55315579
},
55325580
})
55335581
);
5534-
body.append('map', JSON.stringify({ 1: ['variables.upload'] }));
5582+
body.append(
5583+
'map',
5584+
JSON.stringify({ 1: ['variables.input.upload'] })
5585+
);
55355586
body.append('1', 'My File Content', {
55365587
filename: 'myFileName.txt',
55375588
contentType: 'text/plain',
@@ -5547,14 +5598,17 @@ describe('ParseGraphQLServer', () => {
55475598

55485599
const result = JSON.parse(await res.text());
55495600

5550-
expect(result.data.createFile.name).toEqual(
5601+
expect(result.data.createFile.clientMutationId).toEqual(
5602+
clientMutationId
5603+
);
5604+
expect(result.data.createFile.fileInfo.name).toEqual(
55515605
jasmine.stringMatching(/_myFileName.txt$/)
55525606
);
5553-
expect(result.data.createFile.url).toEqual(
5607+
expect(result.data.createFile.fileInfo.url).toEqual(
55545608
jasmine.stringMatching(/_myFileName.txt$/)
55555609
);
55565610

5557-
res = await fetch(result.data.createFile.url);
5611+
res = await fetch(result.data.createFile.fileInfo.url);
55585612

55595613
expect(res.status).toEqual(200);
55605614
expect(await res.text()).toEqual('My File Content');
@@ -7056,19 +7110,26 @@ describe('ParseGraphQLServer', () => {
70567110
'operations',
70577111
JSON.stringify({
70587112
query: `
7059-
mutation CreateFile($upload: Upload!) {
7060-
createFile(upload: $upload) {
7061-
name
7062-
url
7113+
mutation CreateFile($input: CreateFileInput!) {
7114+
createFile(input: $input) {
7115+
fileInfo {
7116+
name
7117+
url
7118+
}
70637119
}
70647120
}
70657121
`,
70667122
variables: {
7067-
upload: null,
7123+
input: {
7124+
upload: null,
7125+
},
70687126
},
70697127
})
70707128
);
7071-
body.append('map', JSON.stringify({ 1: ['variables.upload'] }));
7129+
body.append(
7130+
'map',
7131+
JSON.stringify({ 1: ['variables.input.upload'] })
7132+
);
70727133
body.append('1', 'My File Content', {
70737134
filename: 'myFileName.txt',
70747135
contentType: 'text/plain',
@@ -7084,14 +7145,14 @@ describe('ParseGraphQLServer', () => {
70847145

70857146
const result = JSON.parse(await res.text());
70867147

7087-
expect(result.data.createFile.name).toEqual(
7148+
expect(result.data.createFile.fileInfo.name).toEqual(
70887149
jasmine.stringMatching(/_myFileName.txt$/)
70897150
);
7090-
expect(result.data.createFile.url).toEqual(
7151+
expect(result.data.createFile.fileInfo.url).toEqual(
70917152
jasmine.stringMatching(/_myFileName.txt$/)
70927153
);
70937154

7094-
const someFieldValue = result.data.createFile.name;
7155+
const someFieldValue = result.data.createFile.fileInfo.name;
70957156

70967157
await apolloClient.mutate({
70977158
mutation: gql`
@@ -7180,10 +7241,10 @@ describe('ParseGraphQLServer', () => {
71807241

71817242
expect(typeof getResult.data.someClass.someField).toEqual('object');
71827243
expect(getResult.data.someClass.someField.name).toEqual(
7183-
result.data.createFile.name
7244+
result.data.createFile.fileInfo.name
71847245
);
71857246
expect(getResult.data.someClass.someField.url).toEqual(
7186-
result.data.createFile.url
7247+
result.data.createFile.fileInfo.url
71877248
);
71887249
expect(getResult.data.findSomeClass1.results.length).toEqual(1);
71897250
expect(getResult.data.findSomeClass2.results.length).toEqual(1);

src/GraphQL/ParseGraphQLSchema.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const RESERVED_GRAPHQL_TYPE_NAMES = [
3333
'Query',
3434
'Mutation',
3535
'Subscription',
36+
'CreateFileInput',
37+
'CreateFilePayload',
3638
'Viewer',
3739
'SignUpFieldsInput',
3840
'LogInFieldsInput',

src/GraphQL/loaders/filesMutations.js

Lines changed: 70 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,93 @@
11
import { GraphQLNonNull } from 'graphql';
2+
import { mutationWithClientMutationId } from 'graphql-relay';
23
import { GraphQLUpload } from 'graphql-upload';
34
import Parse from 'parse/node';
45
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
56
import logger from '../../logger';
67

78
const load = parseGraphQLSchema => {
8-
parseGraphQLSchema.addGraphQLMutation(
9-
'createFile',
10-
{
11-
description:
12-
'The create mutation can be used to create and upload a new file.',
13-
args: {
14-
upload: {
15-
description: 'This is the new file to be created and uploaded',
16-
type: new GraphQLNonNull(GraphQLUpload),
17-
},
9+
const createMutation = mutationWithClientMutationId({
10+
name: 'CreateFile',
11+
description:
12+
'The createFile mutation can be used to create and upload a new file.',
13+
inputFields: {
14+
upload: {
15+
description: 'This is the new file to be created and uploaded.',
16+
type: new GraphQLNonNull(GraphQLUpload),
1817
},
19-
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
20-
async resolve(_source, args, context) {
21-
try {
22-
const { upload } = args;
23-
const { config } = context;
18+
},
19+
outputFields: {
20+
fileInfo: {
21+
description: 'This is the created file info.',
22+
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
23+
},
24+
},
25+
mutateAndGetPayload: async (args, context) => {
26+
try {
27+
const { upload } = args;
28+
const { config } = context;
2429

25-
const { createReadStream, filename, mimetype } = await upload;
26-
let data = null;
27-
if (createReadStream) {
28-
const stream = createReadStream();
29-
data = await new Promise((resolve, reject) => {
30-
const chunks = [];
31-
stream
32-
.on('error', reject)
33-
.on('data', chunk => chunks.push(chunk))
34-
.on('end', () => resolve(Buffer.concat(chunks)));
35-
});
36-
}
30+
const { createReadStream, filename, mimetype } = await upload;
31+
let data = null;
32+
if (createReadStream) {
33+
const stream = createReadStream();
34+
data = await new Promise((resolve, reject) => {
35+
const chunks = [];
36+
stream
37+
.on('error', reject)
38+
.on('data', chunk => chunks.push(chunk))
39+
.on('end', () => resolve(Buffer.concat(chunks)));
40+
});
41+
}
3742

38-
if (!data || !data.length) {
39-
throw new Parse.Error(
40-
Parse.Error.FILE_SAVE_ERROR,
41-
'Invalid file upload.'
42-
);
43-
}
43+
if (!data || !data.length) {
44+
throw new Parse.Error(
45+
Parse.Error.FILE_SAVE_ERROR,
46+
'Invalid file upload.'
47+
);
48+
}
4449

45-
if (filename.length > 128) {
46-
throw new Parse.Error(
47-
Parse.Error.INVALID_FILE_NAME,
48-
'Filename too long.'
49-
);
50-
}
50+
if (filename.length > 128) {
51+
throw new Parse.Error(
52+
Parse.Error.INVALID_FILE_NAME,
53+
'Filename too long.'
54+
);
55+
}
5156

52-
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
53-
throw new Parse.Error(
54-
Parse.Error.INVALID_FILE_NAME,
55-
'Filename contains invalid characters.'
56-
);
57-
}
57+
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
58+
throw new Parse.Error(
59+
Parse.Error.INVALID_FILE_NAME,
60+
'Filename contains invalid characters.'
61+
);
62+
}
5863

59-
try {
60-
return await config.filesController.createFile(
64+
try {
65+
return {
66+
fileInfo: await config.filesController.createFile(
6167
config,
6268
filename,
6369
data,
6470
mimetype
65-
);
66-
} catch (e) {
67-
logger.error('Error creating a file: ', e);
68-
throw new Parse.Error(
69-
Parse.Error.FILE_SAVE_ERROR,
70-
`Could not store file: ${filename}.`
71-
);
72-
}
71+
),
72+
};
7373
} catch (e) {
74-
parseGraphQLSchema.handleError(e);
74+
logger.error('Error creating a file: ', e);
75+
throw new Parse.Error(
76+
Parse.Error.FILE_SAVE_ERROR,
77+
`Could not store file: ${filename}.`
78+
);
7579
}
76-
},
80+
} catch (e) {
81+
parseGraphQLSchema.handleError(e);
82+
}
7783
},
84+
});
85+
86+
parseGraphQLSchema.addGraphQLType(createMutation.args.input.type, true, true);
87+
parseGraphQLSchema.addGraphQLType(createMutation.type, true, true);
88+
parseGraphQLSchema.addGraphQLMutation(
89+
'createFile',
90+
createMutation,
7891
true,
7992
true
8093
);

0 commit comments

Comments
 (0)