Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: GraphQL file upload fails in case of use of pointer or relation #8721

Merged
merged 9 commits into from
Feb 14, 2024
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ jobs:
- name: Node 18
MONGODB_VERSION: 4.4.13
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 18.12.1
NODE_VERSION: 18.18.2
fail-fast: false
name: ${{ matrix.name }}
timeout-minutes: 15
Expand Down
114 changes: 111 additions & 3 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6832,7 +6832,7 @@ describe('ParseGraphQLServer', () => {

describe('Files Mutations', () => {
describe('Create', () => {
it_only_node_version('<17')('should return File object', async () => {
it('should return File object', async () => {
const clientMutationId = uuidv4();

parseServer = await global.reconfigureServer({
Expand Down Expand Up @@ -9298,7 +9298,7 @@ describe('ParseGraphQLServer', () => {
expect(result6[0].node.name).toEqual('imACountry3');
});

it_only_node_version('<17')('should support files', async () => {
it('should support files', async () => {
try {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
Expand Down Expand Up @@ -9546,7 +9546,115 @@ describe('ParseGraphQLServer', () => {
}
});

it_only_node_version('<17')('should not upload if file is too large', async () => {
it('should support file upload for on fly creation through pointer and relation', async () => {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
});
const schema = new Parse.Schema('SomeClass');
schema.addFile('someFileField');
schema.addPointer('somePointerField', 'SomeClass');
schema.addRelation('someRelationField', 'SomeClass');
await schema.save();

const body = new FormData();
body.append(
'operations',
JSON.stringify({
query: `
mutation UploadFiles(
$fields: CreateSomeClassFieldsInput
) {
createSomeClass(
input: { fields: $fields }
) {
someClass {
id
someFileField {
name
url
}
somePointerField {
id
someFileField {
name
url
}
}
someRelationField {
edges {
node {
id
someFileField {
name
url
}
}
}
}
}
}
}
`,
variables: {
fields: {
someFileField: { upload: null },
somePointerField: {
createAndLink: {
someFileField: { upload: null },
},
},
someRelationField: {
createAndAdd: [
{
someFileField: { upload: null },
},
],
},
},
},
})
);
body.append(
'map',
JSON.stringify({
1: ['variables.fields.someFileField.upload'],
2: ['variables.fields.somePointerField.createAndLink.someFileField.upload'],
3: ['variables.fields.someRelationField.createAndAdd.0.someFileField.upload'],
})
);
body.append('1', 'My File Content someFileField', {
filename: 'someFileField.txt',
contentType: 'text/plain',
});
body.append('2', 'My File Content somePointerField', {
filename: 'somePointerField.txt',
contentType: 'text/plain',
});
body.append('3', 'My File Content someRelationField', {
filename: 'someRelationField.txt',
contentType: 'text/plain',
});

const res = await fetch('http://localhost:13377/graphql', {
method: 'POST',
headers,
body,
});
expect(res.status).toEqual(200);
const result = await res.json();
console.log(result);
expect(result.data.createSomeClass.someClass.someFileField.name).toEqual(
jasmine.stringMatching(/_someFileField.txt$/)
);
expect(result.data.createSomeClass.someClass.somePointerField.someFileField.name).toEqual(
jasmine.stringMatching(/_somePointerField.txt$/)
);
expect(
result.data.createSomeClass.someClass.someRelationField.edges[0].node.someFileField.name
).toEqual(jasmine.stringMatching(/_someRelationField.txt$/));
});

it('should not upload if file is too large', async () => {
parseGraphQLServer.parseServer.config.maxUploadSize = '1kb';

const body = new FormData();
Expand Down
2 changes: 2 additions & 0 deletions src/GraphQL/loaders/parseClassMutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
const parseFields = await transformTypes('create', fields, {
className,
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});

Expand Down Expand Up @@ -190,6 +191,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG
const parseFields = await transformTypes('update', fields, {
className,
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});

Expand Down
2 changes: 2 additions & 0 deletions src/GraphQL/loaders/usersMutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const load = parseGraphQLSchema => {
const parseFields = await transformTypes('create', fields, {
className: '_User',
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});

Expand Down Expand Up @@ -114,6 +115,7 @@ const load = parseGraphQLSchema => {
const parseFields = await transformTypes('create', fields, {
className: '_User',
parseGraphQLSchema,
originalFields: args.fields,
req: { config, auth, info },
});

Expand Down
30 changes: 25 additions & 5 deletions src/GraphQL/transformers/mutation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as objectsMutations from '../helpers/objectsMutations';
const transformTypes = async (
inputType: 'create' | 'update',
fields,
{ className, parseGraphQLSchema, req }
{ className, parseGraphQLSchema, req, originalFields }
) => {
const {
classGraphQLCreateType,
Expand Down Expand Up @@ -44,13 +44,16 @@ const transformTypes = async (
fields[field] = transformers.polygon(fields[field]);
break;
case inputTypeField.type === defaultGraphQLTypes.FILE_INPUT:
fields[field] = await transformers.file(fields[field], req);
// Use `originalFields` to handle file upload since fields are a deepcopy and do not
// keep the file object
fields[field] = await transformers.file(originalFields[field], req);
break;
case parseClass.fields[field].type === 'Relation':
fields[field] = await transformers.relation(
parseClass.fields[field].targetClass,
field,
fields[field],
originalFields[field],
parseGraphQLSchema,
req
);
Expand All @@ -64,6 +67,7 @@ const transformTypes = async (
parseClass.fields[field].targetClass,
field,
fields[field],
originalFields[field],
parseGraphQLSchema,
req
);
Expand Down Expand Up @@ -135,7 +139,14 @@ const transformers = {
}
return parseACL;
},
relation: async (targetClass, field, value, parseGraphQLSchema, { config, auth, info }) => {
relation: async (
targetClass,
field,
value,
originalValue,
parseGraphQLSchema,
{ config, auth, info }
) => {
if (Object.keys(value).length === 0)
throw new Parse.Error(
Parse.Error.INVALID_POINTER,
Expand All @@ -151,9 +162,10 @@ const transformers = {
if (value.createAndAdd) {
nestedObjectsToAdd = (
await Promise.all(
value.createAndAdd.map(async input => {
value.createAndAdd.map(async (input, i) => {
const parseFields = await transformTypes('create', input, {
className: targetClass,
originalFields: originalValue.createAndAdd[i],
parseGraphQLSchema,
req: { config, auth, info },
});
Expand Down Expand Up @@ -204,7 +216,14 @@ const transformers = {
}
return op;
},
pointer: async (targetClass, field, value, parseGraphQLSchema, { config, auth, info }) => {
pointer: async (
targetClass,
field,
value,
originalValue,
parseGraphQLSchema,
{ config, auth, info }
) => {
if (Object.keys(value).length > 1 || Object.keys(value).length === 0)
throw new Parse.Error(
Parse.Error.INVALID_POINTER,
Expand All @@ -216,6 +235,7 @@ const transformers = {
const parseFields = await transformTypes('create', value.createAndLink, {
className: targetClass,
parseGraphQLSchema,
originalFields: originalValue.createAndLink,
req: { config, auth, info },
});
nestedObjectToAdd = await objectsMutations.createObject(
Expand Down
Loading