GraphQL Support (#5674)
* GraphQL boilerplate
* Create GraphQL schema without using gql
* Introducing loaders
* Generic create mutation
* create mutation is now working for any data type
* Create mutation for each parse class - partial
* Adding more data types to the class
* Get parse class query
* Generic get query
* Generic delete mutation
* Parse class delete mutation
* Parse class find mutation
* Generic update mutation
* Parse class update mutation
* Fixing initialization problems
* Installing node-fetch again
* Basic implementation for Pointer
* Constructor tests
* API tests boilerplate
* _getGraphQLOptions
* applyGraphQL tests
* GraphQL API initial tests
* applyPlayground tests
* createSubscriptions tests
* ParseGrapjQLSchema tests file
* ParseGraphQLSchema tests
* TypeValidationError
* TypeValidationError
* parseStringValue test
* parseIntValue tests
* parseBooleanValue tests
* parseDateValue tests
* parseValue tests
* parseListValues tests
* parseObjectFields tests
* Default types tests
* Get tests
* First permission test at generic Get operation
* Fixing prepare data
* ApolloClient does not work well with different queries runnning in paralell with different headers
* ApolloClient does not work well with different queries runnning in paralell with different headers
* User 3 tests
* User 3 tests
* Get level permission tests
* Get User specific tests
* Get now support keys argument
* Get now supports include argument
* Get now supports read preferences
* Adding tests for read preference enum type
* Find basic test
* Find permissions test
* Find where argument test
* Order, skip and limit tests
* Error handler
* Find now supports count
* Test for FindResult type
* Improving find count
* Find max limit test
* Find now supports keys, include and includeAll
* Find now supports read preferences
* Basic Create test
* Generic create mutation tests
* Basic update test
* UpdateResult object type test
* Update level permissions tests
* Error handler for default mutations
* Delete mutation basic test
* Delete mutation level permission tests
* Test for string
* String test
* Date test
* Pointer test
* Relation tests
* Changing objects mutations location
* Changing objects queries location
* Create file mutation
* Test for file fields
* Test for null values
* Changing parse classes operations location
* Objects mutations refactoring
* Class specific create object mutation now working
* Update class specific mutation now working
* Specific class delete mutation now working
* Get class specific mutation now working
* Find class specific query now working without where and sort
* Find query for custom classes working with where partially
* Almost all data types working for specfic class find where
* Now only missing relation, geopoint, file and ACL
* Additional tests with Parse classes queries and mutations
* Now only missing relation, geopoint, file and ACL
* Files
* Fiels are now working
* Excluding missing order test temporarly
* Refactoring dates
* Refactoring files
* Default types review
* Refeactoring object queries
* Refactoring class scalar type
* Refactoring class types
* Geo queries are now working
* Fixing centerSphere
* Allow sort on class specific queries
* Supporting bytes
* ACL constraint
* Temporarly removing xit tests
* Fixing some tests because of schema cache
* Removing session token from users
* Parse.User queries and mutations
* Remove test using fit
* Fixing include test that was failing because of schema cache
* Fixing count test for postgres. Postgres does not count with where={} (legacy problem). We should solve it later
* Fix null values test for postgres. It is evaluating null as undefined (legacy problem) and we should fix is later.
* Fixing schema change test that was failing because of schema cache
* Add GraphQL File type parseLiteral tests
* Refeactoring users
* Including sign up mutation
* Fix failing test
* Improve default GraphQL types tests coverage
* Including some tests for data types
* Including additional pointer test:
* Fixing some tests
* more data type tests
* Include Bytes and Polygon data types tests
* Polygons test
* Merging other tests
* Fixing some postgres tests
This commit is contained in:
committed by
GitHub
parent
922251a398
commit
fe2e95622f
872
package-lock.json
generated
872
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -19,16 +19,22 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@apollographql/graphql-playground-html": "1.6.6",
|
||||
"@parse/fs-files-adapter": "1.0.1",
|
||||
"@parse/push-adapter": "3.0.0",
|
||||
"@parse/s3-files-adapter": "1.2.1",
|
||||
"@parse/simple-mailgun-adapter": "1.1.0",
|
||||
"apollo-server-express": "2.4.8",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.19.0",
|
||||
"commander": "2.20.0",
|
||||
"cors": "2.8.5",
|
||||
"deepcopy": "2.0.0",
|
||||
"express": "4.17.1",
|
||||
"follow-redirects": "1.7.0",
|
||||
"graphql": "14.2.1",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-upload": "8.0.5",
|
||||
"intersect": "1.0.1",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"lodash": "4.17.11",
|
||||
@@ -40,6 +46,7 @@
|
||||
"pg-promise": "8.7.2",
|
||||
"redis": "2.8.0",
|
||||
"semver": "6.1.1",
|
||||
"subscriptions-transport-ws": "0.9.16",
|
||||
"tv4": "1.3.0",
|
||||
"uuid": "3.3.2",
|
||||
"winston": "3.2.1",
|
||||
@@ -53,6 +60,13 @@
|
||||
"@babel/plugin-transform-flow-strip-types": "7.4.4",
|
||||
"@babel/preset-env": "7.4.5",
|
||||
"@parse/minami": "1.0.0",
|
||||
"apollo-cache-inmemory": "1.5.1",
|
||||
"apollo-client": "2.5.1",
|
||||
"apollo-link": "1.2.11",
|
||||
"apollo-link-http": "1.5.14",
|
||||
"apollo-link-ws": "1.0.17",
|
||||
"apollo-upload-client": "10.0.0",
|
||||
"apollo-utilities": "1.2.1",
|
||||
"babel-eslint": "10.0.2",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "5.2.0",
|
||||
@@ -60,6 +74,7 @@
|
||||
"eslint": "5.16.0",
|
||||
"eslint-plugin-flowtype": "3.10.3",
|
||||
"flow-bin": "0.101.0",
|
||||
"form-data": "2.3.3",
|
||||
"gaze": "1.1.3",
|
||||
"husky": "2.4.1",
|
||||
"jasmine": "3.4.0",
|
||||
@@ -68,6 +83,7 @@
|
||||
"jsdoc-babel": "0.5.0",
|
||||
"lint-staged": "8.2.1",
|
||||
"mongodb-runner": "4.3.2",
|
||||
"node-fetch": "2.5.0",
|
||||
"nyc": "14.1.1",
|
||||
"prettier": "1.18.2",
|
||||
"supports-color": "7.0.0"
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"range": true,
|
||||
"jequal": true,
|
||||
"create": true,
|
||||
"arrayContains": true
|
||||
"arrayContains": true,
|
||||
"expectAsync": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": [0],
|
||||
|
||||
65
spec/ParseGraphQLSchema.spec.js
Normal file
65
spec/ParseGraphQLSchema.spec.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const defaultLogger = require('../lib/logger').default;
|
||||
const { ParseGraphQLSchema } = require('../lib/GraphQL/ParseGraphQLSchema');
|
||||
|
||||
describe('ParseGraphQLSchema', () => {
|
||||
let parseServer;
|
||||
let databaseController;
|
||||
let parseGraphQLSchema;
|
||||
|
||||
beforeAll(async () => {
|
||||
parseServer = await global.reconfigureServer({
|
||||
schemaCacheTTL: 100,
|
||||
});
|
||||
databaseController = parseServer.config.databaseController;
|
||||
parseGraphQLSchema = new ParseGraphQLSchema(
|
||||
databaseController,
|
||||
defaultLogger
|
||||
);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should require a databaseController and a log instance', () => {
|
||||
expect(() => new ParseGraphQLSchema()).toThrow(
|
||||
'You must provide a databaseController instance!'
|
||||
);
|
||||
expect(() => new ParseGraphQLSchema({})).toThrow(
|
||||
'You must provide a log instance!'
|
||||
);
|
||||
expect(() => new ParseGraphQLSchema({}, {})).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should cache schema', async () => {
|
||||
const graphQLSchema = await parseGraphQLSchema.load();
|
||||
expect(graphQLSchema).toBe(await parseGraphQLSchema.load());
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
expect(graphQLSchema).toBe(await parseGraphQLSchema.load());
|
||||
});
|
||||
|
||||
it('should load a brand new GraphQL Schema if Parse Schema changes', async () => {
|
||||
await parseGraphQLSchema.load();
|
||||
const parseClasses = parseGraphQLSchema.parseClasses;
|
||||
const parseClassesString = parseGraphQLSchema.parseClasses;
|
||||
const parseClassTypes = parseGraphQLSchema.parseClasses;
|
||||
const graphQLSchema = parseGraphQLSchema.parseClasses;
|
||||
const graphQLTypes = parseGraphQLSchema.parseClasses;
|
||||
const graphQLQueries = parseGraphQLSchema.parseClasses;
|
||||
const graphQLMutations = parseGraphQLSchema.parseClasses;
|
||||
const graphQLSubscriptions = parseGraphQLSchema.parseClasses;
|
||||
const newClassObject = new Parse.Object('NewClass');
|
||||
await newClassObject.save();
|
||||
await databaseController.schemaCache.clear();
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await parseGraphQLSchema.load();
|
||||
expect(parseClasses).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(parseClassesString).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(parseClassTypes).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(graphQLSchema).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(graphQLTypes).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(graphQLQueries).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(graphQLMutations).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
expect(graphQLSubscriptions).not.toBe(parseGraphQLSchema.parseClasses);
|
||||
});
|
||||
});
|
||||
});
|
||||
4950
spec/ParseGraphQLServer.spec.js
Normal file
4950
spec/ParseGraphQLServer.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
715
spec/defaultGraphQLTypes.spec.js
Normal file
715
spec/defaultGraphQLTypes.spec.js
Normal file
@@ -0,0 +1,715 @@
|
||||
const { Kind } = require('graphql');
|
||||
const {
|
||||
TypeValidationError,
|
||||
parseStringValue,
|
||||
parseIntValue,
|
||||
parseFloatValue,
|
||||
parseBooleanValue,
|
||||
parseDateIsoValue,
|
||||
parseValue,
|
||||
parseListValues,
|
||||
parseObjectFields,
|
||||
BYTES,
|
||||
DATE,
|
||||
FILE,
|
||||
} = require('../lib/GraphQL/loaders/defaultGraphQLTypes');
|
||||
|
||||
function createValue(kind, value, values, fields) {
|
||||
return {
|
||||
kind,
|
||||
value,
|
||||
values,
|
||||
fields,
|
||||
};
|
||||
}
|
||||
|
||||
function createObjectField(name, value) {
|
||||
return {
|
||||
name: {
|
||||
value: name,
|
||||
},
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
describe('defaultGraphQLTypes', () => {
|
||||
describe('TypeValidationError', () => {
|
||||
it('should be an error with specific message', () => {
|
||||
const typeValidationError = new TypeValidationError(
|
||||
'somevalue',
|
||||
'sometype'
|
||||
);
|
||||
expect(typeValidationError).toEqual(jasmine.any(Error));
|
||||
expect(typeValidationError.message).toEqual(
|
||||
'somevalue is not a valid sometype'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseStringValue', () => {
|
||||
it('should return itself if a string', () => {
|
||||
const myString = 'myString';
|
||||
expect(parseStringValue(myString)).toBe(myString);
|
||||
});
|
||||
|
||||
it('should fail if not a string', () => {
|
||||
expect(() => parseStringValue()).toThrow(
|
||||
jasmine.stringMatching('is not a valid String')
|
||||
);
|
||||
expect(() => parseStringValue({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid String')
|
||||
);
|
||||
expect(() => parseStringValue([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid String')
|
||||
);
|
||||
expect(() => parseStringValue(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid String')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseIntValue', () => {
|
||||
it('should parse to number if a string', () => {
|
||||
const myString = '123';
|
||||
expect(parseIntValue(myString)).toBe(123);
|
||||
});
|
||||
|
||||
it('should fail if not a string', () => {
|
||||
expect(() => parseIntValue()).toThrow(
|
||||
jasmine.stringMatching('is not a valid Int')
|
||||
);
|
||||
expect(() => parseIntValue({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Int')
|
||||
);
|
||||
expect(() => parseIntValue([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Int')
|
||||
);
|
||||
expect(() => parseIntValue(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Int')
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if not an integer string', () => {
|
||||
expect(() => parseIntValue('a123')).toThrow(
|
||||
jasmine.stringMatching('is not a valid Int')
|
||||
);
|
||||
expect(() => parseIntValue('123.4')).toThrow(
|
||||
jasmine.stringMatching('is not a valid Int')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseFloatValue', () => {
|
||||
it('should parse to number if a string', () => {
|
||||
expect(parseFloatValue('123')).toBe(123);
|
||||
expect(parseFloatValue('123.4')).toBe(123.4);
|
||||
});
|
||||
|
||||
it('should fail if not a string', () => {
|
||||
expect(() => parseFloatValue()).toThrow(
|
||||
jasmine.stringMatching('is not a valid Float')
|
||||
);
|
||||
expect(() => parseFloatValue({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Float')
|
||||
);
|
||||
expect(() => parseFloatValue([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Float')
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if not a float string', () => {
|
||||
expect(() => parseIntValue('a123')).toThrow(
|
||||
jasmine.stringMatching('is not a valid Int')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseBooleanValue', () => {
|
||||
it('should return itself if a boolean', () => {
|
||||
let myBoolean = true;
|
||||
expect(parseBooleanValue(myBoolean)).toBe(myBoolean);
|
||||
myBoolean = false;
|
||||
expect(parseBooleanValue(myBoolean)).toBe(myBoolean);
|
||||
});
|
||||
|
||||
it('should fail if not a boolean', () => {
|
||||
expect(() => parseBooleanValue()).toThrow(
|
||||
jasmine.stringMatching('is not a valid Boolean')
|
||||
);
|
||||
expect(() => parseBooleanValue({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Boolean')
|
||||
);
|
||||
expect(() => parseBooleanValue([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Boolean')
|
||||
);
|
||||
expect(() => parseBooleanValue(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Boolean')
|
||||
);
|
||||
expect(() => parseBooleanValue('true')).toThrow(
|
||||
jasmine.stringMatching('is not a valid Boolean')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseDateValue', () => {
|
||||
it('should parse to date if a string', () => {
|
||||
const myDateString = '2019-05-09T23:12:00.000Z';
|
||||
const myDate = new Date(Date.UTC(2019, 4, 9, 23, 12, 0, 0));
|
||||
expect(parseDateIsoValue(myDateString)).toEqual(myDate);
|
||||
});
|
||||
|
||||
it('should fail if not a string', () => {
|
||||
expect(() => parseDateIsoValue()).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() => parseDateIsoValue({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() => parseDateIsoValue([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() => parseDateIsoValue(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if not a date string', () => {
|
||||
expect(() => parseDateIsoValue('not a date')).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseValue', () => {
|
||||
const someString = createValue(Kind.STRING, 'somestring');
|
||||
const someInt = createValue(Kind.INT, '123');
|
||||
const someFloat = createValue(Kind.FLOAT, '123.4');
|
||||
const someBoolean = createValue(Kind.BOOLEAN, true);
|
||||
const someOther = createValue(undefined, new Object());
|
||||
const someObject = createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('someString', someString),
|
||||
createObjectField('someInt', someInt),
|
||||
createObjectField('someFloat', someFloat),
|
||||
createObjectField('someBoolean', someBoolean),
|
||||
createObjectField('someOther', someOther),
|
||||
createObjectField(
|
||||
'someList',
|
||||
createValue(Kind.LIST, undefined, [
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('someString', someString),
|
||||
]),
|
||||
])
|
||||
),
|
||||
createObjectField(
|
||||
'someObject',
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('someString', someString),
|
||||
])
|
||||
),
|
||||
]);
|
||||
const someList = createValue(Kind.LIST, undefined, [
|
||||
someString,
|
||||
someInt,
|
||||
someFloat,
|
||||
someBoolean,
|
||||
someObject,
|
||||
someOther,
|
||||
createValue(Kind.LIST, undefined, [
|
||||
someString,
|
||||
someInt,
|
||||
someFloat,
|
||||
someBoolean,
|
||||
someObject,
|
||||
someOther,
|
||||
]),
|
||||
]);
|
||||
|
||||
it('should parse string', () => {
|
||||
expect(parseValue(someString)).toEqual('somestring');
|
||||
});
|
||||
|
||||
it('should parse int', () => {
|
||||
expect(parseValue(someInt)).toEqual(123);
|
||||
});
|
||||
|
||||
it('should parse float', () => {
|
||||
expect(parseValue(someFloat)).toEqual(123.4);
|
||||
});
|
||||
|
||||
it('should parse boolean', () => {
|
||||
expect(parseValue(someBoolean)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should parse list', () => {
|
||||
expect(parseValue(someList)).toEqual([
|
||||
'somestring',
|
||||
123,
|
||||
123.4,
|
||||
true,
|
||||
{
|
||||
someString: 'somestring',
|
||||
someInt: 123,
|
||||
someFloat: 123.4,
|
||||
someBoolean: true,
|
||||
someOther: {},
|
||||
someList: [
|
||||
{
|
||||
someString: 'somestring',
|
||||
},
|
||||
],
|
||||
someObject: {
|
||||
someString: 'somestring',
|
||||
},
|
||||
},
|
||||
{},
|
||||
[
|
||||
'somestring',
|
||||
123,
|
||||
123.4,
|
||||
true,
|
||||
{
|
||||
someString: 'somestring',
|
||||
someInt: 123,
|
||||
someFloat: 123.4,
|
||||
someBoolean: true,
|
||||
someOther: {},
|
||||
someList: [
|
||||
{
|
||||
someString: 'somestring',
|
||||
},
|
||||
],
|
||||
someObject: {
|
||||
someString: 'somestring',
|
||||
},
|
||||
},
|
||||
{},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse object', () => {
|
||||
expect(parseValue(someObject)).toEqual({
|
||||
someString: 'somestring',
|
||||
someInt: 123,
|
||||
someFloat: 123.4,
|
||||
someBoolean: true,
|
||||
someOther: {},
|
||||
someList: [
|
||||
{
|
||||
someString: 'somestring',
|
||||
},
|
||||
],
|
||||
someObject: {
|
||||
someString: 'somestring',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return value otherwise', () => {
|
||||
expect(parseValue(someOther)).toEqual(new Object());
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseListValues', () => {
|
||||
it('should parse to list if an array', () => {
|
||||
expect(
|
||||
parseListValues([
|
||||
{ kind: Kind.STRING, value: 'someString' },
|
||||
{ kind: Kind.INT, value: '123' },
|
||||
])
|
||||
).toEqual(['someString', 123]);
|
||||
});
|
||||
|
||||
it('should fail if not an array', () => {
|
||||
expect(() => parseListValues()).toThrow(
|
||||
jasmine.stringMatching('is not a valid List')
|
||||
);
|
||||
expect(() => parseListValues({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid List')
|
||||
);
|
||||
expect(() => parseListValues('some string')).toThrow(
|
||||
jasmine.stringMatching('is not a valid List')
|
||||
);
|
||||
expect(() => parseListValues(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid List')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseObjectFields', () => {
|
||||
it('should parse to list if an array', () => {
|
||||
expect(
|
||||
parseObjectFields([
|
||||
{
|
||||
name: { value: 'someString' },
|
||||
value: { kind: Kind.STRING, value: 'someString' },
|
||||
},
|
||||
{
|
||||
name: { value: 'someInt' },
|
||||
value: { kind: Kind.INT, value: '123' },
|
||||
},
|
||||
])
|
||||
).toEqual({
|
||||
someString: 'someString',
|
||||
someInt: 123,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if not an array', () => {
|
||||
expect(() => parseObjectFields()).toThrow(
|
||||
jasmine.stringMatching('is not a valid Object')
|
||||
);
|
||||
expect(() => parseObjectFields({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Object')
|
||||
);
|
||||
expect(() => parseObjectFields('some string')).toThrow(
|
||||
jasmine.stringMatching('is not a valid Object')
|
||||
);
|
||||
expect(() => parseObjectFields(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Object')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Date', () => {
|
||||
describe('parse literal', () => {
|
||||
const { parseLiteral } = DATE;
|
||||
|
||||
it('should parse to date if string', () => {
|
||||
const date = '2019-05-09T23:12:00.000Z';
|
||||
expect(parseLiteral(createValue(Kind.STRING, date))).toEqual({
|
||||
__type: 'Date',
|
||||
iso: new Date(date),
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse to date if object', () => {
|
||||
const date = '2019-05-09T23:12:00.000Z';
|
||||
expect(
|
||||
parseLiteral(
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('__type', { value: 'Date' }),
|
||||
createObjectField('iso', { value: date, kind: Kind.STRING }),
|
||||
])
|
||||
)
|
||||
).toEqual({
|
||||
__type: 'Date',
|
||||
iso: new Date(date),
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => parseLiteral({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() =>
|
||||
parseLiteral(
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('__type', { value: 'Foo' }),
|
||||
createObjectField('iso', { value: '2019-05-09T23:12:00.000Z' }),
|
||||
])
|
||||
)
|
||||
).toThrow(jasmine.stringMatching('is not a valid Date'));
|
||||
expect(() => parseLiteral([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() => parseLiteral(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse value', () => {
|
||||
const { parseValue } = DATE;
|
||||
|
||||
it('should parse string value', () => {
|
||||
const date = '2019-05-09T23:12:00.000Z';
|
||||
expect(parseValue(date)).toEqual({
|
||||
__type: 'Date',
|
||||
iso: new Date(date),
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse object value', () => {
|
||||
const input = {
|
||||
__type: 'Date',
|
||||
iso: new Date('2019-05-09T23:12:00.000Z'),
|
||||
};
|
||||
expect(parseValue(input)).toEqual(input);
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => parseValue({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() =>
|
||||
parseValue({
|
||||
__type: 'Foo',
|
||||
iso: '2019-05-09T23:12:00.000Z',
|
||||
})
|
||||
).toThrow(jasmine.stringMatching('is not a valid Date'));
|
||||
expect(() =>
|
||||
parseValue({
|
||||
__type: 'Date',
|
||||
iso: 'foo',
|
||||
})
|
||||
).toThrow(jasmine.stringMatching('is not a valid Date'));
|
||||
expect(() => parseValue([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() => parseValue(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('serialize date type', () => {
|
||||
const { serialize } = DATE;
|
||||
|
||||
it('should do nothing if string', () => {
|
||||
const str = '2019-05-09T23:12:00.000Z';
|
||||
expect(serialize(str)).toBe(str);
|
||||
});
|
||||
|
||||
it('should serialize date', () => {
|
||||
const date = new Date();
|
||||
expect(serialize(date)).toBe(date.toUTCString());
|
||||
});
|
||||
|
||||
it('should return iso value if object', () => {
|
||||
const iso = '2019-05-09T23:12:00.000Z';
|
||||
const date = {
|
||||
__type: 'Date',
|
||||
iso,
|
||||
};
|
||||
expect(serialize(date)).toEqual(iso);
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => serialize({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() =>
|
||||
serialize({
|
||||
__type: 'Foo',
|
||||
iso: '2019-05-09T23:12:00.000Z',
|
||||
})
|
||||
).toThrow(jasmine.stringMatching('is not a valid Date'));
|
||||
expect(() => serialize([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
expect(() => serialize(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Date')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bytes', () => {
|
||||
describe('parse literal', () => {
|
||||
const { parseLiteral } = BYTES;
|
||||
|
||||
it('should parse to bytes if string', () => {
|
||||
expect(parseLiteral(createValue(Kind.STRING, 'bytesContent'))).toEqual({
|
||||
__type: 'Bytes',
|
||||
base64: 'bytesContent',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse to bytes if object', () => {
|
||||
expect(
|
||||
parseLiteral(
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('__type', { value: 'Bytes' }),
|
||||
createObjectField('base64', { value: 'bytesContent' }),
|
||||
])
|
||||
)
|
||||
).toEqual({
|
||||
__type: 'Bytes',
|
||||
base64: 'bytesContent',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => parseLiteral({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
expect(() =>
|
||||
parseLiteral(
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('__type', { value: 'Foo' }),
|
||||
createObjectField('base64', { value: 'bytesContent' }),
|
||||
])
|
||||
)
|
||||
).toThrow(jasmine.stringMatching('is not a valid Bytes'));
|
||||
expect(() => parseLiteral([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
expect(() => parseLiteral(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse value', () => {
|
||||
const { parseValue } = BYTES;
|
||||
|
||||
it('should parse string value', () => {
|
||||
expect(parseValue('bytesContent')).toEqual({
|
||||
__type: 'Bytes',
|
||||
base64: 'bytesContent',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse object value', () => {
|
||||
const input = {
|
||||
__type: 'Bytes',
|
||||
base64: 'bytesContent',
|
||||
};
|
||||
expect(parseValue(input)).toEqual(input);
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => parseValue({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
expect(() =>
|
||||
parseValue({
|
||||
__type: 'Foo',
|
||||
base64: 'bytesContent',
|
||||
})
|
||||
).toThrow(jasmine.stringMatching('is not a valid Bytes'));
|
||||
expect(() => parseValue([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
expect(() => parseValue(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('serialize bytes type', () => {
|
||||
const { serialize } = BYTES;
|
||||
|
||||
it('should do nothing if string', () => {
|
||||
const str = 'foo';
|
||||
expect(serialize(str)).toBe(str);
|
||||
});
|
||||
|
||||
it('should return base64 value if object', () => {
|
||||
const base64Content = 'bytesContent';
|
||||
const bytes = {
|
||||
__type: 'Bytes',
|
||||
base64: base64Content,
|
||||
};
|
||||
expect(serialize(bytes)).toEqual(base64Content);
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => serialize({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
expect(() =>
|
||||
serialize({
|
||||
__type: 'Foo',
|
||||
base64: 'bytesContent',
|
||||
})
|
||||
).toThrow(jasmine.stringMatching('is not a valid Bytes'));
|
||||
expect(() => serialize([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
expect(() => serialize(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid Bytes')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('File', () => {
|
||||
describe('parse literal', () => {
|
||||
const { parseLiteral } = FILE;
|
||||
|
||||
it('should parse to file if string', () => {
|
||||
expect(parseLiteral(createValue(Kind.STRING, 'parsefile'))).toEqual({
|
||||
__type: 'File',
|
||||
name: 'parsefile',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse to file if object', () => {
|
||||
expect(
|
||||
parseLiteral(
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('__type', { value: 'File' }),
|
||||
createObjectField('name', { value: 'parsefile' }),
|
||||
createObjectField('url', { value: 'myurl' }),
|
||||
])
|
||||
)
|
||||
).toEqual({
|
||||
__type: 'File',
|
||||
name: 'parsefile',
|
||||
url: 'myurl',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => parseLiteral({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid File')
|
||||
);
|
||||
expect(() =>
|
||||
parseLiteral(
|
||||
createValue(Kind.OBJECT, undefined, undefined, [
|
||||
createObjectField('__type', { value: 'Foo' }),
|
||||
createObjectField('name', { value: 'parsefile' }),
|
||||
createObjectField('url', { value: 'myurl' }),
|
||||
])
|
||||
)
|
||||
).toThrow(jasmine.stringMatching('is not a valid File'));
|
||||
expect(() => parseLiteral([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid File')
|
||||
);
|
||||
expect(() => parseLiteral(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid File')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('serialize file type', () => {
|
||||
const { serialize } = FILE;
|
||||
|
||||
it('should do nothing if string', () => {
|
||||
const str = 'foo';
|
||||
expect(serialize(str)).toBe(str);
|
||||
});
|
||||
|
||||
it('should return file name if object', () => {
|
||||
const fileName = 'parsefile';
|
||||
const file = {
|
||||
__type: 'File',
|
||||
name: fileName,
|
||||
url: 'myurl',
|
||||
};
|
||||
expect(serialize(file)).toEqual(fileName);
|
||||
});
|
||||
|
||||
it('should fail if not an valid object or string', () => {
|
||||
expect(() => serialize({})).toThrow(
|
||||
jasmine.stringMatching('is not a valid File')
|
||||
);
|
||||
expect(() =>
|
||||
serialize({
|
||||
__type: 'Foo',
|
||||
name: 'parsefile',
|
||||
url: 'myurl',
|
||||
})
|
||||
).toThrow(jasmine.stringMatching('is not a valid File'));
|
||||
expect(() => serialize([])).toThrow(
|
||||
jasmine.stringMatching('is not a valid File')
|
||||
);
|
||||
expect(() => serialize(123)).toThrow(
|
||||
jasmine.stringMatching('is not a valid File')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
117
src/GraphQL/ParseGraphQLSchema.js
Normal file
117
src/GraphQL/ParseGraphQLSchema.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import Parse from 'parse/node';
|
||||
import { GraphQLSchema, GraphQLObjectType } from 'graphql';
|
||||
import { ApolloError } from 'apollo-server-core';
|
||||
import requiredParameter from '../requiredParameter';
|
||||
import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes';
|
||||
import * as parseClassTypes from './loaders/parseClassTypes';
|
||||
import * as parseClassQueries from './loaders/parseClassQueries';
|
||||
import * as parseClassMutations from './loaders/parseClassMutations';
|
||||
import * as defaultGraphQLQueries from './loaders/defaultGraphQLQueries';
|
||||
import * as defaultGraphQLMutations from './loaders/defaultGraphQLMutations';
|
||||
|
||||
class ParseGraphQLSchema {
|
||||
constructor(databaseController, log) {
|
||||
this.databaseController =
|
||||
databaseController ||
|
||||
requiredParameter('You must provide a databaseController instance!');
|
||||
this.log = log || requiredParameter('You must provide a log instance!');
|
||||
}
|
||||
|
||||
async load() {
|
||||
const schemaController = await this.databaseController.loadSchema();
|
||||
const parseClasses = await schemaController.getAllClasses();
|
||||
const parseClassesString = JSON.stringify(parseClasses);
|
||||
|
||||
if (this.graphQLSchema) {
|
||||
if (this.parseClasses === parseClasses) {
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
|
||||
if (this.parseClassesString === parseClassesString) {
|
||||
this.parseClasses = parseClasses;
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
}
|
||||
|
||||
this.parseClasses = parseClasses;
|
||||
this.parseClassesString = parseClassesString;
|
||||
this.parseClassTypes = {};
|
||||
this.meType = null;
|
||||
this.graphQLSchema = null;
|
||||
this.graphQLTypes = [];
|
||||
this.graphQLObjectsQueries = {};
|
||||
this.graphQLQueries = {};
|
||||
this.graphQLObjectsMutations = {};
|
||||
this.graphQLMutations = {};
|
||||
this.graphQLSubscriptions = {};
|
||||
|
||||
defaultGraphQLTypes.load(this);
|
||||
|
||||
parseClasses.forEach(parseClass => {
|
||||
parseClassTypes.load(this, parseClass);
|
||||
|
||||
parseClassQueries.load(this, parseClass);
|
||||
|
||||
parseClassMutations.load(this, parseClass);
|
||||
});
|
||||
|
||||
defaultGraphQLQueries.load(this);
|
||||
|
||||
defaultGraphQLMutations.load(this);
|
||||
|
||||
let graphQLQuery = undefined;
|
||||
if (Object.keys(this.graphQLQueries).length > 0) {
|
||||
graphQLQuery = new GraphQLObjectType({
|
||||
name: 'Query',
|
||||
description: 'Query is the top level type for queries.',
|
||||
fields: this.graphQLQueries,
|
||||
});
|
||||
this.graphQLTypes.push(graphQLQuery);
|
||||
}
|
||||
|
||||
let graphQLMutation = undefined;
|
||||
if (Object.keys(this.graphQLMutations).length > 0) {
|
||||
graphQLMutation = new GraphQLObjectType({
|
||||
name: 'Mutation',
|
||||
description: 'Mutation is the top level type for mutations.',
|
||||
fields: this.graphQLMutations,
|
||||
});
|
||||
this.graphQLTypes.push(graphQLMutation);
|
||||
}
|
||||
|
||||
let graphQLSubscription = undefined;
|
||||
if (Object.keys(this.graphQLSubscriptions).length > 0) {
|
||||
graphQLSubscription = new GraphQLObjectType({
|
||||
name: 'Subscription',
|
||||
description: 'Subscription is the top level type for subscriptions.',
|
||||
fields: this.graphQLSubscriptions,
|
||||
});
|
||||
this.graphQLTypes.push(graphQLSubscription);
|
||||
}
|
||||
|
||||
this.graphQLSchema = new GraphQLSchema({
|
||||
types: this.graphQLTypes,
|
||||
query: graphQLQuery,
|
||||
mutation: graphQLMutation,
|
||||
subscription: graphQLSubscription,
|
||||
});
|
||||
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
|
||||
handleError(error) {
|
||||
let code, message;
|
||||
if (error instanceof Parse.Error) {
|
||||
this.log.error('Parse error: ', error);
|
||||
code = error.code;
|
||||
message = error.message;
|
||||
} else {
|
||||
this.log.error('Uncaught internal server error.', error, error.stack);
|
||||
code = Parse.Error.INTERNAL_SERVER_ERROR;
|
||||
message = 'Internal server error.';
|
||||
}
|
||||
throw new ApolloError(message, code);
|
||||
}
|
||||
}
|
||||
|
||||
export { ParseGraphQLSchema };
|
||||
110
src/GraphQL/ParseGraphQLServer.js
Normal file
110
src/GraphQL/ParseGraphQLServer.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import corsMiddleware from 'cors';
|
||||
import bodyParser from 'body-parser';
|
||||
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 { SubscriptionServer } from 'subscriptions-transport-ws';
|
||||
import { handleParseHeaders } from '../middlewares';
|
||||
import requiredParameter from '../requiredParameter';
|
||||
import defaultLogger from '../logger';
|
||||
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
|
||||
|
||||
class ParseGraphQLServer {
|
||||
constructor(parseServer, config) {
|
||||
this.parseServer =
|
||||
parseServer ||
|
||||
requiredParameter('You must provide a parseServer instance!');
|
||||
if (!config || !config.graphQLPath) {
|
||||
requiredParameter('You must provide a config.graphQLPath!');
|
||||
}
|
||||
this.config = config;
|
||||
this.parseGraphQLSchema = new ParseGraphQLSchema(
|
||||
this.parseServer.config.databaseController,
|
||||
(this.parseServer.config && this.parseServer.config.loggerController) ||
|
||||
defaultLogger
|
||||
);
|
||||
}
|
||||
|
||||
async _getGraphQLOptions(req) {
|
||||
return {
|
||||
schema: await this.parseGraphQLSchema.load(),
|
||||
context: {
|
||||
info: req.info,
|
||||
config: req.config,
|
||||
auth: req.auth,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
applyGraphQL(app) {
|
||||
if (!app || !app.use) {
|
||||
requiredParameter('You must provide an Express.js app instance!');
|
||||
}
|
||||
|
||||
const maxUploadSize = this.parseServer.config.maxUploadSize || '20mb';
|
||||
const maxFileSize =
|
||||
(Number(maxUploadSize.slice(0, -2)) * 1024) ^
|
||||
{
|
||||
kb: 1,
|
||||
mb: 2,
|
||||
gb: 3,
|
||||
}[maxUploadSize.slice(-2).toLowerCase()];
|
||||
|
||||
app.use(this.config.graphQLPath, graphqlUploadExpress({ maxFileSize }));
|
||||
app.use(this.config.graphQLPath, corsMiddleware());
|
||||
app.use(this.config.graphQLPath, bodyParser.json());
|
||||
app.use(this.config.graphQLPath, handleParseHeaders);
|
||||
app.use(
|
||||
this.config.graphQLPath,
|
||||
graphqlExpress(async req => await this._getGraphQLOptions(req))
|
||||
);
|
||||
}
|
||||
|
||||
applyPlayground(app) {
|
||||
if (!app || !app.get) {
|
||||
requiredParameter('You must provide an Express.js app instance!');
|
||||
}
|
||||
app.get(
|
||||
this.config.playgroundPath ||
|
||||
requiredParameter(
|
||||
'You must provide a config.playgroundPath to applyPlayground!'
|
||||
),
|
||||
(_req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(
|
||||
renderPlaygroundPage({
|
||||
endpoint: this.config.graphQLPath,
|
||||
subscriptionEndpoint: this.config.subscriptionsPath,
|
||||
})
|
||||
);
|
||||
res.end();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createSubscriptions(server) {
|
||||
SubscriptionServer.create(
|
||||
{
|
||||
execute,
|
||||
subscribe,
|
||||
onOperation: async (_message, params, webSocket) =>
|
||||
Object.assign(
|
||||
{},
|
||||
params,
|
||||
await this._getGraphQLOptions(webSocket.upgradeReq)
|
||||
),
|
||||
},
|
||||
{
|
||||
server,
|
||||
path:
|
||||
this.config.subscriptionsPath ||
|
||||
requiredParameter(
|
||||
'You must provide a config.subscriptionsPath to createSubscriptions!'
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { ParseGraphQLServer };
|
||||
11
src/GraphQL/loaders/defaultGraphQLMutations.js
Normal file
11
src/GraphQL/loaders/defaultGraphQLMutations.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import * as objectsMutations from './objectsMutations';
|
||||
import * as filesMutations from './filesMutations';
|
||||
import * as usersMutations from './usersMutations';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
objectsMutations.load(parseGraphQLSchema);
|
||||
filesMutations.load(parseGraphQLSchema);
|
||||
usersMutations.load(parseGraphQLSchema);
|
||||
};
|
||||
|
||||
export { load };
|
||||
17
src/GraphQL/loaders/defaultGraphQLQueries.js
Normal file
17
src/GraphQL/loaders/defaultGraphQLQueries.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
import * as usersQueries from './usersQueries';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.graphQLQueries.health = {
|
||||
description:
|
||||
'The health query can be used to check if the server is up and running.',
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
resolve: () => true,
|
||||
};
|
||||
|
||||
objectsQueries.load(parseGraphQLSchema);
|
||||
usersQueries.load(parseGraphQLSchema);
|
||||
};
|
||||
|
||||
export { load };
|
||||
1125
src/GraphQL/loaders/defaultGraphQLTypes.js
Normal file
1125
src/GraphQL/loaders/defaultGraphQLTypes.js
Normal file
File diff suppressed because it is too large
Load Diff
93
src/GraphQL/loaders/filesMutations.js
Normal file
93
src/GraphQL/loaders/filesMutations.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { GraphQLObjectType, GraphQLNonNull } from 'graphql';
|
||||
import { GraphQLUpload } from 'graphql-upload';
|
||||
import Parse from 'parse/node';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import logger from '../../logger';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
const fields = {};
|
||||
|
||||
fields.create = {
|
||||
description:
|
||||
'The create mutation can be used to create and upload a new file.',
|
||||
args: {
|
||||
file: {
|
||||
description: 'This is the new file to be created and uploaded',
|
||||
type: new GraphQLNonNull(GraphQLUpload),
|
||||
},
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { file } = args;
|
||||
const { config } = context;
|
||||
|
||||
const { createReadStream, filename, mimetype } = await file;
|
||||
let data = null;
|
||||
if (createReadStream) {
|
||||
const stream = createReadStream();
|
||||
data = await new Promise((resolve, reject) => {
|
||||
let data = '';
|
||||
stream
|
||||
.on('error', reject)
|
||||
.on('data', chunk => (data += chunk))
|
||||
.on('end', () => resolve(data));
|
||||
});
|
||||
}
|
||||
|
||||
if (!data || !data.length) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
'Invalid file upload.'
|
||||
);
|
||||
}
|
||||
|
||||
if (filename.length > 128) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_FILE_NAME,
|
||||
'Filename too long.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_FILE_NAME,
|
||||
'Filename contains invalid characters.'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return await config.filesController.createFile(
|
||||
config,
|
||||
filename,
|
||||
data,
|
||||
mimetype
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error('Error creating a file: ', e);
|
||||
throw new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
`Could not store file: ${filename}.`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const filesMutation = new GraphQLObjectType({
|
||||
name: 'FilesMutation',
|
||||
description: 'FilesMutation is the top level type for files mutations.',
|
||||
fields,
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(filesMutation);
|
||||
|
||||
parseGraphQLSchema.graphQLMutations.files = {
|
||||
description: 'This is the top level for files mutations.',
|
||||
type: filesMutation,
|
||||
resolve: () => new Object(),
|
||||
};
|
||||
};
|
||||
|
||||
export { load };
|
||||
148
src/GraphQL/loaders/objectsMutations.js
Normal file
148
src/GraphQL/loaders/objectsMutations.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import { GraphQLNonNull, GraphQLBoolean, GraphQLObjectType } from 'graphql';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import rest from '../../rest';
|
||||
|
||||
const parseMap = {
|
||||
_op: '__op',
|
||||
};
|
||||
|
||||
const transformToParse = fields => {
|
||||
if (!fields || typeof fields !== 'object') {
|
||||
return;
|
||||
}
|
||||
Object.keys(fields).forEach(fieldName => {
|
||||
const fieldValue = fields[fieldName];
|
||||
if (parseMap[fieldName]) {
|
||||
delete fields[fieldName];
|
||||
fields[parseMap[fieldName]] = fieldValue;
|
||||
}
|
||||
if (typeof fieldValue === 'object') {
|
||||
transformToParse(fieldValue);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createObject = async (className, fields, config, auth, info) => {
|
||||
if (!fields) {
|
||||
fields = {};
|
||||
}
|
||||
|
||||
transformToParse(fields);
|
||||
|
||||
return (await rest.create(config, auth, className, fields, info.clientSDK))
|
||||
.response;
|
||||
};
|
||||
|
||||
const updateObject = async (
|
||||
className,
|
||||
objectId,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
) => {
|
||||
if (!fields) {
|
||||
fields = {};
|
||||
}
|
||||
|
||||
transformToParse(fields);
|
||||
|
||||
return (await rest.update(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
{ objectId },
|
||||
fields,
|
||||
info.clientSDK
|
||||
)).response;
|
||||
};
|
||||
|
||||
const deleteObject = async (className, objectId, config, auth, info) => {
|
||||
await rest.del(config, auth, className, objectId, info.clientSDK);
|
||||
return true;
|
||||
};
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.graphQLObjectsMutations.create = {
|
||||
description:
|
||||
'The create mutation can be used to create a new object of a certain class.',
|
||||
args: {
|
||||
className: defaultGraphQLTypes.CLASS_NAME_ATT,
|
||||
fields: defaultGraphQLTypes.FIELDS_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { className, fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return await createObject(className, fields, config, auth, info);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
parseGraphQLSchema.graphQLObjectsMutations.update = {
|
||||
description:
|
||||
'The update mutation can be used to update an object of a certain class.',
|
||||
args: {
|
||||
className: defaultGraphQLTypes.CLASS_NAME_ATT,
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
fields: defaultGraphQLTypes.FIELDS_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.UPDATE_RESULT),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { className, objectId, fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return await updateObject(
|
||||
className,
|
||||
objectId,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
parseGraphQLSchema.graphQLObjectsMutations.delete = {
|
||||
description:
|
||||
'The delete mutation can be used to delete an object of a certain class.',
|
||||
args: {
|
||||
className: defaultGraphQLTypes.CLASS_NAME_ATT,
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { className, objectId } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return await deleteObject(className, objectId, config, auth, info);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const objectsMutation = new GraphQLObjectType({
|
||||
name: 'ObjectsMutation',
|
||||
description: 'ObjectsMutation is the top level type for objects mutations.',
|
||||
fields: parseGraphQLSchema.graphQLObjectsMutations,
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(objectsMutation);
|
||||
|
||||
parseGraphQLSchema.graphQLMutations.objects = {
|
||||
description: 'This is the top level for objects mutations.',
|
||||
type: objectsMutation,
|
||||
resolve: () => new Object(),
|
||||
};
|
||||
};
|
||||
|
||||
export { createObject, updateObject, deleteObject, load };
|
||||
367
src/GraphQL/loaders/objectsQueries.js
Normal file
367
src/GraphQL/loaders/objectsQueries.js
Normal file
@@ -0,0 +1,367 @@
|
||||
import {
|
||||
GraphQLNonNull,
|
||||
GraphQLBoolean,
|
||||
GraphQLString,
|
||||
GraphQLObjectType,
|
||||
} from 'graphql';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import Parse from 'parse/node';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import rest from '../../rest';
|
||||
|
||||
const getObject = async (
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
) => {
|
||||
const options = {};
|
||||
if (keys) {
|
||||
options.keys = keys;
|
||||
}
|
||||
if (include) {
|
||||
options.include = include;
|
||||
if (includeReadPreference) {
|
||||
options.includeReadPreference = includeReadPreference;
|
||||
}
|
||||
}
|
||||
if (readPreference) {
|
||||
options.readPreference = readPreference;
|
||||
}
|
||||
|
||||
const response = await rest.get(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
objectId,
|
||||
options,
|
||||
info.clientSDK
|
||||
);
|
||||
|
||||
if (!response.results || response.results.length == 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
|
||||
if (className === '_User') {
|
||||
delete response.results[0].sessionToken;
|
||||
}
|
||||
|
||||
return response.results[0];
|
||||
};
|
||||
|
||||
const parseMap = {
|
||||
_or: '$or',
|
||||
_and: '$and',
|
||||
_nor: '$nor',
|
||||
_relatedTo: '$relatedTo',
|
||||
_eq: '$eq',
|
||||
_ne: '$ne',
|
||||
_lt: '$lt',
|
||||
_lte: '$lte',
|
||||
_gt: '$gt',
|
||||
_gte: '$gte',
|
||||
_in: '$in',
|
||||
_nin: '$nin',
|
||||
_exists: '$exists',
|
||||
_select: '$select',
|
||||
_dontSelect: '$dontSelect',
|
||||
_inQuery: '$inQuery',
|
||||
_notInQuery: '$notInQuery',
|
||||
_containedBy: '$containedBy',
|
||||
_all: '$all',
|
||||
_regex: '$regex',
|
||||
_options: '$options',
|
||||
_text: '$text',
|
||||
_search: '$search',
|
||||
_term: '$term',
|
||||
_language: '$language',
|
||||
_caseSensitive: '$caseSensitive',
|
||||
_diacriticSensitive: '$diacriticSensitive',
|
||||
_nearSphere: '$nearSphere',
|
||||
_maxDistance: '$maxDistance',
|
||||
_maxDistanceInRadians: '$maxDistanceInRadians',
|
||||
_maxDistanceInMiles: '$maxDistanceInMiles',
|
||||
_maxDistanceInKilometers: '$maxDistanceInKilometers',
|
||||
_within: '$within',
|
||||
_box: '$box',
|
||||
_geoWithin: '$geoWithin',
|
||||
_polygon: '$polygon',
|
||||
_centerSphere: '$centerSphere',
|
||||
_geoIntersects: '$geoIntersects',
|
||||
_point: '$point',
|
||||
};
|
||||
|
||||
const transformToParse = constraints => {
|
||||
if (!constraints || typeof constraints !== 'object') {
|
||||
return;
|
||||
}
|
||||
Object.keys(constraints).forEach(fieldName => {
|
||||
let fieldValue = constraints[fieldName];
|
||||
if (parseMap[fieldName]) {
|
||||
delete constraints[fieldName];
|
||||
fieldName = parseMap[fieldName];
|
||||
constraints[fieldName] = fieldValue;
|
||||
}
|
||||
switch (fieldName) {
|
||||
case '$point':
|
||||
case '$nearSphere':
|
||||
if (typeof fieldValue === 'object' && !fieldValue.__type) {
|
||||
fieldValue.__type = 'GeoPoint';
|
||||
}
|
||||
break;
|
||||
case '$box':
|
||||
if (
|
||||
typeof fieldValue === 'object' &&
|
||||
fieldValue.bottomLeft &&
|
||||
fieldValue.upperRight
|
||||
) {
|
||||
fieldValue = [
|
||||
{
|
||||
__type: 'GeoPoint',
|
||||
...fieldValue.bottomLeft,
|
||||
},
|
||||
{
|
||||
__type: 'GeoPoint',
|
||||
...fieldValue.upperRight,
|
||||
},
|
||||
];
|
||||
constraints[fieldName] = fieldValue;
|
||||
}
|
||||
break;
|
||||
case '$polygon':
|
||||
if (fieldValue instanceof Array) {
|
||||
fieldValue.forEach(geoPoint => {
|
||||
if (typeof geoPoint === 'object' && !geoPoint.__type) {
|
||||
geoPoint.__type = 'GeoPoint';
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case '$centerSphere':
|
||||
if (
|
||||
typeof fieldValue === 'object' &&
|
||||
fieldValue.center &&
|
||||
fieldValue.distance
|
||||
) {
|
||||
fieldValue = [
|
||||
{
|
||||
__type: 'GeoPoint',
|
||||
...fieldValue.center,
|
||||
},
|
||||
fieldValue.distance,
|
||||
];
|
||||
constraints[fieldName] = fieldValue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (typeof fieldValue === 'object') {
|
||||
transformToParse(fieldValue);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const findObjects = async (
|
||||
className,
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
keys,
|
||||
include,
|
||||
includeAll,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields
|
||||
) => {
|
||||
if (!where) {
|
||||
where = {};
|
||||
}
|
||||
|
||||
transformToParse(where);
|
||||
|
||||
const options = {};
|
||||
|
||||
if (selectedFields.includes('results')) {
|
||||
if (limit || limit === 0) {
|
||||
options.limit = limit;
|
||||
}
|
||||
if (options.limit !== 0) {
|
||||
if (order) {
|
||||
options.order = order;
|
||||
}
|
||||
if (skip) {
|
||||
options.skip = skip;
|
||||
}
|
||||
if (config.maxLimit && options.limit > config.maxLimit) {
|
||||
// Silently replace the limit on the query with the max configured
|
||||
options.limit = config.maxLimit;
|
||||
}
|
||||
if (keys) {
|
||||
options.keys = keys;
|
||||
}
|
||||
if (includeAll === true) {
|
||||
options.includeAll = includeAll;
|
||||
}
|
||||
if (!options.includeAll && include) {
|
||||
options.include = include;
|
||||
}
|
||||
if ((options.includeAll || options.include) && includeReadPreference) {
|
||||
options.includeReadPreference = includeReadPreference;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
options.limit = 0;
|
||||
}
|
||||
|
||||
if (selectedFields.includes('count')) {
|
||||
options.count = true;
|
||||
}
|
||||
|
||||
if (readPreference) {
|
||||
options.readPreference = readPreference;
|
||||
}
|
||||
if (Object.keys(where).length > 0 && subqueryReadPreference) {
|
||||
options.subqueryReadPreference = subqueryReadPreference;
|
||||
}
|
||||
|
||||
return await rest.find(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
where,
|
||||
options,
|
||||
info.clientSDK
|
||||
);
|
||||
};
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.graphQLObjectsQueries.get = {
|
||||
description:
|
||||
'The get query can be used to get an object of a certain class by its objectId.',
|
||||
args: {
|
||||
className: defaultGraphQLTypes.CLASS_NAME_ATT,
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
keys: defaultGraphQLTypes.KEYS_ATT,
|
||||
include: defaultGraphQLTypes.INCLUDE_ATT,
|
||||
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
||||
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.OBJECT),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const {
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
} = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return await getObject(
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
parseGraphQLSchema.graphQLObjectsQueries.find = {
|
||||
description:
|
||||
'The find query can be used to find objects of a certain class.',
|
||||
args: {
|
||||
className: defaultGraphQLTypes.CLASS_NAME_ATT,
|
||||
where: defaultGraphQLTypes.WHERE_ATT,
|
||||
order: {
|
||||
description:
|
||||
'This is the order in which the objects should be returned',
|
||||
type: GraphQLString,
|
||||
},
|
||||
skip: defaultGraphQLTypes.SKIP_ATT,
|
||||
limit: defaultGraphQLTypes.LIMIT_ATT,
|
||||
keys: defaultGraphQLTypes.KEYS_ATT,
|
||||
include: defaultGraphQLTypes.INCLUDE_ATT,
|
||||
includeAll: {
|
||||
description: 'All pointers will be returned',
|
||||
type: GraphQLBoolean,
|
||||
},
|
||||
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
||||
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
||||
subqueryReadPreference: defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const {
|
||||
className,
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
keys,
|
||||
include,
|
||||
includeAll,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
} = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
return await findObjects(
|
||||
className,
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
keys,
|
||||
include,
|
||||
includeAll,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const objectsQuery = new GraphQLObjectType({
|
||||
name: 'ObjectsQuery',
|
||||
description: 'ObjectsQuery is the top level type for objects queries.',
|
||||
fields: parseGraphQLSchema.graphQLObjectsQueries,
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(objectsQuery);
|
||||
|
||||
parseGraphQLSchema.graphQLQueries.objects = {
|
||||
description: 'This is the top level for objects queries.',
|
||||
type: objectsQuery,
|
||||
resolve: () => new Object(),
|
||||
};
|
||||
};
|
||||
|
||||
export { getObject, findObjects, load };
|
||||
121
src/GraphQL/loaders/parseClassMutations.js
Normal file
121
src/GraphQL/loaders/parseClassMutations.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsMutations from './objectsMutations';
|
||||
|
||||
const load = (parseGraphQLSchema, parseClass) => {
|
||||
const className = parseClass.className;
|
||||
|
||||
const classGraphQLInputType =
|
||||
parseGraphQLSchema.parseClassTypes[className].classGraphQLInputType;
|
||||
const fields = {
|
||||
description: 'These are the fields of the object.',
|
||||
type: classGraphQLInputType,
|
||||
};
|
||||
const classGraphQLInputTypeFields = classGraphQLInputType.getFields();
|
||||
|
||||
const transformTypes = fields => {
|
||||
if (fields) {
|
||||
Object.keys(fields).forEach(field => {
|
||||
if (classGraphQLInputTypeFields[field]) {
|
||||
switch (classGraphQLInputTypeFields[field].type) {
|
||||
case defaultGraphQLTypes.GEO_POINT:
|
||||
fields[field].__type = 'GeoPoint';
|
||||
break;
|
||||
case defaultGraphQLTypes.POLYGON:
|
||||
fields[field] = {
|
||||
__type: 'Polygon',
|
||||
coordinates: fields[field].map(geoPoint => [
|
||||
geoPoint.latitude,
|
||||
geoPoint.longitude,
|
||||
]),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const createGraphQLMutationName = `create${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[createGraphQLMutationName] = {
|
||||
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${className} class.`,
|
||||
args: {
|
||||
fields,
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
transformTypes(fields);
|
||||
|
||||
return await objectsMutations.createObject(
|
||||
className,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const updateGraphQLMutationName = `update${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[updateGraphQLMutationName] = {
|
||||
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${className} class.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
fields,
|
||||
},
|
||||
type: defaultGraphQLTypes.UPDATE_RESULT,
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { objectId, fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
transformTypes(fields);
|
||||
|
||||
return await objectsMutations.updateObject(
|
||||
className,
|
||||
objectId,
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const deleteGraphQLMutationName = `delete${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsMutations[deleteGraphQLMutationName] = {
|
||||
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${className} class.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { objectId } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return await objectsMutations.deleteObject(
|
||||
className,
|
||||
objectId,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export { load };
|
||||
102
src/GraphQL/loaders/parseClassQueries.js
Normal file
102
src/GraphQL/loaders/parseClassQueries.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
import * as parseClassTypes from './parseClassTypes';
|
||||
|
||||
const load = (parseGraphQLSchema, parseClass) => {
|
||||
const className = parseClass.className;
|
||||
|
||||
const {
|
||||
classGraphQLOutputType,
|
||||
classGraphQLFindArgs,
|
||||
classGraphQLFindResultType,
|
||||
} = parseGraphQLSchema.parseClassTypes[className];
|
||||
|
||||
const getGraphQLQueryName = `get${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsQueries[getGraphQLQueryName] = {
|
||||
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${className} class by its id.`,
|
||||
args: {
|
||||
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
||||
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const { objectId, readPreference, includeReadPreference } = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
);
|
||||
|
||||
return await objectsQueries.getObject(
|
||||
className,
|
||||
objectId,
|
||||
keys,
|
||||
include,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const findGraphQLQueryName = `find${className}`;
|
||||
parseGraphQLSchema.graphQLObjectsQueries[findGraphQLQueryName] = {
|
||||
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${className} class.`,
|
||||
args: classGraphQLFindArgs,
|
||||
type: new GraphQLNonNull(classGraphQLFindResultType),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
} = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = parseClassTypes.extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
);
|
||||
const parseOrder = order && order.join(',');
|
||||
|
||||
return await objectsQueries.findObjects(
|
||||
className,
|
||||
where,
|
||||
parseOrder,
|
||||
skip,
|
||||
limit,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0])
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export { load };
|
||||
557
src/GraphQL/loaders/parseClassTypes.js
Normal file
557
src/GraphQL/loaders/parseClassTypes.js
Normal file
@@ -0,0 +1,557 @@
|
||||
import {
|
||||
Kind,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
GraphQLFloat,
|
||||
GraphQLBoolean,
|
||||
GraphQLList,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLNonNull,
|
||||
GraphQLScalarType,
|
||||
GraphQLEnumType,
|
||||
} from 'graphql';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from './objectsQueries';
|
||||
|
||||
const mapInputType = (parseType, targetClass, parseClassTypes) => {
|
||||
switch (parseType) {
|
||||
case 'String':
|
||||
return GraphQLString;
|
||||
case 'Number':
|
||||
return GraphQLFloat;
|
||||
case 'Boolean':
|
||||
return GraphQLBoolean;
|
||||
case 'Array':
|
||||
return new GraphQLList(defaultGraphQLTypes.ANY);
|
||||
case 'Object':
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
case 'Date':
|
||||
return defaultGraphQLTypes.DATE;
|
||||
case 'Pointer':
|
||||
if (parseClassTypes[targetClass]) {
|
||||
return parseClassTypes[targetClass].classGraphQLScalarType;
|
||||
} else {
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
}
|
||||
case 'Relation':
|
||||
if (parseClassTypes[targetClass]) {
|
||||
return parseClassTypes[targetClass].classGraphQLRelationOpType;
|
||||
} else {
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
}
|
||||
case 'File':
|
||||
return defaultGraphQLTypes.FILE;
|
||||
case 'GeoPoint':
|
||||
return defaultGraphQLTypes.GEO_POINT;
|
||||
case 'Polygon':
|
||||
return defaultGraphQLTypes.POLYGON;
|
||||
case 'Bytes':
|
||||
return defaultGraphQLTypes.BYTES;
|
||||
case 'ACL':
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const mapOutputType = (parseType, targetClass, parseClassTypes) => {
|
||||
switch (parseType) {
|
||||
case 'String':
|
||||
return GraphQLString;
|
||||
case 'Number':
|
||||
return GraphQLFloat;
|
||||
case 'Boolean':
|
||||
return GraphQLBoolean;
|
||||
case 'Array':
|
||||
return new GraphQLList(defaultGraphQLTypes.ANY);
|
||||
case 'Object':
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
case 'Date':
|
||||
return defaultGraphQLTypes.DATE;
|
||||
case 'Pointer':
|
||||
if (parseClassTypes[targetClass]) {
|
||||
return parseClassTypes[targetClass].classGraphQLOutputType;
|
||||
} else {
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
}
|
||||
case 'Relation':
|
||||
if (parseClassTypes[targetClass]) {
|
||||
return new GraphQLNonNull(
|
||||
parseClassTypes[targetClass].classGraphQLFindResultType
|
||||
);
|
||||
} else {
|
||||
return new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT);
|
||||
}
|
||||
case 'File':
|
||||
return defaultGraphQLTypes.FILE_INFO;
|
||||
case 'GeoPoint':
|
||||
return defaultGraphQLTypes.GEO_POINT_INFO;
|
||||
case 'Polygon':
|
||||
return defaultGraphQLTypes.POLYGON_INFO;
|
||||
case 'Bytes':
|
||||
return defaultGraphQLTypes.BYTES;
|
||||
case 'ACL':
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const mapConstraintType = (parseType, targetClass, parseClassTypes) => {
|
||||
switch (parseType) {
|
||||
case 'String':
|
||||
return defaultGraphQLTypes.STRING_CONSTRAINT;
|
||||
case 'Number':
|
||||
return defaultGraphQLTypes.NUMBER_CONSTRAINT;
|
||||
case 'Boolean':
|
||||
return defaultGraphQLTypes.BOOLEAN_CONSTRAINT;
|
||||
case 'Array':
|
||||
return defaultGraphQLTypes.ARRAY_CONSTRAINT;
|
||||
case 'Object':
|
||||
return defaultGraphQLTypes.OBJECT_CONSTRAINT;
|
||||
case 'Date':
|
||||
return defaultGraphQLTypes.DATE_CONSTRAINT;
|
||||
case 'Pointer':
|
||||
if (parseClassTypes[targetClass]) {
|
||||
return parseClassTypes[targetClass].classGraphQLConstraintType;
|
||||
} else {
|
||||
return defaultGraphQLTypes.OBJECT;
|
||||
}
|
||||
case 'File':
|
||||
return defaultGraphQLTypes.FILE_CONSTRAINT;
|
||||
case 'GeoPoint':
|
||||
return defaultGraphQLTypes.GEO_POINT_CONSTRAINT;
|
||||
case 'Polygon':
|
||||
return defaultGraphQLTypes.POLYGON_CONSTRAINT;
|
||||
case 'Bytes':
|
||||
return defaultGraphQLTypes.BYTES_CONSTRAINT;
|
||||
case 'ACL':
|
||||
return defaultGraphQLTypes.OBJECT_CONSTRAINT;
|
||||
case 'Relation':
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const extractKeysAndInclude = selectedFields => {
|
||||
selectedFields = selectedFields.filter(
|
||||
field => !field.includes('__typename')
|
||||
);
|
||||
let keys = undefined;
|
||||
let include = undefined;
|
||||
if (selectedFields && selectedFields.length > 0) {
|
||||
keys = selectedFields.join(',');
|
||||
include = selectedFields
|
||||
.reduce((fields, field) => {
|
||||
fields = fields.slice();
|
||||
let pointIndex = field.lastIndexOf('.');
|
||||
while (pointIndex > 0) {
|
||||
const lastField = field.slice(pointIndex + 1);
|
||||
field = field.slice(0, pointIndex);
|
||||
if (!fields.includes(field) && lastField !== 'objectId') {
|
||||
fields.push(field);
|
||||
}
|
||||
pointIndex = field.lastIndexOf('.');
|
||||
}
|
||||
return fields;
|
||||
}, [])
|
||||
.join(',');
|
||||
}
|
||||
return { keys, include };
|
||||
};
|
||||
|
||||
const load = (parseGraphQLSchema, parseClass) => {
|
||||
const className = parseClass.className;
|
||||
|
||||
const classFields = Object.keys(parseClass.fields);
|
||||
|
||||
const classCustomFields = classFields.filter(
|
||||
field => !Object.keys(defaultGraphQLTypes.CLASS_FIELDS).includes(field)
|
||||
);
|
||||
|
||||
const classGraphQLScalarTypeName = `${className}Pointer`;
|
||||
const parseScalarValue = value => {
|
||||
if (typeof value === 'string') {
|
||||
return {
|
||||
__type: 'Pointer',
|
||||
className,
|
||||
objectId: value,
|
||||
};
|
||||
} else if (
|
||||
typeof value === 'object' &&
|
||||
value.__type === 'Pointer' &&
|
||||
value.className === className &&
|
||||
typeof value.objectId === 'string'
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new defaultGraphQLTypes.TypeValidationError(
|
||||
value,
|
||||
classGraphQLScalarTypeName
|
||||
);
|
||||
};
|
||||
const classGraphQLScalarType = new GraphQLScalarType({
|
||||
name: classGraphQLScalarTypeName,
|
||||
description: `The ${classGraphQLScalarTypeName} is used in operations that involve ${className} pointers.`,
|
||||
parseValue: parseScalarValue,
|
||||
serialize(value) {
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
} else if (
|
||||
typeof value === 'object' &&
|
||||
value.__type === 'Pointer' &&
|
||||
value.className === className &&
|
||||
typeof value.objectId === 'string'
|
||||
) {
|
||||
return value.objectId;
|
||||
}
|
||||
|
||||
throw new defaultGraphQLTypes.TypeValidationError(
|
||||
value,
|
||||
classGraphQLScalarTypeName
|
||||
);
|
||||
},
|
||||
parseLiteral(ast) {
|
||||
if (ast.kind === Kind.STRING) {
|
||||
return parseScalarValue(ast.value);
|
||||
} else if (ast.kind === Kind.OBJECT) {
|
||||
const __type = ast.fields.find(field => field.name.value === '__type');
|
||||
const className = ast.fields.find(
|
||||
field => field.name.value === 'className'
|
||||
);
|
||||
const objectId = ast.fields.find(
|
||||
field => field.name.value === 'objectId'
|
||||
);
|
||||
if (
|
||||
__type &&
|
||||
__type.value &&
|
||||
className &&
|
||||
className.value &&
|
||||
objectId &&
|
||||
objectId.value
|
||||
) {
|
||||
return parseScalarValue({
|
||||
__type: __type.value.value,
|
||||
className: className.value.value,
|
||||
objectId: objectId.value.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
throw new defaultGraphQLTypes.TypeValidationError(
|
||||
ast.kind,
|
||||
classGraphQLScalarTypeName
|
||||
);
|
||||
},
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLScalarType);
|
||||
|
||||
const classGraphQLRelationOpTypeName = `${className}RelationOp`;
|
||||
const classGraphQLRelationOpType = new GraphQLInputObjectType({
|
||||
name: classGraphQLRelationOpTypeName,
|
||||
description: `The ${classGraphQLRelationOpTypeName} input type is used in operations that involve relations with the ${className} class.`,
|
||||
fields: () => ({
|
||||
_op: {
|
||||
description: 'This is the operation to be executed.',
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.RELATION_OP),
|
||||
},
|
||||
ops: {
|
||||
description:
|
||||
'In the case of a Batch operation, this is the list of operations to be executed.',
|
||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLRelationOpType)),
|
||||
},
|
||||
objects: {
|
||||
description:
|
||||
'In the case of a AddRelation or RemoveRelation operation, this is the list of objects to be added/removed.',
|
||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLScalarType)),
|
||||
},
|
||||
}),
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLRelationOpType);
|
||||
|
||||
const classGraphQLInputTypeName = `${className}Fields`;
|
||||
const classGraphQLInputType = new GraphQLInputObjectType({
|
||||
name: classGraphQLInputTypeName,
|
||||
description: `The ${classGraphQLInputTypeName} input type is used in operations that involve inputting objects of ${className} class.`,
|
||||
fields: () =>
|
||||
classCustomFields.reduce(
|
||||
(fields, field) => {
|
||||
const type = mapInputType(
|
||||
parseClass.fields[field].type,
|
||||
parseClass.fields[field].targetClass,
|
||||
parseGraphQLSchema.parseClassTypes
|
||||
);
|
||||
if (type) {
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
},
|
||||
{
|
||||
ACL: defaultGraphQLTypes.ACL_ATT,
|
||||
}
|
||||
),
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLInputType);
|
||||
|
||||
const classGraphQLConstraintTypeName = `${className}PointerConstraint`;
|
||||
const classGraphQLConstraintType = new GraphQLInputObjectType({
|
||||
name: classGraphQLConstraintTypeName,
|
||||
description: `The ${classGraphQLConstraintTypeName} input type is used in operations that involve filtering objects by a pointer field to ${className} class.`,
|
||||
fields: {
|
||||
_eq: defaultGraphQLTypes._eq(classGraphQLScalarType),
|
||||
_ne: defaultGraphQLTypes._ne(classGraphQLScalarType),
|
||||
_in: defaultGraphQLTypes._in(classGraphQLScalarType),
|
||||
_nin: defaultGraphQLTypes._nin(classGraphQLScalarType),
|
||||
_exists: defaultGraphQLTypes._exists,
|
||||
_select: defaultGraphQLTypes._select,
|
||||
_dontSelect: defaultGraphQLTypes._dontSelect,
|
||||
_inQuery: {
|
||||
description:
|
||||
'This is the $inQuery operator to specify a constraint to select the objects where a field equals to any of the ids in the result of a different query.',
|
||||
type: defaultGraphQLTypes.SUBQUERY,
|
||||
},
|
||||
_notInQuery: {
|
||||
description:
|
||||
'This is the $notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the ids in the result of a different query.',
|
||||
type: defaultGraphQLTypes.SUBQUERY,
|
||||
},
|
||||
},
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLConstraintType);
|
||||
|
||||
const classGraphQLConstraintsTypeName = `${className}Constraints`;
|
||||
const classGraphQLConstraintsType = new GraphQLInputObjectType({
|
||||
name: classGraphQLConstraintsTypeName,
|
||||
description: `The ${classGraphQLConstraintsTypeName} input type is used in operations that involve filtering objects of ${className} class.`,
|
||||
fields: () => ({
|
||||
...classFields.reduce((fields, field) => {
|
||||
const type = mapConstraintType(
|
||||
parseClass.fields[field].type,
|
||||
parseClass.fields[field].targetClass,
|
||||
parseGraphQLSchema.parseClassTypes
|
||||
);
|
||||
if (type) {
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
}, {}),
|
||||
_or: {
|
||||
description: 'This is the $or operator to compound constraints.',
|
||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLConstraintsType)),
|
||||
},
|
||||
_and: {
|
||||
description: 'This is the $and operator to compound constraints.',
|
||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLConstraintsType)),
|
||||
},
|
||||
_nor: {
|
||||
description: 'This is the $nor operator to compound constraints.',
|
||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLConstraintsType)),
|
||||
},
|
||||
}),
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLConstraintsType);
|
||||
|
||||
const classGraphQLOrderTypeName = `${className}Order`;
|
||||
const classGraphQLOrderType = new GraphQLEnumType({
|
||||
name: classGraphQLOrderTypeName,
|
||||
description: `The ${classGraphQLOrderTypeName} input type is used when sorting objects of the ${className} class.`,
|
||||
values: classFields.reduce((orderFields, field) => {
|
||||
return {
|
||||
...orderFields,
|
||||
[`${field}_ASC`]: { value: field },
|
||||
[`${field}_DESC`]: { value: `-${field}` },
|
||||
};
|
||||
}, {}),
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLOrderType);
|
||||
|
||||
const classGraphQLFindArgs = {
|
||||
where: {
|
||||
description:
|
||||
'These are the conditions that the objects need to match in order to be found.',
|
||||
type: classGraphQLConstraintsType,
|
||||
},
|
||||
order: {
|
||||
description: 'The fields to be used when sorting the data fetched.',
|
||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLOrderType)),
|
||||
},
|
||||
skip: defaultGraphQLTypes.SKIP_ATT,
|
||||
limit: defaultGraphQLTypes.LIMIT_ATT,
|
||||
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
|
||||
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
|
||||
subqueryReadPreference: defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT,
|
||||
};
|
||||
|
||||
const classGraphQLOutputTypeName = `${className}Class`;
|
||||
const outputFields = () => {
|
||||
return classCustomFields.reduce((fields, field) => {
|
||||
const type = mapOutputType(
|
||||
parseClass.fields[field].type,
|
||||
parseClass.fields[field].targetClass,
|
||||
parseGraphQLSchema.parseClassTypes
|
||||
);
|
||||
if (parseClass.fields[field].type === 'Relation') {
|
||||
const targetParseClassTypes =
|
||||
parseGraphQLSchema.parseClassTypes[
|
||||
parseClass.fields[field].targetClass
|
||||
];
|
||||
const args = targetParseClassTypes
|
||||
? targetParseClassTypes.classGraphQLFindArgs
|
||||
: undefined;
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
args,
|
||||
type,
|
||||
async resolve(source, args, context, queryInfo) {
|
||||
try {
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
} = args;
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
);
|
||||
|
||||
return await objectsQueries.findObjects(
|
||||
source[field].className,
|
||||
{
|
||||
_relatedTo: {
|
||||
object: {
|
||||
__type: 'Pointer',
|
||||
className,
|
||||
objectId: source.objectId,
|
||||
},
|
||||
key: field,
|
||||
},
|
||||
...(where || {}),
|
||||
},
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
subqueryReadPreference,
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0])
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (parseClass.fields[field].type === 'Polygon') {
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type,
|
||||
async resolve(source) {
|
||||
if (source[field] && source[field].coordinates) {
|
||||
return source[field].coordinates.map(coordinate => ({
|
||||
latitude: coordinate[0],
|
||||
longitude: coordinate[1],
|
||||
}));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (type) {
|
||||
return {
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
}, defaultGraphQLTypes.CLASS_FIELDS);
|
||||
};
|
||||
const classGraphQLOutputType = new GraphQLObjectType({
|
||||
name: classGraphQLOutputTypeName,
|
||||
description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${className} class.`,
|
||||
interfaces: [defaultGraphQLTypes.CLASS],
|
||||
fields: outputFields,
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLOutputType);
|
||||
|
||||
const classGraphQLFindResultTypeName = `${className}FindResult`;
|
||||
const classGraphQLFindResultType = new GraphQLObjectType({
|
||||
name: classGraphQLFindResultTypeName,
|
||||
description: `The ${classGraphQLFindResultTypeName} object type is used in the ${className} find query to return the data of the matched objects.`,
|
||||
fields: {
|
||||
results: {
|
||||
description: 'This is the objects returned by the query',
|
||||
type: new GraphQLNonNull(
|
||||
new GraphQLList(new GraphQLNonNull(classGraphQLOutputType))
|
||||
),
|
||||
},
|
||||
count: defaultGraphQLTypes.COUNT_ATT,
|
||||
},
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(classGraphQLFindResultType);
|
||||
|
||||
parseGraphQLSchema.parseClassTypes[className] = {
|
||||
classGraphQLScalarType,
|
||||
classGraphQLRelationOpType,
|
||||
classGraphQLInputType,
|
||||
classGraphQLConstraintType,
|
||||
classGraphQLConstraintsType,
|
||||
classGraphQLFindArgs,
|
||||
classGraphQLOutputType,
|
||||
classGraphQLFindResultType,
|
||||
};
|
||||
|
||||
if (className === '_User') {
|
||||
const meType = new GraphQLObjectType({
|
||||
name: 'Me',
|
||||
description: `The Me object type is used in operations that involve outputting the current user data.`,
|
||||
interfaces: [defaultGraphQLTypes.CLASS],
|
||||
fields: () => ({
|
||||
...outputFields(),
|
||||
sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT,
|
||||
}),
|
||||
});
|
||||
parseGraphQLSchema.meType = meType;
|
||||
parseGraphQLSchema.graphQLTypes.push(meType);
|
||||
}
|
||||
};
|
||||
|
||||
export { extractKeysAndInclude, load };
|
||||
110
src/GraphQL/loaders/usersMutations.js
Normal file
110
src/GraphQL/loaders/usersMutations.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
GraphQLBoolean,
|
||||
GraphQLNonNull,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
import UsersRouter from '../../Routers/UsersRouter';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsMutations from './objectsMutations';
|
||||
|
||||
const usersRouter = new UsersRouter();
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
const fields = {};
|
||||
|
||||
fields.signUp = {
|
||||
description: 'The signUp mutation can be used to sign the user up.',
|
||||
args: {
|
||||
fields: {
|
||||
descriptions: 'These are the fields of the user.',
|
||||
type: parseGraphQLSchema.parseClassTypes['_User'].classGraphQLInputType,
|
||||
},
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.SIGN_UP_RESULT),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { fields } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return await objectsMutations.createObject(
|
||||
'_User',
|
||||
fields,
|
||||
config,
|
||||
auth,
|
||||
info
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
fields.logIn = {
|
||||
description: 'The logIn mutation can be used to log the user in.',
|
||||
args: {
|
||||
username: {
|
||||
description: 'This is the username used to log the user in.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
password: {
|
||||
description: 'This is the password used to log the user in.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
},
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.meType),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { username, password } = args;
|
||||
const { config, auth, info } = context;
|
||||
|
||||
return (await usersRouter.handleLogIn({
|
||||
body: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
query: {},
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
})).response;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
fields.logOut = {
|
||||
description: 'The logOut mutation can be used to log the user out.',
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
async resolve(_source, _args, context) {
|
||||
try {
|
||||
const { config, auth, info } = context;
|
||||
|
||||
await usersRouter.handleLogOut({
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const usersMutation = new GraphQLObjectType({
|
||||
name: 'UsersMutation',
|
||||
description: 'UsersMutation is the top level type for files mutations.',
|
||||
fields,
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(usersMutation);
|
||||
|
||||
parseGraphQLSchema.graphQLMutations.users = {
|
||||
description: 'This is the top level for users mutations.',
|
||||
type: usersMutation,
|
||||
resolve: () => new Object(),
|
||||
};
|
||||
};
|
||||
|
||||
export { load };
|
||||
36
src/GraphQL/loaders/usersQueries.js
Normal file
36
src/GraphQL/loaders/usersQueries.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { GraphQLNonNull, GraphQLObjectType } from 'graphql';
|
||||
import UsersRouter from '../../Routers/UsersRouter';
|
||||
|
||||
const usersRouter = new UsersRouter();
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
const fields = {};
|
||||
|
||||
fields.me = {
|
||||
description: 'The Me query can be used to return the current user data.',
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.meType),
|
||||
async resolve(_source, _args, context) {
|
||||
try {
|
||||
const { config, auth, info } = context;
|
||||
return (await usersRouter.handleMe({ config, auth, info })).response;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const usersQuery = new GraphQLObjectType({
|
||||
name: 'UsersQuery',
|
||||
description: 'UsersQuery is the top level type for users queries.',
|
||||
fields,
|
||||
});
|
||||
parseGraphQLSchema.graphQLTypes.push(usersQuery);
|
||||
|
||||
parseGraphQLSchema.graphQLQueries.users = {
|
||||
description: 'This is the top level for users queries.',
|
||||
type: usersQuery,
|
||||
resolve: () => new Object(),
|
||||
};
|
||||
};
|
||||
|
||||
export { load };
|
||||
@@ -10,6 +10,7 @@ import { useExternal } from './deprecated';
|
||||
import { getLogger } from './logger';
|
||||
import { PushWorker } from './Push/PushWorker';
|
||||
import { ParseServerOptions } from './Options';
|
||||
import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer';
|
||||
|
||||
// Factory function
|
||||
const _ParseServer = function(options: ParseServerOptions) {
|
||||
@@ -37,5 +38,6 @@ export {
|
||||
LRUCacheAdapter,
|
||||
TestUtils,
|
||||
PushWorker,
|
||||
ParseGraphQLServer,
|
||||
_ParseServer as ParseServer,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user