From 4a16f8cbebda86a69786e21508004aa024f70efd Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Tue, 29 Aug 2023 10:20:39 +0200 Subject: [PATCH 1/5] fix: graphql upload --- spec/ParseGraphQLServer.spec.js | 114 ++++++++++++++++++++- src/GraphQL/loaders/parseClassMutations.js | 2 + src/GraphQL/loaders/usersMutations.js | 2 + src/GraphQL/transformers/mutation.js | 30 +++++- 4 files changed, 140 insertions(+), 8 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 87718da13a..da665a64cd 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -6833,7 +6833,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({ @@ -9299,7 +9299,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', @@ -9547,7 +9547,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(); diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index 41d7c09dde..aa15f70505 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -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 }, }); @@ -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 }, }); diff --git a/src/GraphQL/loaders/usersMutations.js b/src/GraphQL/loaders/usersMutations.js index 183268a191..58236cdbd4 100644 --- a/src/GraphQL/loaders/usersMutations.js +++ b/src/GraphQL/loaders/usersMutations.js @@ -38,6 +38,7 @@ const load = parseGraphQLSchema => { const parseFields = await transformTypes('create', fields, { className: '_User', parseGraphQLSchema, + originalFields: args.fields, req: { config, auth, info }, }); @@ -114,6 +115,7 @@ const load = parseGraphQLSchema => { const parseFields = await transformTypes('create', fields, { className: '_User', parseGraphQLSchema, + originalFields: args.fields, req: { config, auth, info }, }); diff --git a/src/GraphQL/transformers/mutation.js b/src/GraphQL/transformers/mutation.js index 5b72d6f05d..c11ed1073e 100644 --- a/src/GraphQL/transformers/mutation.js +++ b/src/GraphQL/transformers/mutation.js @@ -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, @@ -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); + // We need to use the originalFields to handle the 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 ); @@ -64,6 +67,7 @@ const transformTypes = async ( parseClass.fields[field].targetClass, field, fields[field], + originalFields[field], parseGraphQLSchema, req ); @@ -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, @@ -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 }, }); @@ -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, @@ -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( From 1c235332dee5897683b85c3922023435e8aa1d4e Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sun, 29 Oct 2023 12:29:12 +0100 Subject: [PATCH 2/5] fix: test --- spec/ParseGraphQLServer.spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 7ed1714d3c..3802f9e9be 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -36,6 +36,8 @@ const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); const { ReadPreference, Collection } = require('mongodb'); const { v4: uuidv4 } = require('uuid'); +const testAgent = new http.Agent(); + function handleError(e) { if (e && e.networkError && e.networkError.result && e.networkError.result.errors) { fail(e.networkError.result.errors); @@ -6872,6 +6874,7 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, + agent: testAgent, }); expect(res.status).toEqual(200); @@ -9335,6 +9338,7 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, + agent: testAgent, }); expect(res.status).toEqual(200); @@ -9447,6 +9451,7 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body: body2, + agent: testAgent, }); expect(res.status).toEqual(200); const result2 = JSON.parse(await res.text()); @@ -9639,6 +9644,7 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, + agent: testAgent, }); expect(res.status).toEqual(200); const result = await res.json(); @@ -9692,6 +9698,7 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, + agent: testAgent, }); const result = JSON.parse(await res.text()); From f5fb9021e4a3f7e66b4527d21d29bd791f8b4f42 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sun, 29 Oct 2023 12:36:59 +0100 Subject: [PATCH 3/5] test: update node version 18 --- .github/workflows/ci.yml | 110 ++++++++++++++++---------------- package.json | 2 +- spec/ParseGraphQLServer.spec.js | 7 -- 3 files changed, 56 insertions(+), 63 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 756e4e2419..b525062732 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: ci on: push: - branches: [ release, alpha, beta, next-major, 'release-[0-9]+.x.x' ] + branches: [release, alpha, beta, next-major, 'release-[0-9]+.x.x'] pull_request: branches: - '**' @@ -21,17 +21,17 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript' ] + language: ['javascript'] steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - source-root: src - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Checkout repository + uses: actions/checkout@v3 + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + source-root: src + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 check-ci: name: Node Engine Check timeout-minutes: 15 @@ -54,30 +54,30 @@ jobs: - name: CI Node Engine Check run: npm run ci:checkNodeEngine check-lint: - name: Lint - timeout-minutes: 15 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - name: Cache Node.js modules - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}- - - name: Install dependencies - run: npm ci - - run: npm run lint + name: Lint + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.NODE_VERSION }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - name: Cache Node.js modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}- + - name: Install dependencies + run: npm ci + - run: npm run lint check-definitions: - name: Check Definitions - timeout-minutes: 5 - runs-on: ubuntu-latest - steps: + name: Check Definitions + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} uses: actions/setup-node@v2 @@ -95,25 +95,25 @@ jobs: - name: CI Definitions Check run: npm run ci:definitionsCheck check-circular: - name: Circular Dependencies - timeout-minutes: 5 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - name: Cache Node.js modules - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}- - - name: Install dependencies - run: npm ci - - run: npm run madge:circular + name: Circular Dependencies + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.NODE_VERSION }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - name: Cache Node.js modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}- + - name: Install dependencies + run: npm ci + - run: npm run madge:circular check-docker: name: Docker Build timeout-minutes: 15 @@ -177,7 +177,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 @@ -186,7 +186,7 @@ jobs: redis: image: redis ports: - - 6379:6379 + - 6379:6379 env: MONGODB_VERSION: ${{ matrix.MONGODB_VERSION }} MONGODB_TOPOLOGY: ${{ matrix.MONGODB_TOPOLOGY }} diff --git a/package.json b/package.json index 1248432259..ff9f9e2cea 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "test:mongodb:5.3.2": "npm run test:mongodb --dbversion=5.3.2", "test:mongodb:6.0.2": "npm run test:mongodb --dbversion=6.0.2", "posttest:mongodb": "mongodb-runner stop", - "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start", + "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=6.0.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start", "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "test": "npm run testonly", "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner stop", diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 3802f9e9be..7ed1714d3c 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -36,8 +36,6 @@ const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); const { ReadPreference, Collection } = require('mongodb'); const { v4: uuidv4 } = require('uuid'); -const testAgent = new http.Agent(); - function handleError(e) { if (e && e.networkError && e.networkError.result && e.networkError.result.errors) { fail(e.networkError.result.errors); @@ -6874,7 +6872,6 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, - agent: testAgent, }); expect(res.status).toEqual(200); @@ -9338,7 +9335,6 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, - agent: testAgent, }); expect(res.status).toEqual(200); @@ -9451,7 +9447,6 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body: body2, - agent: testAgent, }); expect(res.status).toEqual(200); const result2 = JSON.parse(await res.text()); @@ -9644,7 +9639,6 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, - agent: testAgent, }); expect(res.status).toEqual(200); const result = await res.json(); @@ -9698,7 +9692,6 @@ describe('ParseGraphQLServer', () => { method: 'POST', headers, body, - agent: testAgent, }); const result = JSON.parse(await res.text()); From 7ee2dc3034f94a741c075c8b9e789d6b279888bb Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sun, 29 Oct 2023 12:41:16 +0100 Subject: [PATCH 4/5] test: restore mongodb version test script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ff9f9e2cea..1248432259 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "test:mongodb:5.3.2": "npm run test:mongodb --dbversion=5.3.2", "test:mongodb:6.0.2": "npm run test:mongodb --dbversion=6.0.2", "posttest:mongodb": "mongodb-runner stop", - "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=6.0.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start", + "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start", "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine", "test": "npm run testonly", "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner stop", From d1c259d98d59918afd95cd13a5262e4fed85074b Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sun, 29 Oct 2023 22:24:47 +0100 Subject: [PATCH 5/5] Update src/GraphQL/transformers/mutation.js Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/GraphQL/transformers/mutation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL/transformers/mutation.js b/src/GraphQL/transformers/mutation.js index c11ed1073e..17dd6a8d4b 100644 --- a/src/GraphQL/transformers/mutation.js +++ b/src/GraphQL/transformers/mutation.js @@ -44,8 +44,8 @@ const transformTypes = async ( fields[field] = transformers.polygon(fields[field]); break; case inputTypeField.type === defaultGraphQLTypes.FILE_INPUT: - // We need to use the originalFields to handle the file upload - // since fields are a deepcopy and do not keep the file object + // 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':