Skip to content
Open
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
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ SECRET=
SERVER_URL=
PORT=

# Upload Settings
MAX_FILE_SIZE=
MAX_FILES=

# Banana Mailer Settings
BANANA_SERVICE=
BANANA_EMAIL=
BANANA_PASS=

# Google Service Settings
GOOGLE_CREDS=
GOOGLE_PRIVATE_KEY=
GOOGLE_CLIENT_EMAIL=
GOOGLE_FOLDER_ID=

# Bcrypt Settings
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"apollo-server-express": "^2.1.0",
"apollo-server-express": "^2.3.1",
"axios": "^0.18.0",
"backpack-core": "^0.7.0",
"backpack-core": "^0.8.3",
"banana-mail": "^1.0.0",
"bcrypt": "^3.0.2",
"dotenv": "^6.1.0",
"eslint-config-airbnb": "^17.1.0",
"express": "^4.16.4",
"express-jwt": "^5.3.1",
"googleapis": "^35.0.0",
"graphql": "^14.0.2",
"jsonwebtoken": "^8.3.0",
"mongoose": "^5.3.13"
Expand All @@ -26,6 +26,7 @@
},
"devDependencies": {
"eslint": "^5.8.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.2",
"eslint-plugin-react": "^7.11.1"
Expand Down
16 changes: 8 additions & 8 deletions src/database/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ const {
DB_USER, DB_PASSWORD, DB_HOST, DB_NAME,
} = process.env;

const mongoURL = `${DB_HOST}/${DB_NAME}`;

const options = {
user: DB_USER,
pass: DB_PASSWORD,
useNewUrlParser: true,
useCreateIndex: true,
};

const db = () => Promise.resolve(
mongoose.connect(
`${DB_HOST}/${DB_NAME}`,
options,
),
const initMongoose = async () => mongoose.connect(
mongoURL,
options,
);


db()
.then(() => console.log('> DB Connected'))
.catch(e => console.log(e.message));
initMongoose()
.then(() => console.log(`> Connected to Mongo instance at ${mongoURL}`))
.catch(err => console.error('Error connecting to Mongo instance:', err));
20 changes: 20 additions & 0 deletions src/graphql/apollo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ApolloServer } from 'apollo-server-express';

import typeDefs from './schema.gql';
import resolvers from './resolvers';

const apollo = new ApolloServer({
typeDefs,
resolvers,
formatError(err) {
console.error(err);
const { message, extensions: { code } } = err;
return { message, extensions: { code } };
},
context(ctx) {
const { req } = ctx;
return req.user;
},
});

export default apollo;
4 changes: 2 additions & 2 deletions src/graphql/resolvers/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import user from './queries/user';

import { signUp, logIn, verify } from './mutations/user';
import { updateApplication, submitApplication } from './mutations/application';

import user from './queries/user';

const resolvers = {
Query: {
user,
Expand Down
4 changes: 1 addition & 3 deletions src/graphql/resolvers/mutations/application.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import userService from '../../../services/user';
import applicationService from '../../../services/application';

const updateApplication = async (root, args, context) => {
Expand All @@ -14,8 +13,7 @@ const updateApplication = async (root, args, context) => {
const submitApplication = async (root, args, context) => {
try {
const { id } = context;
const application = await applicationService.update(id, args);
await userService.updateStatus(id, 'SUBMITTED');
const application = await applicationService.submit(id, args);
return application;
} catch (err) {
throw err;
Expand Down
8 changes: 2 additions & 6 deletions src/graphql/resolvers/queries/user.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { AuthenticationError } from 'apollo-server-express';
import User from '../../../models';
import userService from '../../../services/user';

const user = async (root, args, context) => {
try {
const { id, level } = context;
if (level !== 'ADMIN' && id.toString() !== args.id) {
throw new AuthenticationError('Not allowed to fetch this user');
}
const requestedUser = await User.findById(args.id);
const requestedUser = await userService.find(id, level, args.id);
return requestedUser;
} catch (err) {
throw err;
Expand Down
13 changes: 13 additions & 0 deletions src/graphql/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ type Team {
members: [User!]
}

type File {
name: String!
path: String!
}

type Application {
id: ID!
firstName: String
lastName: String
levelOfStudy: LevelOfStudy
school: String
major: String
shirtSize: ShirtSize
gender: Gender
resume: File
}

type Query {
Expand All @@ -40,17 +47,21 @@ type Mutation {
firstName: String
lastName: String
levelOfStudy: LevelOfStudy
school: String
major: String
shirtSize: ShirtSize
gender: Gender
resume: Upload
): Application!
submitApplication(
firstName: String!
lastName: String!
levelOfStudy: LevelOfStudy!
school: String!
major: String!
shirtSize: ShirtSize!
gender: Gender!
resume: Upload
): Application!
}

Expand Down Expand Up @@ -84,6 +95,8 @@ enum LevelOfStudy {
JUNIOR
SENIOR
SENIORPLUS
GRADUATE
OTHER
}

enum Gender {
Expand Down
28 changes: 6 additions & 22 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,17 @@
import './env';
import './database';

import express from 'express';
import jwt from 'express-jwt';
import { ApolloServer } from 'apollo-server-express';
import expressJWT from 'express-jwt';

import typeDefs from './graphql/schema.gql';
import resolvers from './graphql/resolvers';
import './database';
import apollo from './graphql/apollo';

const { PORT, SECRET, SERVER_URL } = process.env;

const app = express();

const server = new ApolloServer({
typeDefs,
resolvers,
formatError(err) {
console.error(err);
const { message, extensions: { code } } = err;
return { message, extensions: { code } };
},
context(ctx) {
const { req } = ctx;
return req.user;
},
});

app.use(jwt({ secret: SECRET, credentialsRequired: false }));
app.use(expressJWT({ secret: SECRET, credentialsRequired: false }));

server.applyMiddleware({ app });
apollo.applyMiddleware({ app });

app.listen({ port: PORT }, () => console.log(`🍑 Server up on ${SERVER_URL}:${PORT}${server.graphqlPath}`));
app.listen({ port: PORT }, () => console.log(`🍑 Server up on ${SERVER_URL}:${PORT}${apollo.graphqlPath}`));
4 changes: 3 additions & 1 deletion src/models/application.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const ApplicationSchema = Mongoose => new Mongoose.Schema({
const ApplicationSchema = (mongoose, Resume) => new mongoose.Schema({
firstName: String,
lastName: String,
levelOfStudy: String,
major: String,
shirtSize: String,
gender: String,
school: String,
resume: Resume,
});

export default ApplicationSchema;
10 changes: 7 additions & 3 deletions src/models/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import mongoose from 'mongoose';

import ResumeSchema from './resume';
import ApplicationSchema from './application';
import UserSchema from './user';

const Application = ApplicationSchema(mongoose);
const User = UserSchema(mongoose, Application);
import UserModel from './user';

const Resume = ResumeSchema(mongoose);
const Application = ApplicationSchema(mongoose, Resume);
const User = UserModel(mongoose, Application);

export default User;
6 changes: 6 additions & 0 deletions src/models/resume.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const ResumeSchema = Mongoose => new Mongoose.Schema({
name: String,
path: String,
});

export default ResumeSchema;
6 changes: 3 additions & 3 deletions src/models/user.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const UserSchema = (Mongoose, application) => Mongoose.model('users', new Mongoose.Schema({
const UserModel = (Mongoose, Application) => Mongoose.model('users', new Mongoose.Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
level: { type: String, required: true },
status: { type: String, required: true },
application,
application: Application,
}));

export default UserSchema;
export default UserModel;
66 changes: 60 additions & 6 deletions src/services/application.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,84 @@
import { ForbiddenError } from 'apollo-server-express';
import { ForbiddenError, ValidationError } from 'apollo-server-express';

import User from '../models';

import userService from './user';
import driveService from './drive';

/**
* Updates the given user's application with the given arguments.
* @param {number} userId The user's ID.
* @param {string} userId The user's ID.
* @param {Object} args The arguments with which to update the application.
*/
const update = async (userId, options) => {
const update = async (userId, args) => {
try {
if (!userId) {
throw new ForbiddenError('User is not logged in.');
}
const { status, level } = await User.findById(userId);
const { email, status, level } = await User.findById(userId);
if (level !== 'HACKER') {
throw new ForbiddenError('User is not a HACKER.');
}
if (status !== 'VERIFIED') {
throw new ForbiddenError('User has already submitted an application.');
}

const user = await User.findByIdAndUpdate(userId, { application: options }, { new: true });
const {
firstName, lastName, levelOfStudy, gender, major, shirtSize, resume, school,
} = args;

let path;
let name;
if (resume) {
const { filename, mimetype, createReadStream } = await resume;
const stream = createReadStream();
name = filename;
path = await driveService.upload({ filename: email, mimetype, stream });
}
const user = await User.findByIdAndUpdate(userId, {
application: {
firstName,
lastName,
levelOfStudy,
gender,
major,
shirtSize,
school,
resume: path ? { name, path } : null,
},
}, { new: true });
const { application } = user;
return application;
} catch (err) {
throw err;
}
};

export default { update };
/**
* Returns whether or not an application is valid, i.e. completed.
* @param {Object} application The application to validate.
*/
const validate = application => (
Object.keys(application).reduce((valid, key) => application[key] != null && valid, true)
);

/**
* Submits a user's completed application.
* @param {string} userId The user's ID.
* @param {Object} args The arguments with which to submit the application.
*/
const submit = async (userId, args) => {
try {
const application = await update(userId, args);
const valid = validate(application);
if (!valid) {
throw new ValidationError('Application incomplete.');
}
await userService.updateStatus(userId, 'SUBMITTED');
return application;
} catch (err) {
throw err;
}
};

export default { update, submit };
Loading