GraphQL Support (#5674)

* GraphQL boilerplate

* Create GraphQL schema without using gql

* Introducing loaders

* Generic create mutation

* create mutation is now working for any data type

* Create mutation for each parse class - partial

* Adding more data types to the class

* Get parse class query

* Generic get query

* Generic delete mutation

* Parse class delete mutation

* Parse class find mutation

* Generic update mutation

* Parse class update mutation

* Fixing initialization problems

* Installing node-fetch again

* Basic implementation for Pointer

* Constructor tests

* API tests boilerplate

* _getGraphQLOptions

* applyGraphQL tests

* GraphQL API initial tests

* applyPlayground tests

* createSubscriptions tests

* ParseGrapjQLSchema tests file

* ParseGraphQLSchema tests

* TypeValidationError

* TypeValidationError

* parseStringValue test

* parseIntValue tests

* parseBooleanValue tests

* parseDateValue tests

* parseValue tests

* parseListValues tests

* parseObjectFields tests

* Default types tests

* Get tests

* First permission test at generic Get operation

* Fixing prepare data

* ApolloClient does not work well with different queries runnning in paralell with different headers

* ApolloClient does not work well with different queries runnning in paralell with different headers

* User 3 tests

* User 3 tests

* Get level permission tests

* Get User specific tests

* Get now support keys argument

* Get now supports include argument

* Get now supports read preferences

* Adding tests for read preference enum type

* Find basic test

* Find permissions test

* Find where argument test

* Order, skip and limit tests

* Error handler

* Find now supports count

* Test for FindResult type

* Improving find count

* Find max limit test

* Find now supports keys, include and includeAll

* Find now supports read preferences

* Basic Create test

* Generic create mutation tests

* Basic update test

* UpdateResult object type test

* Update level permissions tests

* Error handler for default mutations

* Delete mutation basic test

* Delete mutation level permission tests

* Test for string

* String test

* Date test

* Pointer test

* Relation tests

* Changing objects mutations location

* Changing objects queries location

* Create file mutation

* Test for file fields

* Test for null values

* Changing parse classes operations location

* Objects mutations refactoring

* Class specific create object mutation now working

* Update class specific mutation now working

* Specific class delete mutation now working

* Get class specific mutation now working

* Find class specific query now working without where and sort

* Find query for custom classes working with where partially

* Almost all data types working for specfic class find where

* Now only missing relation, geopoint, file and ACL

* Additional tests with Parse classes queries and mutations

* Now only missing relation, geopoint, file and ACL

* Files

* Fiels are now working

* Excluding missing order test temporarly

* Refactoring dates

* Refactoring files

* Default types review

* Refeactoring object queries

* Refactoring class scalar type

* Refactoring class types

* Geo queries are now working

* Fixing centerSphere

* Allow sort on class specific queries

* Supporting bytes

* ACL constraint

* Temporarly removing xit tests

* Fixing some tests because of schema cache

* Removing session token from users

* Parse.User queries and mutations

* Remove test using fit

* Fixing include test that was failing because of schema cache

* Fixing count test for postgres. Postgres does not count with where={} (legacy problem). We should solve it later

* Fix null values test for postgres. It is evaluating null as undefined (legacy problem) and we should fix is later.

* Fixing schema change test that was failing because of schema cache

* Add GraphQL File type parseLiteral tests

* Refeactoring users

* Including sign up mutation

* Fix failing test

* Improve default GraphQL types tests coverage

* Including some tests for data types

* Including additional pointer test:

* Fixing some tests

* more data type tests

* Include Bytes and Polygon data types tests

* Polygons test

* Merging other tests

* Fixing some postgres tests
This commit is contained in:
Antonio Davi Macedo Coelho de Castro
2019-06-19 17:19:47 -07:00
committed by GitHub
parent 922251a398
commit fe2e95622f
20 changed files with 9532 additions and 5 deletions

View File

@@ -0,0 +1,117 @@
import Parse from 'parse/node';
import { GraphQLSchema, GraphQLObjectType } from 'graphql';
import { ApolloError } from 'apollo-server-core';
import requiredParameter from '../requiredParameter';
import * as defaultGraphQLTypes from './loaders/defaultGraphQLTypes';
import * as parseClassTypes from './loaders/parseClassTypes';
import * as parseClassQueries from './loaders/parseClassQueries';
import * as parseClassMutations from './loaders/parseClassMutations';
import * as defaultGraphQLQueries from './loaders/defaultGraphQLQueries';
import * as defaultGraphQLMutations from './loaders/defaultGraphQLMutations';
class ParseGraphQLSchema {
constructor(databaseController, log) {
this.databaseController =
databaseController ||
requiredParameter('You must provide a databaseController instance!');
this.log = log || requiredParameter('You must provide a log instance!');
}
async load() {
const schemaController = await this.databaseController.loadSchema();
const parseClasses = await schemaController.getAllClasses();
const parseClassesString = JSON.stringify(parseClasses);
if (this.graphQLSchema) {
if (this.parseClasses === parseClasses) {
return this.graphQLSchema;
}
if (this.parseClassesString === parseClassesString) {
this.parseClasses = parseClasses;
return this.graphQLSchema;
}
}
this.parseClasses = parseClasses;
this.parseClassesString = parseClassesString;
this.parseClassTypes = {};
this.meType = null;
this.graphQLSchema = null;
this.graphQLTypes = [];
this.graphQLObjectsQueries = {};
this.graphQLQueries = {};
this.graphQLObjectsMutations = {};
this.graphQLMutations = {};
this.graphQLSubscriptions = {};
defaultGraphQLTypes.load(this);
parseClasses.forEach(parseClass => {
parseClassTypes.load(this, parseClass);
parseClassQueries.load(this, parseClass);
parseClassMutations.load(this, parseClass);
});
defaultGraphQLQueries.load(this);
defaultGraphQLMutations.load(this);
let graphQLQuery = undefined;
if (Object.keys(this.graphQLQueries).length > 0) {
graphQLQuery = new GraphQLObjectType({
name: 'Query',
description: 'Query is the top level type for queries.',
fields: this.graphQLQueries,
});
this.graphQLTypes.push(graphQLQuery);
}
let graphQLMutation = undefined;
if (Object.keys(this.graphQLMutations).length > 0) {
graphQLMutation = new GraphQLObjectType({
name: 'Mutation',
description: 'Mutation is the top level type for mutations.',
fields: this.graphQLMutations,
});
this.graphQLTypes.push(graphQLMutation);
}
let graphQLSubscription = undefined;
if (Object.keys(this.graphQLSubscriptions).length > 0) {
graphQLSubscription = new GraphQLObjectType({
name: 'Subscription',
description: 'Subscription is the top level type for subscriptions.',
fields: this.graphQLSubscriptions,
});
this.graphQLTypes.push(graphQLSubscription);
}
this.graphQLSchema = new GraphQLSchema({
types: this.graphQLTypes,
query: graphQLQuery,
mutation: graphQLMutation,
subscription: graphQLSubscription,
});
return this.graphQLSchema;
}
handleError(error) {
let code, message;
if (error instanceof Parse.Error) {
this.log.error('Parse error: ', error);
code = error.code;
message = error.message;
} else {
this.log.error('Uncaught internal server error.', error, error.stack);
code = Parse.Error.INTERNAL_SERVER_ERROR;
message = 'Internal server error.';
}
throw new ApolloError(message, code);
}
}
export { ParseGraphQLSchema };

View File

@@ -0,0 +1,110 @@
import corsMiddleware from 'cors';
import bodyParser from 'body-parser';
import { graphqlUploadExpress } from 'graphql-upload';
import { graphqlExpress } from 'apollo-server-express/dist/expressApollo';
import { renderPlaygroundPage } from '@apollographql/graphql-playground-html';
import { execute, subscribe } from 'graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { handleParseHeaders } from '../middlewares';
import requiredParameter from '../requiredParameter';
import defaultLogger from '../logger';
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
class ParseGraphQLServer {
constructor(parseServer, config) {
this.parseServer =
parseServer ||
requiredParameter('You must provide a parseServer instance!');
if (!config || !config.graphQLPath) {
requiredParameter('You must provide a config.graphQLPath!');
}
this.config = config;
this.parseGraphQLSchema = new ParseGraphQLSchema(
this.parseServer.config.databaseController,
(this.parseServer.config && this.parseServer.config.loggerController) ||
defaultLogger
);
}
async _getGraphQLOptions(req) {
return {
schema: await this.parseGraphQLSchema.load(),
context: {
info: req.info,
config: req.config,
auth: req.auth,
},
};
}
applyGraphQL(app) {
if (!app || !app.use) {
requiredParameter('You must provide an Express.js app instance!');
}
const maxUploadSize = this.parseServer.config.maxUploadSize || '20mb';
const maxFileSize =
(Number(maxUploadSize.slice(0, -2)) * 1024) ^
{
kb: 1,
mb: 2,
gb: 3,
}[maxUploadSize.slice(-2).toLowerCase()];
app.use(this.config.graphQLPath, graphqlUploadExpress({ maxFileSize }));
app.use(this.config.graphQLPath, corsMiddleware());
app.use(this.config.graphQLPath, bodyParser.json());
app.use(this.config.graphQLPath, handleParseHeaders);
app.use(
this.config.graphQLPath,
graphqlExpress(async req => await this._getGraphQLOptions(req))
);
}
applyPlayground(app) {
if (!app || !app.get) {
requiredParameter('You must provide an Express.js app instance!');
}
app.get(
this.config.playgroundPath ||
requiredParameter(
'You must provide a config.playgroundPath to applyPlayground!'
),
(_req, res) => {
res.setHeader('Content-Type', 'text/html');
res.write(
renderPlaygroundPage({
endpoint: this.config.graphQLPath,
subscriptionEndpoint: this.config.subscriptionsPath,
})
);
res.end();
}
);
}
createSubscriptions(server) {
SubscriptionServer.create(
{
execute,
subscribe,
onOperation: async (_message, params, webSocket) =>
Object.assign(
{},
params,
await this._getGraphQLOptions(webSocket.upgradeReq)
),
},
{
server,
path:
this.config.subscriptionsPath ||
requiredParameter(
'You must provide a config.subscriptionsPath to createSubscriptions!'
),
}
);
}
}
export { ParseGraphQLServer };

View File

@@ -0,0 +1,11 @@
import * as objectsMutations from './objectsMutations';
import * as filesMutations from './filesMutations';
import * as usersMutations from './usersMutations';
const load = parseGraphQLSchema => {
objectsMutations.load(parseGraphQLSchema);
filesMutations.load(parseGraphQLSchema);
usersMutations.load(parseGraphQLSchema);
};
export { load };

View File

@@ -0,0 +1,17 @@
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
import * as objectsQueries from './objectsQueries';
import * as usersQueries from './usersQueries';
const load = parseGraphQLSchema => {
parseGraphQLSchema.graphQLQueries.health = {
description:
'The health query can be used to check if the server is up and running.',
type: new GraphQLNonNull(GraphQLBoolean),
resolve: () => true,
};
objectsQueries.load(parseGraphQLSchema);
usersQueries.load(parseGraphQLSchema);
};
export { load };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
import { GraphQLObjectType, GraphQLNonNull } from 'graphql';
import { GraphQLUpload } from 'graphql-upload';
import Parse from 'parse/node';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import logger from '../../logger';
const load = parseGraphQLSchema => {
const fields = {};
fields.create = {
description:
'The create mutation can be used to create and upload a new file.',
args: {
file: {
description: 'This is the new file to be created and uploaded',
type: new GraphQLNonNull(GraphQLUpload),
},
},
type: new GraphQLNonNull(defaultGraphQLTypes.FILE_INFO),
async resolve(_source, args, context) {
try {
const { file } = args;
const { config } = context;
const { createReadStream, filename, mimetype } = await file;
let data = null;
if (createReadStream) {
const stream = createReadStream();
data = await new Promise((resolve, reject) => {
let data = '';
stream
.on('error', reject)
.on('data', chunk => (data += chunk))
.on('end', () => resolve(data));
});
}
if (!data || !data.length) {
throw new Parse.Error(
Parse.Error.FILE_SAVE_ERROR,
'Invalid file upload.'
);
}
if (filename.length > 128) {
throw new Parse.Error(
Parse.Error.INVALID_FILE_NAME,
'Filename too long.'
);
}
if (!filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
throw new Parse.Error(
Parse.Error.INVALID_FILE_NAME,
'Filename contains invalid characters.'
);
}
try {
return await config.filesController.createFile(
config,
filename,
data,
mimetype
);
} catch (e) {
logger.error('Error creating a file: ', e);
throw new Parse.Error(
Parse.Error.FILE_SAVE_ERROR,
`Could not store file: ${filename}.`
);
}
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const filesMutation = new GraphQLObjectType({
name: 'FilesMutation',
description: 'FilesMutation is the top level type for files mutations.',
fields,
});
parseGraphQLSchema.graphQLTypes.push(filesMutation);
parseGraphQLSchema.graphQLMutations.files = {
description: 'This is the top level for files mutations.',
type: filesMutation,
resolve: () => new Object(),
};
};
export { load };

View File

@@ -0,0 +1,148 @@
import { GraphQLNonNull, GraphQLBoolean, GraphQLObjectType } from 'graphql';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import rest from '../../rest';
const parseMap = {
_op: '__op',
};
const transformToParse = fields => {
if (!fields || typeof fields !== 'object') {
return;
}
Object.keys(fields).forEach(fieldName => {
const fieldValue = fields[fieldName];
if (parseMap[fieldName]) {
delete fields[fieldName];
fields[parseMap[fieldName]] = fieldValue;
}
if (typeof fieldValue === 'object') {
transformToParse(fieldValue);
}
});
};
const createObject = async (className, fields, config, auth, info) => {
if (!fields) {
fields = {};
}
transformToParse(fields);
return (await rest.create(config, auth, className, fields, info.clientSDK))
.response;
};
const updateObject = async (
className,
objectId,
fields,
config,
auth,
info
) => {
if (!fields) {
fields = {};
}
transformToParse(fields);
return (await rest.update(
config,
auth,
className,
{ objectId },
fields,
info.clientSDK
)).response;
};
const deleteObject = async (className, objectId, config, auth, info) => {
await rest.del(config, auth, className, objectId, info.clientSDK);
return true;
};
const load = parseGraphQLSchema => {
parseGraphQLSchema.graphQLObjectsMutations.create = {
description:
'The create mutation can be used to create a new object of a certain class.',
args: {
className: defaultGraphQLTypes.CLASS_NAME_ATT,
fields: defaultGraphQLTypes.FIELDS_ATT,
},
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
async resolve(_source, args, context) {
try {
const { className, fields } = args;
const { config, auth, info } = context;
return await createObject(className, fields, config, auth, info);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
parseGraphQLSchema.graphQLObjectsMutations.update = {
description:
'The update mutation can be used to update an object of a certain class.',
args: {
className: defaultGraphQLTypes.CLASS_NAME_ATT,
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
fields: defaultGraphQLTypes.FIELDS_ATT,
},
type: new GraphQLNonNull(defaultGraphQLTypes.UPDATE_RESULT),
async resolve(_source, args, context) {
try {
const { className, objectId, fields } = args;
const { config, auth, info } = context;
return await updateObject(
className,
objectId,
fields,
config,
auth,
info
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
parseGraphQLSchema.graphQLObjectsMutations.delete = {
description:
'The delete mutation can be used to delete an object of a certain class.',
args: {
className: defaultGraphQLTypes.CLASS_NAME_ATT,
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
},
type: new GraphQLNonNull(GraphQLBoolean),
async resolve(_source, args, context) {
try {
const { className, objectId } = args;
const { config, auth, info } = context;
return await deleteObject(className, objectId, config, auth, info);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const objectsMutation = new GraphQLObjectType({
name: 'ObjectsMutation',
description: 'ObjectsMutation is the top level type for objects mutations.',
fields: parseGraphQLSchema.graphQLObjectsMutations,
});
parseGraphQLSchema.graphQLTypes.push(objectsMutation);
parseGraphQLSchema.graphQLMutations.objects = {
description: 'This is the top level for objects mutations.',
type: objectsMutation,
resolve: () => new Object(),
};
};
export { createObject, updateObject, deleteObject, load };

View File

@@ -0,0 +1,367 @@
import {
GraphQLNonNull,
GraphQLBoolean,
GraphQLString,
GraphQLObjectType,
} from 'graphql';
import getFieldNames from 'graphql-list-fields';
import Parse from 'parse/node';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import rest from '../../rest';
const getObject = async (
className,
objectId,
keys,
include,
readPreference,
includeReadPreference,
config,
auth,
info
) => {
const options = {};
if (keys) {
options.keys = keys;
}
if (include) {
options.include = include;
if (includeReadPreference) {
options.includeReadPreference = includeReadPreference;
}
}
if (readPreference) {
options.readPreference = readPreference;
}
const response = await rest.get(
config,
auth,
className,
objectId,
options,
info.clientSDK
);
if (!response.results || response.results.length == 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
if (className === '_User') {
delete response.results[0].sessionToken;
}
return response.results[0];
};
const parseMap = {
_or: '$or',
_and: '$and',
_nor: '$nor',
_relatedTo: '$relatedTo',
_eq: '$eq',
_ne: '$ne',
_lt: '$lt',
_lte: '$lte',
_gt: '$gt',
_gte: '$gte',
_in: '$in',
_nin: '$nin',
_exists: '$exists',
_select: '$select',
_dontSelect: '$dontSelect',
_inQuery: '$inQuery',
_notInQuery: '$notInQuery',
_containedBy: '$containedBy',
_all: '$all',
_regex: '$regex',
_options: '$options',
_text: '$text',
_search: '$search',
_term: '$term',
_language: '$language',
_caseSensitive: '$caseSensitive',
_diacriticSensitive: '$diacriticSensitive',
_nearSphere: '$nearSphere',
_maxDistance: '$maxDistance',
_maxDistanceInRadians: '$maxDistanceInRadians',
_maxDistanceInMiles: '$maxDistanceInMiles',
_maxDistanceInKilometers: '$maxDistanceInKilometers',
_within: '$within',
_box: '$box',
_geoWithin: '$geoWithin',
_polygon: '$polygon',
_centerSphere: '$centerSphere',
_geoIntersects: '$geoIntersects',
_point: '$point',
};
const transformToParse = constraints => {
if (!constraints || typeof constraints !== 'object') {
return;
}
Object.keys(constraints).forEach(fieldName => {
let fieldValue = constraints[fieldName];
if (parseMap[fieldName]) {
delete constraints[fieldName];
fieldName = parseMap[fieldName];
constraints[fieldName] = fieldValue;
}
switch (fieldName) {
case '$point':
case '$nearSphere':
if (typeof fieldValue === 'object' && !fieldValue.__type) {
fieldValue.__type = 'GeoPoint';
}
break;
case '$box':
if (
typeof fieldValue === 'object' &&
fieldValue.bottomLeft &&
fieldValue.upperRight
) {
fieldValue = [
{
__type: 'GeoPoint',
...fieldValue.bottomLeft,
},
{
__type: 'GeoPoint',
...fieldValue.upperRight,
},
];
constraints[fieldName] = fieldValue;
}
break;
case '$polygon':
if (fieldValue instanceof Array) {
fieldValue.forEach(geoPoint => {
if (typeof geoPoint === 'object' && !geoPoint.__type) {
geoPoint.__type = 'GeoPoint';
}
});
}
break;
case '$centerSphere':
if (
typeof fieldValue === 'object' &&
fieldValue.center &&
fieldValue.distance
) {
fieldValue = [
{
__type: 'GeoPoint',
...fieldValue.center,
},
fieldValue.distance,
];
constraints[fieldName] = fieldValue;
}
break;
}
if (typeof fieldValue === 'object') {
transformToParse(fieldValue);
}
});
};
const findObjects = async (
className,
where,
order,
skip,
limit,
keys,
include,
includeAll,
readPreference,
includeReadPreference,
subqueryReadPreference,
config,
auth,
info,
selectedFields
) => {
if (!where) {
where = {};
}
transformToParse(where);
const options = {};
if (selectedFields.includes('results')) {
if (limit || limit === 0) {
options.limit = limit;
}
if (options.limit !== 0) {
if (order) {
options.order = order;
}
if (skip) {
options.skip = skip;
}
if (config.maxLimit && options.limit > config.maxLimit) {
// Silently replace the limit on the query with the max configured
options.limit = config.maxLimit;
}
if (keys) {
options.keys = keys;
}
if (includeAll === true) {
options.includeAll = includeAll;
}
if (!options.includeAll && include) {
options.include = include;
}
if ((options.includeAll || options.include) && includeReadPreference) {
options.includeReadPreference = includeReadPreference;
}
}
} else {
options.limit = 0;
}
if (selectedFields.includes('count')) {
options.count = true;
}
if (readPreference) {
options.readPreference = readPreference;
}
if (Object.keys(where).length > 0 && subqueryReadPreference) {
options.subqueryReadPreference = subqueryReadPreference;
}
return await rest.find(
config,
auth,
className,
where,
options,
info.clientSDK
);
};
const load = parseGraphQLSchema => {
parseGraphQLSchema.graphQLObjectsQueries.get = {
description:
'The get query can be used to get an object of a certain class by its objectId.',
args: {
className: defaultGraphQLTypes.CLASS_NAME_ATT,
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
keys: defaultGraphQLTypes.KEYS_ATT,
include: defaultGraphQLTypes.INCLUDE_ATT,
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
},
type: new GraphQLNonNull(defaultGraphQLTypes.OBJECT),
async resolve(_source, args, context) {
try {
const {
className,
objectId,
keys,
include,
readPreference,
includeReadPreference,
} = args;
const { config, auth, info } = context;
return await getObject(
className,
objectId,
keys,
include,
readPreference,
includeReadPreference,
config,
auth,
info
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
parseGraphQLSchema.graphQLObjectsQueries.find = {
description:
'The find query can be used to find objects of a certain class.',
args: {
className: defaultGraphQLTypes.CLASS_NAME_ATT,
where: defaultGraphQLTypes.WHERE_ATT,
order: {
description:
'This is the order in which the objects should be returned',
type: GraphQLString,
},
skip: defaultGraphQLTypes.SKIP_ATT,
limit: defaultGraphQLTypes.LIMIT_ATT,
keys: defaultGraphQLTypes.KEYS_ATT,
include: defaultGraphQLTypes.INCLUDE_ATT,
includeAll: {
description: 'All pointers will be returned',
type: GraphQLBoolean,
},
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
subqueryReadPreference: defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT,
},
type: new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT),
async resolve(_source, args, context, queryInfo) {
try {
const {
className,
where,
order,
skip,
limit,
keys,
include,
includeAll,
readPreference,
includeReadPreference,
subqueryReadPreference,
} = args;
const { config, auth, info } = context;
const selectedFields = getFieldNames(queryInfo);
return await findObjects(
className,
where,
order,
skip,
limit,
keys,
include,
includeAll,
readPreference,
includeReadPreference,
subqueryReadPreference,
config,
auth,
info,
selectedFields
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const objectsQuery = new GraphQLObjectType({
name: 'ObjectsQuery',
description: 'ObjectsQuery is the top level type for objects queries.',
fields: parseGraphQLSchema.graphQLObjectsQueries,
});
parseGraphQLSchema.graphQLTypes.push(objectsQuery);
parseGraphQLSchema.graphQLQueries.objects = {
description: 'This is the top level for objects queries.',
type: objectsQuery,
resolve: () => new Object(),
};
};
export { getObject, findObjects, load };

View File

@@ -0,0 +1,121 @@
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as objectsMutations from './objectsMutations';
const load = (parseGraphQLSchema, parseClass) => {
const className = parseClass.className;
const classGraphQLInputType =
parseGraphQLSchema.parseClassTypes[className].classGraphQLInputType;
const fields = {
description: 'These are the fields of the object.',
type: classGraphQLInputType,
};
const classGraphQLInputTypeFields = classGraphQLInputType.getFields();
const transformTypes = fields => {
if (fields) {
Object.keys(fields).forEach(field => {
if (classGraphQLInputTypeFields[field]) {
switch (classGraphQLInputTypeFields[field].type) {
case defaultGraphQLTypes.GEO_POINT:
fields[field].__type = 'GeoPoint';
break;
case defaultGraphQLTypes.POLYGON:
fields[field] = {
__type: 'Polygon',
coordinates: fields[field].map(geoPoint => [
geoPoint.latitude,
geoPoint.longitude,
]),
};
break;
}
}
});
}
};
const createGraphQLMutationName = `create${className}`;
parseGraphQLSchema.graphQLObjectsMutations[createGraphQLMutationName] = {
description: `The ${createGraphQLMutationName} mutation can be used to create a new object of the ${className} class.`,
args: {
fields,
},
type: new GraphQLNonNull(defaultGraphQLTypes.CREATE_RESULT),
async resolve(_source, args, context) {
try {
const { fields } = args;
const { config, auth, info } = context;
transformTypes(fields);
return await objectsMutations.createObject(
className,
fields,
config,
auth,
info
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const updateGraphQLMutationName = `update${className}`;
parseGraphQLSchema.graphQLObjectsMutations[updateGraphQLMutationName] = {
description: `The ${updateGraphQLMutationName} mutation can be used to update an object of the ${className} class.`,
args: {
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
fields,
},
type: defaultGraphQLTypes.UPDATE_RESULT,
async resolve(_source, args, context) {
try {
const { objectId, fields } = args;
const { config, auth, info } = context;
transformTypes(fields);
return await objectsMutations.updateObject(
className,
objectId,
fields,
config,
auth,
info
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const deleteGraphQLMutationName = `delete${className}`;
parseGraphQLSchema.graphQLObjectsMutations[deleteGraphQLMutationName] = {
description: `The ${deleteGraphQLMutationName} mutation can be used to delete an object of the ${className} class.`,
args: {
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
},
type: new GraphQLNonNull(GraphQLBoolean),
async resolve(_source, args, context) {
try {
const { objectId } = args;
const { config, auth, info } = context;
return await objectsMutations.deleteObject(
className,
objectId,
config,
auth,
info
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
};
export { load };

View File

@@ -0,0 +1,102 @@
import { GraphQLNonNull } from 'graphql';
import getFieldNames from 'graphql-list-fields';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as objectsQueries from './objectsQueries';
import * as parseClassTypes from './parseClassTypes';
const load = (parseGraphQLSchema, parseClass) => {
const className = parseClass.className;
const {
classGraphQLOutputType,
classGraphQLFindArgs,
classGraphQLFindResultType,
} = parseGraphQLSchema.parseClassTypes[className];
const getGraphQLQueryName = `get${className}`;
parseGraphQLSchema.graphQLObjectsQueries[getGraphQLQueryName] = {
description: `The ${getGraphQLQueryName} query can be used to get an object of the ${className} class by its id.`,
args: {
objectId: defaultGraphQLTypes.OBJECT_ID_ATT,
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
},
type: new GraphQLNonNull(classGraphQLOutputType),
async resolve(_source, args, context, queryInfo) {
try {
const { objectId, readPreference, includeReadPreference } = args;
const { config, auth, info } = context;
const selectedFields = getFieldNames(queryInfo);
const { keys, include } = parseClassTypes.extractKeysAndInclude(
selectedFields
);
return await objectsQueries.getObject(
className,
objectId,
keys,
include,
readPreference,
includeReadPreference,
config,
auth,
info
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const findGraphQLQueryName = `find${className}`;
parseGraphQLSchema.graphQLObjectsQueries[findGraphQLQueryName] = {
description: `The ${findGraphQLQueryName} query can be used to find objects of the ${className} class.`,
args: classGraphQLFindArgs,
type: new GraphQLNonNull(classGraphQLFindResultType),
async resolve(_source, args, context, queryInfo) {
try {
const {
where,
order,
skip,
limit,
readPreference,
includeReadPreference,
subqueryReadPreference,
} = args;
const { config, auth, info } = context;
const selectedFields = getFieldNames(queryInfo);
const { keys, include } = parseClassTypes.extractKeysAndInclude(
selectedFields
.filter(field => field.includes('.'))
.map(field => field.slice(field.indexOf('.') + 1))
);
const parseOrder = order && order.join(',');
return await objectsQueries.findObjects(
className,
where,
parseOrder,
skip,
limit,
keys,
include,
false,
readPreference,
includeReadPreference,
subqueryReadPreference,
config,
auth,
info,
selectedFields.map(field => field.split('.', 1)[0])
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
};
export { load };

View File

@@ -0,0 +1,557 @@
import {
Kind,
GraphQLObjectType,
GraphQLString,
GraphQLFloat,
GraphQLBoolean,
GraphQLList,
GraphQLInputObjectType,
GraphQLNonNull,
GraphQLScalarType,
GraphQLEnumType,
} from 'graphql';
import getFieldNames from 'graphql-list-fields';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as objectsQueries from './objectsQueries';
const mapInputType = (parseType, targetClass, parseClassTypes) => {
switch (parseType) {
case 'String':
return GraphQLString;
case 'Number':
return GraphQLFloat;
case 'Boolean':
return GraphQLBoolean;
case 'Array':
return new GraphQLList(defaultGraphQLTypes.ANY);
case 'Object':
return defaultGraphQLTypes.OBJECT;
case 'Date':
return defaultGraphQLTypes.DATE;
case 'Pointer':
if (parseClassTypes[targetClass]) {
return parseClassTypes[targetClass].classGraphQLScalarType;
} else {
return defaultGraphQLTypes.OBJECT;
}
case 'Relation':
if (parseClassTypes[targetClass]) {
return parseClassTypes[targetClass].classGraphQLRelationOpType;
} else {
return defaultGraphQLTypes.OBJECT;
}
case 'File':
return defaultGraphQLTypes.FILE;
case 'GeoPoint':
return defaultGraphQLTypes.GEO_POINT;
case 'Polygon':
return defaultGraphQLTypes.POLYGON;
case 'Bytes':
return defaultGraphQLTypes.BYTES;
case 'ACL':
return defaultGraphQLTypes.OBJECT;
default:
return undefined;
}
};
const mapOutputType = (parseType, targetClass, parseClassTypes) => {
switch (parseType) {
case 'String':
return GraphQLString;
case 'Number':
return GraphQLFloat;
case 'Boolean':
return GraphQLBoolean;
case 'Array':
return new GraphQLList(defaultGraphQLTypes.ANY);
case 'Object':
return defaultGraphQLTypes.OBJECT;
case 'Date':
return defaultGraphQLTypes.DATE;
case 'Pointer':
if (parseClassTypes[targetClass]) {
return parseClassTypes[targetClass].classGraphQLOutputType;
} else {
return defaultGraphQLTypes.OBJECT;
}
case 'Relation':
if (parseClassTypes[targetClass]) {
return new GraphQLNonNull(
parseClassTypes[targetClass].classGraphQLFindResultType
);
} else {
return new GraphQLNonNull(defaultGraphQLTypes.FIND_RESULT);
}
case 'File':
return defaultGraphQLTypes.FILE_INFO;
case 'GeoPoint':
return defaultGraphQLTypes.GEO_POINT_INFO;
case 'Polygon':
return defaultGraphQLTypes.POLYGON_INFO;
case 'Bytes':
return defaultGraphQLTypes.BYTES;
case 'ACL':
return defaultGraphQLTypes.OBJECT;
default:
return undefined;
}
};
const mapConstraintType = (parseType, targetClass, parseClassTypes) => {
switch (parseType) {
case 'String':
return defaultGraphQLTypes.STRING_CONSTRAINT;
case 'Number':
return defaultGraphQLTypes.NUMBER_CONSTRAINT;
case 'Boolean':
return defaultGraphQLTypes.BOOLEAN_CONSTRAINT;
case 'Array':
return defaultGraphQLTypes.ARRAY_CONSTRAINT;
case 'Object':
return defaultGraphQLTypes.OBJECT_CONSTRAINT;
case 'Date':
return defaultGraphQLTypes.DATE_CONSTRAINT;
case 'Pointer':
if (parseClassTypes[targetClass]) {
return parseClassTypes[targetClass].classGraphQLConstraintType;
} else {
return defaultGraphQLTypes.OBJECT;
}
case 'File':
return defaultGraphQLTypes.FILE_CONSTRAINT;
case 'GeoPoint':
return defaultGraphQLTypes.GEO_POINT_CONSTRAINT;
case 'Polygon':
return defaultGraphQLTypes.POLYGON_CONSTRAINT;
case 'Bytes':
return defaultGraphQLTypes.BYTES_CONSTRAINT;
case 'ACL':
return defaultGraphQLTypes.OBJECT_CONSTRAINT;
case 'Relation':
default:
return undefined;
}
};
const extractKeysAndInclude = selectedFields => {
selectedFields = selectedFields.filter(
field => !field.includes('__typename')
);
let keys = undefined;
let include = undefined;
if (selectedFields && selectedFields.length > 0) {
keys = selectedFields.join(',');
include = selectedFields
.reduce((fields, field) => {
fields = fields.slice();
let pointIndex = field.lastIndexOf('.');
while (pointIndex > 0) {
const lastField = field.slice(pointIndex + 1);
field = field.slice(0, pointIndex);
if (!fields.includes(field) && lastField !== 'objectId') {
fields.push(field);
}
pointIndex = field.lastIndexOf('.');
}
return fields;
}, [])
.join(',');
}
return { keys, include };
};
const load = (parseGraphQLSchema, parseClass) => {
const className = parseClass.className;
const classFields = Object.keys(parseClass.fields);
const classCustomFields = classFields.filter(
field => !Object.keys(defaultGraphQLTypes.CLASS_FIELDS).includes(field)
);
const classGraphQLScalarTypeName = `${className}Pointer`;
const parseScalarValue = value => {
if (typeof value === 'string') {
return {
__type: 'Pointer',
className,
objectId: value,
};
} else if (
typeof value === 'object' &&
value.__type === 'Pointer' &&
value.className === className &&
typeof value.objectId === 'string'
) {
return value;
}
throw new defaultGraphQLTypes.TypeValidationError(
value,
classGraphQLScalarTypeName
);
};
const classGraphQLScalarType = new GraphQLScalarType({
name: classGraphQLScalarTypeName,
description: `The ${classGraphQLScalarTypeName} is used in operations that involve ${className} pointers.`,
parseValue: parseScalarValue,
serialize(value) {
if (typeof value === 'string') {
return value;
} else if (
typeof value === 'object' &&
value.__type === 'Pointer' &&
value.className === className &&
typeof value.objectId === 'string'
) {
return value.objectId;
}
throw new defaultGraphQLTypes.TypeValidationError(
value,
classGraphQLScalarTypeName
);
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return parseScalarValue(ast.value);
} else if (ast.kind === Kind.OBJECT) {
const __type = ast.fields.find(field => field.name.value === '__type');
const className = ast.fields.find(
field => field.name.value === 'className'
);
const objectId = ast.fields.find(
field => field.name.value === 'objectId'
);
if (
__type &&
__type.value &&
className &&
className.value &&
objectId &&
objectId.value
) {
return parseScalarValue({
__type: __type.value.value,
className: className.value.value,
objectId: objectId.value.value,
});
}
}
throw new defaultGraphQLTypes.TypeValidationError(
ast.kind,
classGraphQLScalarTypeName
);
},
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLScalarType);
const classGraphQLRelationOpTypeName = `${className}RelationOp`;
const classGraphQLRelationOpType = new GraphQLInputObjectType({
name: classGraphQLRelationOpTypeName,
description: `The ${classGraphQLRelationOpTypeName} input type is used in operations that involve relations with the ${className} class.`,
fields: () => ({
_op: {
description: 'This is the operation to be executed.',
type: new GraphQLNonNull(defaultGraphQLTypes.RELATION_OP),
},
ops: {
description:
'In the case of a Batch operation, this is the list of operations to be executed.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLRelationOpType)),
},
objects: {
description:
'In the case of a AddRelation or RemoveRelation operation, this is the list of objects to be added/removed.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLScalarType)),
},
}),
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLRelationOpType);
const classGraphQLInputTypeName = `${className}Fields`;
const classGraphQLInputType = new GraphQLInputObjectType({
name: classGraphQLInputTypeName,
description: `The ${classGraphQLInputTypeName} input type is used in operations that involve inputting objects of ${className} class.`,
fields: () =>
classCustomFields.reduce(
(fields, field) => {
const type = mapInputType(
parseClass.fields[field].type,
parseClass.fields[field].targetClass,
parseGraphQLSchema.parseClassTypes
);
if (type) {
return {
...fields,
[field]: {
description: `This is the object ${field}.`,
type,
},
};
} else {
return fields;
}
},
{
ACL: defaultGraphQLTypes.ACL_ATT,
}
),
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLInputType);
const classGraphQLConstraintTypeName = `${className}PointerConstraint`;
const classGraphQLConstraintType = new GraphQLInputObjectType({
name: classGraphQLConstraintTypeName,
description: `The ${classGraphQLConstraintTypeName} input type is used in operations that involve filtering objects by a pointer field to ${className} class.`,
fields: {
_eq: defaultGraphQLTypes._eq(classGraphQLScalarType),
_ne: defaultGraphQLTypes._ne(classGraphQLScalarType),
_in: defaultGraphQLTypes._in(classGraphQLScalarType),
_nin: defaultGraphQLTypes._nin(classGraphQLScalarType),
_exists: defaultGraphQLTypes._exists,
_select: defaultGraphQLTypes._select,
_dontSelect: defaultGraphQLTypes._dontSelect,
_inQuery: {
description:
'This is the $inQuery operator to specify a constraint to select the objects where a field equals to any of the ids in the result of a different query.',
type: defaultGraphQLTypes.SUBQUERY,
},
_notInQuery: {
description:
'This is the $notInQuery operator to specify a constraint to select the objects where a field do not equal to any of the ids in the result of a different query.',
type: defaultGraphQLTypes.SUBQUERY,
},
},
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLConstraintType);
const classGraphQLConstraintsTypeName = `${className}Constraints`;
const classGraphQLConstraintsType = new GraphQLInputObjectType({
name: classGraphQLConstraintsTypeName,
description: `The ${classGraphQLConstraintsTypeName} input type is used in operations that involve filtering objects of ${className} class.`,
fields: () => ({
...classFields.reduce((fields, field) => {
const type = mapConstraintType(
parseClass.fields[field].type,
parseClass.fields[field].targetClass,
parseGraphQLSchema.parseClassTypes
);
if (type) {
return {
...fields,
[field]: {
description: `This is the object ${field}.`,
type,
},
};
} else {
return fields;
}
}, {}),
_or: {
description: 'This is the $or operator to compound constraints.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLConstraintsType)),
},
_and: {
description: 'This is the $and operator to compound constraints.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLConstraintsType)),
},
_nor: {
description: 'This is the $nor operator to compound constraints.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLConstraintsType)),
},
}),
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLConstraintsType);
const classGraphQLOrderTypeName = `${className}Order`;
const classGraphQLOrderType = new GraphQLEnumType({
name: classGraphQLOrderTypeName,
description: `The ${classGraphQLOrderTypeName} input type is used when sorting objects of the ${className} class.`,
values: classFields.reduce((orderFields, field) => {
return {
...orderFields,
[`${field}_ASC`]: { value: field },
[`${field}_DESC`]: { value: `-${field}` },
};
}, {}),
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLOrderType);
const classGraphQLFindArgs = {
where: {
description:
'These are the conditions that the objects need to match in order to be found.',
type: classGraphQLConstraintsType,
},
order: {
description: 'The fields to be used when sorting the data fetched.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLOrderType)),
},
skip: defaultGraphQLTypes.SKIP_ATT,
limit: defaultGraphQLTypes.LIMIT_ATT,
readPreference: defaultGraphQLTypes.READ_PREFERENCE_ATT,
includeReadPreference: defaultGraphQLTypes.INCLUDE_READ_PREFERENCE_ATT,
subqueryReadPreference: defaultGraphQLTypes.SUBQUERY_READ_PREFERENCE_ATT,
};
const classGraphQLOutputTypeName = `${className}Class`;
const outputFields = () => {
return classCustomFields.reduce((fields, field) => {
const type = mapOutputType(
parseClass.fields[field].type,
parseClass.fields[field].targetClass,
parseGraphQLSchema.parseClassTypes
);
if (parseClass.fields[field].type === 'Relation') {
const targetParseClassTypes =
parseGraphQLSchema.parseClassTypes[
parseClass.fields[field].targetClass
];
const args = targetParseClassTypes
? targetParseClassTypes.classGraphQLFindArgs
: undefined;
return {
...fields,
[field]: {
description: `This is the object ${field}.`,
args,
type,
async resolve(source, args, context, queryInfo) {
try {
const {
where,
order,
skip,
limit,
readPreference,
includeReadPreference,
subqueryReadPreference,
} = args;
const { config, auth, info } = context;
const selectedFields = getFieldNames(queryInfo);
const { keys, include } = extractKeysAndInclude(
selectedFields
.filter(field => field.includes('.'))
.map(field => field.slice(field.indexOf('.') + 1))
);
return await objectsQueries.findObjects(
source[field].className,
{
_relatedTo: {
object: {
__type: 'Pointer',
className,
objectId: source.objectId,
},
key: field,
},
...(where || {}),
},
order,
skip,
limit,
keys,
include,
false,
readPreference,
includeReadPreference,
subqueryReadPreference,
config,
auth,
info,
selectedFields.map(field => field.split('.', 1)[0])
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
},
};
} else if (parseClass.fields[field].type === 'Polygon') {
return {
...fields,
[field]: {
description: `This is the object ${field}.`,
type,
async resolve(source) {
if (source[field] && source[field].coordinates) {
return source[field].coordinates.map(coordinate => ({
latitude: coordinate[0],
longitude: coordinate[1],
}));
} else {
return null;
}
},
},
};
} else if (type) {
return {
...fields,
[field]: {
description: `This is the object ${field}.`,
type,
},
};
} else {
return fields;
}
}, defaultGraphQLTypes.CLASS_FIELDS);
};
const classGraphQLOutputType = new GraphQLObjectType({
name: classGraphQLOutputTypeName,
description: `The ${classGraphQLOutputTypeName} object type is used in operations that involve outputting objects of ${className} class.`,
interfaces: [defaultGraphQLTypes.CLASS],
fields: outputFields,
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLOutputType);
const classGraphQLFindResultTypeName = `${className}FindResult`;
const classGraphQLFindResultType = new GraphQLObjectType({
name: classGraphQLFindResultTypeName,
description: `The ${classGraphQLFindResultTypeName} object type is used in the ${className} find query to return the data of the matched objects.`,
fields: {
results: {
description: 'This is the objects returned by the query',
type: new GraphQLNonNull(
new GraphQLList(new GraphQLNonNull(classGraphQLOutputType))
),
},
count: defaultGraphQLTypes.COUNT_ATT,
},
});
parseGraphQLSchema.graphQLTypes.push(classGraphQLFindResultType);
parseGraphQLSchema.parseClassTypes[className] = {
classGraphQLScalarType,
classGraphQLRelationOpType,
classGraphQLInputType,
classGraphQLConstraintType,
classGraphQLConstraintsType,
classGraphQLFindArgs,
classGraphQLOutputType,
classGraphQLFindResultType,
};
if (className === '_User') {
const meType = new GraphQLObjectType({
name: 'Me',
description: `The Me object type is used in operations that involve outputting the current user data.`,
interfaces: [defaultGraphQLTypes.CLASS],
fields: () => ({
...outputFields(),
sessionToken: defaultGraphQLTypes.SESSION_TOKEN_ATT,
}),
});
parseGraphQLSchema.meType = meType;
parseGraphQLSchema.graphQLTypes.push(meType);
}
};
export { extractKeysAndInclude, load };

View File

@@ -0,0 +1,110 @@
import {
GraphQLBoolean,
GraphQLNonNull,
GraphQLObjectType,
GraphQLString,
} from 'graphql';
import UsersRouter from '../../Routers/UsersRouter';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as objectsMutations from './objectsMutations';
const usersRouter = new UsersRouter();
const load = parseGraphQLSchema => {
const fields = {};
fields.signUp = {
description: 'The signUp mutation can be used to sign the user up.',
args: {
fields: {
descriptions: 'These are the fields of the user.',
type: parseGraphQLSchema.parseClassTypes['_User'].classGraphQLInputType,
},
},
type: new GraphQLNonNull(defaultGraphQLTypes.SIGN_UP_RESULT),
async resolve(_source, args, context) {
try {
const { fields } = args;
const { config, auth, info } = context;
return await objectsMutations.createObject(
'_User',
fields,
config,
auth,
info
);
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
fields.logIn = {
description: 'The logIn mutation can be used to log the user in.',
args: {
username: {
description: 'This is the username used to log the user in.',
type: new GraphQLNonNull(GraphQLString),
},
password: {
description: 'This is the password used to log the user in.',
type: new GraphQLNonNull(GraphQLString),
},
},
type: new GraphQLNonNull(parseGraphQLSchema.meType),
async resolve(_source, args, context) {
try {
const { username, password } = args;
const { config, auth, info } = context;
return (await usersRouter.handleLogIn({
body: {
username,
password,
},
query: {},
config,
auth,
info,
})).response;
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
fields.logOut = {
description: 'The logOut mutation can be used to log the user out.',
type: new GraphQLNonNull(GraphQLBoolean),
async resolve(_source, _args, context) {
try {
const { config, auth, info } = context;
await usersRouter.handleLogOut({
config,
auth,
info,
});
return true;
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const usersMutation = new GraphQLObjectType({
name: 'UsersMutation',
description: 'UsersMutation is the top level type for files mutations.',
fields,
});
parseGraphQLSchema.graphQLTypes.push(usersMutation);
parseGraphQLSchema.graphQLMutations.users = {
description: 'This is the top level for users mutations.',
type: usersMutation,
resolve: () => new Object(),
};
};
export { load };

View File

@@ -0,0 +1,36 @@
import { GraphQLNonNull, GraphQLObjectType } from 'graphql';
import UsersRouter from '../../Routers/UsersRouter';
const usersRouter = new UsersRouter();
const load = parseGraphQLSchema => {
const fields = {};
fields.me = {
description: 'The Me query can be used to return the current user data.',
type: new GraphQLNonNull(parseGraphQLSchema.meType),
async resolve(_source, _args, context) {
try {
const { config, auth, info } = context;
return (await usersRouter.handleMe({ config, auth, info })).response;
} catch (e) {
parseGraphQLSchema.handleError(e);
}
},
};
const usersQuery = new GraphQLObjectType({
name: 'UsersQuery',
description: 'UsersQuery is the top level type for users queries.',
fields,
});
parseGraphQLSchema.graphQLTypes.push(usersQuery);
parseGraphQLSchema.graphQLQueries.users = {
description: 'This is the top level for users queries.',
type: usersQuery,
resolve: () => new Object(),
};
};
export { load };