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",
|
||||
"graphql": "14.5.8",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-relay": "^0.6.0",
|
||||
"graphql-tools": "^4.0.5",
|
||||
"graphql-upload": "8.1.0",
|
||||
"intersect": "1.0.1",
|
||||
|
||||
@@ -54,8 +54,14 @@ describe('AuthenticationProviders', function() {
|
||||
Promise.prototype.constructor
|
||||
);
|
||||
jequal(validateAppIdPromise.constructor, Promise.prototype.constructor);
|
||||
validateAuthDataPromise.then(() => {}, () => {});
|
||||
validateAppIdPromise.then(() => {}, () => {});
|
||||
validateAuthDataPromise.then(
|
||||
() => {},
|
||||
() => {}
|
||||
);
|
||||
validateAppIdPromise.then(
|
||||
() => {},
|
||||
() => {}
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
@@ -97,7 +97,11 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
const lng3 = 65;
|
||||
const polygon = {
|
||||
__type: 'Polygon',
|
||||
coordinates: [[lat1, lng1], [lat2, lng2], [lat3, lng3]],
|
||||
coordinates: [
|
||||
[lat1, lng1],
|
||||
[lat2, lng2],
|
||||
[lat3, lng3],
|
||||
],
|
||||
};
|
||||
const out = transform.parseObjectToMongoObjectForCreate(
|
||||
null,
|
||||
@@ -107,7 +111,12 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
}
|
||||
);
|
||||
expect(out.location.coordinates).toEqual([
|
||||
[[lng1, lat1], [lng2, lat2], [lng3, lat3], [lng1, lat1]],
|
||||
[
|
||||
[lng1, lat1],
|
||||
[lng2, lat2],
|
||||
[lng3, lat3],
|
||||
[lng1, lat1],
|
||||
],
|
||||
]);
|
||||
done();
|
||||
});
|
||||
@@ -217,7 +226,15 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
const lng = 45;
|
||||
// Mongo stores polygon in WGS84 lng/lat
|
||||
const input = {
|
||||
location: { type: 'Polygon', coordinates: [[[lat, lng], [lat, lng]]] },
|
||||
location: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[lat, lng],
|
||||
[lat, lng],
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
const output = transform.mongoObjectToParseObject(null, input, {
|
||||
fields: { location: { type: 'Polygon' } },
|
||||
@@ -225,7 +242,10 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
expect(typeof output.location).toEqual('object');
|
||||
expect(output.location).toEqual({
|
||||
__type: 'Polygon',
|
||||
coordinates: [[lng, lat], [lng, lat]],
|
||||
coordinates: [
|
||||
[lng, lat],
|
||||
[lng, lat],
|
||||
],
|
||||
});
|
||||
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 { GraphQLSchema, GraphQLObjectType } from 'graphql';
|
||||
import {
|
||||
GraphQLSchema,
|
||||
GraphQLObjectType,
|
||||
DocumentNode,
|
||||
GraphQLNamedType,
|
||||
} from 'graphql';
|
||||
import { mergeSchemas, SchemaDirectiveVisitor } from 'graphql-tools';
|
||||
import requiredParameter from '../requiredParameter';
|
||||
import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes';
|
||||
@@ -16,6 +21,7 @@ import { toGraphQLError } from './parseGraphQLUtils';
|
||||
import * as schemaDirectives from './loaders/schemaDirectives';
|
||||
import * as schemaTypes from './loaders/schemaTypes';
|
||||
import { getFunctionNames } from '../triggers';
|
||||
import * as defaultRelaySchema from './loaders/defaultRelaySchema';
|
||||
|
||||
const RESERVED_GRAPHQL_TYPE_NAMES = [
|
||||
'String',
|
||||
@@ -27,10 +33,25 @@ const RESERVED_GRAPHQL_TYPE_NAMES = [
|
||||
'Query',
|
||||
'Mutation',
|
||||
'Subscription',
|
||||
'CreateFileInput',
|
||||
'CreateFilePayload',
|
||||
'Viewer',
|
||||
'SignUpFieldsInput',
|
||||
'LogInFieldsInput',
|
||||
'SignUpInput',
|
||||
'SignUpPayload',
|
||||
'LogInInput',
|
||||
'LogInPayload',
|
||||
'LogOutInput',
|
||||
'LogOutPayload',
|
||||
'CloudCodeFunction',
|
||||
'CallCloudCodeInput',
|
||||
'CallCloudCodePayload',
|
||||
'CreateClassInput',
|
||||
'CreateClassPayload',
|
||||
'UpdateClassInput',
|
||||
'UpdateClassPayload',
|
||||
'DeleteClassInput',
|
||||
'DeleteClassPayload',
|
||||
'PageInfo',
|
||||
];
|
||||
const RESERVED_GRAPHQL_QUERY_NAMES = ['health', 'viewer', 'class', 'classes'];
|
||||
const RESERVED_GRAPHQL_MUTATION_NAMES = [
|
||||
@@ -48,7 +69,14 @@ class ParseGraphQLSchema {
|
||||
databaseController: DatabaseController;
|
||||
parseGraphQLController: ParseGraphQLController;
|
||||
parseGraphQLConfig: ParseGraphQLConfig;
|
||||
graphQLCustomTypeDefs: any;
|
||||
log: any;
|
||||
appId: string;
|
||||
graphQLCustomTypeDefs: ?(
|
||||
| string
|
||||
| GraphQLSchema
|
||||
| DocumentNode
|
||||
| GraphQLNamedType[]
|
||||
);
|
||||
|
||||
constructor(
|
||||
params: {
|
||||
@@ -56,6 +84,12 @@ class ParseGraphQLSchema {
|
||||
parseGraphQLController: ParseGraphQLController,
|
||||
log: any,
|
||||
appId: string,
|
||||
graphQLCustomTypeDefs: ?(
|
||||
| string
|
||||
| GraphQLSchema
|
||||
| DocumentNode
|
||||
| GraphQLNamedType[]
|
||||
),
|
||||
} = {}
|
||||
) {
|
||||
this.parseGraphQLController =
|
||||
@@ -105,8 +139,10 @@ class ParseGraphQLSchema {
|
||||
this.graphQLSubscriptions = {};
|
||||
this.graphQLSchemaDirectivesDefinitions = null;
|
||||
this.graphQLSchemaDirectives = {};
|
||||
this.relayNodeInterface = null;
|
||||
|
||||
defaultGraphQLTypes.load(this);
|
||||
defaultRelaySchema.load(this);
|
||||
schemaTypes.load(this);
|
||||
|
||||
this._getParseClassesWithConfig(parseClasses, parseGraphQLConfig).forEach(
|
||||
@@ -208,10 +244,16 @@ class ParseGraphQLSchema {
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
|
||||
addGraphQLType(type, throwError = false, ignoreReserved = false) {
|
||||
addGraphQLType(
|
||||
type,
|
||||
throwError = false,
|
||||
ignoreReserved = false,
|
||||
ignoreConnection = false
|
||||
) {
|
||||
if (
|
||||
(!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.`;
|
||||
if (throwError) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Parse from 'parse/node';
|
||||
import { offsetToCursor, cursorToOffset } from 'graphql-relay';
|
||||
import rest from '../../rest';
|
||||
import { transformQueryInputToParse } from '../transformers/query';
|
||||
|
||||
@@ -51,8 +52,11 @@ const findObjects = async (
|
||||
className,
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
limit,
|
||||
skipInput,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
keys,
|
||||
include,
|
||||
includeAll,
|
||||
@@ -68,11 +72,52 @@ const findObjects = async (
|
||||
if (!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 = {};
|
||||
|
||||
if (selectedFields.includes('results')) {
|
||||
if (
|
||||
selectedFields.find(
|
||||
field => field.startsWith('edges.') || field.startsWith('pageInfo.')
|
||||
)
|
||||
) {
|
||||
if (limit || limit === 0) {
|
||||
options.limit = limit;
|
||||
}
|
||||
@@ -104,7 +149,12 @@ const findObjects = async (
|
||||
options.limit = 0;
|
||||
}
|
||||
|
||||
if (selectedFields.includes('count')) {
|
||||
if (
|
||||
(selectedFields.includes('count') ||
|
||||
selectedFields.includes('pageInfo.hasPreviousPage') ||
|
||||
selectedFields.includes('pageInfo.hasNextPage')) &&
|
||||
!needToPreCount
|
||||
) {
|
||||
options.count = true;
|
||||
}
|
||||
|
||||
@@ -115,7 +165,151 @@ const findObjects = async (
|
||||
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),
|
||||
};
|
||||
|
||||
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 = {
|
||||
description: 'This is the object id.',
|
||||
type: OBJECT_ID,
|
||||
resolve: ({ objectId }) => objectId,
|
||||
};
|
||||
|
||||
const CREATED_AT_ATT = {
|
||||
@@ -611,7 +616,7 @@ const INPUT_FIELDS = {
|
||||
};
|
||||
|
||||
const CREATE_RESULT_FIELDS = {
|
||||
id: OBJECT_ID_ATT,
|
||||
objectId: OBJECT_ID_ATT,
|
||||
createdAt: CREATED_AT_ATT,
|
||||
};
|
||||
|
||||
@@ -637,7 +642,7 @@ const PARSE_OBJECT = new GraphQLInterfaceType({
|
||||
});
|
||||
|
||||
const SESSION_TOKEN_ATT = {
|
||||
description: 'The user session token',
|
||||
description: 'The current user session token.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
};
|
||||
|
||||
@@ -926,6 +931,25 @@ const options = {
|
||||
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({
|
||||
name: 'StringWhereInput',
|
||||
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({
|
||||
name: 'Element',
|
||||
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(GEO_WITHIN_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(GEO_INTERSECTS_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(ID_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(STRING_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(NUMBER_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(BOOLEAN_WHERE_INPUT, true);
|
||||
@@ -1258,9 +1270,7 @@ const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.addGraphQLType(FILE_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(GEO_POINT_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(POLYGON_WHERE_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(FIND_RESULT, true);
|
||||
parseGraphQLSchema.addGraphQLType(ELEMENT, true);
|
||||
parseGraphQLSchema.addGraphQLType(OBJECT_ID, true);
|
||||
parseGraphQLSchema.addGraphQLType(ACL_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(USER_ACL_INPUT, true);
|
||||
parseGraphQLSchema.addGraphQLType(ROLE_ACL_INPUT, true);
|
||||
@@ -1296,6 +1306,7 @@ export {
|
||||
POLYGON,
|
||||
OBJECT_ID,
|
||||
CLASS_NAME_ATT,
|
||||
GLOBAL_OR_OBJECT_ID_ATT,
|
||||
OBJECT_ID_ATT,
|
||||
UPDATED_AT_ATT,
|
||||
CREATED_AT_ATT,
|
||||
@@ -1337,6 +1348,7 @@ export {
|
||||
notInQueryKey,
|
||||
matchesRegex,
|
||||
options,
|
||||
ID_WHERE_INPUT,
|
||||
STRING_WHERE_INPUT,
|
||||
NUMBER_WHERE_INPUT,
|
||||
BOOLEAN_WHERE_INPUT,
|
||||
@@ -1348,7 +1360,6 @@ export {
|
||||
FILE_WHERE_INPUT,
|
||||
GEO_POINT_WHERE_INPUT,
|
||||
POLYGON_WHERE_INPUT,
|
||||
FIND_RESULT,
|
||||
ARRAY_RESULT,
|
||||
ELEMENT,
|
||||
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 { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import { GraphQLUpload } from 'graphql-upload';
|
||||
import Parse from 'parse/node';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import logger from '../../logger';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'createFile',
|
||||
{
|
||||
description:
|
||||
'The create mutation can be used to create and upload a new file.',
|
||||
args: {
|
||||
upload: {
|
||||
description: 'This is the new file to be created and uploaded',
|
||||
type: new GraphQLNonNull(GraphQLUpload),
|
||||
},
|
||||
const createMutation = mutationWithClientMutationId({
|
||||
name: 'CreateFile',
|
||||
description:
|
||||
'The createFile mutation can be used to create and upload a new file.',
|
||||
inputFields: {
|
||||
upload: {
|
||||
description: 'This is the new file to be created and uploaded.',
|
||||
type: new GraphQLNonNull(GraphQLUpload),
|
||||
},
|
||||
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
|
||||
async resolve(_source, args, context) {
|
||||
},
|
||||
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 {
|
||||
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 {
|
||||
return await config.filesController.createFile(
|
||||
return {
|
||||
fileInfo: await config.filesController.createFile(
|
||||
config,
|
||||
filename,
|
||||
data,
|
||||
mimetype
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error('Error creating a file: ', e);
|
||||
throw new Parse.Error(
|
||||
Parse.Error.FILE_SAVE_ERROR,
|
||||
`Could not store file: ${filename}.`
|
||||
);
|
||||
}
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
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
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLNonNull, GraphQLEnumType } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import { FunctionsRouter } from '../../Routers/FunctionsRouter';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
|
||||
@@ -21,28 +22,34 @@ const load = parseGraphQLSchema => {
|
||||
true
|
||||
);
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'callCloudCode',
|
||||
{
|
||||
description:
|
||||
'The call mutation can be used to invoke a cloud code function.',
|
||||
args: {
|
||||
functionName: {
|
||||
description: 'This is the function to be called.',
|
||||
type: new GraphQLNonNull(cloudCodeFunctionEnum),
|
||||
},
|
||||
params: {
|
||||
description: 'These are the params to be passed to the function.',
|
||||
type: defaultGraphQLTypes.OBJECT,
|
||||
},
|
||||
const callCloudCodeMutation = mutationWithClientMutationId({
|
||||
name: 'CallCloudCode',
|
||||
description:
|
||||
'The callCloudCode mutation can be used to invoke a cloud code function.',
|
||||
inputFields: {
|
||||
functionName: {
|
||||
description: 'This is the function to be called.',
|
||||
type: new GraphQLNonNull(cloudCodeFunctionEnum),
|
||||
},
|
||||
type: defaultGraphQLTypes.ANY,
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
const { functionName, params } = args;
|
||||
const { config, auth, info } = context;
|
||||
params: {
|
||||
description: 'These are the params to be passed to the function.',
|
||||
type: defaultGraphQLTypes.OBJECT,
|
||||
},
|
||||
},
|
||||
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: {
|
||||
functionName,
|
||||
},
|
||||
@@ -50,12 +57,23 @@ const load = parseGraphQLSchema => {
|
||||
auth,
|
||||
info,
|
||||
body: params,
|
||||
})).response.result;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
})).response.result,
|
||||
};
|
||||
} 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
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import {
|
||||
@@ -17,13 +18,16 @@ const getOnlyRequiredFields = (
|
||||
includedFieldsString,
|
||||
nativeObjectFields
|
||||
) => {
|
||||
const includedFields = includedFieldsString.split(',');
|
||||
const selectedFields = selectedFieldsString.split(',');
|
||||
const includedFields = includedFieldsString
|
||||
? includedFieldsString.split(',')
|
||||
: [];
|
||||
const selectedFields = selectedFieldsString
|
||||
? selectedFieldsString.split(',')
|
||||
: [];
|
||||
const missingFields = selectedFields
|
||||
.filter(
|
||||
field =>
|
||||
!nativeObjectFields.includes(field) ||
|
||||
includedFields.includes(field)
|
||||
!nativeObjectFields.includes(field) || includedFields.includes(field)
|
||||
)
|
||||
.join(',');
|
||||
if (!missingFields.length) {
|
||||
@@ -40,6 +44,8 @@ const load = function(
|
||||
) {
|
||||
const className = parseClass.className;
|
||||
const graphQLClassName = transformClassNameToGraphQL(className);
|
||||
const getGraphQLQueryName =
|
||||
graphQLClassName.charAt(0).toLowerCase() + graphQLClassName.slice(1);
|
||||
|
||||
const {
|
||||
create: isCreateEnabled = true,
|
||||
@@ -55,18 +61,25 @@ const load = function(
|
||||
|
||||
if (isCreateEnabled) {
|
||||
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.`,
|
||||
args: {
|
||||
inputFields: {
|
||||
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: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, mutationInfo) {
|
||||
outputFields: {
|
||||
[getGraphQLQueryName]: {
|
||||
description: 'This is the created object.',
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
let { fields } = args;
|
||||
if (!fields) fields = {};
|
||||
@@ -85,13 +98,15 @@ const load = function(
|
||||
auth,
|
||||
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: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||
fields,
|
||||
keys,
|
||||
include,
|
||||
['id', 'createdAt', 'updatedAt']
|
||||
['id', 'objectId', 'createdAt', 'updatedAt']
|
||||
);
|
||||
let optimizedObject = {};
|
||||
if (needGet) {
|
||||
@@ -108,37 +123,65 @@ const load = function(
|
||||
);
|
||||
}
|
||||
return {
|
||||
...createdObject,
|
||||
updatedAt: createdObject.createdAt,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
[getGraphQLQueryName]: {
|
||||
...createdObject,
|
||||
updatedAt: createdObject.createdAt,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
createGraphQLMutation.args.input.type.ofType
|
||||
) &&
|
||||
parseGraphQLSchema.addGraphQLType(createGraphQLMutation.type)
|
||||
) {
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
createGraphQLMutationName,
|
||||
createGraphQLMutation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isUpdateEnabled) {
|
||||
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.`,
|
||||
args: {
|
||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
inputFields: {
|
||||
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||
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: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, mutationInfo) {
|
||||
outputFields: {
|
||||
[getGraphQLQueryName]: {
|
||||
description: 'This is the updated object.',
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
const { id, fields } = args;
|
||||
let { id, fields } = args;
|
||||
if (!fields) fields = {};
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const globalIdObject = fromGlobalId(id);
|
||||
|
||||
if (globalIdObject.type === className) {
|
||||
id = globalIdObject.id;
|
||||
}
|
||||
|
||||
const parseFields = await transformTypes('update', fields, {
|
||||
className,
|
||||
parseGraphQLSchema,
|
||||
@@ -154,15 +197,17 @@ const load = function(
|
||||
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: requiredKeys, needGet } = getOnlyRequiredFields(
|
||||
fields,
|
||||
keys,
|
||||
include,
|
||||
['id', 'updatedAt']
|
||||
['id', 'objectId', 'updatedAt']
|
||||
);
|
||||
|
||||
let optimizedObject = {};
|
||||
if (needGet) {
|
||||
optimizedObject = await objectsQueries.getObject(
|
||||
@@ -178,38 +223,69 @@ const load = function(
|
||||
);
|
||||
}
|
||||
return {
|
||||
id,
|
||||
...updatedObject,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
[getGraphQLQueryName]: {
|
||||
objectId: id,
|
||||
...updatedObject,
|
||||
...parseFields,
|
||||
...optimizedObject,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
parseGraphQLSchema.addGraphQLType(
|
||||
updateGraphQLMutation.args.input.type.ofType
|
||||
) &&
|
||||
parseGraphQLSchema.addGraphQLType(updateGraphQLMutation.type)
|
||||
) {
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
updateGraphQLMutationName,
|
||||
updateGraphQLMutation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDestroyEnabled) {
|
||||
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.`,
|
||||
args: {
|
||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
inputFields: {
|
||||
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, mutationInfo) {
|
||||
outputFields: {
|
||||
[getGraphQLQueryName]: {
|
||||
description: 'This is the deleted object.',
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLOutputType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
},
|
||||
},
|
||||
mutateAndGetPayload: async (args, context, mutationInfo) => {
|
||||
try {
|
||||
const { id } = args;
|
||||
let { id } = args;
|
||||
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 = {};
|
||||
const splitedKeys = keys.split(',');
|
||||
if (splitedKeys.length > 1 || splitedKeys[0] !== 'id') {
|
||||
if (
|
||||
keys &&
|
||||
keys.split(',').filter(key => !['id', 'objectId'].includes(key))
|
||||
.length > 0
|
||||
) {
|
||||
optimizedObject = await objectsQueries.getObject(
|
||||
className,
|
||||
id,
|
||||
@@ -229,12 +305,29 @@ const load = function(
|
||||
auth,
|
||||
info
|
||||
);
|
||||
return { id, ...optimizedObject };
|
||||
return {
|
||||
[getGraphQLQueryName]: {
|
||||
objectId: id,
|
||||
...optimizedObject,
|
||||
},
|
||||
};
|
||||
} catch (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 { fromGlobalId } from 'graphql-relay';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import pluralize from 'pluralize';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
@@ -14,11 +15,18 @@ const getParseClassQueryConfig = function(
|
||||
};
|
||||
|
||||
const getQuery = async (className, _source, args, context, queryInfo) => {
|
||||
const { id, options } = args;
|
||||
let { id } = args;
|
||||
const { options } = args;
|
||||
const { readPreference, includeReadPreference } = options || {};
|
||||
const { config, auth, info } = context;
|
||||
const selectedFields = getFieldNames(queryInfo);
|
||||
|
||||
const globalIdObject = fromGlobalId(id);
|
||||
|
||||
if (globalIdObject.type === className) {
|
||||
id = globalIdObject.id;
|
||||
}
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(selectedFields);
|
||||
|
||||
return await objectsQueries.getObject(
|
||||
@@ -58,7 +66,7 @@ const load = function(
|
||||
parseGraphQLSchema.addGraphQLQuery(getGraphQLQueryName, {
|
||||
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${graphQLClassName} class by its id.`,
|
||||
args: {
|
||||
id: defaultGraphQLTypes.OBJECT_ID_ATT,
|
||||
id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT,
|
||||
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(
|
||||
@@ -82,11 +90,20 @@ const load = function(
|
||||
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${graphQLClassName} class.`,
|
||||
args: classGraphQLFindArgs,
|
||||
type: new GraphQLNonNull(
|
||||
classGraphQLFindResultType || defaultGraphQLTypes.FIND_RESULT
|
||||
classGraphQLFindResultType || defaultGraphQLTypes.OBJECT
|
||||
),
|
||||
async resolve(_source, args, context, queryInfo) {
|
||||
try {
|
||||
const { where, order, skip, limit, options } = args;
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
options,
|
||||
} = args;
|
||||
const {
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
@@ -97,8 +114,8 @@ const load = function(
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
.filter(field => field.startsWith('edges.node.'))
|
||||
.map(field => field.replace('edges.node.', ''))
|
||||
);
|
||||
const parseOrder = order && order.join(',');
|
||||
|
||||
@@ -107,7 +124,10 @@ const load = function(
|
||||
where,
|
||||
parseOrder,
|
||||
skip,
|
||||
limit,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
@@ -117,7 +137,7 @@ const load = function(
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0]),
|
||||
selectedFields,
|
||||
parseClass.fields
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
GraphQLNonNull,
|
||||
GraphQLEnumType,
|
||||
} from 'graphql';
|
||||
import {
|
||||
globalIdField,
|
||||
connectionArgs,
|
||||
connectionDefinitions,
|
||||
} from 'graphql-relay';
|
||||
import getFieldNames from 'graphql-list-fields';
|
||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||
import * as objectsQueries from '../helpers/objectsQueries';
|
||||
@@ -30,9 +35,7 @@ const getInputFieldsAndConstraints = function(
|
||||
parseClass,
|
||||
parseClassConfig: ?ParseGraphQLClassConfig
|
||||
) {
|
||||
const classFields = Object.keys(parseClass.fields)
|
||||
.filter(field => field !== 'objectId')
|
||||
.concat('id');
|
||||
const classFields = Object.keys(parseClass.fields).concat('id');
|
||||
const {
|
||||
inputFields: allowedInputFields,
|
||||
outputFields: allowedOutputFields,
|
||||
@@ -48,8 +51,9 @@ const getInputFieldsAndConstraints = function(
|
||||
|
||||
// All allowed customs fields
|
||||
const classCustomFields = classFields.filter(field => {
|
||||
return !Object.keys(defaultGraphQLTypes.PARSE_OBJECT_FIELDS).includes(
|
||||
field
|
||||
return (
|
||||
!Object.keys(defaultGraphQLTypes.PARSE_OBJECT_FIELDS).includes(field) &&
|
||||
field !== 'id'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -153,7 +157,11 @@ const load = (
|
||||
...fields,
|
||||
[field]: {
|
||||
description: `This is the object ${field}.`,
|
||||
type,
|
||||
type:
|
||||
className === '_User' &&
|
||||
(field === 'username' || field === 'password')
|
||||
? new GraphQLNonNull(type)
|
||||
: type,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
@@ -209,7 +217,7 @@ const load = (
|
||||
fields: () => {
|
||||
const fields = {
|
||||
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,
|
||||
},
|
||||
};
|
||||
@@ -233,17 +241,17 @@ const load = (
|
||||
fields: () => {
|
||||
const fields = {
|
||||
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),
|
||||
},
|
||||
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),
|
||||
},
|
||||
};
|
||||
if (isCreateEnabled) {
|
||||
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)),
|
||||
};
|
||||
}
|
||||
@@ -268,12 +276,12 @@ const load = (
|
||||
notInQueryKey: defaultGraphQLTypes.notInQueryKey,
|
||||
inQuery: {
|
||||
description:
|
||||
'This is the inQuery operator to specify a constraint to select the objects where a field equals to any of the ids in the result of a different query.',
|
||||
'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,
|
||||
},
|
||||
notInQuery: {
|
||||
description:
|
||||
'This is the notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the ids in the result of a different query.',
|
||||
'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,
|
||||
},
|
||||
},
|
||||
@@ -298,7 +306,8 @@ const load = (
|
||||
const type = transformConstraintTypeToGraphQL(
|
||||
parseClass.fields[parseField].type,
|
||||
parseClass.fields[parseField].targetClass,
|
||||
parseGraphQLSchema.parseClassTypes
|
||||
parseGraphQLSchema.parseClassTypes,
|
||||
field
|
||||
);
|
||||
if (type) {
|
||||
return {
|
||||
@@ -339,11 +348,12 @@ const load = (
|
||||
const updatedSortFields = {
|
||||
...sortFields,
|
||||
};
|
||||
const value = field === 'id' ? 'objectId' : field;
|
||||
if (asc) {
|
||||
updatedSortFields[`${field}_ASC`] = { value: field };
|
||||
updatedSortFields[`${field}_ASC`] = { value };
|
||||
}
|
||||
if (desc) {
|
||||
updatedSortFields[`${field}_DESC`] = { value: `-${field}` };
|
||||
updatedSortFields[`${field}_DESC`] = { value: `-${value}` };
|
||||
}
|
||||
return updatedSortFields;
|
||||
}, {}),
|
||||
@@ -365,10 +375,18 @@ const load = (
|
||||
: GraphQLString,
|
||||
},
|
||||
skip: defaultGraphQLTypes.SKIP_ATT,
|
||||
limit: defaultGraphQLTypes.LIMIT_ATT,
|
||||
...connectionArgs,
|
||||
options: defaultGraphQLTypes.READ_OPTIONS_ATT,
|
||||
};
|
||||
const classGraphQLOutputTypeName = `${graphQLClassName}`;
|
||||
const interfaces = [
|
||||
defaultGraphQLTypes.PARSE_OBJECT,
|
||||
parseGraphQLSchema.relayNodeInterface,
|
||||
];
|
||||
const parseObjectFields = {
|
||||
id: globalIdField(className, obj => obj.objectId),
|
||||
...defaultGraphQLTypes.PARSE_OBJECT_FIELDS,
|
||||
};
|
||||
const outputFields = () => {
|
||||
return classOutputFields.reduce((fields, field) => {
|
||||
const type = transformOutputTypeToGraphQL(
|
||||
@@ -392,7 +410,16 @@ const load = (
|
||||
type,
|
||||
async resolve(source, args, context, queryInfo) {
|
||||
try {
|
||||
const { where, order, skip, limit, options } = args;
|
||||
const {
|
||||
where,
|
||||
order,
|
||||
skip,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
options,
|
||||
} = args;
|
||||
const {
|
||||
readPreference,
|
||||
includeReadPreference,
|
||||
@@ -403,9 +430,11 @@ const load = (
|
||||
|
||||
const { keys, include } = extractKeysAndInclude(
|
||||
selectedFields
|
||||
.filter(field => field.includes('.'))
|
||||
.map(field => field.slice(field.indexOf('.') + 1))
|
||||
.filter(field => field.startsWith('edges.node.'))
|
||||
.map(field => field.replace('edges.node.', ''))
|
||||
);
|
||||
const parseOrder = order && order.join(',');
|
||||
|
||||
return await objectsQueries.findObjects(
|
||||
source[field].className,
|
||||
{
|
||||
@@ -419,9 +448,12 @@ const load = (
|
||||
},
|
||||
...(where || {}),
|
||||
},
|
||||
order,
|
||||
parseOrder,
|
||||
skip,
|
||||
limit,
|
||||
first,
|
||||
after,
|
||||
last,
|
||||
before,
|
||||
keys,
|
||||
include,
|
||||
false,
|
||||
@@ -431,8 +463,11 @@ const load = (
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
selectedFields.map(field => field.split('.', 1)[0]),
|
||||
parseClass.fields
|
||||
selectedFields,
|
||||
parseGraphQLSchema.parseClasses.find(
|
||||
parseClass =>
|
||||
parseClass.className === source[field].className
|
||||
).fields
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
@@ -491,39 +526,32 @@ const load = (
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
}, defaultGraphQLTypes.PARSE_OBJECT_FIELDS);
|
||||
}, parseObjectFields);
|
||||
};
|
||||
let classGraphQLOutputType = new GraphQLObjectType({
|
||||
name: classGraphQLOutputTypeName,
|
||||
description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${graphQLClassName} class.`,
|
||||
interfaces: [defaultGraphQLTypes.PARSE_OBJECT],
|
||||
interfaces,
|
||||
fields: outputFields,
|
||||
});
|
||||
classGraphQLOutputType = parseGraphQLSchema.addGraphQLType(
|
||||
classGraphQLOutputType
|
||||
);
|
||||
|
||||
const classGraphQLFindResultTypeName = `${graphQLClassName}FindResult`;
|
||||
let classGraphQLFindResultType = new GraphQLObjectType({
|
||||
name: classGraphQLFindResultTypeName,
|
||||
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
|
||||
)
|
||||
)
|
||||
),
|
||||
},
|
||||
const { connectionType, edgeType } = connectionDefinitions({
|
||||
name: graphQLClassName,
|
||||
connectionFields: {
|
||||
count: defaultGraphQLTypes.COUNT_ATT,
|
||||
},
|
||||
nodeType: classGraphQLOutputType || defaultGraphQLTypes.OBJECT,
|
||||
});
|
||||
classGraphQLFindResultType = parseGraphQLSchema.addGraphQLType(
|
||||
classGraphQLFindResultType
|
||||
);
|
||||
let classGraphQLFindResultType = undefined;
|
||||
if (
|
||||
parseGraphQLSchema.addGraphQLType(edgeType) &&
|
||||
parseGraphQLSchema.addGraphQLType(connectionType, false, false, true)
|
||||
) {
|
||||
classGraphQLFindResultType = connectionType;
|
||||
}
|
||||
|
||||
parseGraphQLSchema.parseClassTypes[className] = {
|
||||
classGraphQLPointerType,
|
||||
@@ -546,67 +574,16 @@ const load = (
|
||||
const viewerType = new GraphQLObjectType({
|
||||
name: 'Viewer',
|
||||
description: `The Viewer object type is used in operations that involve outputting the current user data.`,
|
||||
interfaces: [defaultGraphQLTypes.PARSE_OBJECT],
|
||||
fields: () => ({
|
||||
...outputFields(),
|
||||
sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT,
|
||||
user: {
|
||||
description: 'This is the current user.',
|
||||
type: new GraphQLNonNull(classGraphQLOutputType),
|
||||
},
|
||||
}),
|
||||
});
|
||||
parseGraphQLSchema.viewerType = viewerType;
|
||||
parseGraphQLSchema.addGraphQLType(viewerType, true, true);
|
||||
|
||||
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;
|
||||
parseGraphQLSchema.viewerType = viewerType;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Parse from 'parse/node';
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import * as schemaTypes from './schemaTypes';
|
||||
import {
|
||||
transformToParse,
|
||||
@@ -9,135 +10,183 @@ import { enforceMasterKeyAccess } from '../parseGraphQLUtils';
|
||||
import { getClass } from './schemaQueries';
|
||||
|
||||
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(
|
||||
'createClass',
|
||||
{
|
||||
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);
|
||||
}
|
||||
},
|
||||
},
|
||||
createClassMutation,
|
||||
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(
|
||||
'updateClass',
|
||||
{
|
||||
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);
|
||||
}
|
||||
},
|
||||
},
|
||||
updateClassMutation,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'deleteClass',
|
||||
{
|
||||
description:
|
||||
'The deleteClass mutation can be used to delete an existing object class.',
|
||||
args: {
|
||||
name: schemaTypes.CLASS_NAME_ATT,
|
||||
},
|
||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||
resolve: async (_source, 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 {
|
||||
name: existingParseClass.className,
|
||||
schemaFields: transformToGraphQL(existingParseClass.fields),
|
||||
};
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
const deleteClassMutation = mutationWithClientMutationId({
|
||||
name: 'DeleteClass',
|
||||
description:
|
||||
'The deleteClass mutation can be used to delete an existing object class.',
|
||||
inputFields: {
|
||||
name: schemaTypes.CLASS_NAME_ATT,
|
||||
},
|
||||
outputFields: {
|
||||
class: {
|
||||
description: 'This is the deleted class.',
|
||||
type: new GraphQLNonNull(schemaTypes.CLASS),
|
||||
},
|
||||
},
|
||||
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
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import { GraphQLNonNull, GraphQLString } from 'graphql';
|
||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||
import UsersRouter from '../../Routers/UsersRouter';
|
||||
import * as objectsMutations from '../helpers/objectsMutations';
|
||||
import { getUserFromSessionToken } from './usersQueries';
|
||||
@@ -10,110 +11,166 @@ const load = parseGraphQLSchema => {
|
||||
return;
|
||||
}
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'signUp',
|
||||
{
|
||||
description: 'The signUp mutation can be used to sign the user up.',
|
||||
args: {
|
||||
fields: {
|
||||
descriptions: 'These are the fields of the user.',
|
||||
type: parseGraphQLSchema.parseClassTypes['_User'].signUpInputType,
|
||||
},
|
||||
},
|
||||
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);
|
||||
}
|
||||
const signUpMutation = mutationWithClientMutationId({
|
||||
name: 'SignUp',
|
||||
description:
|
||||
'The signUp mutation can be used to create and sign up a new user.',
|
||||
inputFields: {
|
||||
userFields: {
|
||||
descriptions:
|
||||
'These are the fields of the new user to be created and signed up.',
|
||||
type:
|
||||
parseGraphQLSchema.parseClassTypes['_User'].classGraphQLCreateType,
|
||||
},
|
||||
},
|
||||
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
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(signUpMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation('signUp', signUpMutation, true, true);
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'logIn',
|
||||
{
|
||||
description: 'The logIn mutation can be used to log the user in.',
|
||||
args: {
|
||||
fields: {
|
||||
description: 'This is data needed to login',
|
||||
type: parseGraphQLSchema.parseClassTypes['_User'].logInInputType,
|
||||
},
|
||||
const logInMutation = mutationWithClientMutationId({
|
||||
name: 'LogIn',
|
||||
description: 'The logIn mutation can be used to log in an existing user.',
|
||||
inputFields: {
|
||||
username: {
|
||||
description: 'This is the username used to log in the user.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
async resolve(_source, args, context) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
password: {
|
||||
description: 'This is the password used to log in the user.',
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
},
|
||||
},
|
||||
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
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(logInMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation('logIn', logInMutation, true, true);
|
||||
|
||||
parseGraphQLSchema.addGraphQLMutation(
|
||||
'logOut',
|
||||
{
|
||||
description: 'The logOut mutation can be used to log the user out.',
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
async resolve(_source, _args, context, mutationInfo) {
|
||||
try {
|
||||
const { config, auth, info } = context;
|
||||
|
||||
const viewer = await getUserFromSessionToken(
|
||||
config,
|
||||
info,
|
||||
mutationInfo
|
||||
);
|
||||
|
||||
await usersRouter.handleLogOut({
|
||||
config,
|
||||
auth,
|
||||
info,
|
||||
});
|
||||
|
||||
return viewer;
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
const logOutMutation = mutationWithClientMutationId({
|
||||
name: 'LogOut',
|
||||
description: 'The logOut mutation can be used to log out an existing user.',
|
||||
outputFields: {
|
||||
viewer: {
|
||||
description:
|
||||
'This is the existing user that was logged out and returned as a viewer.',
|
||||
type: new GraphQLNonNull(parseGraphQLSchema.viewerType),
|
||||
},
|
||||
},
|
||||
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
|
||||
);
|
||||
parseGraphQLSchema.addGraphQLType(logOutMutation.type, true, true);
|
||||
parseGraphQLSchema.addGraphQLMutation('logOut', logOutMutation, true, true);
|
||||
};
|
||||
|
||||
export { load };
|
||||
|
||||
@@ -5,7 +5,13 @@ import rest from '../../rest';
|
||||
import Auth from '../../Auth';
|
||||
import { extractKeysAndInclude } from './parseClassTypes';
|
||||
|
||||
const getUserFromSessionToken = async (config, info, queryInfo) => {
|
||||
const getUserFromSessionToken = async (
|
||||
config,
|
||||
info,
|
||||
queryInfo,
|
||||
keysPrefix,
|
||||
validatedToken
|
||||
) => {
|
||||
if (!info || !info.sessionToken) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
@@ -13,20 +19,42 @@ const getUserFromSessionToken = async (config, info, queryInfo) => {
|
||||
);
|
||||
}
|
||||
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(
|
||||
config,
|
||||
Auth.master(config),
|
||||
'_Session',
|
||||
{ sessionToken },
|
||||
{
|
||||
include: include
|
||||
.split(',')
|
||||
.map(included => `user.${included}`)
|
||||
.join(','),
|
||||
},
|
||||
options,
|
||||
info.clientVersion
|
||||
);
|
||||
if (
|
||||
@@ -40,8 +68,10 @@ const getUserFromSessionToken = async (config, info, queryInfo) => {
|
||||
);
|
||||
} else {
|
||||
const user = response.results[0].user;
|
||||
user.sessionToken = sessionToken;
|
||||
return user;
|
||||
return {
|
||||
sessionToken,
|
||||
user,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,7 +89,13 @@ const load = parseGraphQLSchema => {
|
||||
async resolve(_source, _args, context, queryInfo) {
|
||||
try {
|
||||
const { config, info } = context;
|
||||
return await getUserFromSessionToken(config, info, queryInfo);
|
||||
return await getUserFromSessionToken(
|
||||
config,
|
||||
info,
|
||||
queryInfo,
|
||||
'user.',
|
||||
false
|
||||
);
|
||||
} catch (e) {
|
||||
parseGraphQLSchema.handleError(e);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@ import * as defaultGraphQLTypes from '../loaders/defaultGraphQLTypes';
|
||||
const transformConstraintTypeToGraphQL = (
|
||||
parseType,
|
||||
targetClass,
|
||||
parseClassTypes
|
||||
parseClassTypes,
|
||||
fieldName
|
||||
) => {
|
||||
if (fieldName === 'id' || fieldName === 'objectId') {
|
||||
return defaultGraphQLTypes.ID_WHERE_INPUT;
|
||||
}
|
||||
|
||||
switch (parseType) {
|
||||
case 'String':
|
||||
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 objectsMutations from '../helpers/objectsMutations';
|
||||
|
||||
@@ -108,8 +110,9 @@ const transformers = {
|
||||
{ config, auth, info }
|
||||
) => {
|
||||
if (Object.keys(value) === 0)
|
||||
throw new Error(
|
||||
`You need to provide atleast one operation on the relation mutation of field ${field}`
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_POINTER,
|
||||
`You need to provide at least one operation on the relation mutation of field ${field}`
|
||||
);
|
||||
|
||||
const op = {
|
||||
@@ -143,11 +146,17 @@ const transformers = {
|
||||
|
||||
if (value.add || nestedObjectsToAdd.length > 0) {
|
||||
if (!value.add) value.add = [];
|
||||
value.add = value.add.map(input => ({
|
||||
__type: 'Pointer',
|
||||
className: targetClass,
|
||||
objectId: input,
|
||||
}));
|
||||
value.add = value.add.map(input => {
|
||||
const globalIdObject = fromGlobalId(input);
|
||||
if (globalIdObject.type === targetClass) {
|
||||
input = globalIdObject.id;
|
||||
}
|
||||
return {
|
||||
__type: 'Pointer',
|
||||
className: targetClass,
|
||||
objectId: input,
|
||||
};
|
||||
});
|
||||
op.ops.push({
|
||||
__op: 'AddRelation',
|
||||
objects: [...value.add, ...nestedObjectsToAdd],
|
||||
@@ -157,11 +166,17 @@ const transformers = {
|
||||
if (value.remove) {
|
||||
op.ops.push({
|
||||
__op: 'RemoveRelation',
|
||||
objects: value.remove.map(input => ({
|
||||
__type: 'Pointer',
|
||||
className: targetClass,
|
||||
objectId: input,
|
||||
})),
|
||||
objects: value.remove.map(input => {
|
||||
const globalIdObject = fromGlobalId(input);
|
||||
if (globalIdObject.type === targetClass) {
|
||||
input = globalIdObject.id;
|
||||
}
|
||||
return {
|
||||
__type: 'Pointer',
|
||||
className: targetClass,
|
||||
objectId: input,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
return op;
|
||||
@@ -174,7 +189,8 @@ const transformers = {
|
||||
{ config, auth, info }
|
||||
) => {
|
||||
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}`
|
||||
);
|
||||
|
||||
@@ -199,10 +215,15 @@ const transformers = {
|
||||
};
|
||||
}
|
||||
if (value.link) {
|
||||
let objectId = value.link;
|
||||
const globalIdObject = fromGlobalId(objectId);
|
||||
if (globalIdObject.type === targetClass) {
|
||||
objectId = globalIdObject.id;
|
||||
}
|
||||
return {
|
||||
__type: 'Pointer',
|
||||
className: targetClass,
|
||||
objectId: value.link,
|
||||
objectId,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ const transformOutputTypeToGraphQL = (
|
||||
parseClassTypes[targetClass].classGraphQLFindResultType
|
||||
);
|
||||
} else {
|
||||
return new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT);
|
||||
return new GraphQLNonNull(defaultGraphQLTypes.OBJECT);
|
||||
}
|
||||
case 'File':
|
||||
return defaultGraphQLTypes.FILE_INFO;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { fromGlobalId } from 'graphql-relay';
|
||||
|
||||
const parseQueryMap = {
|
||||
id: 'objectId',
|
||||
OR: '$or',
|
||||
@@ -102,10 +104,15 @@ const transformQueryConstraintInputToParse = (
|
||||
typeof fieldValue === 'string'
|
||||
) {
|
||||
const { targetClass } = fields[parentFieldName];
|
||||
let objectId = fieldValue;
|
||||
const globalIdObject = fromGlobalId(objectId);
|
||||
if (globalIdObject.type === targetClass) {
|
||||
objectId = globalIdObject.id;
|
||||
}
|
||||
constraints[fieldName] = {
|
||||
__type: 'Pointer',
|
||||
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') {
|
||||
return;
|
||||
}
|
||||
@@ -191,9 +198,30 @@ const transformQueryInputToParse = (constraints, fields) => {
|
||||
|
||||
if (fieldName !== 'objectId') {
|
||||
fieldValue.forEach(fieldValueItem => {
|
||||
transformQueryInputToParse(fieldValueItem, fields);
|
||||
transformQueryInputToParse(fieldValueItem, fields, className);
|
||||
});
|
||||
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