Skip to content
This repository has been archived by the owner on Oct 21, 2020. It is now read-only.

Commit

Permalink
feat: a tiny GraphQL POC served from AWS Lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
ojongerius committed Apr 17, 2018
1 parent dae8378 commit 349da8d
Show file tree
Hide file tree
Showing 11 changed files with 10,588 additions and 5,430 deletions.
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,11 @@ typings/
.env

# next.js build output
.next
.next

# Webpack
.webpack

# Serverless
.serverless
.webpack
21 changes: 21 additions & 0 deletions db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const mongoose = require("mongoose");
const bluebird = require("bluebird");
mongoose.Promise = bluebird;
mongoose.Promise = global.Promise;

// Only reconnect if needed. State is saved and outlives a handler invocation
let isConnected;

const connectToDatabase = () => {
if (isConnected) {
console.log("Re-using existing database connection");
return Promise.resolve();
}

console.log("Creating new database connection");
return mongoose.connect(process.env.MONGODB_URL).then(db => {
isConnected = db.connections[0].readyState;
});
};

module.exports = connectToDatabase;
58 changes: 58 additions & 0 deletions handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { graphqlLambda, graphiqlLambda } from "apollo-server-lambda";
import lambdaPlayground from "graphql-playground-middleware-lambda";
import { makeExecutableSchema } from "graphql-tools";
import { mergeResolvers, mergeTypes } from "merge-graphql-schemas";
import { userType } from "./types/user";
import { userResolver } from "./resolvers/user";

const types = mergeTypes([userType]);
const solvers = mergeResolvers([userResolver]);
const graphqlSchema = makeExecutableSchema({
typeDefs: types,
resolvers: solvers,
logger: console
});

// Database connection logic lives outside of the handler for performance reasons
const connectToDatabase = require("./db");

const server = require("apollo-server-lambda");

exports.graphqlHandler = function graphqlHandler(event, context, callback) {
/* Cause Lambda to freeze the process and save state data after
the callback is called the effect is that new handler invocations
will be able to re-use the database connection.
See https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
and https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs */
context.callbackWaitsForEmptyEventLoop = false;

function callbackFilter(error, output) {
if (!output.headers) {
output.headers = {};
}
// eslint-disable-next-line no-param-reassign
output.headers["Access-Control-Allow-Origin"] = "*";
output.headers["Access-Control-Allow-Credentials"] = true;
output.headers["Content-Type"] = "application/json";

callback(error, output);
}

const handler = server.graphqlLambda({ schema: graphqlSchema });

connectToDatabase()
.then(() => {
return handler(event, context, callbackFilter);
})
.catch(err => {
console.log("MongoDB connection error: ", err);
// TODO: return 500?
process.exit();
});
};

exports.apiHandler = lambdaPlayground({
endpoint: process.env.GRAPHQL_ENDPOINT_URL
? process.env.GRAPHQL_ENDPOINT_URL
: "/production/graphql"
});
268 changes: 268 additions & 0 deletions model/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
const mongoose = require("mongoose");
const validator = require("validator");

const Schema = mongoose.Schema;
const SchemaTypes = Schema.Types;

const userSchema = new Schema({
email: {
type: "string"
},
newEmail: {
type: "string"
},
emailVerifyTTL: {
type: "date"
},
emailVerified: {
type: "boolean",
default: false
},
emailAuthLinkTTL: {
type: "date"
},
password: {
type: "string"
},
progressTimestamps: {
type: "array",
default: []
},
isBanned: {
type: "boolean",
description: "User is banned from posting to camper news",
default: false
},
isCheater: {
type: "boolean",
description:
"Users who are confirmed to have broken academic honesty policy are marked as cheaters",
default: false
},
isGithubCool: {
type: "boolean",
default: false
},
githubId: {
type: "string"
},
githubURL: {
type: "string"
},
githubEmail: {
type: "string"
},
joinedGithubOn: {
type: "date"
},
website: {
type: "string"
},
githubProfile: {
type: "string"
},
_csrf: {
type: "string"
},
isMigrationGrandfathered: {
type: "boolean",
default: false
},
username: {
type: "string"
},
bio: {
type: "string",
default: ""
},
about: {
type: "string",
default: ""
},
name: {
type: "string",
default: ""
},
gender: {
type: "string",
default: ""
},
location: {
type: "string",
default: ""
},
picture: {
type: "string",
default: ""
},
linkedin: {
type: "string"
},
codepen: {
type: "string"
},
twitter: {
type: "string"
},
currentStreak: {
type: "number",
default: 0
},
longestStreak: {
type: "number",
default: 0
},
sendMonthlyEmail: {
type: "boolean",
default: true
},
sendNotificationEmail: {
type: "boolean",
default: true
},
sendQuincyEmail: {
type: "boolean",
default: true
},
isLocked: {
type: "boolean",
description:
"Campers profile does not show challenges/certificates to the public",
default: false
},
currentChallengeId: {
type: "string",
description: "The challenge last visited by the user",
default: ""
},
currentChallenge: {
type: {},
description: "deprecated"
},
isUniqMigrated: {
type: "boolean",
description: "Campers completedChallenges array is free of duplicates",
default: false
},
isHonest: {
type: "boolean",
description: "Camper has signed academic honesty policy",
default: false
},
isFrontEndCert: {
type: "boolean",
description: "Camper is front end certified",
default: false
},
isDataVisCert: {
type: "boolean",
description: "Camper is data visualization certified",
default: false
},
isBackEndCert: {
type: "boolean",
description: "Campers is back end certified",
default: false
},
isFullStackCert: {
type: "boolean",
description: "Campers is full stack certified",
default: false
},
isRespWebDesignCert: {
type: "boolean",
description: "Camper is responsive web design certified",
default: false
},
is2018DataVisCert: {
type: "boolean",
description: "Camper is data visualization certified (2018)",
default: false
},
isFrontEndLibsCert: {
type: "boolean",
description: "Camper is front end libraries certified",
default: false
},
isJsAlgoDataStructCert: {
type: "boolean",
description:
"Camper is javascript algorithms and data structures certified",
default: false
},
isApisMicroservicesCert: {
type: "boolean",
description: "Camper is apis and microservices certified",
default: false
},
isInfosecQaCert: {
type: "boolean",
description:
"Camper is information security and quality assurance certified",
default: false
},
isChallengeMapMigrated: {
type: "boolean",
description: "Migrate completedChallenges array to challenge map",
default: false
},
challengeMap: {
type: "object",
description: "A map by ID of all the user completed challenges",
default: {}
},
completedChallenges: {
type: [
{
completedDate: "number",
lastUpdated: "number",
numOfAttempts: "number",
id: "string",
name: "string",
completedWith: "string",
solution: "string",
githubLink: "string",
verified: "boolean",
challengeType: {
type: "number",
default: 0
}
}
],
default: []
},
portfolio: {
type: "array",
default: []
},
rand: {
type: "number",
index: true
},
tshirtVote: {
type: "number"
},
timezone: {
type: "string"
},
theme: {
type: "string",
default: "default"
},
languageTag: {
type: "string",
description: "An IETF language tag",
default: "en"
},
badges: {
type: {
coreTeam: {
type: "array",
default: []
}
},
default: {}
}
});

module.exports = mongoose.model("User", userSchema, "user");
Loading

0 comments on commit 349da8d

Please sign in to comment.