feat: replace GraphQL Apollo with GraphQL Yoga (#7967)
This commit is contained in:
1308
package-lock.json
generated
1308
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,14 +19,11 @@
|
|||||||
],
|
],
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "3.5.10",
|
|
||||||
"@apollographql/graphql-playground-html": "1.6.29",
|
|
||||||
"@graphql-tools/links": "8.2.11",
|
|
||||||
"@graphql-tools/stitch": "6.2.4",
|
"@graphql-tools/stitch": "6.2.4",
|
||||||
"@graphql-tools/utils": "6.2.4",
|
"@graphql-tools/utils": "6.2.4",
|
||||||
|
"@graphql-yoga/node": "2.6.0",
|
||||||
"@parse/fs-files-adapter": "1.2.2",
|
"@parse/fs-files-adapter": "1.2.2",
|
||||||
"@parse/push-adapter": "4.1.2",
|
"@parse/push-adapter": "4.1.2",
|
||||||
"apollo-server-express": "2.25.2",
|
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.0",
|
"body-parser": "1.20.0",
|
||||||
"commander": "5.1.0",
|
"commander": "5.1.0",
|
||||||
@@ -38,7 +35,6 @@
|
|||||||
"graphql-list-fields": "2.0.2",
|
"graphql-list-fields": "2.0.2",
|
||||||
"graphql-relay": "0.7.0",
|
"graphql-relay": "0.7.0",
|
||||||
"graphql-tag": "2.12.6",
|
"graphql-tag": "2.12.6",
|
||||||
"graphql-upload": "11.0.0",
|
|
||||||
"intersect": "1.0.1",
|
"intersect": "1.0.1",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"jwks-rsa": "2.0.5",
|
"jwks-rsa": "2.0.5",
|
||||||
@@ -63,6 +59,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/core": "1.2.6",
|
"@actions/core": "1.2.6",
|
||||||
|
"@apollo/client": "3.6.1",
|
||||||
"@babel/cli": "7.10.0",
|
"@babel/cli": "7.10.0",
|
||||||
"@babel/core": "7.10.0",
|
"@babel/core": "7.10.0",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "7.10.0",
|
"@babel/plugin-proposal-object-rest-spread": "7.10.0",
|
||||||
@@ -98,7 +95,7 @@
|
|||||||
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
|
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
|
||||||
"mongodb-runner": "4.8.1",
|
"mongodb-runner": "4.8.1",
|
||||||
"mongodb-version-list": "1.0.0",
|
"mongodb-version-list": "1.0.0",
|
||||||
"node-fetch": "3.1.1",
|
"node-fetch": "3.2.4",
|
||||||
"nyc": "15.1.0",
|
"nyc": "15.1.0",
|
||||||
"prettier": "2.0.5",
|
"prettier": "2.0.5",
|
||||||
"semantic-release": "17.4.6",
|
"semantic-release": "17.4.6",
|
||||||
|
|||||||
@@ -98,6 +98,24 @@ describe('ParseGraphQLServer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('_getServer', () => {
|
||||||
|
it('should only return new server on schema changes', async () => {
|
||||||
|
parseGraphQLServer.server = undefined;
|
||||||
|
const server1 = await parseGraphQLServer._getServer();
|
||||||
|
const server2 = await parseGraphQLServer._getServer();
|
||||||
|
expect(server1).toBe(server2);
|
||||||
|
|
||||||
|
// Trigger a schema change
|
||||||
|
const obj = new Parse.Object('SomeClass');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const server3 = await parseGraphQLServer._getServer();
|
||||||
|
const server4 = await parseGraphQLServer._getServer();
|
||||||
|
expect(server3).not.toBe(server2);
|
||||||
|
expect(server3).toBe(server4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('_getGraphQLOptions', () => {
|
describe('_getGraphQLOptions', () => {
|
||||||
const req = {
|
const req = {
|
||||||
info: new Object(),
|
info: new Object(),
|
||||||
@@ -106,11 +124,15 @@ describe('ParseGraphQLServer', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it("should return schema and context with req's info, config and auth", async () => {
|
it("should return schema and context with req's info, config and auth", async () => {
|
||||||
const options = await parseGraphQLServer._getGraphQLOptions(req);
|
const options = await parseGraphQLServer._getGraphQLOptions();
|
||||||
|
expect(options.multipart).toEqual({
|
||||||
|
fileSize: 20971520,
|
||||||
|
});
|
||||||
expect(options.schema).toEqual(parseGraphQLServer.parseGraphQLSchema.graphQLSchema);
|
expect(options.schema).toEqual(parseGraphQLServer.parseGraphQLSchema.graphQLSchema);
|
||||||
expect(options.context.info).toEqual(req.info);
|
const contextResponse = options.context({ req });
|
||||||
expect(options.context.config).toEqual(req.config);
|
expect(contextResponse.info).toEqual(req.info);
|
||||||
expect(options.context.auth).toEqual(req.auth);
|
expect(contextResponse.config).toEqual(req.config);
|
||||||
|
expect(contextResponse.auth).toEqual(req.auth);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load GraphQL schema in every call', async () => {
|
it('should load GraphQL schema in every call', async () => {
|
||||||
@@ -467,7 +489,7 @@ describe('ParseGraphQLServer', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be cors enabled', async () => {
|
it('should be cors enabled and scope the response within the source origin', async () => {
|
||||||
let checked = false;
|
let checked = false;
|
||||||
const apolloClient = new ApolloClient({
|
const apolloClient = new ApolloClient({
|
||||||
link: new ApolloLink((operation, forward) => {
|
link: new ApolloLink((operation, forward) => {
|
||||||
@@ -476,7 +498,7 @@ describe('ParseGraphQLServer', () => {
|
|||||||
const {
|
const {
|
||||||
response: { headers },
|
response: { headers },
|
||||||
} = context;
|
} = context;
|
||||||
expect(headers.get('access-control-allow-origin')).toEqual('*');
|
expect(headers.get('access-control-allow-origin')).toEqual('http://example.com');
|
||||||
checked = true;
|
checked = true;
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
@@ -486,7 +508,7 @@ describe('ParseGraphQLServer', () => {
|
|||||||
fetch,
|
fetch,
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
Origin: 'http://someorigin.com',
|
Origin: 'http://example.com',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@@ -504,14 +526,25 @@ describe('ParseGraphQLServer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle Parse headers', async () => {
|
it('should handle Parse headers', async () => {
|
||||||
let checked = false;
|
const test = {
|
||||||
|
context: ({ req: { info, config, auth } }) => {
|
||||||
|
expect(req.info).toBeDefined();
|
||||||
|
expect(req.config).toBeDefined();
|
||||||
|
expect(req.auth).toBeDefined();
|
||||||
|
return {
|
||||||
|
info,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const contextSpy = spyOn(test, 'context');
|
||||||
const originalGetGraphQLOptions = parseGraphQLServer._getGraphQLOptions;
|
const originalGetGraphQLOptions = parseGraphQLServer._getGraphQLOptions;
|
||||||
parseGraphQLServer._getGraphQLOptions = async req => {
|
parseGraphQLServer._getGraphQLOptions = async () => {
|
||||||
expect(req.info).toBeDefined();
|
return {
|
||||||
expect(req.config).toBeDefined();
|
schema: await parseGraphQLServer.parseGraphQLSchema.load(),
|
||||||
expect(req.auth).toBeDefined();
|
context: test.context,
|
||||||
checked = true;
|
};
|
||||||
return await originalGetGraphQLOptions.bind(parseGraphQLServer)(req);
|
|
||||||
};
|
};
|
||||||
const health = (
|
const health = (
|
||||||
await apolloClient.query({
|
await apolloClient.query({
|
||||||
@@ -523,7 +556,7 @@ describe('ParseGraphQLServer', () => {
|
|||||||
})
|
})
|
||||||
).data.health;
|
).data.health;
|
||||||
expect(health).toBeTruthy();
|
expect(health).toBeTruthy();
|
||||||
expect(checked).toBeTruthy();
|
expect(contextSpy).toHaveBeenCalledTimes(1);
|
||||||
parseGraphQLServer._getGraphQLOptions = originalGetGraphQLOptions;
|
parseGraphQLServer._getGraphQLOptions = originalGetGraphQLOptions;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -6786,7 +6819,7 @@ describe('ParseGraphQLServer', () => {
|
|||||||
|
|
||||||
expect(queryResult.data.customers.edges.length).toEqual(1);
|
expect(queryResult.data.customers.edges.length).toEqual(1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(JSON.stringify(e));
|
console.error(JSON.stringify(e));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -9107,15 +9140,15 @@ describe('ParseGraphQLServer', () => {
|
|||||||
'operations',
|
'operations',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
query: `
|
query: `
|
||||||
mutation CreateFile($input: CreateFileInput!) {
|
mutation CreateFile($input: CreateFileInput!) {
|
||||||
createFile(input: $input) {
|
createFile(input: $input) {
|
||||||
fileInfo {
|
fileInfo {
|
||||||
name
|
name
|
||||||
url
|
url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
`,
|
||||||
`,
|
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
upload: null,
|
upload: null,
|
||||||
@@ -9176,46 +9209,46 @@ describe('ParseGraphQLServer', () => {
|
|||||||
'operations',
|
'operations',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
query: `
|
query: `
|
||||||
mutation CreateSomeObject(
|
mutation CreateSomeObject(
|
||||||
$fields1: CreateSomeClassFieldsInput
|
$fields1: CreateSomeClassFieldsInput
|
||||||
$fields2: CreateSomeClassFieldsInput
|
$fields2: CreateSomeClassFieldsInput
|
||||||
$fields3: CreateSomeClassFieldsInput
|
$fields3: CreateSomeClassFieldsInput
|
||||||
) {
|
|
||||||
createSomeClass1: createSomeClass(
|
|
||||||
input: { fields: $fields1 }
|
|
||||||
) {
|
) {
|
||||||
someClass {
|
createSomeClass1: createSomeClass(
|
||||||
id
|
input: { fields: $fields1 }
|
||||||
someField {
|
) {
|
||||||
name
|
someClass {
|
||||||
url
|
id
|
||||||
|
someField {
|
||||||
|
name
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createSomeClass2: createSomeClass(
|
||||||
|
input: { fields: $fields2 }
|
||||||
|
) {
|
||||||
|
someClass {
|
||||||
|
id
|
||||||
|
someField {
|
||||||
|
name
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createSomeClass3: createSomeClass(
|
||||||
|
input: { fields: $fields3 }
|
||||||
|
) {
|
||||||
|
someClass {
|
||||||
|
id
|
||||||
|
someField {
|
||||||
|
name
|
||||||
|
url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createSomeClass2: createSomeClass(
|
`,
|
||||||
input: { fields: $fields2 }
|
|
||||||
) {
|
|
||||||
someClass {
|
|
||||||
id
|
|
||||||
someField {
|
|
||||||
name
|
|
||||||
url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createSomeClass3: createSomeClass(
|
|
||||||
input: { fields: $fields3 }
|
|
||||||
) {
|
|
||||||
someClass {
|
|
||||||
id
|
|
||||||
someField {
|
|
||||||
name
|
|
||||||
url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
variables: {
|
||||||
fields1: {
|
fields1: {
|
||||||
someField: { file: someFieldValue },
|
someField: { file: someFieldValue },
|
||||||
@@ -9344,6 +9377,51 @@ describe('ParseGraphQLServer', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it_only_node_version('<17')('should not upload if file is too large', async () => {
|
||||||
|
parseGraphQLServer.parseServer.config.maxUploadSize = '1kb';
|
||||||
|
|
||||||
|
const body = new FormData();
|
||||||
|
body.append(
|
||||||
|
'operations',
|
||||||
|
JSON.stringify({
|
||||||
|
query: `
|
||||||
|
mutation CreateFile($input: CreateFileInput!) {
|
||||||
|
createFile(input: $input) {
|
||||||
|
fileInfo {
|
||||||
|
name
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
upload: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
body.append('map', JSON.stringify({ 1: ['variables.input.upload'] }));
|
||||||
|
body.append(
|
||||||
|
'1',
|
||||||
|
Buffer.alloc(parseGraphQLServer._transformMaxUploadSizeToBytes('2kb'), 1),
|
||||||
|
{
|
||||||
|
filename: 'myFileName.txt',
|
||||||
|
contentType: 'text/plain',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = await fetch('http://localhost:13377/graphql', {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = JSON.parse(await res.text());
|
||||||
|
expect(res.status).toEqual(500);
|
||||||
|
expect(result.errors[0].message).toEqual('File size limit exceeded: 1024 bytes');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support object values', async () => {
|
it('should support object values', async () => {
|
||||||
try {
|
try {
|
||||||
const someObjectFieldValue = {
|
const someObjectFieldValue = {
|
||||||
|
|||||||
@@ -89,13 +89,14 @@ class ParseGraphQLSchema {
|
|||||||
this.graphQLCustomTypeDefs = params.graphQLCustomTypeDefs;
|
this.graphQLCustomTypeDefs = params.graphQLCustomTypeDefs;
|
||||||
this.appId = params.appId || requiredParameter('You must provide the appId!');
|
this.appId = params.appId || requiredParameter('You must provide the appId!');
|
||||||
this.schemaCache = SchemaCache;
|
this.schemaCache = SchemaCache;
|
||||||
|
this.logCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
const { parseGraphQLConfig } = await this._initializeSchemaAndConfig();
|
const { parseGraphQLConfig } = await this._initializeSchemaAndConfig();
|
||||||
const parseClassesArray = await this._getClassesForSchema(parseGraphQLConfig);
|
const parseClassesArray = await this._getClassesForSchema(parseGraphQLConfig);
|
||||||
const functionNames = await this._getFunctionNames();
|
const functionNames = await this._getFunctionNames();
|
||||||
const functionNamesString = JSON.stringify(functionNames);
|
const functionNamesString = functionNames.join();
|
||||||
|
|
||||||
const parseClasses = parseClassesArray.reduce((acc, clazz) => {
|
const parseClasses = parseClassesArray.reduce((acc, clazz) => {
|
||||||
acc[clazz.className] = clazz;
|
acc[clazz.className] = clazz;
|
||||||
@@ -331,6 +332,14 @@ class ParseGraphQLSchema {
|
|||||||
return this.graphQLSchema;
|
return this.graphQLSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logOnce(severity, message) {
|
||||||
|
if (this.logCache[message]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.log[severity](message);
|
||||||
|
this.logCache[message] = true;
|
||||||
|
}
|
||||||
|
|
||||||
addGraphQLType(type, throwError = false, ignoreReserved = false, ignoreConnection = false) {
|
addGraphQLType(type, throwError = false, ignoreReserved = false, ignoreConnection = false) {
|
||||||
if (
|
if (
|
||||||
(!ignoreReserved && RESERVED_GRAPHQL_TYPE_NAMES.includes(type.name)) ||
|
(!ignoreReserved && RESERVED_GRAPHQL_TYPE_NAMES.includes(type.name)) ||
|
||||||
@@ -341,7 +350,7 @@ class ParseGraphQLSchema {
|
|||||||
if (throwError) {
|
if (throwError) {
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
this.log.warn(message);
|
this._logOnce('warn', message);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
this.graphQLTypes.push(type);
|
this.graphQLTypes.push(type);
|
||||||
@@ -357,7 +366,7 @@ class ParseGraphQLSchema {
|
|||||||
if (throwError) {
|
if (throwError) {
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
this.log.warn(message);
|
this._logOnce('warn', message);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
this.graphQLQueries[fieldName] = field;
|
this.graphQLQueries[fieldName] = field;
|
||||||
@@ -373,7 +382,7 @@ class ParseGraphQLSchema {
|
|||||||
if (throwError) {
|
if (throwError) {
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
this.log.warn(message);
|
this._logOnce('warn', message);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
this.graphQLMutations[fieldName] = field;
|
this.graphQLMutations[fieldName] = field;
|
||||||
@@ -482,7 +491,8 @@ class ParseGraphQLSchema {
|
|||||||
if (/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(functionName)) {
|
if (/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(functionName)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.log.warn(
|
this._logOnce(
|
||||||
|
'warn',
|
||||||
`Function ${functionName} could not be added to the auto schema because GraphQL names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/.`
|
`Function ${functionName} could not be added to the auto schema because GraphQL names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/.`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import corsMiddleware from 'cors';
|
import corsMiddleware from 'cors';
|
||||||
import bodyParser from 'body-parser';
|
import { createServer, renderGraphiQL } from '@graphql-yoga/node';
|
||||||
import { graphqlUploadExpress } from 'graphql-upload';
|
|
||||||
import { graphqlExpress } from 'apollo-server-express/dist/expressApollo';
|
|
||||||
import { renderPlaygroundPage } from '@apollographql/graphql-playground-html';
|
|
||||||
import { execute, subscribe } from 'graphql';
|
import { execute, subscribe } from 'graphql';
|
||||||
import { SubscriptionServer } from 'subscriptions-transport-ws';
|
import { SubscriptionServer } from 'subscriptions-transport-ws';
|
||||||
import { handleParseErrors, handleParseHeaders } from '../middlewares';
|
import { handleParseErrors, handleParseHeaders } from '../middlewares';
|
||||||
@@ -32,18 +29,19 @@ class ParseGraphQLServer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getGraphQLOptions(req) {
|
async _getGraphQLOptions() {
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
schema: await this.parseGraphQLSchema.load(),
|
schema: await this.parseGraphQLSchema.load(),
|
||||||
context: {
|
context: ({ req: { info, config, auth } }) => ({
|
||||||
info: req.info,
|
info,
|
||||||
config: req.config,
|
config,
|
||||||
auth: req.auth,
|
auth,
|
||||||
},
|
}),
|
||||||
formatError: error => {
|
multipart: {
|
||||||
// Allow to console.log here to debug
|
fileSize: this._transformMaxUploadSizeToBytes(
|
||||||
return error;
|
this.parseServer.config.maxUploadSize || '20mb'
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -52,6 +50,17 @@ class ParseGraphQLServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getServer() {
|
||||||
|
const schemaRef = this.parseGraphQLSchema.graphQLSchema;
|
||||||
|
const newSchemaRef = await this.parseGraphQLSchema.load();
|
||||||
|
if (schemaRef === newSchemaRef && this._server) {
|
||||||
|
return this._server;
|
||||||
|
}
|
||||||
|
const options = await this._getGraphQLOptions();
|
||||||
|
this._server = createServer(options);
|
||||||
|
return this._server;
|
||||||
|
}
|
||||||
|
|
||||||
_transformMaxUploadSizeToBytes(maxUploadSize) {
|
_transformMaxUploadSizeToBytes(maxUploadSize) {
|
||||||
const unitMap = {
|
const unitMap = {
|
||||||
kb: 1,
|
kb: 1,
|
||||||
@@ -70,22 +79,13 @@ class ParseGraphQLServer {
|
|||||||
requiredParameter('You must provide an Express.js app instance!');
|
requiredParameter('You must provide an Express.js app instance!');
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(
|
|
||||||
this.config.graphQLPath,
|
|
||||||
graphqlUploadExpress({
|
|
||||||
maxFileSize: this._transformMaxUploadSizeToBytes(
|
|
||||||
this.parseServer.config.maxUploadSize || '20mb'
|
|
||||||
),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
app.use(this.config.graphQLPath, corsMiddleware());
|
app.use(this.config.graphQLPath, corsMiddleware());
|
||||||
app.use(this.config.graphQLPath, bodyParser.json());
|
|
||||||
app.use(this.config.graphQLPath, handleParseHeaders);
|
app.use(this.config.graphQLPath, handleParseHeaders);
|
||||||
app.use(this.config.graphQLPath, handleParseErrors);
|
app.use(this.config.graphQLPath, handleParseErrors);
|
||||||
app.use(
|
app.use(this.config.graphQLPath, async (req, res) => {
|
||||||
this.config.graphQLPath,
|
const server = await this._getServer();
|
||||||
graphqlExpress(async req => await this._getGraphQLOptions(req))
|
return server(req, res);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPlayground(app) {
|
applyPlayground(app) {
|
||||||
@@ -98,14 +98,13 @@ class ParseGraphQLServer {
|
|||||||
(_req, res) => {
|
(_req, res) => {
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
res.write(
|
res.write(
|
||||||
renderPlaygroundPage({
|
renderGraphiQL({
|
||||||
endpoint: this.config.graphQLPath,
|
endpoint: this.config.graphQLPath,
|
||||||
version: '1.7.25',
|
|
||||||
subscriptionEndpoint: this.config.subscriptionsPath,
|
subscriptionEndpoint: this.config.subscriptionsPath,
|
||||||
headers: {
|
headers: JSON.stringify({
|
||||||
'X-Parse-Application-Id': this.parseServer.config.appId,
|
'X-Parse-Application-Id': this.parseServer.config.appId,
|
||||||
'X-Parse-Master-Key': this.parseServer.config.masterKey,
|
'X-Parse-Master-Key': this.parseServer.config.masterKey,
|
||||||
},
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const getObject = async (
|
|||||||
options.keys = keys;
|
options.keys = keys;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
if (include) {
|
if (include) {
|
||||||
options.include = include;
|
options.include = include;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
GraphQLUnionType,
|
GraphQLUnionType,
|
||||||
} from 'graphql';
|
} from 'graphql';
|
||||||
import { toGlobalId } from 'graphql-relay';
|
import { toGlobalId } from 'graphql-relay';
|
||||||
import { GraphQLUpload } from '@graphql-tools/links';
|
|
||||||
|
|
||||||
class TypeValidationError extends Error {
|
class TypeValidationError extends Error {
|
||||||
constructor(value, type) {
|
constructor(value, type) {
|
||||||
@@ -223,6 +222,11 @@ const DATE = new GraphQLScalarType({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const GraphQLUpload = new GraphQLScalarType({
|
||||||
|
name: 'Upload',
|
||||||
|
description: 'The Upload scalar type represents a file upload.',
|
||||||
|
});
|
||||||
|
|
||||||
const BYTES = new GraphQLScalarType({
|
const BYTES = new GraphQLScalarType({
|
||||||
name: 'Bytes',
|
name: 'Bytes',
|
||||||
description:
|
description:
|
||||||
@@ -1265,6 +1269,7 @@ const load = parseGraphQLSchema => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
GraphQLUpload,
|
||||||
TypeValidationError,
|
TypeValidationError,
|
||||||
parseStringValue,
|
parseStringValue,
|
||||||
parseIntValue,
|
parseIntValue,
|
||||||
|
|||||||
@@ -1,43 +1,33 @@
|
|||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull } from 'graphql';
|
||||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||||
import { GraphQLUpload } from '@graphql-tools/links';
|
|
||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
|
|
||||||
const handleUpload = async (upload, config) => {
|
const handleUpload = async (upload, config) => {
|
||||||
const { createReadStream, filename, mimetype } = await upload;
|
const data = Buffer.from(await upload.arrayBuffer());
|
||||||
let data = null;
|
const fileName = upload.name;
|
||||||
if (createReadStream) {
|
const type = upload.type;
|
||||||
const stream = createReadStream();
|
|
||||||
data = await new Promise((resolve, reject) => {
|
|
||||||
const chunks = [];
|
|
||||||
stream
|
|
||||||
.on('error', reject)
|
|
||||||
.on('data', chunk => chunks.push(chunk))
|
|
||||||
.on('end', () => resolve(Buffer.concat(chunks)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || !data.length) {
|
if (!data || !data.length) {
|
||||||
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.');
|
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filename.length > 128) {
|
if (fileName.length > 128) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.');
|
throw new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
if (!fileName.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename contains invalid characters.');
|
throw new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename contains invalid characters.');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
fileInfo: await config.filesController.createFile(config, filename, data, mimetype),
|
fileInfo: await config.filesController.createFile(config, fileName, data, type),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error creating a file: ', e);
|
logger.error('Error creating a file: ', e);
|
||||||
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `Could not store file: ${filename}.`);
|
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `Could not store file: ${fileName}.`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,7 +38,7 @@ const load = parseGraphQLSchema => {
|
|||||||
inputFields: {
|
inputFields: {
|
||||||
upload: {
|
upload: {
|
||||||
description: 'This is the new file to be created and uploaded.',
|
description: 'This is the new file to be created and uploaded.',
|
||||||
type: new GraphQLNonNull(GraphQLUpload),
|
type: new GraphQLNonNull(defaultGraphQLTypes.GraphQLUpload),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
outputFields: {
|
outputFields: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
import { ApolloError } from 'apollo-server-core';
|
import { GraphQLYogaError } from '@graphql-yoga/node';
|
||||||
|
|
||||||
export function enforceMasterKeyAccess(auth) {
|
export function enforceMasterKeyAccess(auth) {
|
||||||
if (!auth.isMaster) {
|
if (!auth.isMaster) {
|
||||||
@@ -16,7 +16,7 @@ export function toGraphQLError(error) {
|
|||||||
code = Parse.Error.INTERNAL_SERVER_ERROR;
|
code = Parse.Error.INTERNAL_SERVER_ERROR;
|
||||||
message = 'Internal server error';
|
message = 'Internal server error';
|
||||||
}
|
}
|
||||||
return new ApolloError(message, code);
|
return new GraphQLYogaError(message, { code });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractKeysAndInclude = selectedFields => {
|
export const extractKeysAndInclude = selectedFields => {
|
||||||
|
|||||||
Reference in New Issue
Block a user