2017-11-21 18:33:52 +09:00
|
|
|
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
|
2017-02-06 14:33:34 +08:00
|
|
|
import bodyParser from 'body-parser';
|
|
|
|
import express from 'express';
|
2017-02-12 22:00:13 +08:00
|
|
|
import { makeExecutableSchema } from 'graphql-tools';
|
2017-02-06 14:33:34 +08:00
|
|
|
import deepmerge from 'deepmerge';
|
2017-04-15 12:03:25 +09:00
|
|
|
import DataLoader from 'dataloader';
|
2017-06-30 11:18:16 +09:00
|
|
|
import { formatError } from 'apollo-errors';
|
2017-11-21 10:03:11 +09:00
|
|
|
import compression from 'compression';
|
2017-02-06 14:33:34 +08:00
|
|
|
import { Meteor } from 'meteor/meteor';
|
|
|
|
import { check } from 'meteor/check';
|
|
|
|
import { Accounts } from 'meteor/accounts-base';
|
2017-11-21 10:03:11 +09:00
|
|
|
import { Engine } from 'apollo-engine';
|
2017-02-06 14:33:34 +08:00
|
|
|
|
2017-07-03 10:54:10 +09:00
|
|
|
import { GraphQLSchema } from '../modules/graphql.js';
|
|
|
|
import { Utils } from '../modules/utils.js';
|
2017-03-11 11:17:50 +08:00
|
|
|
import { webAppConnectHandlersUse } from './meteor_patch.js';
|
2017-02-06 14:33:34 +08:00
|
|
|
|
2017-11-21 10:03:11 +09:00
|
|
|
import { getSetting } from '../modules/settings.js';
|
2017-04-15 12:03:25 +09:00
|
|
|
import { Collections } from '../modules/collections.js';
|
|
|
|
import findByIds from '../modules/findbyids.js';
|
2017-08-29 17:57:04 +09:00
|
|
|
import { runCallbacks } from '../modules/callbacks.js';
|
2017-04-15 12:03:25 +09:00
|
|
|
|
2017-08-19 16:13:01 +09:00
|
|
|
export let executableSchema;
|
|
|
|
|
2017-11-21 10:03:11 +09:00
|
|
|
const engineApiKey = getSetting('apolloEngine.apiKey');
|
|
|
|
let engine;
|
|
|
|
if (engineApiKey) {
|
|
|
|
engine = new Engine({ engineConfig: { apiKey: engineApiKey } });
|
|
|
|
engine.start();
|
|
|
|
}
|
|
|
|
|
2017-02-12 22:00:13 +08:00
|
|
|
// defaults
|
2017-02-06 14:33:34 +08:00
|
|
|
const defaultConfig = {
|
|
|
|
path: '/graphql',
|
|
|
|
maxAccountsCacheSizeInMB: 1,
|
2017-02-12 22:00:13 +08:00
|
|
|
graphiql: Meteor.isDevelopment,
|
|
|
|
graphiqlPath: '/graphiql',
|
|
|
|
graphiqlOptions: {
|
|
|
|
passHeader: "'Authorization': localStorage['Meteor.loginToken']", // eslint-disable-line quotes
|
2017-02-06 14:33:34 +08:00
|
|
|
},
|
|
|
|
configServer: (graphQLServer) => {},
|
|
|
|
};
|
|
|
|
|
|
|
|
const defaultOptions = {
|
|
|
|
formatError: e => ({
|
|
|
|
message: e.message,
|
|
|
|
locations: e.locations,
|
2017-02-12 22:00:13 +08:00
|
|
|
path: e.path,
|
2017-02-06 14:33:34 +08:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
2017-02-12 22:00:13 +08:00
|
|
|
if (Meteor.isDevelopment) {
|
|
|
|
defaultOptions.debug = true;
|
|
|
|
}
|
2017-02-06 14:33:34 +08:00
|
|
|
|
2017-02-12 22:00:13 +08:00
|
|
|
// createApolloServer
|
|
|
|
const createApolloServer = (givenOptions = {}, givenConfig = {}) => {
|
|
|
|
const graphiqlOptions = { ...defaultConfig.graphiqlOptions, ...givenConfig.graphiqlOptions };
|
|
|
|
const config = { ...defaultConfig, ...givenConfig };
|
2017-02-06 14:33:34 +08:00
|
|
|
config.graphiqlOptions = graphiqlOptions;
|
|
|
|
|
|
|
|
const graphQLServer = express();
|
|
|
|
|
2017-02-12 22:00:13 +08:00
|
|
|
config.configServer(graphQLServer);
|
2017-02-06 14:33:34 +08:00
|
|
|
|
2017-11-21 10:03:11 +09:00
|
|
|
// Use Engine middleware
|
|
|
|
if (engineApiKey) {
|
|
|
|
graphQLServer.use(engine.expressMiddleware());
|
|
|
|
}
|
|
|
|
|
|
|
|
// compression
|
|
|
|
graphQLServer.use(compression());
|
|
|
|
|
2017-02-06 14:33:34 +08:00
|
|
|
// GraphQL endpoint
|
2017-03-11 11:17:50 +08:00
|
|
|
graphQLServer.use(config.path, bodyParser.json(), graphqlExpress(async (req) => {
|
2017-02-12 22:00:13 +08:00
|
|
|
let options;
|
|
|
|
let user = null;
|
2017-02-06 14:33:34 +08:00
|
|
|
|
2017-02-12 22:00:13 +08:00
|
|
|
if (typeof givenOptions === 'function') {
|
2017-02-06 14:33:34 +08:00
|
|
|
options = givenOptions(req);
|
2017-02-12 22:00:13 +08:00
|
|
|
} else {
|
2017-02-06 14:33:34 +08:00
|
|
|
options = givenOptions;
|
2017-02-12 22:00:13 +08:00
|
|
|
}
|
2017-02-06 14:33:34 +08:00
|
|
|
|
|
|
|
// Merge in the defaults
|
2017-02-12 22:00:13 +08:00
|
|
|
options = { ...defaultOptions, ...options };
|
2017-02-06 14:33:34 +08:00
|
|
|
if (options.context) {
|
|
|
|
// don't mutate the context provided in options
|
2017-02-12 22:00:13 +08:00
|
|
|
options.context = { ...options.context };
|
2017-02-06 14:33:34 +08:00
|
|
|
} else {
|
|
|
|
options.context = {};
|
|
|
|
}
|
|
|
|
|
2017-11-21 18:33:52 +09:00
|
|
|
// enable tracing and caching
|
|
|
|
options.tracing = true;
|
|
|
|
options.cacheControl = true;
|
2017-02-06 14:33:34 +08:00
|
|
|
|
|
|
|
// Get the token from the header
|
|
|
|
if (req.headers.authorization) {
|
|
|
|
const token = req.headers.authorization;
|
|
|
|
check(token, String);
|
|
|
|
const hashedToken = Accounts._hashLoginToken(token);
|
|
|
|
|
|
|
|
// Get the user from the database
|
2017-02-12 22:00:13 +08:00
|
|
|
user = await Meteor.users.findOne(
|
|
|
|
{ 'services.resume.loginTokens.hashedToken': hashedToken },
|
|
|
|
);
|
2017-02-06 14:33:34 +08:00
|
|
|
|
|
|
|
if (user) {
|
2017-02-12 22:00:13 +08:00
|
|
|
const loginToken = Utils.findWhere(user.services.resume.loginTokens, { hashedToken });
|
|
|
|
const expiresAt = Accounts._tokenExpiration(loginToken.when);
|
2017-02-06 14:33:34 +08:00
|
|
|
const isExpired = expiresAt < new Date();
|
|
|
|
|
|
|
|
if (!isExpired) {
|
|
|
|
options.context.userId = user._id;
|
|
|
|
options.context.currentUser = user;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-15 12:03:25 +09:00
|
|
|
// merge with custom context
|
2017-02-06 14:33:34 +08:00
|
|
|
options.context = deepmerge(options.context, GraphQLSchema.context);
|
|
|
|
|
2017-04-15 12:03:25 +09:00
|
|
|
// go over context and add Dataloader to each collection
|
|
|
|
Collections.forEach(collection => {
|
|
|
|
options.context[collection.options.collectionName].loader = new DataLoader(ids => findByIds(collection, ids, options.context), { cache: true });
|
|
|
|
});
|
|
|
|
|
2017-06-30 11:18:16 +09:00
|
|
|
// add error formatting from apollo-errors
|
|
|
|
options.formatError = formatError;
|
|
|
|
|
2017-02-06 14:33:34 +08:00
|
|
|
return options;
|
2017-03-11 11:17:50 +08:00
|
|
|
}));
|
2017-02-06 14:33:34 +08:00
|
|
|
|
|
|
|
// Start GraphiQL if enabled
|
|
|
|
if (config.graphiql) {
|
2017-03-11 11:17:50 +08:00
|
|
|
graphQLServer.use(config.graphiqlPath, graphiqlExpress({ ...config.graphiqlOptions, endpointURL: config.path }));
|
2017-02-06 14:33:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// This binds the specified paths to the Express server running Apollo + GraphiQL
|
2017-03-11 11:17:50 +08:00
|
|
|
webAppConnectHandlersUse(Meteor.bindEnvironment(graphQLServer), {
|
2017-02-12 22:00:13 +08:00
|
|
|
name: 'graphQLServerMiddleware_bindEnvironment',
|
|
|
|
order: 30,
|
|
|
|
});
|
2017-02-06 14:33:34 +08:00
|
|
|
};
|
|
|
|
|
2017-02-12 22:00:13 +08:00
|
|
|
// createApolloServer when server startup
|
|
|
|
Meteor.startup(() => {
|
|
|
|
|
2017-08-29 17:57:04 +09:00
|
|
|
runCallbacks('graphql.init.before');
|
|
|
|
|
2017-02-12 22:00:13 +08:00
|
|
|
// typeDefs
|
|
|
|
const generateTypeDefs = () => [`
|
|
|
|
scalar JSON
|
|
|
|
scalar Date
|
|
|
|
|
|
|
|
${GraphQLSchema.getCollectionsSchemas()}
|
|
|
|
${GraphQLSchema.getAdditionalSchemas()}
|
|
|
|
|
|
|
|
type Query {
|
|
|
|
${GraphQLSchema.queries.join('\n')}
|
|
|
|
}
|
|
|
|
|
|
|
|
${GraphQLSchema.mutations.length > 0 ? `
|
|
|
|
type Mutation {
|
|
|
|
${GraphQLSchema.mutations.join('\n')}
|
|
|
|
}
|
|
|
|
` : ''}
|
|
|
|
`];
|
|
|
|
|
2017-02-06 14:33:34 +08:00
|
|
|
const typeDefs = generateTypeDefs();
|
|
|
|
|
|
|
|
GraphQLSchema.finalSchema = typeDefs;
|
|
|
|
|
2017-08-19 16:13:01 +09:00
|
|
|
executableSchema = makeExecutableSchema({
|
2017-02-06 14:33:34 +08:00
|
|
|
typeDefs,
|
|
|
|
resolvers: GraphQLSchema.resolvers,
|
|
|
|
});
|
|
|
|
|
|
|
|
createApolloServer({
|
2017-08-19 16:13:01 +09:00
|
|
|
schema: executableSchema,
|
2017-02-06 14:33:34 +08:00
|
|
|
});
|
|
|
|
});
|