Skip to content

Commit

Permalink
add apollo graphql to graphql branch (#159) (#173)
Browse files Browse the repository at this point in the history
* add graphql to graphql branch (#159)

* graphql

* update

* update

* update

* update

* update

* update per review comments

* update

* update

* update apollo and graphql package vesions

* update

* update

* update

* update

* update

* update

* bump package.json versions

* update

* update

* update

* update

* update

* update

* update

* update

* update

* share port 3333 between apollo and reset api

* fix lint

* update

* add timeout value for stream init

* temporarily skip vulnerability 1217 since it is impact dev not prod until a fix is available

* add message type of resource

* bump versions and fix a timeout value

* update

* update

* fix package.json

* remove port 8000

* enable steaming topic sharding for apollo

* update
  • Loading branch information
yingwang-us authored Mar 5, 2020
1 parent 6985dfa commit 0206ebd
Show file tree
Hide file tree
Showing 95 changed files with 9,186 additions and 1,141 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ build-tools/
.nyc_output/
coverage/
test/
compose/
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Local files
local
/local

# Logs
logs
Expand Down
9 changes: 7 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
language: node_js
node_js:
- "11"
- "lts/*"

services:
- docker

script:
# Audit npm packages. Fail build whan a PR audit fails, otherwise report the vulnerability and proceed.
- if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then npx audit-ci --low; else npx audit-ci --low || true; fi
# since mongodb-memory-server is only used for api test under dev and not included prod, temporarily mark it
# off until https://github.com/kevva/decompress/issues/71 and https://github.com/kevva/decompress/pull/73
# fixed/merged.
- if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then npx audit-ci --low -a 1217 -w mongodb-memory-server; else npx audit-ci --low || true; fi
- npm run lint
- npm test
- npm run test:apollo:local
- npm run test:apollo:passport.local
- docker build --rm -t "quay.io/razee/razeedash-api:${TRAVIS_COMMIT}" .
- if [ -n "${TRAVIS_TAG}" ]; then docker tag quay.io/razee/razeedash-api:${TRAVIS_COMMIT} quay.io/razee/razeedash-api:${TRAVIS_TAG}; fi
- docker images
Expand Down
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#Build an intermediate image
FROM node:alpine as buildImg
#Build an intermediate image, have to use 12.x.x node since 13.x is not working for bcrypt yet
FROM node:lts-alpine as buildImg

RUN apk update
RUN apk --no-cache add gnupg python make
RUN apk add --upgrade --no-cache libssl1.1
RUN apk add --no-cache g++

RUN mkdir -p /usr/src/
ENV PATH="$PATH:/usr/src/"
Expand All @@ -16,7 +17,7 @@ RUN npm install --production --loglevel=warn
COPY . /usr/src/

# Build the production image
FROM node:alpine
FROM node:lts-alpine
RUN apk add --upgrade --no-cache libssl1.1

RUN mkdir -p /usr/src/
Expand Down
182 changes: 182 additions & 0 deletions app/apollo/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* Copyright 2020 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const http = require('http');
const express = require('express');
const router = express.Router();
const ebl = require('express-bunyan-logger');
const bunyan = require('bunyan');
const { ApolloServer } = require('apollo-server-express');
const addRequestId = require('express-request-id')();

const { getBunyanConfig } = require('./utils/bunyan');
const { AUTH_MODEL, GRAPHQL_PATH } = require('./models/const');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const { models, connectDb, setupDistributedCollections, closeDistributedConnections } = require('./models');
const bunyanConfig = getBunyanConfig('apollo');
const logger = bunyan.createLogger(bunyanConfig);

const initModule = require(`./init.${AUTH_MODEL}`);

const createDefaultApp = () => {
const app = express();
app.set('trust proxy', true);
app.use(addRequestId);
app.use(function errorHandler(err, req, res, next) {
if (err) {
if (req.log && req.log.error) req.log.error(err);
else logger.error(err);
if (!res.headersSent) {
const statusCode = err.statusCode || 500;
return res.status(statusCode).send();
}
return next(err);
}
return next();
});
return app;
};

const buildCommonApolloContext = async ({ models, req, res, connection, logger }) => {
const context = await initModule.buildApolloContext({
models,
req,
res,
connection,
logger,
});
// populate req_id to apollo context
if (connection) {
context.req_id = connection.context.upgradeReq ? connection.context.upgradeReq.id : undefined;
} else if (req) {
context.req_id = req.id;
}
return context;
};

const createApolloServer = () => {
const server = new ApolloServer({
introspection: true,
playground: process.env.NODE_ENV !== 'production',
typeDefs,
resolvers,
formatError: error => {
// remove the internal sequelize error message
// leave only the important validation error
const message = error.message
.replace('SequelizeValidationError: ', '')
.replace('Validation error: ', '');
return {
...error,
message,
};
},
context: async ({ req, res, connection }) => {
return buildCommonApolloContext({
models,
req,
res,
connection,
logger,
});
},
subscriptions: {
path: GRAPHQL_PATH,
onConnect: async (connectionParams, webSocket, context) => {
logger.trace({ req_id: webSocket.upgradeReq.id, connectionParams, context }, 'subscriptions:onConnect');
const me = await models.User.getMeFromConnectionParams(
connectionParams,
);
logger.debug({ me }, 'subscriptions:onConnect upgradeReq getMe');
if (me === undefined) {
throw Error(
'Can not find the session for this subscription request.',
);
}
// add original upgrade request to the context
return { me, upgradeReq: webSocket.upgradeReq, logger, };
},
onDisconnect: (webSocket, context) => {
logger.debug(
{ req_id: webSocket.upgradeReq.id, headers: context.request.headers },
'subscriptions:onDisconnect upgradeReq getMe',
);
},
},
});
return server;
};

const stop = async (apollo) => {
await apollo.db.connection.close();
await closeDistributedConnections();
await apollo.server.stop();
await apollo.httpServer.close(() => {
console.log('🏄 Apollo Server closed.');
});
};

const apollo = async (options = {}) => {

if (!process.env.AUTH_MODEL) {
logger.error('apollo server is enabled, however AUTH_MODEL is not defined.');
process.exit(1);
}

try {
const db = await connectDb(options.mongo_url);
const mongoUrls =
options.mongo_urls ||
process.env.MONGO_URLS ||
options.mongo_url ||
process.env.MONGO_URL ||
'mongodb://localhost:3001/meteor';
await setupDistributedCollections(mongoUrls);

const app = options.app ? options.app : createDefaultApp();
router.use(ebl(getBunyanConfig('apollo')));
app.use(GRAPHQL_PATH, router);
initModule.initApp(app, models, logger);

const server = createApolloServer();
server.applyMiddleware({ app, path: GRAPHQL_PATH });

const httpServer = options.httpServer ? options.httpServer : http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.on('listening', () => {
const addrHost = httpServer.address().address;
const addrPort = httpServer.address().port;
logger.info(
`🏄 Apollo server listening on http://[${addrHost}]:${addrPort}${GRAPHQL_PATH}`,
);
});

if (!options.httpServer) {
let port = process.env.GRAPHQL_PORT || 8000;
if (options.graphql_port !== undefined) {
port = options.graphql_port;
}
httpServer.listen({ port });
}
return { db, server, httpServer, stop};
} catch (err) {
logger.error(err, 'Apollo api error');
process.exit(1);
}
};

module.exports = apollo;
46 changes: 46 additions & 0 deletions app/apollo/init.local.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright 2020 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const cors = require('cors');
const { SECRET } = require('./models/const');

const initApp = (app, models, logger) => {
logger.info('Initialize apollo application for local auth');
app.use(cors());
};

const buildApolloContext = async ({ models, req, res, connection, logger }) => {
if (connection) {
logger.trace({ connection, req, res }, 'context websocket connection is');
return {
models,
me: connection.context.me,
logger,
};
}
if (req) {
const me = await models.User.getMeFromRequest(req);
return {
models,
me,
secret: SECRET,
logger,
};
}
return { models, me: {}, logger };
};

module.exports = { initApp, buildApolloContext };
71 changes: 71 additions & 0 deletions app/apollo/init.passport.local.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright 2020 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const bcrypt=require('bcrypt');
const passport=require('passport');
const GraphQLLocalStrategy=require('graphql-passport').GraphQLLocalStrategy;
const buildContext=require('graphql-passport').buildContext;
const { SECRET, GRAPHQL_PATH} = require('./models/const');


const initApp = (app, models, logger) => {
logger.info('initialize apollo application for passport local auth');
passport.use(
new GraphQLLocalStrategy(async function(email, password, done) {
const matchingUser = await models.User.find({
'services.passportlocal.email': email,
});
let error = matchingUser[0] ? null : new Error('No matching user');
if (matchingUser[0]) {
const validPass = await bcrypt.compare(
password,
matchingUser[0].services.passportlocal.password,
);
error = validPass ? null : new Error('Password didn\'t match');
done(error, matchingUser[0]);
}
done(error);
}),
);
app.use(GRAPHQL_PATH, passport.initialize());
};

const buildApolloContext = async ({models, req, res, connection, logger}) => {
if (connection) {
logger.debug({ connection }, 'context websocket connection is');
return buildContext({
req,
res,
models,
me: connection.context.me,
logger,
});
}
if (req) {
const me = await models.User.getMeFromRequest(req);
return buildContext({
req,
res,
models,
me,
secret: SECRET,
logger,
});
}
return buildContext({ req, res, me: {}, logger, models });
};

module.exports = { initApp, buildApolloContext };
22 changes: 22 additions & 0 deletions app/apollo/models/cluster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright 2020 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const mongoose = require('mongoose');
const ClusterSchema = require('./cluster.schema');

const Cluster = mongoose.model('clusters', ClusterSchema);

module.exports = Cluster;
Loading

0 comments on commit 0206ebd

Please sign in to comment.