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

feat: add tiny GraphQL Lambda #42

Merged
merged 1 commit into from
Apr 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");

This comment was marked as off-topic.

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({

This comment was marked as off-topic.

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");

This comment was marked as off-topic.


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