Relay Spec (#6089)
* Install graphql-relay * Add relayNodeInterface to ParseGraphQLSchema * Add support to global id * Add support to global id in other operations * Fix sort by glboal id * Fix where by global id * Introduce IdWhereInput * Add Relay object identification tests * Client mutation id on createFile mutation * Client mutation id on callCloudCode mutation * Client mutation id on signUp mutation * Client mutation id on logIn mutation * Client mutation id on logOut mutation * Client mutation id on createClass mutation * Client mutation id on updateClass mutation * Client mutation id on deleteClass mutation * Client mutation id on create object mutation * Improve Viewer type * Client mutation id on update object mutation * Client mutation id on delete object mutation * Introducing connections * Fix tests * Add pagination test * Fix file location * Fix postgres tests * Add comments * Tests to calculateSkipAndLimit
This commit is contained in:
committed by
GitHub
parent
67e3c33ffe
commit
a9066e20dc
1817
package-lock.json
generated
1817
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@
|
|||||||
"follow-redirects": "1.9.0",
|
"follow-redirects": "1.9.0",
|
||||||
"graphql": "14.5.8",
|
"graphql": "14.5.8",
|
||||||
"graphql-list-fields": "2.0.2",
|
"graphql-list-fields": "2.0.2",
|
||||||
|
"graphql-relay": "^0.6.0",
|
||||||
"graphql-tools": "^4.0.5",
|
"graphql-tools": "^4.0.5",
|
||||||
"graphql-upload": "8.1.0",
|
"graphql-upload": "8.1.0",
|
||||||
"intersect": "1.0.1",
|
"intersect": "1.0.1",
|
||||||
|
|||||||
@@ -54,8 +54,14 @@ describe('AuthenticationProviders', function() {
|
|||||||
Promise.prototype.constructor
|
Promise.prototype.constructor
|
||||||
);
|
);
|
||||||
jequal(validateAppIdPromise.constructor, Promise.prototype.constructor);
|
jequal(validateAppIdPromise.constructor, Promise.prototype.constructor);
|
||||||
validateAuthDataPromise.then(() => {}, () => {});
|
validateAuthDataPromise.then(
|
||||||
validateAppIdPromise.then(() => {}, () => {});
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
validateAppIdPromise.then(
|
||||||
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,11 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
|||||||
const lng3 = 65;
|
const lng3 = 65;
|
||||||
const polygon = {
|
const polygon = {
|
||||||
__type: 'Polygon',
|
__type: 'Polygon',
|
||||||
coordinates: [[lat1, lng1], [lat2, lng2], [lat3, lng3]],
|
coordinates: [
|
||||||
|
[lat1, lng1],
|
||||||
|
[lat2, lng2],
|
||||||
|
[lat3, lng3],
|
||||||
|
],
|
||||||
};
|
};
|
||||||
const out = transform.parseObjectToMongoObjectForCreate(
|
const out = transform.parseObjectToMongoObjectForCreate(
|
||||||
null,
|
null,
|
||||||
@@ -107,7 +111,12 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
expect(out.location.coordinates).toEqual([
|
expect(out.location.coordinates).toEqual([
|
||||||
[[lng1, lat1], [lng2, lat2], [lng3, lat3], [lng1, lat1]],
|
[
|
||||||
|
[lng1, lat1],
|
||||||
|
[lng2, lat2],
|
||||||
|
[lng3, lat3],
|
||||||
|
[lng1, lat1],
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -217,7 +226,15 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
|||||||
const lng = 45;
|
const lng = 45;
|
||||||
// Mongo stores polygon in WGS84 lng/lat
|
// Mongo stores polygon in WGS84 lng/lat
|
||||||
const input = {
|
const input = {
|
||||||
location: { type: 'Polygon', coordinates: [[[lat, lng], [lat, lng]]] },
|
location: {
|
||||||
|
type: 'Polygon',
|
||||||
|
coordinates: [
|
||||||
|
[
|
||||||
|
[lat, lng],
|
||||||
|
[lat, lng],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const output = transform.mongoObjectToParseObject(null, input, {
|
const output = transform.mongoObjectToParseObject(null, input, {
|
||||||
fields: { location: { type: 'Polygon' } },
|
fields: { location: { type: 'Polygon' } },
|
||||||
@@ -225,7 +242,10 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
|||||||
expect(typeof output.location).toEqual('object');
|
expect(typeof output.location).toEqual('object');
|
||||||
expect(output.location).toEqual({
|
expect(output.location).toEqual({
|
||||||
__type: 'Polygon',
|
__type: 'Polygon',
|
||||||
coordinates: [[lng, lat], [lng, lat]],
|
coordinates: [
|
||||||
|
[lng, lat],
|
||||||
|
[lng, lat],
|
||||||
|
],
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
158
spec/graphQLObjectsQueries.js
Normal file
158
spec/graphQLObjectsQueries.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
const { offsetToCursor } = require('graphql-relay');
|
||||||
|
const {
|
||||||
|
calculateSkipAndLimit,
|
||||||
|
} = require('../lib/GraphQL/helpers/objectsQueries');
|
||||||
|
|
||||||
|
describe('GraphQL objectsQueries', () => {
|
||||||
|
describe('calculateSkipAndLimit', () => {
|
||||||
|
it('should fail with invalid params', () => {
|
||||||
|
expect(() => calculateSkipAndLimit(-1)).toThrow(
|
||||||
|
jasmine.stringMatching('Skip should be a positive number')
|
||||||
|
);
|
||||||
|
expect(() => calculateSkipAndLimit(1, -1)).toThrow(
|
||||||
|
jasmine.stringMatching('First should be a positive number')
|
||||||
|
);
|
||||||
|
expect(() => calculateSkipAndLimit(1, 1, offsetToCursor(-1))).toThrow(
|
||||||
|
jasmine.stringMatching('After is not a valid curso')
|
||||||
|
);
|
||||||
|
expect(() => calculateSkipAndLimit(1, 1, offsetToCursor(1), -1)).toThrow(
|
||||||
|
jasmine.stringMatching('Last should be a positive number')
|
||||||
|
);
|
||||||
|
expect(() =>
|
||||||
|
calculateSkipAndLimit(1, 1, offsetToCursor(1), 1, offsetToCursor(-1))
|
||||||
|
).toThrow(jasmine.stringMatching('Before is not a valid curso'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work only with skip', () => {
|
||||||
|
expect(calculateSkipAndLimit(10)).toEqual({
|
||||||
|
skip: 10,
|
||||||
|
limit: undefined,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work only with after', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(undefined, undefined, offsetToCursor(9))
|
||||||
|
).toEqual({
|
||||||
|
skip: 10,
|
||||||
|
limit: undefined,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with limit and after', () => {
|
||||||
|
expect(calculateSkipAndLimit(10, undefined, offsetToCursor(9))).toEqual({
|
||||||
|
skip: 20,
|
||||||
|
limit: undefined,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('first alone should set the limit', () => {
|
||||||
|
expect(calculateSkipAndLimit(10, 30, offsetToCursor(9))).toEqual({
|
||||||
|
skip: 20,
|
||||||
|
limit: 30,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if before cursor is less than skipped items, no objects will be returned', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(
|
||||||
|
10,
|
||||||
|
30,
|
||||||
|
offsetToCursor(9),
|
||||||
|
undefined,
|
||||||
|
offsetToCursor(5)
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
skip: 20,
|
||||||
|
limit: 0,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if before cursor is greater than returned objects set by limit, nothing is changed', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(
|
||||||
|
10,
|
||||||
|
30,
|
||||||
|
offsetToCursor(9),
|
||||||
|
undefined,
|
||||||
|
offsetToCursor(100)
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
skip: 20,
|
||||||
|
limit: 30,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if before cursor is less than returned objects set by limit, limit is adjusted', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(
|
||||||
|
10,
|
||||||
|
30,
|
||||||
|
offsetToCursor(9),
|
||||||
|
undefined,
|
||||||
|
offsetToCursor(40)
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
skip: 20,
|
||||||
|
limit: 20,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('last should work alone but requires pre count', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(undefined, undefined, undefined, 10)
|
||||||
|
).toEqual({
|
||||||
|
skip: undefined,
|
||||||
|
limit: 10,
|
||||||
|
needToPreCount: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('last should be adjusted to max limit', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(undefined, undefined, undefined, 10, undefined, 5)
|
||||||
|
).toEqual({
|
||||||
|
skip: undefined,
|
||||||
|
limit: 5,
|
||||||
|
needToPreCount: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('no objects will be returned if last is equal to 0', () => {
|
||||||
|
expect(calculateSkipAndLimit(undefined, undefined, undefined, 0)).toEqual(
|
||||||
|
{
|
||||||
|
skip: undefined,
|
||||||
|
limit: 0,
|
||||||
|
needToPreCount: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nothing changes if last is bigger than the calculared limit', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(10, 30, offsetToCursor(9), 30, offsetToCursor(40))
|
||||||
|
).toEqual({
|
||||||
|
skip: 20,
|
||||||
|
limit: 20,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('If last is small than limit, new limit is calculated', () => {
|
||||||
|
expect(
|
||||||
|
calculateSkipAndLimit(10, 30, offsetToCursor(9), 10, offsetToCursor(40))
|
||||||
|
).toEqual({
|
||||||
|
skip: 30,
|
||||||
|
limit: 10,
|
||||||
|
needToPreCount: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
import { GraphQLSchema, GraphQLObjectType } from 'graphql';
|
import {
|
||||||
|
GraphQLSchema,
|
||||||
|
GraphQLObjectType,
|
||||||
|
DocumentNode,
|
||||||
|
GraphQLNamedType,
|
||||||
|
} from 'graphql';
|
||||||
import { mergeSchemas, SchemaDirectiveVisitor } from 'graphql-tools';
|
import { mergeSchemas, SchemaDirectiveVisitor } from 'graphql-tools';
|
||||||
import requiredParameter from '../requiredParameter';
|
import requiredParameter from '../requiredParameter';
|
||||||
import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes';
|
||||||
@@ -16,6 +21,7 @@ import { toGraphQLError } from './parseGraphQLUtils';
|
|||||||
import * as schemaDirectives from './loaders/schemaDirectives';
|
import * as schemaDirectives from './loaders/schemaDirectives';
|
||||||
import * as schemaTypes from './loaders/schemaTypes';
|
import * as schemaTypes from './loaders/schemaTypes';
|
||||||
import { getFunctionNames } from '../triggers';
|
import { getFunctionNames } from '../triggers';
|
||||||
|
import * as defaultRelaySchema from './loaders/defaultRelaySchema';
|
||||||
|
|
||||||
const RESERVED_GRAPHQL_TYPE_NAMES = [
|
const RESERVED_GRAPHQL_TYPE_NAMES = [
|
||||||
'String',
|
'String',
|
||||||
@@ -27,10 +33,25 @@ const RESERVED_GRAPHQL_TYPE_NAMES = [
|
|||||||
'Query',
|
'Query',
|
||||||
'Mutation',
|
'Mutation',
|
||||||
'Subscription',
|
'Subscription',
|
||||||
|
'CreateFileInput',
|
||||||
|
'CreateFilePayload',
|
||||||
'Viewer',
|
'Viewer',
|
||||||
'SignUpFieldsInput',
|
'SignUpInput',
|
||||||
'LogInFieldsInput',
|
'SignUpPayload',
|
||||||
|
'LogInInput',
|
||||||
|
'LogInPayload',
|
||||||
|
'LogOutInput',
|
||||||
|
'LogOutPayload',
|
||||||
'CloudCodeFunction',
|
'CloudCodeFunction',
|
||||||
|
'CallCloudCodeInput',
|
||||||
|
'CallCloudCodePayload',
|
||||||
|
'CreateClassInput',
|
||||||
|
'CreateClassPayload',
|
||||||
|
'UpdateClassInput',
|
||||||
|
'UpdateClassPayload',
|
||||||
|
'DeleteClassInput',
|
||||||
|
'DeleteClassPayload',
|
||||||
|
'PageInfo',
|
||||||
];
|
];
|
||||||
const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'class', 'classes'];
|
const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'class', 'classes'];
|
||||||
const RESERVED_GRAPHQL_MUTATION_NAMES = [
|
const RESERVED_GRAPHQL_MUTATION_NAMES = [
|
||||||
@@ -48,7 +69,14 @@ class ParseGraphQLSchema {
|
|||||||
databaseController: DatabaseController;
|
databaseController: DatabaseController;
|
||||||
parseGraphQLController: ParseGraphQLController;
|
parseGraphQLController: ParseGraphQLController;
|
||||||
parseGraphQLConfig: ParseGraphQLConfig;
|
parseGraphQLConfig: ParseGraphQLConfig;
|
||||||
graphQLCustomTypeDefs: any;
|
log: any;
|
||||||
|
appId: string;
|
||||||
|
graphQLCustomTypeDefs: ?(
|
||||||
|
| string
|
||||||
|
| GraphQLSchema
|
||||||
|
| DocumentNode
|
||||||
|
| GraphQLNamedType[]
|
||||||
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
params: {
|
params: {
|
||||||
@@ -56,6 +84,12 @@ class ParseGraphQLSchema {
|
|||||||
parseGraphQLController: ParseGraphQLController,
|
parseGraphQLController: ParseGraphQLController,
|
||||||
log: any,
|
log: any,
|
||||||
appId: string,
|
appId: string,
|
||||||
|
graphQLCustomTypeDefs: ?(
|
||||||
|
| string
|
||||||
|
| GraphQLSchema
|
||||||
|
| DocumentNode
|
||||||
|
| GraphQLNamedType[]
|
||||||
|
),
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
this.parseGraphQLController =
|
this.parseGraphQLController =
|
||||||
@@ -105,8 +139,10 @@ class ParseGraphQLSchema {
|
|||||||
this.graphQLSubscriptions = {};
|
this.graphQLSubscriptions = {};
|
||||||
this.graphQLSchemaDirectivesDefinitions = null;
|
this.graphQLSchemaDirectivesDefinitions = null;
|
||||||
this.graphQLSchemaDirectives = {};
|
this.graphQLSchemaDirectives = {};
|
||||||
|
this.relayNodeInterface = null;
|
||||||
|
|
||||||
defaultGraphQLTypes.load(this);
|
defaultGraphQLTypes.load(this);
|
||||||
|
defaultRelaySchema.load(this);
|
||||||
schemaTypes.load(this);
|
schemaTypes.load(this);
|
||||||
|
|
||||||
this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach(
|
this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach(
|
||||||
@@ -208,10 +244,16 @@ class ParseGraphQLSchema {
|
|||||||
return this.graphQLSchema;
|
return this.graphQLSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
addGraphQLType(type, throwError = false, ignoreReserved = 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)) ||
|
||||||
this.graphQLTypes.find(existingType => existingType.name === type.name)
|
this.graphQLTypes.find(existingType => existingType.name === type.name) ||
|
||||||
|
(!ignoreConnection && type.name.endsWith('Connection'))
|
||||||
) {
|
) {
|
||||||
const message = `Type ${type.name} could not be added to the auto schema because it collided with an existing type.`;
|
const message = `Type ${type.name} could not be added to the auto schema because it collided with an existing type.`;
|
||||||
if (throwError) {
|
if (throwError) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
|
import { offsetToCursor, cursorToOffset } from 'graphql-relay';
|
||||||
import rest from '../../rest';
|
import rest from '../../rest';
|
||||||
import { transformQueryInputToParse } from '../transformers/query';
|
import { transformQueryInputToParse } from '../transformers/query';
|
||||||
|
|
||||||
@@ -51,8 +52,11 @@ const findObjects = async (
|
|||||||
className,
|
className,
|
||||||
where,
|
where,
|
||||||
order,
|
order,
|
||||||
skip,
|
skipInput,
|
||||||
limit,
|
first,
|
||||||
|
after,
|
||||||
|
last,
|
||||||
|
before,
|
||||||
keys,
|
keys,
|
||||||
include,
|
include,
|
||||||
includeAll,
|
includeAll,
|
||||||
@@ -68,11 +72,52 @@ const findObjects = async (
|
|||||||
if (!where) {
|
if (!where) {
|
||||||
where = {};
|
where = {};
|
||||||
}
|
}
|
||||||
transformQueryInputToParse(where, fields);
|
transformQueryInputToParse(where, fields, className);
|
||||||
|
|
||||||
|
const skipAndLimitCalculation = calculateSkipAndLimit(
|
||||||
|
skipInput,
|
||||||
|
first,
|
||||||
|
after,
|
||||||
|
last,
|
||||||
|
before,
|
||||||
|
config.maxLimit
|
||||||
|
);
|
||||||
|
let { skip } = skipAndLimitCalculation;
|
||||||
|
const { limit, needToPreCount } = skipAndLimitCalculation;
|
||||||
|
let preCount = undefined;
|
||||||
|
if (needToPreCount) {
|
||||||
|
const preCountOptions = {
|
||||||
|
limit: 0,
|
||||||
|
count: true,
|
||||||
|
};
|
||||||
|
if (readPreference) {
|
||||||
|
preCountOptions.readPreference = readPreference;
|
||||||
|
}
|
||||||
|
if (Object.keys(where).length > 0 && subqueryReadPreference) {
|
||||||
|
preCountOptions.subqueryReadPreference = subqueryReadPreference;
|
||||||
|
}
|
||||||
|
preCount = (
|
||||||
|
await rest.find(
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
where,
|
||||||
|
preCountOptions,
|
||||||
|
info.clientSDK
|
||||||
|
)
|
||||||
|
).count;
|
||||||
|
if ((skip || 0) + limit < preCount) {
|
||||||
|
skip = preCount - limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options = {};
|
const options = {};
|
||||||
|
|
||||||
if (selectedFields.includes('results')) {
|
if (
|
||||||
|
selectedFields.find(
|
||||||
|
field => field.startsWith('edges.') || field.startsWith('pageInfo.')
|
||||||
|
)
|
||||||
|
) {
|
||||||
if (limit || limit === 0) {
|
if (limit || limit === 0) {
|
||||||
options.limit = limit;
|
options.limit = limit;
|
||||||
}
|
}
|
||||||
@@ -104,7 +149,12 @@ const findObjects = async (
|
|||||||
options.limit = 0;
|
options.limit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedFields.includes('count')) {
|
if (
|
||||||
|
(selectedFields.includes('count') ||
|
||||||
|
selectedFields.includes('pageInfo.hasPreviousPage') ||
|
||||||
|
selectedFields.includes('pageInfo.hasNextPage')) &&
|
||||||
|
!needToPreCount
|
||||||
|
) {
|
||||||
options.count = true;
|
options.count = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +165,151 @@ const findObjects = async (
|
|||||||
options.subqueryReadPreference = subqueryReadPreference;
|
options.subqueryReadPreference = subqueryReadPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rest.find(config, auth, className, where, options, info.clientSDK);
|
let results, count;
|
||||||
|
if (options.count || !options.limit || (options.limit && options.limit > 0)) {
|
||||||
|
const findResult = await rest.find(
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
where,
|
||||||
|
options,
|
||||||
|
info.clientSDK
|
||||||
|
);
|
||||||
|
results = findResult.results;
|
||||||
|
count = findResult.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
let edges = null;
|
||||||
|
let pageInfo = null;
|
||||||
|
if (results) {
|
||||||
|
edges = results.map((result, index) => ({
|
||||||
|
cursor: offsetToCursor((skip || 0) + index),
|
||||||
|
node: result,
|
||||||
|
}));
|
||||||
|
|
||||||
|
pageInfo = {
|
||||||
|
hasPreviousPage:
|
||||||
|
((preCount && preCount > 0) || (count && count > 0)) &&
|
||||||
|
skip !== undefined &&
|
||||||
|
skip > 0,
|
||||||
|
startCursor: offsetToCursor(skip || 0),
|
||||||
|
endCursor: offsetToCursor((skip || 0) + (results.length || 1) - 1),
|
||||||
|
hasNextPage: (preCount || count) > (skip || 0) + results.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
edges,
|
||||||
|
pageInfo,
|
||||||
|
count: preCount || count,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getObject, findObjects };
|
const calculateSkipAndLimit = (
|
||||||
|
skipInput,
|
||||||
|
first,
|
||||||
|
after,
|
||||||
|
last,
|
||||||
|
before,
|
||||||
|
maxLimit
|
||||||
|
) => {
|
||||||
|
let skip = undefined;
|
||||||
|
let limit = undefined;
|
||||||
|
let needToPreCount = false;
|
||||||
|
|
||||||
|
// Validates the skip input
|
||||||
|
if (skipInput || skipInput === 0) {
|
||||||
|
if (skipInput < 0) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_QUERY,
|
||||||
|
'Skip should be a positive number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
skip = skipInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates the after param
|
||||||
|
if (after) {
|
||||||
|
after = cursorToOffset(after);
|
||||||
|
if ((!after && after !== 0) || after < 0) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_QUERY,
|
||||||
|
'After is not a valid cursor'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If skip and after are passed, a new skip is calculated by adding them
|
||||||
|
skip = (skip || 0) + (after + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates the first param
|
||||||
|
if (first || first === 0) {
|
||||||
|
if (first < 0) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_QUERY,
|
||||||
|
'First should be a positive number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first param is translated to the limit param of the Parse legacy API
|
||||||
|
limit = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates the before param
|
||||||
|
if (before || before === 0) {
|
||||||
|
// This method converts the cursor to the index of the object
|
||||||
|
before = cursorToOffset(before);
|
||||||
|
if ((!before && before !== 0) || before < 0) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_QUERY,
|
||||||
|
'Before is not a valid cursor'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((skip || 0) >= before) {
|
||||||
|
// If the before index is less then the skip, no objects will be returned
|
||||||
|
limit = 0;
|
||||||
|
} else if ((!limit && limit !== 0) || (skip || 0) + limit > before) {
|
||||||
|
// If there is no limit set, the limit is calculated. Or, if the limit (plus skip) is bigger than the before index, the new limit is set.
|
||||||
|
limit = before - (skip || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates the last param
|
||||||
|
if (last || last === 0) {
|
||||||
|
if (last < 0) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_QUERY,
|
||||||
|
'Last should be a positive number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last > maxLimit) {
|
||||||
|
// Last can't be bigger than Parse server maxLimit config.
|
||||||
|
last = maxLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit || limit === 0) {
|
||||||
|
// If there is a previous limit set, it may be adjusted
|
||||||
|
if (last < limit) {
|
||||||
|
// if last is less than the current limit
|
||||||
|
skip = (skip || 0) + (limit - last); // The skip is adjusted
|
||||||
|
limit = last; // the limit is adjusted
|
||||||
|
}
|
||||||
|
} else if (last === 0) {
|
||||||
|
// No objects will be returned
|
||||||
|
limit = 0;
|
||||||
|
} else {
|
||||||
|
// No previous limit set, the limit will be equal to last and pre count is needed.
|
||||||
|
limit = last;
|
||||||
|
needToPreCount = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
skip,
|
||||||
|
limit,
|
||||||
|
needToPreCount,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getObject, findObjects, calculateSkipAndLimit };
|
||||||
|
|||||||
@@ -588,10 +588,15 @@ const CLASS_NAME_ATT = {
|
|||||||
type: new GraphQLNonNull(GraphQLString),
|
type: new GraphQLNonNull(GraphQLString),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GLOBAL_OR_OBJECT_ID_ATT = {
|
||||||
|
description:
|
||||||
|
'This is the object id. You can use either the global or the object id.',
|
||||||
|
type: OBJECT_ID,
|
||||||
|
};
|
||||||
|
|
||||||
const OBJECT_ID_ATT = {
|
const OBJECT_ID_ATT = {
|
||||||
description: 'This is the object id.',
|
description: 'This is the object id.',
|
||||||
type: OBJECT_ID,
|
type: OBJECT_ID,
|
||||||
resolve: ({ objectId }) => objectId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CREATED_AT_ATT = {
|
const CREATED_AT_ATT = {
|
||||||
@@ -611,7 +616,7 @@ const INPUT_FIELDS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CREATE_RESULT_FIELDS = {
|
const CREATE_RESULT_FIELDS = {
|
||||||
id: OBJECT_ID_ATT,
|
objectId: OBJECT_ID_ATT,
|
||||||
createdAt: CREATED_AT_ATT,
|
createdAt: CREATED_AT_ATT,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -637,7 +642,7 @@ const PARSE_OBJECT = new GraphQLInterfaceType({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const SESSION_TOKEN_ATT = {
|
const SESSION_TOKEN_ATT = {
|
||||||
description: 'The user session token',
|
description: 'The current user session token.',
|
||||||
type: new GraphQLNonNull(GraphQLString),
|
type: new GraphQLNonNull(GraphQLString),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -926,6 +931,25 @@ const options = {
|
|||||||
type: GraphQLString,
|
type: GraphQLString,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ID_WHERE_INPUT = new GraphQLInputObjectType({
|
||||||
|
name: 'IdWhereInput',
|
||||||
|
description:
|
||||||
|
'The IdWhereInput input type is used in operations that involve filtering objects by an id.',
|
||||||
|
fields: {
|
||||||
|
equalTo: equalTo(GraphQLID),
|
||||||
|
notEqualTo: notEqualTo(GraphQLID),
|
||||||
|
lessThan: lessThan(GraphQLID),
|
||||||
|
lessThanOrEqualTo: lessThanOrEqualTo(GraphQLID),
|
||||||
|
greaterThan: greaterThan(GraphQLID),
|
||||||
|
greaterThanOrEqualTo: greaterThanOrEqualTo(GraphQLID),
|
||||||
|
in: inOp(GraphQLID),
|
||||||
|
notIn: notIn(GraphQLID),
|
||||||
|
exists,
|
||||||
|
inQueryKey,
|
||||||
|
notInQueryKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const STRING_WHERE_INPUT = new GraphQLInputObjectType({
|
const STRING_WHERE_INPUT = new GraphQLInputObjectType({
|
||||||
name: 'StringWhereInput',
|
name: 'StringWhereInput',
|
||||||
description:
|
description:
|
||||||
@@ -1164,19 +1188,6 @@ const POLYGON_WHERE_INPUT = new GraphQLInputObjectType({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const FIND_RESULT = new GraphQLObjectType({
|
|
||||||
name: 'FindResult',
|
|
||||||
description:
|
|
||||||
'The FindResult object type is used in the find queries 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(OBJECT))),
|
|
||||||
},
|
|
||||||
count: COUNT_ATT,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const ELEMENT = new GraphQLObjectType({
|
const ELEMENT = new GraphQLObjectType({
|
||||||
name: 'Element',
|
name: 'Element',
|
||||||
description: "The Element object type is used to return array items' value.",
|
description: "The Element object type is used to return array items' value.",
|
||||||
@@ -1247,6 +1258,7 @@ const load = parseGraphQLSchema => {
|
|||||||
parseGraphQLSchema.addGraphQLType(CENTER_SPHERE_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(CENTER_SPHERE_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(GEO_WITHIN_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(GEO_WITHIN_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(GEO_INTERSECTS_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(GEO_INTERSECTS_INPUT, true);
|
||||||
|
parseGraphQLSchema.addGraphQLType(ID_WHERE_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(STRING_WHERE_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(STRING_WHERE_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(NUMBER_WHERE_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(NUMBER_WHERE_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(BOOLEAN_WHERE_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(BOOLEAN_WHERE_INPUT, true);
|
||||||
@@ -1258,9 +1270,7 @@ const load = parseGraphQLSchema => {
|
|||||||
parseGraphQLSchema.addGraphQLType(FILE_WHERE_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(FILE_WHERE_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(GEO_POINT_WHERE_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(GEO_POINT_WHERE_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(POLYGON_WHERE_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(POLYGON_WHERE_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(FIND_RESULT, true);
|
|
||||||
parseGraphQLSchema.addGraphQLType(ELEMENT, true);
|
parseGraphQLSchema.addGraphQLType(ELEMENT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(OBJECT_ID, true);
|
|
||||||
parseGraphQLSchema.addGraphQLType(ACL_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(ACL_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(USER_ACL_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(USER_ACL_INPUT, true);
|
||||||
parseGraphQLSchema.addGraphQLType(ROLE_ACL_INPUT, true);
|
parseGraphQLSchema.addGraphQLType(ROLE_ACL_INPUT, true);
|
||||||
@@ -1296,6 +1306,7 @@ export {
|
|||||||
POLYGON,
|
POLYGON,
|
||||||
OBJECT_ID,
|
OBJECT_ID,
|
||||||
CLASS_NAME_ATT,
|
CLASS_NAME_ATT,
|
||||||
|
GLOBAL_OR_OBJECT_ID_ATT,
|
||||||
OBJECT_ID_ATT,
|
OBJECT_ID_ATT,
|
||||||
UPDATED_AT_ATT,
|
UPDATED_AT_ATT,
|
||||||
CREATED_AT_ATT,
|
CREATED_AT_ATT,
|
||||||
@@ -1337,6 +1348,7 @@ export {
|
|||||||
notInQueryKey,
|
notInQueryKey,
|
||||||
matchesRegex,
|
matchesRegex,
|
||||||
options,
|
options,
|
||||||
|
ID_WHERE_INPUT,
|
||||||
STRING_WHERE_INPUT,
|
STRING_WHERE_INPUT,
|
||||||
NUMBER_WHERE_INPUT,
|
NUMBER_WHERE_INPUT,
|
||||||
BOOLEAN_WHERE_INPUT,
|
BOOLEAN_WHERE_INPUT,
|
||||||
@@ -1348,7 +1360,6 @@ export {
|
|||||||
FILE_WHERE_INPUT,
|
FILE_WHERE_INPUT,
|
||||||
GEO_POINT_WHERE_INPUT,
|
GEO_POINT_WHERE_INPUT,
|
||||||
POLYGON_WHERE_INPUT,
|
POLYGON_WHERE_INPUT,
|
||||||
FIND_RESULT,
|
|
||||||
ARRAY_RESULT,
|
ARRAY_RESULT,
|
||||||
ELEMENT,
|
ELEMENT,
|
||||||
ACL_INPUT,
|
ACL_INPUT,
|
||||||
|
|||||||
51
src/GraphQL/loaders/defaultRelaySchema.js
Normal file
51
src/GraphQL/loaders/defaultRelaySchema.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { nodeDefinitions, fromGlobalId } from 'graphql-relay';
|
||||||
|
import getFieldNames from 'graphql-list-fields';
|
||||||
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
|
import * as objectsQueries from '../helpers/objectsQueries';
|
||||||
|
import { extractKeysAndInclude } from './parseClassTypes';
|
||||||
|
|
||||||
|
const GLOBAL_ID_ATT = {
|
||||||
|
description: 'This is the global id.',
|
||||||
|
type: defaultGraphQLTypes.OBJECT_ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = parseGraphQLSchema => {
|
||||||
|
const { nodeInterface, nodeField } = nodeDefinitions(
|
||||||
|
async (globalId, context, queryInfo) => {
|
||||||
|
try {
|
||||||
|
const { type, id } = fromGlobalId(globalId);
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
const selectedFields = getFieldNames(queryInfo);
|
||||||
|
|
||||||
|
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||||
|
|
||||||
|
return {
|
||||||
|
className: type,
|
||||||
|
...(await objectsQueries.getObject(
|
||||||
|
type,
|
||||||
|
id,
|
||||||
|
keys,
|
||||||
|
include,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
info
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
parseGraphQLSchema.handleError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
obj => {
|
||||||
|
return parseGraphQLSchema.parseClassTypes[obj.className]
|
||||||
|
.classGraphQLOutputType;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(nodeInterface, true);
|
||||||
|
parseGraphQLSchema.relayNodeInterface = nodeInterface;
|
||||||
|
parseGraphQLSchema.addGraphQLQuery('node', nodeField, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { GLOBAL_ID_ATT, load };
|
||||||
@@ -1,80 +1,97 @@
|
|||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull } from 'graphql';
|
||||||
|
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||||
import { GraphQLUpload } from 'graphql-upload';
|
import { GraphQLUpload } from 'graphql-upload';
|
||||||
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 load = parseGraphQLSchema => {
|
const load = parseGraphQLSchema => {
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
const createMutation = mutationWithClientMutationId({
|
||||||
'createFile',
|
name: 'CreateFile',
|
||||||
{
|
description:
|
||||||
description:
|
'The createFile mutation can be used to create and upload a new file.',
|
||||||
'The create mutation can be used to create and upload a new file.',
|
inputFields: {
|
||||||
args: {
|
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(GraphQLUpload),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
|
},
|
||||||
async resolve(_source, args, context) {
|
outputFields: {
|
||||||
|
fileInfo: {
|
||||||
|
description: 'This is the created file info.',
|
||||||
|
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context) => {
|
||||||
|
try {
|
||||||
|
const { upload } = args;
|
||||||
|
const { config } = context;
|
||||||
|
|
||||||
|
const { createReadStream, filename, mimetype } = await upload;
|
||||||
|
let data = null;
|
||||||
|
if (createReadStream) {
|
||||||
|
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) {
|
||||||
|
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 {
|
try {
|
||||||
const { upload } = args;
|
return {
|
||||||
const { config } = context;
|
fileInfo: await config.filesController.createFile(
|
||||||
|
|
||||||
const { createReadStream, filename, mimetype } = await upload;
|
|
||||||
let data = null;
|
|
||||||
if (createReadStream) {
|
|
||||||
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) {
|
|
||||||
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,
|
config,
|
||||||
filename,
|
filename,
|
||||||
data,
|
data,
|
||||||
mimetype
|
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) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(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);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
createMutation.args.input.type.ofType,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(createMutation.type, true, true);
|
||||||
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
|
'createFile',
|
||||||
|
createMutation,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { GraphQLNonNull, GraphQLEnumType } from 'graphql';
|
import { GraphQLNonNull, GraphQLEnumType } from 'graphql';
|
||||||
|
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||||
import { FunctionsRouter } from '../../Routers/FunctionsRouter';
|
import { FunctionsRouter } from '../../Routers/FunctionsRouter';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
|
|
||||||
@@ -21,28 +22,34 @@ const load = parseGraphQLSchema => {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
const callCloudCodeMutation = mutationWithClientMutationId({
|
||||||
'callCloudCode',
|
name: 'CallCloudCode',
|
||||||
{
|
description:
|
||||||
description:
|
'The callCloudCode mutation can be used to invoke a cloud code function.',
|
||||||
'The call mutation can be used to invoke a cloud code function.',
|
inputFields: {
|
||||||
args: {
|
functionName: {
|
||||||
functionName: {
|
description: 'This is the function to be called.',
|
||||||
description: 'This is the function to be called.',
|
type: new GraphQLNonNull(cloudCodeFunctionEnum),
|
||||||
type: new GraphQLNonNull(cloudCodeFunctionEnum),
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
description: 'These are the params to be passed to the function.',
|
|
||||||
type: defaultGraphQLTypes.OBJECT,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
type: defaultGraphQLTypes.ANY,
|
params: {
|
||||||
async resolve(_source, args, context) {
|
description: 'These are the params to be passed to the function.',
|
||||||
try {
|
type: defaultGraphQLTypes.OBJECT,
|
||||||
const { functionName, params } = args;
|
},
|
||||||
const { config, auth, info } = context;
|
},
|
||||||
|
outputFields: {
|
||||||
|
result: {
|
||||||
|
description:
|
||||||
|
'This is the result value of the cloud code function execution.',
|
||||||
|
type: defaultGraphQLTypes.ANY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context) => {
|
||||||
|
try {
|
||||||
|
const { functionName, params } = args;
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
return (await FunctionsRouter.handleCloudFunction({
|
return {
|
||||||
|
result: (await FunctionsRouter.handleCloudFunction({
|
||||||
params: {
|
params: {
|
||||||
functionName,
|
functionName,
|
||||||
},
|
},
|
||||||
@@ -50,12 +57,23 @@ const load = parseGraphQLSchema => {
|
|||||||
auth,
|
auth,
|
||||||
info,
|
info,
|
||||||
body: params,
|
body: params,
|
||||||
})).response.result;
|
})).response.result,
|
||||||
} catch (e) {
|
};
|
||||||
parseGraphQLSchema.handleError(e);
|
} catch (e) {
|
||||||
}
|
parseGraphQLSchema.handleError(e);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
callCloudCodeMutation.args.input.type.ofType,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(callCloudCodeMutation.type, true, true);
|
||||||
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
|
'callCloudCode',
|
||||||
|
callCloudCodeMutation,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull } from 'graphql';
|
||||||
|
import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay';
|
||||||
import getFieldNames from 'graphql-list-fields';
|
import getFieldNames from 'graphql-list-fields';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import {
|
import {
|
||||||
@@ -17,13 +18,16 @@ const getOnlyRequiredFields = (
|
|||||||
includedFieldsString,
|
includedFieldsString,
|
||||||
nativeObjectFields
|
nativeObjectFields
|
||||||
) => {
|
) => {
|
||||||
const includedFields = includedFieldsString.split(',');
|
const includedFields = includedFieldsString
|
||||||
const selectedFields = selectedFieldsString.split(',');
|
? includedFieldsString.split(',')
|
||||||
|
: [];
|
||||||
|
const selectedFields = selectedFieldsString
|
||||||
|
? selectedFieldsString.split(',')
|
||||||
|
: [];
|
||||||
const missingFields = selectedFields
|
const missingFields = selectedFields
|
||||||
.filter(
|
.filter(
|
||||||
field =>
|
field =>
|
||||||
!nativeObjectFields.includes(field) ||
|
!nativeObjectFields.includes(field) || includedFields.includes(field)
|
||||||
includedFields.includes(field)
|
|
||||||
)
|
)
|
||||||
.join(',');
|
.join(',');
|
||||||
if (!missingFields.length) {
|
if (!missingFields.length) {
|
||||||
@@ -40,6 +44,8 @@ const load = function(
|
|||||||
) {
|
) {
|
||||||
const className = parseClass.className;
|
const className = parseClass.className;
|
||||||
const graphQLClassName = transformClassNameToGraphQL(className);
|
const graphQLClassName = transformClassNameToGraphQL(className);
|
||||||
|
const getGraphQLQueryName =
|
||||||
|
graphQLClassName.charAt(0).toLowerCase() + graphQLClassName.slice(1);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
create: isCreateEnabled = true,
|
create: isCreateEnabled = true,
|
||||||
@@ -55,18 +61,25 @@ const load = function(
|
|||||||
|
|
||||||
if (isCreateEnabled) {
|
if (isCreateEnabled) {
|
||||||
const createGraphQLMutationName = `create${graphQLClassName}`;
|
const createGraphQLMutationName = `create${graphQLClassName}`;
|
||||||
parseGraphQLSchema.addGraphQLMutation(createGraphQLMutationName, {
|
const createGraphQLMutation = mutationWithClientMutationId({
|
||||||
|
name: `Create${graphQLClassName}`,
|
||||||
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${graphQLClassName} class.`,
|
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${graphQLClassName} class.`,
|
||||||
args: {
|
inputFields: {
|
||||||
fields: {
|
fields: {
|
||||||
description: 'These are the fields used to create the object.',
|
description:
|
||||||
|
'These are the fields that will be used to create the new object.',
|
||||||
type: classGraphQLCreateType || defaultGraphQLTypes.OBJECT,
|
type: classGraphQLCreateType || defaultGraphQLTypes.OBJECT,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type: new GraphQLNonNull(
|
outputFields: {
|
||||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
[getGraphQLQueryName]: {
|
||||||
),
|
description: 'This is the created object.',
|
||||||
async resolve(_source, args, context, mutationInfo) {
|
type: new GraphQLNonNull(
|
||||||
|
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||||
try {
|
try {
|
||||||
let { fields } = args;
|
let { fields } = args;
|
||||||
if (!fields) fields = {};
|
if (!fields) fields = {};
|
||||||
@@ -85,13 +98,15 @@ const load = function(
|
|||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
);
|
);
|
||||||
const selectedFields = getFieldNames(mutationInfo);
|
const selectedFields = getFieldNames(mutationInfo)
|
||||||
|
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
|
||||||
|
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
|
||||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||||
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||||
fields,
|
fields,
|
||||||
keys,
|
keys,
|
||||||
include,
|
include,
|
||||||
['id', 'createdAt', 'updatedAt']
|
['id', 'objectId', 'createdAt', 'updatedAt']
|
||||||
);
|
);
|
||||||
let optimizedObject = {};
|
let optimizedObject = {};
|
||||||
if (needGet) {
|
if (needGet) {
|
||||||
@@ -108,37 +123,65 @@ const load = function(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...createdObject,
|
[getGraphQLQueryName]: {
|
||||||
updatedAt: createdObject.createdAt,
|
...createdObject,
|
||||||
...parseFields,
|
updatedAt: createdObject.createdAt,
|
||||||
...optimizedObject,
|
...parseFields,
|
||||||
|
...optimizedObject,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
createGraphQLMutation.args.input.type.ofType
|
||||||
|
) &&
|
||||||
|
parseGraphQLSchema.addGraphQLType(createGraphQLMutation.type)
|
||||||
|
) {
|
||||||
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
|
createGraphQLMutationName,
|
||||||
|
createGraphQLMutation
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUpdateEnabled) {
|
if (isUpdateEnabled) {
|
||||||
const updateGraphQLMutationName = `update${graphQLClassName}`;
|
const updateGraphQLMutationName = `update${graphQLClassName}`;
|
||||||
parseGraphQLSchema.addGraphQLMutation(updateGraphQLMutationName, {
|
const updateGraphQLMutation = mutationWithClientMutationId({
|
||||||
|
name: `Update${graphQLClassName}`,
|
||||||
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${graphQLClassName} class.`,
|
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${graphQLClassName} class.`,
|
||||||
args: {
|
inputFields: {
|
||||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||||
fields: {
|
fields: {
|
||||||
description: 'These are the fields used to update the object.',
|
description:
|
||||||
|
'These are the fields that will be used to update the object.',
|
||||||
type: classGraphQLUpdateType || defaultGraphQLTypes.OBJECT,
|
type: classGraphQLUpdateType || defaultGraphQLTypes.OBJECT,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type: new GraphQLNonNull(
|
outputFields: {
|
||||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
[getGraphQLQueryName]: {
|
||||||
),
|
description: 'This is the updated object.',
|
||||||
async resolve(_source, args, context, mutationInfo) {
|
type: new GraphQLNonNull(
|
||||||
|
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||||
try {
|
try {
|
||||||
const { id, fields } = args;
|
let { id, fields } = args;
|
||||||
|
if (!fields) fields = {};
|
||||||
const { config, auth, info } = context;
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
|
const globalIdObject = fromGlobalId(id);
|
||||||
|
|
||||||
|
if (globalIdObject.type === className) {
|
||||||
|
id = globalIdObject.id;
|
||||||
|
}
|
||||||
|
|
||||||
const parseFields = await transformTypes('update', fields, {
|
const parseFields = await transformTypes('update', fields, {
|
||||||
className,
|
className,
|
||||||
parseGraphQLSchema,
|
parseGraphQLSchema,
|
||||||
@@ -154,15 +197,17 @@ const load = function(
|
|||||||
info
|
info
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedFields = getFieldNames(mutationInfo);
|
const selectedFields = getFieldNames(mutationInfo)
|
||||||
|
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
|
||||||
|
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
|
||||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||||
|
|
||||||
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||||
fields,
|
fields,
|
||||||
keys,
|
keys,
|
||||||
include,
|
include,
|
||||||
['id', 'updatedAt']
|
['id', 'objectId', 'updatedAt']
|
||||||
);
|
);
|
||||||
|
|
||||||
let optimizedObject = {};
|
let optimizedObject = {};
|
||||||
if (needGet) {
|
if (needGet) {
|
||||||
optimizedObject = await objectsQueries.getObject(
|
optimizedObject = await objectsQueries.getObject(
|
||||||
@@ -178,38 +223,69 @@ const load = function(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id,
|
[getGraphQLQueryName]: {
|
||||||
...updatedObject,
|
objectId: id,
|
||||||
...parseFields,
|
...updatedObject,
|
||||||
...optimizedObject,
|
...parseFields,
|
||||||
|
...optimizedObject,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
updateGraphQLMutation.args.input.type.ofType
|
||||||
|
) &&
|
||||||
|
parseGraphQLSchema.addGraphQLType(updateGraphQLMutation.type)
|
||||||
|
) {
|
||||||
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
|
updateGraphQLMutationName,
|
||||||
|
updateGraphQLMutation
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDestroyEnabled) {
|
if (isDestroyEnabled) {
|
||||||
const deleteGraphQLMutationName = `delete${graphQLClassName}`;
|
const deleteGraphQLMutationName = `delete${graphQLClassName}`;
|
||||||
parseGraphQLSchema.addGraphQLMutation(deleteGraphQLMutationName, {
|
const deleteGraphQLMutation = mutationWithClientMutationId({
|
||||||
|
name: `Delete${graphQLClassName}`,
|
||||||
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${graphQLClassName} class.`,
|
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${graphQLClassName} class.`,
|
||||||
args: {
|
inputFields: {
|
||||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||||
},
|
},
|
||||||
type: new GraphQLNonNull(
|
outputFields: {
|
||||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
[getGraphQLQueryName]: {
|
||||||
),
|
description: 'This is the deleted object.',
|
||||||
async resolve(_source, args, context, mutationInfo) {
|
type: new GraphQLNonNull(
|
||||||
|
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||||
try {
|
try {
|
||||||
const { id } = args;
|
let { id } = args;
|
||||||
const { config, auth, info } = context;
|
const { config, auth, info } = context;
|
||||||
const selectedFields = getFieldNames(mutationInfo);
|
|
||||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
|
||||||
|
|
||||||
|
const globalIdObject = fromGlobalId(id);
|
||||||
|
|
||||||
|
if (globalIdObject.type === className) {
|
||||||
|
id = globalIdObject.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedFields = getFieldNames(mutationInfo)
|
||||||
|
.filter(field => field.startsWith(`${getGraphQLQueryName}.`))
|
||||||
|
.map(field => field.replace(`${getGraphQLQueryName}.`, ''));
|
||||||
|
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||||
let optimizedObject = {};
|
let optimizedObject = {};
|
||||||
const splitedKeys = keys.split(',');
|
if (
|
||||||
if (splitedKeys.length > 1 || splitedKeys[0] !== 'id') {
|
keys &&
|
||||||
|
keys.split(',').filter(key => !['id', 'objectId'].includes(key))
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
optimizedObject = await objectsQueries.getObject(
|
optimizedObject = await objectsQueries.getObject(
|
||||||
className,
|
className,
|
||||||
id,
|
id,
|
||||||
@@ -229,12 +305,29 @@ const load = function(
|
|||||||
auth,
|
auth,
|
||||||
info
|
info
|
||||||
);
|
);
|
||||||
return { id, ...optimizedObject };
|
return {
|
||||||
|
[getGraphQLQueryName]: {
|
||||||
|
objectId: id,
|
||||||
|
...optimizedObject,
|
||||||
|
},
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
deleteGraphQLMutation.args.input.type.ofType
|
||||||
|
) &&
|
||||||
|
parseGraphQLSchema.addGraphQLType(deleteGraphQLMutation.type)
|
||||||
|
) {
|
||||||
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
|
deleteGraphQLMutationName,
|
||||||
|
deleteGraphQLMutation
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull } from 'graphql';
|
||||||
|
import { fromGlobalId } from 'graphql-relay';
|
||||||
import getFieldNames from 'graphql-list-fields';
|
import getFieldNames from 'graphql-list-fields';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
@@ -14,11 +15,18 @@ const getParseClassQueryConfig = function(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getQuery = async (className, _source, args, context, queryInfo) => {
|
const getQuery = async (className, _source, args, context, queryInfo) => {
|
||||||
const { id, options } = args;
|
let { id } = args;
|
||||||
|
const { options } = args;
|
||||||
const { readPreference, includeReadPreference } = options || {};
|
const { readPreference, includeReadPreference } = options || {};
|
||||||
const { config, auth, info } = context;
|
const { config, auth, info } = context;
|
||||||
const selectedFields = getFieldNames(queryInfo);
|
const selectedFields = getFieldNames(queryInfo);
|
||||||
|
|
||||||
|
const globalIdObject = fromGlobalId(id);
|
||||||
|
|
||||||
|
if (globalIdObject.type === className) {
|
||||||
|
id = globalIdObject.id;
|
||||||
|
}
|
||||||
|
|
||||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||||
|
|
||||||
return await objectsQueries.getObject(
|
return await objectsQueries.getObject(
|
||||||
@@ -58,7 +66,7 @@ const load = function(
|
|||||||
parseGraphQLSchema.addGraphQLQuery(getGraphQLQueryName, {
|
parseGraphQLSchema.addGraphQLQuery(getGraphQLQueryName, {
|
||||||
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${graphQLClassName} class by its id.`,
|
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${graphQLClassName} class by its id.`,
|
||||||
args: {
|
args: {
|
||||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||||
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
||||||
},
|
},
|
||||||
type: new GraphQLNonNull(
|
type: new GraphQLNonNull(
|
||||||
@@ -82,11 +90,20 @@ const load = function(
|
|||||||
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${graphQLClassName} class.`,
|
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${graphQLClassName} class.`,
|
||||||
args: classGraphQLFindArgs,
|
args: classGraphQLFindArgs,
|
||||||
type: new GraphQLNonNull(
|
type: new GraphQLNonNull(
|
||||||
classGraphQLFindResultType || defaultGraphQLTypes.FIND_RESULT
|
classGraphQLFindResultType || defaultGraphQLTypes.OBJECT
|
||||||
),
|
),
|
||||||
async resolve(_source, args, context, queryInfo) {
|
async resolve(_source, args, context, queryInfo) {
|
||||||
try {
|
try {
|
||||||
const { where, order, skip, limit, options } = args;
|
const {
|
||||||
|
where,
|
||||||
|
order,
|
||||||
|
skip,
|
||||||
|
first,
|
||||||
|
after,
|
||||||
|
last,
|
||||||
|
before,
|
||||||
|
options,
|
||||||
|
} = args;
|
||||||
const {
|
const {
|
||||||
readPreference,
|
readPreference,
|
||||||
includeReadPreference,
|
includeReadPreference,
|
||||||
@@ -97,8 +114,8 @@ const load = function(
|
|||||||
|
|
||||||
const { keys, include } = extractKeysAndInclude(
|
const { keys, include } = extractKeysAndInclude(
|
||||||
selectedFields
|
selectedFields
|
||||||
.filter(field => field.includes('.'))
|
.filter(field => field.startsWith('edges.node.'))
|
||||||
.map(field => field.slice(field.indexOf('.') + 1))
|
.map(field => field.replace('edges.node.', ''))
|
||||||
);
|
);
|
||||||
const parseOrder = order && order.join(',');
|
const parseOrder = order && order.join(',');
|
||||||
|
|
||||||
@@ -107,7 +124,10 @@ const load = function(
|
|||||||
where,
|
where,
|
||||||
parseOrder,
|
parseOrder,
|
||||||
skip,
|
skip,
|
||||||
limit,
|
first,
|
||||||
|
after,
|
||||||
|
last,
|
||||||
|
before,
|
||||||
keys,
|
keys,
|
||||||
include,
|
include,
|
||||||
false,
|
false,
|
||||||
@@ -117,7 +137,7 @@ const load = function(
|
|||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info,
|
info,
|
||||||
selectedFields.map(field => field.split('.', 1)[0]),
|
selectedFields,
|
||||||
parseClass.fields
|
parseClass.fields
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import {
|
|||||||
GraphQLNonNull,
|
GraphQLNonNull,
|
||||||
GraphQLEnumType,
|
GraphQLEnumType,
|
||||||
} from 'graphql';
|
} from 'graphql';
|
||||||
|
import {
|
||||||
|
globalIdField,
|
||||||
|
connectionArgs,
|
||||||
|
connectionDefinitions,
|
||||||
|
} from 'graphql-relay';
|
||||||
import getFieldNames from 'graphql-list-fields';
|
import getFieldNames from 'graphql-list-fields';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import * as objectsQueries from '../helpers/objectsQueries';
|
import * as objectsQueries from '../helpers/objectsQueries';
|
||||||
@@ -30,9 +35,7 @@ const getInputFieldsAndConstraints = function(
|
|||||||
parseClass,
|
parseClass,
|
||||||
parseClassConfig: ?ParseGraphQLClassConfig
|
parseClassConfig: ?ParseGraphQLClassConfig
|
||||||
) {
|
) {
|
||||||
const classFields = Object.keys(parseClass.fields)
|
const classFields = Object.keys(parseClass.fields).concat('id');
|
||||||
.filter(field => field !== 'objectId')
|
|
||||||
.concat('id');
|
|
||||||
const {
|
const {
|
||||||
inputFields: allowedInputFields,
|
inputFields: allowedInputFields,
|
||||||
outputFields: allowedOutputFields,
|
outputFields: allowedOutputFields,
|
||||||
@@ -48,8 +51,9 @@ const getInputFieldsAndConstraints = function(
|
|||||||
|
|
||||||
// All allowed customs fields
|
// All allowed customs fields
|
||||||
const classCustomFields = classFields.filter(field => {
|
const classCustomFields = classFields.filter(field => {
|
||||||
return !Object.keys(defaultGraphQLTypes.PARSE_OBJECT_FIELDS).includes(
|
return (
|
||||||
field
|
!Object.keys(defaultGraphQLTypes.PARSE_OBJECT_FIELDS).includes(field) &&
|
||||||
|
field !== 'id'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,7 +157,11 @@ const load = (
|
|||||||
...fields,
|
...fields,
|
||||||
[field]: {
|
[field]: {
|
||||||
description: `This is the object ${field}.`,
|
description: `This is the object ${field}.`,
|
||||||
type,
|
type:
|
||||||
|
className === '_User' &&
|
||||||
|
(field === 'username' || field === 'password')
|
||||||
|
? new GraphQLNonNull(type)
|
||||||
|
: type,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -209,7 +217,7 @@ const load = (
|
|||||||
fields: () => {
|
fields: () => {
|
||||||
const fields = {
|
const fields = {
|
||||||
link: {
|
link: {
|
||||||
description: `Link an existing object from ${graphQLClassName} class.`,
|
description: `Link an existing object from ${graphQLClassName} class. You can use either the global or the object id.`,
|
||||||
type: GraphQLID,
|
type: GraphQLID,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -233,17 +241,17 @@ const load = (
|
|||||||
fields: () => {
|
fields: () => {
|
||||||
const fields = {
|
const fields = {
|
||||||
add: {
|
add: {
|
||||||
description: `Add an existing object from the ${graphQLClassName} class into the relation.`,
|
description: `Add existing objects from the ${graphQLClassName} class into the relation. You can use either the global or the object ids.`,
|
||||||
type: new GraphQLList(defaultGraphQLTypes.OBJECT_ID),
|
type: new GraphQLList(defaultGraphQLTypes.OBJECT_ID),
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
description: `Remove an existing object from the ${graphQLClassName} class out of the relation.`,
|
description: `Remove existing objects from the ${graphQLClassName} class out of the relation. You can use either the global or the object ids.`,
|
||||||
type: new GraphQLList(defaultGraphQLTypes.OBJECT_ID),
|
type: new GraphQLList(defaultGraphQLTypes.OBJECT_ID),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (isCreateEnabled) {
|
if (isCreateEnabled) {
|
||||||
fields['createAndAdd'] = {
|
fields['createAndAdd'] = {
|
||||||
description: `Create and add an object of the ${graphQLClassName} class into the relation.`,
|
description: `Create and add objects of the ${graphQLClassName} class into the relation.`,
|
||||||
type: new GraphQLList(new GraphQLNonNull(classGraphQLCreateType)),
|
type: new GraphQLList(new GraphQLNonNull(classGraphQLCreateType)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -268,12 +276,12 @@ const load = (
|
|||||||
notInQueryKey: defaultGraphQLTypes.notInQueryKey,
|
notInQueryKey: defaultGraphQLTypes.notInQueryKey,
|
||||||
inQuery: {
|
inQuery: {
|
||||||
description:
|
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.',
|
'This is the inQuery operator to specify a constraint to select the objects where a field equals to any of the object ids in the result of a different query.',
|
||||||
type: defaultGraphQLTypes.SUBQUERY_INPUT,
|
type: defaultGraphQLTypes.SUBQUERY_INPUT,
|
||||||
},
|
},
|
||||||
notInQuery: {
|
notInQuery: {
|
||||||
description:
|
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.',
|
'This is the notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the object ids in the result of a different query.',
|
||||||
type: defaultGraphQLTypes.SUBQUERY_INPUT,
|
type: defaultGraphQLTypes.SUBQUERY_INPUT,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -298,7 +306,8 @@ const load = (
|
|||||||
const type = transformConstraintTypeToGraphQL(
|
const type = transformConstraintTypeToGraphQL(
|
||||||
parseClass.fields[parseField].type,
|
parseClass.fields[parseField].type,
|
||||||
parseClass.fields[parseField].targetClass,
|
parseClass.fields[parseField].targetClass,
|
||||||
parseGraphQLSchema.parseClassTypes
|
parseGraphQLSchema.parseClassTypes,
|
||||||
|
field
|
||||||
);
|
);
|
||||||
if (type) {
|
if (type) {
|
||||||
return {
|
return {
|
||||||
@@ -339,11 +348,12 @@ const load = (
|
|||||||
const updatedSortFields = {
|
const updatedSortFields = {
|
||||||
...sortFields,
|
...sortFields,
|
||||||
};
|
};
|
||||||
|
const value = field === 'id' ? 'objectId' : field;
|
||||||
if (asc) {
|
if (asc) {
|
||||||
updatedSortFields[`${field}_ASC`] = { value: field };
|
updatedSortFields[`${field}_ASC`] = { value };
|
||||||
}
|
}
|
||||||
if (desc) {
|
if (desc) {
|
||||||
updatedSortFields[`${field}_DESC`] = { value: `-${field}` };
|
updatedSortFields[`${field}_DESC`] = { value: `-${value}` };
|
||||||
}
|
}
|
||||||
return updatedSortFields;
|
return updatedSortFields;
|
||||||
}, {}),
|
}, {}),
|
||||||
@@ -365,10 +375,18 @@ const load = (
|
|||||||
: GraphQLString,
|
: GraphQLString,
|
||||||
},
|
},
|
||||||
skip: defaultGraphQLTypes.SKIP_ATT,
|
skip: defaultGraphQLTypes.SKIP_ATT,
|
||||||
limit: defaultGraphQLTypes.LIMIT_ATT,
|
...connectionArgs,
|
||||||
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
||||||
};
|
};
|
||||||
const classGraphQLOutputTypeName = `${graphQLClassName}`;
|
const classGraphQLOutputTypeName = `${graphQLClassName}`;
|
||||||
|
const interfaces = [
|
||||||
|
defaultGraphQLTypes.PARSE_OBJECT,
|
||||||
|
parseGraphQLSchema.relayNodeInterface,
|
||||||
|
];
|
||||||
|
const parseObjectFields = {
|
||||||
|
id: globalIdField(className, obj => obj.objectId),
|
||||||
|
...defaultGraphQLTypes.PARSE_OBJECT_FIELDS,
|
||||||
|
};
|
||||||
const outputFields = () => {
|
const outputFields = () => {
|
||||||
return classOutputFields.reduce((fields, field) => {
|
return classOutputFields.reduce((fields, field) => {
|
||||||
const type = transformOutputTypeToGraphQL(
|
const type = transformOutputTypeToGraphQL(
|
||||||
@@ -392,7 +410,16 @@ const load = (
|
|||||||
type,
|
type,
|
||||||
async resolve(source, args, context, queryInfo) {
|
async resolve(source, args, context, queryInfo) {
|
||||||
try {
|
try {
|
||||||
const { where, order, skip, limit, options } = args;
|
const {
|
||||||
|
where,
|
||||||
|
order,
|
||||||
|
skip,
|
||||||
|
first,
|
||||||
|
after,
|
||||||
|
last,
|
||||||
|
before,
|
||||||
|
options,
|
||||||
|
} = args;
|
||||||
const {
|
const {
|
||||||
readPreference,
|
readPreference,
|
||||||
includeReadPreference,
|
includeReadPreference,
|
||||||
@@ -403,9 +430,11 @@ const load = (
|
|||||||
|
|
||||||
const { keys, include } = extractKeysAndInclude(
|
const { keys, include } = extractKeysAndInclude(
|
||||||
selectedFields
|
selectedFields
|
||||||
.filter(field => field.includes('.'))
|
.filter(field => field.startsWith('edges.node.'))
|
||||||
.map(field => field.slice(field.indexOf('.') + 1))
|
.map(field => field.replace('edges.node.', ''))
|
||||||
);
|
);
|
||||||
|
const parseOrder = order && order.join(',');
|
||||||
|
|
||||||
return await objectsQueries.findObjects(
|
return await objectsQueries.findObjects(
|
||||||
source[field].className,
|
source[field].className,
|
||||||
{
|
{
|
||||||
@@ -419,9 +448,12 @@ const load = (
|
|||||||
},
|
},
|
||||||
...(where || {}),
|
...(where || {}),
|
||||||
},
|
},
|
||||||
order,
|
parseOrder,
|
||||||
skip,
|
skip,
|
||||||
limit,
|
first,
|
||||||
|
after,
|
||||||
|
last,
|
||||||
|
before,
|
||||||
keys,
|
keys,
|
||||||
include,
|
include,
|
||||||
false,
|
false,
|
||||||
@@ -431,8 +463,11 @@ const load = (
|
|||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
info,
|
info,
|
||||||
selectedFields.map(field => field.split('.', 1)[0]),
|
selectedFields,
|
||||||
parseClass.fields
|
parseGraphQLSchema.parseClasses.find(
|
||||||
|
parseClass =>
|
||||||
|
parseClass.className === source[field].className
|
||||||
|
).fields
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
@@ -491,39 +526,32 @@ const load = (
|
|||||||
} else {
|
} else {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
}, defaultGraphQLTypes.PARSE_OBJECT_FIELDS);
|
}, parseObjectFields);
|
||||||
};
|
};
|
||||||
let classGraphQLOutputType = new GraphQLObjectType({
|
let classGraphQLOutputType = new GraphQLObjectType({
|
||||||
name: classGraphQLOutputTypeName,
|
name: classGraphQLOutputTypeName,
|
||||||
description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${graphQLClassName} class.`,
|
description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${graphQLClassName} class.`,
|
||||||
interfaces: [defaultGraphQLTypes.PARSE_OBJECT],
|
interfaces,
|
||||||
fields: outputFields,
|
fields: outputFields,
|
||||||
});
|
});
|
||||||
classGraphQLOutputType = parseGraphQLSchema.addGraphQLType(
|
classGraphQLOutputType = parseGraphQLSchema.addGraphQLType(
|
||||||
classGraphQLOutputType
|
classGraphQLOutputType
|
||||||
);
|
);
|
||||||
|
|
||||||
const classGraphQLFindResultTypeName = `${graphQLClassName}FindResult`;
|
const { connectionType, edgeType } = connectionDefinitions({
|
||||||
let classGraphQLFindResultType = new GraphQLObjectType({
|
name: graphQLClassName,
|
||||||
name: classGraphQLFindResultTypeName,
|
connectionFields: {
|
||||||
description: `The ${classGraphQLFindResultTypeName} object type is used in the ${graphQLClassName} 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 || defaultGraphQLTypes.OBJECT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
count: defaultGraphQLTypes.COUNT_ATT,
|
count: defaultGraphQLTypes.COUNT_ATT,
|
||||||
},
|
},
|
||||||
|
nodeType: classGraphQLOutputType || defaultGraphQLTypes.OBJECT,
|
||||||
});
|
});
|
||||||
classGraphQLFindResultType = parseGraphQLSchema.addGraphQLType(
|
let classGraphQLFindResultType = undefined;
|
||||||
classGraphQLFindResultType
|
if (
|
||||||
);
|
parseGraphQLSchema.addGraphQLType(edgeType) &&
|
||||||
|
parseGraphQLSchema.addGraphQLType(connectionType, false, false, true)
|
||||||
|
) {
|
||||||
|
classGraphQLFindResultType = connectionType;
|
||||||
|
}
|
||||||
|
|
||||||
parseGraphQLSchema.parseClassTypes[className] = {
|
parseGraphQLSchema.parseClassTypes[className] = {
|
||||||
classGraphQLPointerType,
|
classGraphQLPointerType,
|
||||||
@@ -546,67 +574,16 @@ const load = (
|
|||||||
const viewerType = new GraphQLObjectType({
|
const viewerType = new GraphQLObjectType({
|
||||||
name: 'Viewer',
|
name: 'Viewer',
|
||||||
description: `The Viewer object type is used in operations that involve outputting the current user data.`,
|
description: `The Viewer object type is used in operations that involve outputting the current user data.`,
|
||||||
interfaces: [defaultGraphQLTypes.PARSE_OBJECT],
|
|
||||||
fields: () => ({
|
fields: () => ({
|
||||||
...outputFields(),
|
|
||||||
sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT,
|
sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT,
|
||||||
|
user: {
|
||||||
|
description: 'This is the current user.',
|
||||||
|
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
parseGraphQLSchema.viewerType = viewerType;
|
|
||||||
parseGraphQLSchema.addGraphQLType(viewerType, true, true);
|
parseGraphQLSchema.addGraphQLType(viewerType, true, true);
|
||||||
|
parseGraphQLSchema.viewerType = viewerType;
|
||||||
const userSignUpInputTypeName = 'SignUpFieldsInput';
|
|
||||||
const userSignUpInputType = new GraphQLInputObjectType({
|
|
||||||
name: userSignUpInputTypeName,
|
|
||||||
description: `The ${userSignUpInputTypeName} input type is used in operations that involve inputting objects of ${graphQLClassName} class when signing up.`,
|
|
||||||
fields: () =>
|
|
||||||
classCreateFields.reduce((fields, field) => {
|
|
||||||
const type = transformInputTypeToGraphQL(
|
|
||||||
parseClass.fields[field].type,
|
|
||||||
parseClass.fields[field].targetClass,
|
|
||||||
parseGraphQLSchema.parseClassTypes
|
|
||||||
);
|
|
||||||
if (type) {
|
|
||||||
return {
|
|
||||||
...fields,
|
|
||||||
[field]: {
|
|
||||||
description: `This is the object ${field}.`,
|
|
||||||
type:
|
|
||||||
field === 'username' || field === 'password'
|
|
||||||
? new GraphQLNonNull(type)
|
|
||||||
: type,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
}, {}),
|
|
||||||
});
|
|
||||||
parseGraphQLSchema.addGraphQLType(userSignUpInputType, true, true);
|
|
||||||
|
|
||||||
const userLogInInputTypeName = 'LogInFieldsInput';
|
|
||||||
const userLogInInputType = new GraphQLInputObjectType({
|
|
||||||
name: userLogInInputTypeName,
|
|
||||||
description: `The ${userLogInInputTypeName} input type is used to login.`,
|
|
||||||
fields: {
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
parseGraphQLSchema.addGraphQLType(userLogInInputType, true, true);
|
|
||||||
|
|
||||||
parseGraphQLSchema.parseClassTypes[
|
|
||||||
className
|
|
||||||
].signUpInputType = userSignUpInputType;
|
|
||||||
parseGraphQLSchema.parseClassTypes[
|
|
||||||
className
|
|
||||||
].logInInputType = userLogInInputType;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull } from 'graphql';
|
||||||
|
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||||
import * as schemaTypes from './schemaTypes';
|
import * as schemaTypes from './schemaTypes';
|
||||||
import {
|
import {
|
||||||
transformToParse,
|
transformToParse,
|
||||||
@@ -9,135 +10,183 @@ import { enforceMasterKeyAccess } from '../parseGraphQLUtils';
|
|||||||
import { getClass } from './schemaQueries';
|
import { getClass } from './schemaQueries';
|
||||||
|
|
||||||
const load = parseGraphQLSchema => {
|
const load = parseGraphQLSchema => {
|
||||||
|
const createClassMutation = mutationWithClientMutationId({
|
||||||
|
name: 'CreateClass',
|
||||||
|
description:
|
||||||
|
'The createClass mutation can be used to create the schema for a new object class.',
|
||||||
|
inputFields: {
|
||||||
|
name: schemaTypes.CLASS_NAME_ATT,
|
||||||
|
schemaFields: {
|
||||||
|
description: "These are the schema's fields of the object class.",
|
||||||
|
type: schemaTypes.SCHEMA_FIELDS_INPUT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputFields: {
|
||||||
|
class: {
|
||||||
|
description: 'This is the created class.',
|
||||||
|
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context) => {
|
||||||
|
try {
|
||||||
|
const { name, schemaFields } = args;
|
||||||
|
const { config, auth } = context;
|
||||||
|
|
||||||
|
enforceMasterKeyAccess(auth);
|
||||||
|
|
||||||
|
if (auth.isReadOnly) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OPERATION_FORBIDDEN,
|
||||||
|
"read-only masterKey isn't allowed to create a schema."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema({ clearCache: true });
|
||||||
|
const parseClass = await schema.addClassIfNotExists(
|
||||||
|
name,
|
||||||
|
transformToParse(schemaFields)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
class: {
|
||||||
|
name: parseClass.className,
|
||||||
|
schemaFields: transformToGraphQL(parseClass.fields),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
parseGraphQLSchema.handleError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
createClassMutation.args.input.type.ofType,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(createClassMutation.type, true, true);
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
'createClass',
|
'createClass',
|
||||||
{
|
createClassMutation,
|
||||||
description:
|
|
||||||
'The createClass mutation can be used to create the schema for a new object class.',
|
|
||||||
args: {
|
|
||||||
name: schemaTypes.CLASS_NAME_ATT,
|
|
||||||
schemaFields: {
|
|
||||||
description: "These are the schema's fields of the object class.",
|
|
||||||
type: schemaTypes.SCHEMA_FIELDS_INPUT,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
|
||||||
resolve: async (_source, args, context) => {
|
|
||||||
try {
|
|
||||||
const { name, schemaFields } = args;
|
|
||||||
const { config, auth } = context;
|
|
||||||
|
|
||||||
enforceMasterKeyAccess(auth);
|
|
||||||
|
|
||||||
if (auth.isReadOnly) {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.OPERATION_FORBIDDEN,
|
|
||||||
"read-only masterKey isn't allowed to create a schema."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = await config.database.loadSchema({ clearCache: true });
|
|
||||||
const parseClass = await schema.addClassIfNotExists(
|
|
||||||
name,
|
|
||||||
transformToParse(schemaFields)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
name: parseClass.className,
|
|
||||||
schemaFields: transformToGraphQL(parseClass.fields),
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
parseGraphQLSchema.handleError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updateClassMutation = mutationWithClientMutationId({
|
||||||
|
name: 'UpdateClass',
|
||||||
|
description:
|
||||||
|
'The updateClass mutation can be used to update the schema for an existing object class.',
|
||||||
|
inputFields: {
|
||||||
|
name: schemaTypes.CLASS_NAME_ATT,
|
||||||
|
schemaFields: {
|
||||||
|
description: "These are the schema's fields of the object class.",
|
||||||
|
type: schemaTypes.SCHEMA_FIELDS_INPUT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputFields: {
|
||||||
|
class: {
|
||||||
|
description: 'This is the updated class.',
|
||||||
|
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context) => {
|
||||||
|
try {
|
||||||
|
const { name, schemaFields } = args;
|
||||||
|
const { config, auth } = context;
|
||||||
|
|
||||||
|
enforceMasterKeyAccess(auth);
|
||||||
|
|
||||||
|
if (auth.isReadOnly) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OPERATION_FORBIDDEN,
|
||||||
|
"read-only masterKey isn't allowed to update a schema."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema({ clearCache: true });
|
||||||
|
const existingParseClass = await getClass(name, schema);
|
||||||
|
const parseClass = await schema.updateClass(
|
||||||
|
name,
|
||||||
|
transformToParse(schemaFields, existingParseClass.fields),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
config.database
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
class: {
|
||||||
|
name: parseClass.className,
|
||||||
|
schemaFields: transformToGraphQL(parseClass.fields),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
parseGraphQLSchema.handleError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
updateClassMutation.args.input.type.ofType,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(updateClassMutation.type, true, true);
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
'updateClass',
|
'updateClass',
|
||||||
{
|
updateClassMutation,
|
||||||
description:
|
|
||||||
'The updateClass mutation can be used to update the schema for an existing object class.',
|
|
||||||
args: {
|
|
||||||
name: schemaTypes.CLASS_NAME_ATT,
|
|
||||||
schemaFields: {
|
|
||||||
description: "These are the schema's fields of the object class.",
|
|
||||||
type: schemaTypes.SCHEMA_FIELDS_INPUT,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
|
||||||
resolve: async (_source, args, context) => {
|
|
||||||
try {
|
|
||||||
const { name, schemaFields } = args;
|
|
||||||
const { config, auth } = context;
|
|
||||||
|
|
||||||
enforceMasterKeyAccess(auth);
|
|
||||||
|
|
||||||
if (auth.isReadOnly) {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.OPERATION_FORBIDDEN,
|
|
||||||
"read-only masterKey isn't allowed to update a schema."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = await config.database.loadSchema({ clearCache: true });
|
|
||||||
const existingParseClass = await getClass(name, schema);
|
|
||||||
const parseClass = await schema.updateClass(
|
|
||||||
name,
|
|
||||||
transformToParse(schemaFields, existingParseClass.fields),
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
config.database
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
name: parseClass.className,
|
|
||||||
schemaFields: transformToGraphQL(parseClass.fields),
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
parseGraphQLSchema.handleError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
const deleteClassMutation = mutationWithClientMutationId({
|
||||||
'deleteClass',
|
name: 'DeleteClass',
|
||||||
{
|
description:
|
||||||
description:
|
'The deleteClass mutation can be used to delete an existing object class.',
|
||||||
'The deleteClass mutation can be used to delete an existing object class.',
|
inputFields: {
|
||||||
args: {
|
name: schemaTypes.CLASS_NAME_ATT,
|
||||||
name: schemaTypes.CLASS_NAME_ATT,
|
},
|
||||||
},
|
outputFields: {
|
||||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
class: {
|
||||||
resolve: async (_source, args, context) => {
|
description: 'This is the deleted class.',
|
||||||
try {
|
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||||
const { name } = args;
|
|
||||||
const { config, auth } = context;
|
|
||||||
|
|
||||||
enforceMasterKeyAccess(auth);
|
|
||||||
|
|
||||||
if (auth.isReadOnly) {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.OPERATION_FORBIDDEN,
|
|
||||||
"read-only masterKey isn't allowed to delete a schema."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = await config.database.loadSchema({ clearCache: true });
|
|
||||||
const existingParseClass = await getClass(name, schema);
|
|
||||||
await config.database.deleteSchema(name);
|
|
||||||
return {
|
|
||||||
name: existingParseClass.className,
|
|
||||||
schemaFields: transformToGraphQL(existingParseClass.fields),
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
parseGraphQLSchema.handleError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mutateAndGetPayload: async (args, context) => {
|
||||||
|
try {
|
||||||
|
const { name } = args;
|
||||||
|
const { config, auth } = context;
|
||||||
|
|
||||||
|
enforceMasterKeyAccess(auth);
|
||||||
|
|
||||||
|
if (auth.isReadOnly) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OPERATION_FORBIDDEN,
|
||||||
|
"read-only masterKey isn't allowed to delete a schema."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema({ clearCache: true });
|
||||||
|
const existingParseClass = await getClass(name, schema);
|
||||||
|
await config.database.deleteSchema(name);
|
||||||
|
return {
|
||||||
|
class: {
|
||||||
|
name: existingParseClass.className,
|
||||||
|
schemaFields: transformToGraphQL(existingParseClass.fields),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
parseGraphQLSchema.handleError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
deleteClassMutation.args.input.type.ofType,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(deleteClassMutation.type, true, true);
|
||||||
|
parseGraphQLSchema.addGraphQLMutation(
|
||||||
|
'deleteClass',
|
||||||
|
deleteClassMutation,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull, GraphQLString } from 'graphql';
|
||||||
|
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||||
import UsersRouter from '../../Routers/UsersRouter';
|
import UsersRouter from '../../Routers/UsersRouter';
|
||||||
import * as objectsMutations from '../helpers/objectsMutations';
|
import * as objectsMutations from '../helpers/objectsMutations';
|
||||||
import { getUserFromSessionToken } from './usersQueries';
|
import { getUserFromSessionToken } from './usersQueries';
|
||||||
@@ -10,110 +11,166 @@ const load = parseGraphQLSchema => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
const signUpMutation = mutationWithClientMutationId({
|
||||||
'signUp',
|
name: 'SignUp',
|
||||||
{
|
description:
|
||||||
description: 'The signUp mutation can be used to sign the user up.',
|
'The signUp mutation can be used to create and sign up a new user.',
|
||||||
args: {
|
inputFields: {
|
||||||
fields: {
|
userFields: {
|
||||||
descriptions: 'These are the fields of the user.',
|
descriptions:
|
||||||
type: parseGraphQLSchema.parseClassTypes['_User'].signUpInputType,
|
'These are the fields of the new user to be created and signed up.',
|
||||||
},
|
type:
|
||||||
},
|
parseGraphQLSchema.parseClassTypes['_User'].classGraphQLCreateType,
|
||||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
|
||||||
async resolve(_source, args, context, mutationInfo) {
|
|
||||||
try {
|
|
||||||
const { fields } = args;
|
|
||||||
|
|
||||||
const { config, auth, info } = context;
|
|
||||||
|
|
||||||
const { sessionToken } = await objectsMutations.createObject(
|
|
||||||
'_User',
|
|
||||||
fields,
|
|
||||||
config,
|
|
||||||
auth,
|
|
||||||
info
|
|
||||||
);
|
|
||||||
|
|
||||||
info.sessionToken = sessionToken;
|
|
||||||
|
|
||||||
return await getUserFromSessionToken(config, info, mutationInfo);
|
|
||||||
} catch (e) {
|
|
||||||
parseGraphQLSchema.handleError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
outputFields: {
|
||||||
|
viewer: {
|
||||||
|
description:
|
||||||
|
'This is the new user that was created, signed up and returned as a viewer.',
|
||||||
|
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||||
|
try {
|
||||||
|
const { userFields } = args;
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
|
const { sessionToken } = await objectsMutations.createObject(
|
||||||
|
'_User',
|
||||||
|
userFields,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
info
|
||||||
|
);
|
||||||
|
|
||||||
|
info.sessionToken = sessionToken;
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewer: await getUserFromSessionToken(
|
||||||
|
config,
|
||||||
|
info,
|
||||||
|
mutationInfo,
|
||||||
|
'viewer.user.',
|
||||||
|
true
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
parseGraphQLSchema.handleError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
signUpMutation.args.input.type.ofType,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(signUpMutation.type, true, true);
|
||||||
|
parseGraphQLSchema.addGraphQLMutation('signUp', signUpMutation, true, true);
|
||||||
|
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
const logInMutation = mutationWithClientMutationId({
|
||||||
'logIn',
|
name: 'LogIn',
|
||||||
{
|
description: 'The logIn mutation can be used to log in an existing user.',
|
||||||
description: 'The logIn mutation can be used to log the user in.',
|
inputFields: {
|
||||||
args: {
|
username: {
|
||||||
fields: {
|
description: 'This is the username used to log in the user.',
|
||||||
description: 'This is data needed to login',
|
type: new GraphQLNonNull(GraphQLString),
|
||||||
type: parseGraphQLSchema.parseClassTypes['_User'].logInInputType,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
password: {
|
||||||
async resolve(_source, args, context) {
|
description: 'This is the password used to log in the user.',
|
||||||
try {
|
type: new GraphQLNonNull(GraphQLString),
|
||||||
const {
|
|
||||||
fields: { 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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
outputFields: {
|
||||||
|
viewer: {
|
||||||
|
description:
|
||||||
|
'This is the existing user that was logged in and returned as a viewer.',
|
||||||
|
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||||
|
try {
|
||||||
|
const { username, password } = args;
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
|
const { sessionToken } = (await usersRouter.handleLogIn({
|
||||||
|
body: {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
query: {},
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
info,
|
||||||
|
})).response;
|
||||||
|
|
||||||
|
info.sessionToken = sessionToken;
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewer: await getUserFromSessionToken(
|
||||||
|
config,
|
||||||
|
info,
|
||||||
|
mutationInfo,
|
||||||
|
'viewer.user.',
|
||||||
|
true
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
parseGraphQLSchema.handleError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
logInMutation.args.input.type.ofType,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(logInMutation.type, true, true);
|
||||||
|
parseGraphQLSchema.addGraphQLMutation('logIn', logInMutation, true, true);
|
||||||
|
|
||||||
parseGraphQLSchema.addGraphQLMutation(
|
const logOutMutation = mutationWithClientMutationId({
|
||||||
'logOut',
|
name: 'LogOut',
|
||||||
{
|
description: 'The logOut mutation can be used to log out an existing user.',
|
||||||
description: 'The logOut mutation can be used to log the user out.',
|
outputFields: {
|
||||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
viewer: {
|
||||||
async resolve(_source, _args, context, mutationInfo) {
|
description:
|
||||||
try {
|
'This is the existing user that was logged out and returned as a viewer.',
|
||||||
const { config, auth, info } = context;
|
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||||
|
|
||||||
const viewer = await getUserFromSessionToken(
|
|
||||||
config,
|
|
||||||
info,
|
|
||||||
mutationInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
await usersRouter.handleLogOut({
|
|
||||||
config,
|
|
||||||
auth,
|
|
||||||
info,
|
|
||||||
});
|
|
||||||
|
|
||||||
return viewer;
|
|
||||||
} catch (e) {
|
|
||||||
parseGraphQLSchema.handleError(e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mutateAndGetPayload: async (_args, context, mutationInfo) => {
|
||||||
|
try {
|
||||||
|
const { config, auth, info } = context;
|
||||||
|
|
||||||
|
const viewer = await getUserFromSessionToken(
|
||||||
|
config,
|
||||||
|
info,
|
||||||
|
mutationInfo,
|
||||||
|
'viewer.user.',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
await usersRouter.handleLogOut({
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
info,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { viewer };
|
||||||
|
} catch (e) {
|
||||||
|
parseGraphQLSchema.handleError(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
parseGraphQLSchema.addGraphQLType(
|
||||||
|
logOutMutation.args.input.type.ofType,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
parseGraphQLSchema.addGraphQLType(logOutMutation.type, true, true);
|
||||||
|
parseGraphQLSchema.addGraphQLMutation('logOut', logOutMutation, true, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { load };
|
export { load };
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import rest from '../../rest';
|
|||||||
import Auth from '../../Auth';
|
import Auth from '../../Auth';
|
||||||
import { extractKeysAndInclude } from './parseClassTypes';
|
import { extractKeysAndInclude } from './parseClassTypes';
|
||||||
|
|
||||||
const getUserFromSessionToken = async (config, info, queryInfo) => {
|
const getUserFromSessionToken = async (
|
||||||
|
config,
|
||||||
|
info,
|
||||||
|
queryInfo,
|
||||||
|
keysPrefix,
|
||||||
|
validatedToken
|
||||||
|
) => {
|
||||||
if (!info || !info.sessionToken) {
|
if (!info || !info.sessionToken) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.INVALID_SESSION_TOKEN,
|
Parse.Error.INVALID_SESSION_TOKEN,
|
||||||
@@ -13,20 +19,42 @@ const getUserFromSessionToken = async (config, info, queryInfo) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const sessionToken = info.sessionToken;
|
const sessionToken = info.sessionToken;
|
||||||
const selectedFields = getFieldNames(queryInfo);
|
const selectedFields = getFieldNames(queryInfo)
|
||||||
|
.filter(field => field.startsWith(keysPrefix))
|
||||||
|
.map(field => field.replace(keysPrefix, ''));
|
||||||
|
|
||||||
|
const keysAndInclude = extractKeysAndInclude(selectedFields);
|
||||||
|
const { keys } = keysAndInclude;
|
||||||
|
let { include } = keysAndInclude;
|
||||||
|
|
||||||
|
if (validatedToken && !keys && !include) {
|
||||||
|
return {
|
||||||
|
sessionToken,
|
||||||
|
};
|
||||||
|
} else if (keys && !include) {
|
||||||
|
include = 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {};
|
||||||
|
if (keys) {
|
||||||
|
options.keys = keys
|
||||||
|
.split(',')
|
||||||
|
.map(key => `user.${key}`)
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
if (include) {
|
||||||
|
options.include = include
|
||||||
|
.split(',')
|
||||||
|
.map(included => `user.${included}`)
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
const { include } = extractKeysAndInclude(selectedFields);
|
|
||||||
const response = await rest.find(
|
const response = await rest.find(
|
||||||
config,
|
config,
|
||||||
Auth.master(config),
|
Auth.master(config),
|
||||||
'_Session',
|
'_Session',
|
||||||
{ sessionToken },
|
{ sessionToken },
|
||||||
{
|
options,
|
||||||
include: include
|
|
||||||
.split(',')
|
|
||||||
.map(included => `user.${included}`)
|
|
||||||
.join(','),
|
|
||||||
},
|
|
||||||
info.clientVersion
|
info.clientVersion
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
@@ -40,8 +68,10 @@ const getUserFromSessionToken = async (config, info, queryInfo) => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const user = response.results[0].user;
|
const user = response.results[0].user;
|
||||||
user.sessionToken = sessionToken;
|
return {
|
||||||
return user;
|
sessionToken,
|
||||||
|
user,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +89,13 @@ const load = parseGraphQLSchema => {
|
|||||||
async resolve(_source, _args, context, queryInfo) {
|
async resolve(_source, _args, context, queryInfo) {
|
||||||
try {
|
try {
|
||||||
const { config, info } = context;
|
const { config, info } = context;
|
||||||
return await getUserFromSessionToken(config, info, queryInfo);
|
return await getUserFromSessionToken(
|
||||||
|
config,
|
||||||
|
info,
|
||||||
|
queryInfo,
|
||||||
|
'user.',
|
||||||
|
false
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parseGraphQLSchema.handleError(e);
|
parseGraphQLSchema.handleError(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,13 @@ import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes';
|
|||||||
const transformConstraintTypeToGraphQL = (
|
const transformConstraintTypeToGraphQL = (
|
||||||
parseType,
|
parseType,
|
||||||
targetClass,
|
targetClass,
|
||||||
parseClassTypes
|
parseClassTypes,
|
||||||
|
fieldName
|
||||||
) => {
|
) => {
|
||||||
|
if (fieldName === 'id' || fieldName === 'objectId') {
|
||||||
|
return defaultGraphQLTypes.ID_WHERE_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
switch (parseType) {
|
switch (parseType) {
|
||||||
case 'String':
|
case 'String':
|
||||||
return defaultGraphQLTypes.STRING_WHERE_INPUT;
|
return defaultGraphQLTypes.STRING_WHERE_INPUT;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import Parse from 'parse/node';
|
||||||
|
import { fromGlobalId } from 'graphql-relay';
|
||||||
import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes';
|
||||||
import * as objectsMutations from '../helpers/objectsMutations';
|
import * as objectsMutations from '../helpers/objectsMutations';
|
||||||
|
|
||||||
@@ -108,8 +110,9 @@ const transformers = {
|
|||||||
{ config, auth, info }
|
{ config, auth, info }
|
||||||
) => {
|
) => {
|
||||||
if (Object.keys(value) === 0)
|
if (Object.keys(value) === 0)
|
||||||
throw new Error(
|
throw new Parse.Error(
|
||||||
`You need to provide atleast one operation on the relation mutation of field ${field}`
|
Parse.Error.INVALID_POINTER,
|
||||||
|
`You need to provide at least one operation on the relation mutation of field ${field}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const op = {
|
const op = {
|
||||||
@@ -143,11 +146,17 @@ const transformers = {
|
|||||||
|
|
||||||
if (value.add || nestedObjectsToAdd.length > 0) {
|
if (value.add || nestedObjectsToAdd.length > 0) {
|
||||||
if (!value.add) value.add = [];
|
if (!value.add) value.add = [];
|
||||||
value.add = value.add.map(input => ({
|
value.add = value.add.map(input => {
|
||||||
__type: 'Pointer',
|
const globalIdObject = fromGlobalId(input);
|
||||||
className: targetClass,
|
if (globalIdObject.type === targetClass) {
|
||||||
objectId: input,
|
input = globalIdObject.id;
|
||||||
}));
|
}
|
||||||
|
return {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: targetClass,
|
||||||
|
objectId: input,
|
||||||
|
};
|
||||||
|
});
|
||||||
op.ops.push({
|
op.ops.push({
|
||||||
__op: 'AddRelation',
|
__op: 'AddRelation',
|
||||||
objects: [...value.add, ...nestedObjectsToAdd],
|
objects: [...value.add, ...nestedObjectsToAdd],
|
||||||
@@ -157,11 +166,17 @@ const transformers = {
|
|||||||
if (value.remove) {
|
if (value.remove) {
|
||||||
op.ops.push({
|
op.ops.push({
|
||||||
__op: 'RemoveRelation',
|
__op: 'RemoveRelation',
|
||||||
objects: value.remove.map(input => ({
|
objects: value.remove.map(input => {
|
||||||
__type: 'Pointer',
|
const globalIdObject = fromGlobalId(input);
|
||||||
className: targetClass,
|
if (globalIdObject.type === targetClass) {
|
||||||
objectId: input,
|
input = globalIdObject.id;
|
||||||
})),
|
}
|
||||||
|
return {
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: targetClass,
|
||||||
|
objectId: input,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return op;
|
return op;
|
||||||
@@ -174,7 +189,8 @@ const transformers = {
|
|||||||
{ config, auth, info }
|
{ config, auth, info }
|
||||||
) => {
|
) => {
|
||||||
if (Object.keys(value) > 1 || Object.keys(value) === 0)
|
if (Object.keys(value) > 1 || Object.keys(value) === 0)
|
||||||
throw new Error(
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_POINTER,
|
||||||
`You need to provide link OR createLink on the pointer mutation of field ${field}`
|
`You need to provide link OR createLink on the pointer mutation of field ${field}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -199,10 +215,15 @@ const transformers = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (value.link) {
|
if (value.link) {
|
||||||
|
let objectId = value.link;
|
||||||
|
const globalIdObject = fromGlobalId(objectId);
|
||||||
|
if (globalIdObject.type === targetClass) {
|
||||||
|
objectId = globalIdObject.id;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
__type: 'Pointer',
|
__type: 'Pointer',
|
||||||
className: targetClass,
|
className: targetClass,
|
||||||
objectId: value.link,
|
objectId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const transformOutputTypeToGraphQL = (
|
|||||||
parseClassTypes[targetClass].classGraphQLFindResultType
|
parseClassTypes[targetClass].classGraphQLFindResultType
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT);
|
return new GraphQLNonNull(defaultGraphQLTypes.OBJECT);
|
||||||
}
|
}
|
||||||
case 'File':
|
case 'File':
|
||||||
return defaultGraphQLTypes.FILE_INFO;
|
return defaultGraphQLTypes.FILE_INFO;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { fromGlobalId } from 'graphql-relay';
|
||||||
|
|
||||||
const parseQueryMap = {
|
const parseQueryMap = {
|
||||||
id: 'objectId',
|
id: 'objectId',
|
||||||
OR: '$or',
|
OR: '$or',
|
||||||
@@ -102,10 +104,15 @@ const transformQueryConstraintInputToParse = (
|
|||||||
typeof fieldValue === 'string'
|
typeof fieldValue === 'string'
|
||||||
) {
|
) {
|
||||||
const { targetClass } = fields[parentFieldName];
|
const { targetClass } = fields[parentFieldName];
|
||||||
|
let objectId = fieldValue;
|
||||||
|
const globalIdObject = fromGlobalId(objectId);
|
||||||
|
if (globalIdObject.type === targetClass) {
|
||||||
|
objectId = globalIdObject.id;
|
||||||
|
}
|
||||||
constraints[fieldName] = {
|
constraints[fieldName] = {
|
||||||
__type: 'Pointer',
|
__type: 'Pointer',
|
||||||
className: targetClass,
|
className: targetClass,
|
||||||
objectId: fieldValue,
|
objectId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +183,7 @@ const transformQueryConstraintInputToParse = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformQueryInputToParse = (constraints, fields) => {
|
const transformQueryInputToParse = (constraints, fields, className) => {
|
||||||
if (!constraints || typeof constraints !== 'object') {
|
if (!constraints || typeof constraints !== 'object') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -191,9 +198,30 @@ const transformQueryInputToParse = (constraints, fields) => {
|
|||||||
|
|
||||||
if (fieldName !== 'objectId') {
|
if (fieldName !== 'objectId') {
|
||||||
fieldValue.forEach(fieldValueItem => {
|
fieldValue.forEach(fieldValueItem => {
|
||||||
transformQueryInputToParse(fieldValueItem, fields);
|
transformQueryInputToParse(fieldValueItem, fields, className);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
} else if (className) {
|
||||||
|
Object.keys(fieldValue).forEach(constraintName => {
|
||||||
|
const constraintValue = fieldValue[constraintName];
|
||||||
|
if (typeof constraintValue === 'string') {
|
||||||
|
const globalIdObject = fromGlobalId(constraintValue);
|
||||||
|
|
||||||
|
if (globalIdObject.type === className) {
|
||||||
|
fieldValue[constraintName] = globalIdObject.id;
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(constraintValue)) {
|
||||||
|
fieldValue[constraintName] = constraintValue.map(value => {
|
||||||
|
const globalIdObject = fromGlobalId(value);
|
||||||
|
|
||||||
|
if (globalIdObject.type === className) {
|
||||||
|
return globalIdObject.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user