From 4c6b19dd7377e4230e22636a6af7b44e5bcd864d Mon Sep 17 00:00:00 2001 From: logan-r Date: Mon, 30 Dec 2019 22:14:24 -0500 Subject: [PATCH 01/14] Create travel model --- constants/general.constant.js | 22 +++++++++++++++++++++ models/travel.model.js | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 models/travel.model.js diff --git a/constants/general.constant.js b/constants/general.constant.js index ab21eb18..7b42b357 100644 --- a/constants/general.constant.js +++ b/constants/general.constant.js @@ -26,6 +26,21 @@ const HACKER_STATUSES = [ HACKER_STATUS_DECLINED ]; +const TRAVEL_STATUS_NONE = "None"; // Hacker has not been offered compensation for travelling +const TRAVEL_STATUS_BUS = "Bus"; // Hacker is taking bus to hackathon +const TRAVEL_STATUS_OFFERED = "Offered"; // Hacker has been offered some amount of compensation for travelling, but we have not verified their reciepts yet +const TRAVEL_STATUS_VALID = "Valid"; // Hacker has been offered some amount of compensation for travelling and have uploaded reciepts which we have confirmed to be an approprate amount +const TRAVEL_STATUS_INVALID = "Invalid"; // Hacker has been offered some amount of compensation for travelling but have uploaded reciepts which we have confirmed to be an inapproprate amount +const TRAVEL_STATUS_CLAIMED = "Claimed"; // Hacker has been offered some amount of compensation and has recieved such the funds +const TRAVEL_STATUSES = [ + TRAVEL_STATUS_NONE, + TRAVEL_STATUS_BUS, + TRAVEL_STATUS_OFFERED, + TRAVEL_STATUS_VALID, + TRAVEL_STATUS_INVALID, + TRAVEL_STATUS_CLAIMED +]; + const SAMPLE_DIET_RESTRICTIONS = [ "None", "Vegan", @@ -160,6 +175,13 @@ module.exports = { HACKER_STATUS_WITHDRAWN: HACKER_STATUS_WITHDRAWN, HACKER_STATUS_CHECKED_IN: HACKER_STATUS_CHECKED_IN, HACKER_STATUSES: HACKER_STATUSES, + TRAVEL_STATUS_NONE: TRAVEL_STATUS_NONE, + TRAVEL_STATUS_BUS: TRAVEL_STATUS_BUS, + TRAVEL_STATUS_OFFERED: TRAVEL_STATUS_OFFERED, + TRAVEL_STATUS_VALID: TRAVEL_STATUS_VALID, + TRAVEL_STATUS_INVALID: TRAVEL_STATUS_INVALID, + TRAVEL_STATUS_CLAIMED: TRAVEL_STATUS_CLAIMED, + TRAVEL_STATUSES: TRAVEL_STATUSES, REQUEST_TYPES: REQUEST_TYPES, JOB_INTERESTS: JOB_INTERESTS, SHIRT_SIZES: SHIRT_SIZES, diff --git a/models/travel.model.js b/models/travel.model.js new file mode 100644 index 00000000..afd29e17 --- /dev/null +++ b/models/travel.model.js @@ -0,0 +1,36 @@ +"use strict"; +const Constants = require("../constants/general.constant"); +const mongoose = require("mongoose"); +//describes the data type +const TravelSchema = new mongoose.Schema({ + hackerId: { // The hacker this travel data is associated with + type: mongoose.Schema.Types.ObjectId, + ref: "Hacker", + required: true + }, + status: { // Has this hacker been approved for funds, etc. + type: String, + enum: Constants.TRAVEL_STATUSES, + required: true, + default: "None" + }, + request: { // Amount of money hacker has requested for travel + type: Number, + required: true + }, + offer: { // Amount of money we have offered hacker for travel + type: Number, + default: 0 + } +}); + +TravelSchema.methods.toJSON = function () { + const hs = this.toObject(); + delete hs.__v; + hs.id = hs._id; + delete hs._id; + return hs; +}; + +//export the model +module.exports = mongoose.model("Travel", TravelSchema); From 00fe61b9c36e851d63e16af380c26c06f5922f9d Mon Sep 17 00:00:00 2001 From: logan-r Date: Mon, 30 Dec 2019 23:35:12 -0500 Subject: [PATCH 02/14] Create travel service and begin implementing travel router --- constants/error.constant.js | 1 + middlewares/travel.middleware.js | 59 +++ models/travel.model.js | 5 + routes/api/travel.js | 787 +++++++++++++++++++++++++++++++ services/hacker.service.js | 2 +- services/travel.service.js | 112 +++++ 6 files changed, 965 insertions(+), 1 deletion(-) create mode 100644 middlewares/travel.middleware.js create mode 100644 routes/api/travel.js create mode 100644 services/travel.service.js diff --git a/constants/error.constant.js b/constants/error.constant.js index f2e6c535..4c398b73 100644 --- a/constants/error.constant.js +++ b/constants/error.constant.js @@ -7,6 +7,7 @@ const RESUME_404_MESSAGE = "Resume not found"; const SPONSOR_404_MESSAGE = "Sponsor not found"; const VOLUNTEER_404_MESSAGE = "Volunteer not found"; const SETTINGS_404_MESSAGE = "Settings not found"; +const TRAVEL_404_MESSAGE = "Travel not found"; const ACCOUNT_TYPE_409_MESSAGE = "Wrong account type"; const ACCOUNT_EMAIL_409_MESSAGE = "Email already in use"; diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js new file mode 100644 index 00000000..ba4f819a --- /dev/null +++ b/middlewares/travel.middleware.js @@ -0,0 +1,59 @@ +"use strict"; + +const TAG = `[ TRAVEL.MIDDLEWARE.js ]`; +const mongoose = require("mongoose"); +const Services = { + //Hacker: require("../services/hacker.service"), + //Storage: require("../services/storage.service"), + //Email: require("../services/email.service"), + //Account: require("../services/account.service"), + //Env: require("../services/env.service") +}; +const Middleware = { + Util: require("./util.middleware") +}; +const Constants = { + General: require("../constants/general.constant"), + Error: require("../constants/error.constant") +}; + +/** + * Finds the travel information of the logged in user + * and places that information in req.body.travel + * @param {{user: {id: string}}} req + * @param {*} res + * @param {(err?)=>void} next + */ +async function findSelf(req, res, next) { + if ( + req.user.accountType != Constants.General.HACKER || + !req.user.confirmed + ) { + return next({ + status: 409, + message: Constants.Error.ACCOUNT_TYPE_409_MESSAGE, + error: { + id: req.user.id + } + }); + } + + const travel = await Services.Travel.findByAccountId(req.user.id); + + if (!!travel) { + req.body.travel = travel; + return next(); + } else { + return next({ + status: 409, + message: Constants.Error.TRAVEL_404_MESSAGE, + error: { + id: req.user.id + } + }); + } +} + +module.exports = { + findSelf: Middleware.Util.asyncMiddleware(findSelf) +}; diff --git a/models/travel.model.js b/models/travel.model.js index afd29e17..8e7999e4 100644 --- a/models/travel.model.js +++ b/models/travel.model.js @@ -3,6 +3,11 @@ const Constants = require("../constants/general.constant"); const mongoose = require("mongoose"); //describes the data type const TravelSchema = new mongoose.Schema({ + accountId: { // The account this travel data is associated with + type: mongoose.Schema.Types.ObjectId, + ref: "Account", + required: true + }, hackerId: { // The hacker this travel data is associated with type: mongoose.Schema.Types.ObjectId, ref: "Hacker", diff --git a/routes/api/travel.js b/routes/api/travel.js new file mode 100644 index 00000000..7c23a08c --- /dev/null +++ b/routes/api/travel.js @@ -0,0 +1,787 @@ +"use strict"; +const express = require("express"); +const Controllers = { + Travel: require("../../controllers/travel.controller") +}; +const Middleware = { + Validator: { + /* Insert the require statement to the validator file here */ + Travel: require("../../middlewares/validators/travel.validator"), + RouteParam: require("../../middlewares/validators/routeParam.validator") + }, + /* Insert all of ther middleware require statements here */ + parseBody: require("../../middlewares/parse-body.middleware"), + Util: require("../../middlewares/util.middleware"), + Travel: require("../../middlewares/travel.middleware"), + Auth: require("../../middlewares/auth.middleware"), + Search: require("../../middlewares/search.middleware") +}; +const Services = { + Travel: require('../../services/travel.service'), + Hacker: require("../../services/hacker.service") +}; +const CONSTANTS = require("../../constants/general.constant"); + +module.exports = { + activate: function (apiRouter) { + const travelRouter = express.Router(); + + /** + * @api {get} /travel/self get information about own hacker's travel + * @apiName self + * @apiGroup Travel + * @apiVersion 2.0.1 + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Travel object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Travel found by logged in account id", + "data": { + "id":"5bff4d736f86be0a41badb91", + "status": "Claimed" + "request": 90, + "offer": 80 + } + } + + * @apiError {string} message Error message + * @apiError {object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Travel not found", "data": {}} + */ + hackerRouter.route("/self").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), + + Middleware.Hacker.findSelf, + Controllers.Hacker.showHacker + ); + + /** + * @api {post} /hacker/ create a new hacker + * @apiName createHacker + * @apiGroup Hacker + * @apiVersion 0.0.8 + * + * @apiParam (body) {MongoID} accountId ObjectID of the respective account + * @apiParam (body) {String} school Name of the school the hacker goes to + * @apiParam (body) {String} gender Gender of the hacker + * @apiParam (body) {Number} travel Whether the hacker requires a bus for transportation + * @apiParam (body) {String[]} ethnicity the ethnicities of the hacker + * @apiParam (body) {String[]} major the major of the hacker + * @apiParam (body) {Number} graduationYear the graduation year of the hacker + * @apiParam (body) {Boolean} codeOfConduct acceptance of the code of conduct + * @apiParam (body) {Json} application The hacker's application. Resume and jobInterest fields are required. + * @apiParamExample {Json} application: + * { + "application":{ + "general":{ + "school": "McGill University", + "degree": "Undergraduate", + "fieldOfStudy": "Computer Science", + "graduationYear": "2021", + "jobInterest":"Internship", + "URL":{ + "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", + "github":"https://github.com/abcd", + "dropler":"https://dribbble.com/abcd", + "personal":"https://www.hi.com/", + "linkedIn":"https://linkedin.com/in/abcd", + "other":"https://github.com/hackmcgill/hackerAPI/issues/168" + }, + }, + "shortAnswer": { + "skills":["Javascript","Typescript"], + "question1": "I love McHacks", + "question2":"Pls accept me", + "comments":"hi!", + }, + "other:" { + "gender": "male", + "ethnicity": "Asian or Pacific Islander", + "privacyPolicy": true, + "codeOfConduct": true, + } + "accomodation": { + "travel": 0 + }, + } + + * } + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Hacker creation successful", + * "data": { + "id":"5bff4d736f86be0a41badb91", + "application":{ + "general":{ + "school": "McGill University", + "degree": "Undergraduate", + "fieldOfStudy": "Computer Science", + "graduationYear": "2021", + "jobInterest":"Internship", + "URL":{ + "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", + "github":"https://github.com/abcd", + "dropler":"https://dribbble.com/abcd", + "personal":"https://www.hi.com/", + "linkedIn":"https://linkedin.com/in/abcd", + "other":"https://github.com/hackmcgill/hackerAPI/issues/168" + }, + }, + "shortAnswer": { + "skills":["Javascript","Typescript"], + "question1": "I love McHacks", + "question2":"Pls accept me", + "comments":"hi!", + }, + "other:" { + "gender": "male", + "ethnicity": "Asian or Pacific Islander", + "privacyPolicy": true, + "codeOfConduct": true, + } + "accomodation": { + "travel": 0 + }, + } + * } + + * @apiError {string} message Error message + * @apiError {object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Error while creating hacker", "data": {}} + */ + hackerRouter.route("/").post( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), + Middleware.Validator.Hacker.newHackerValidator, + + Middleware.parseBody.middleware, + // validate type + Middleware.Hacker.validateConfirmedStatusFromAccountId, + // validate that the accountId is not being used for any other thing + Middleware.Hacker.checkDuplicateAccountLinks, + + Middleware.Hacker.parseHacker, + + Middleware.Hacker.addDefaultStatus, + Middleware.Auth.createRoleBindings(CONSTANTS.HACKER), + Middleware.Hacker.createHacker, + Middleware.Hacker.sendAppliedStatusEmail, + + Controllers.Hacker.createdHacker + ); + + /** + * @api {get} /hacker/stats + * Gets the stats of all of the hackers who have applied. + * @apiName getHackerStats + * @apiGroup Hacker + * @apiVersion 0.0.9 + * + * @apiParam (query) {String} model the model to be searched (Only hacker supported) + * @apiParam (query) {Array} q the query to be executed. For more information on how to format this, please see https://docs.mchacks.ca/architecture/ + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Retrieved stats", + * "data": { + * "stats" : { + * "total": 10, + "status": { "Applied": 10 }, + "school": { "McGill University": 3, "Harvard University": 7 }, + degree: { "Undergraduate": 10 }, + gender: { "Male": 1, "Female": 9 }, + travel: { "true": 7, "false": 3 }, + ethnicity: { "White": 10, }, + jobInterest: { "Internship": 10 }, + major: { "Computer Science": 10 }, + graduationYear: { "2019": 10 }, + dietaryRestrictions: { "None": 10 }, + shirtSize: { "M": 3, "XL": 7 }, + age: { "22": 10 } + } + * } + * } + * + */ + hackerRouter + .route("/stats") + .get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), + Middleware.Validator.Hacker.statsValidator, + Middleware.parseBody.middleware, + Middleware.Search.setExpandTrue, + Middleware.Search.parseQuery, + Middleware.Search.executeQuery, + Middleware.Hacker.getStats, + Controllers.Hacker.gotStats + ); + + /** + * @api {patch} /hacker/status/:id update a hacker's status + * @apiName patchHackerStatus + * @apiGroup Hacker + * @apiVersion 0.0.9 + * + * @apiParam (body) {string} [status] Status of the hacker's application ("None"|"Applied"|"Accepted"|"Declined"|"Waitlisted"|"Confirmed"|"Withdrawn"|"Checked-in") + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Changed hacker information", + * "data": { + * "status": "Accepted" + * } + * } + * @apiPermission Administrator + */ + hackerRouter.route("/status/:id").patch( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + Middleware.Validator.Hacker.updateStatusValidator, + Middleware.parseBody.middleware, + Middleware.Hacker.parsePatch, + Middleware.Hacker.validateConfirmedStatusFromHackerId, + + Middleware.Hacker.updateHacker, + Middleware.Hacker.sendStatusUpdateEmail, + Controllers.Hacker.updatedHacker + ); + + /** + * @api {patch} /hacker/accept/:id accept a Hacker + * @apiName acceptHacker + * @apiGroup Hacker + * @apiVersion 2.0.0 + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Changed hacker information", + * "data": { + * "status": "Accepted" + * } + * } + * @apiPermission Administrator + */ + hackerRouter + .route("/accept/:id") + .patch( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Hacker.parseAccept, + Middleware.Hacker.updateHacker, + Middleware.Hacker.sendStatusUpdateEmail, + Controllers.Hacker.updatedHacker + ); + + /** + * @api {patch} /hacker/checkin/:id update a hacker's status to be 'Checked-in'. Note that the Hacker must eitehr be Accepted or Confirmed. + * @apiName checkinHacker + * @apiGroup Hacker + * @apiVersion 0.0.9 + * @apiParam (body) {string} [status] Check-in status. "Checked-in" + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Changed hacker information", + * "data": { + * "status": "Checked-in" + * } + * } + * @apiPermission Administrator + * @apiPermission Volunteer + */ + hackerRouter.route("/checkin/:id").patch( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + + Middleware.parseBody.middleware, + Middleware.Hacker.parsePatch, + Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Hacker.checkStatus([ + CONSTANTS.HACKER_STATUS_ACCEPTED, + CONSTANTS.HACKER_STATUS_CONFIRMED + ]), + Middleware.Hacker.parseCheckIn, + Middleware.Hacker.updateHacker, + + Middleware.Hacker.sendStatusUpdateEmail, + Controllers.Hacker.updatedHacker + ); + + /** + * @api {patch} /hacker/:id update a hacker's information. + * @apiDescription This route only contains the ability to update a subset of a hacker's information. If you want to update a status, you must have Admin priviledges and use PATCH /hacker/status/:id. + * @apiName patchHacker + * @apiGroup Hacker + * @apiVersion 0.0.8 + * + * @apiParam (body) {String} [school] Name of the school the hacker goes to + * @apiParam (body) {String} [gender] Gender of the hacker + * @apiParam (body) {Number} [travel] How much the hacker requires a bus for transportation + * @apiParam (body) {String[]} [ethnicity] the ethnicities of the hacker + * @apiParam (body) {String[]} [major] the major of the hacker + * @apiParam (body) {Number} [graduationYear] the graduation year of the hacker + * @apiParam (body) {Json} [application] The hacker's application + * @apiParamExample {Json} application: + * { + "application":{ + "general":{ + "school": "McGill University", + "degree": "Undergraduate", + "fieldOfStudy": "Computer Science", + "graduationYear": "2021", + "jobInterest":"Internship", + "URL":{ + "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", + "github":"https://github.com/abcd", + "dropler":"https://dribbble.com/abcd", + "personal":"https://www.hi.com/", + "linkedIn":"https://linkedin.com/in/abcd", + "other":"https://github.com/hackmcgill/hackerAPI/issues/168" + }, + }, + "shortAnswer": { + "skills":["Javascript","Typescript"], + "question1": "I love McHacks", + "question2":"Pls accept me", + "comments":"hi!", + }, + "other:" { + "gender": "male", + "ethnicity": "Asian or Pacific Islander", + "privacyPolicy": true, + "codeOfConduct": true, + } + "accomodation": { + "travel": 0 + }, + } + } + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Changed hacker information", + * "data": { + "id":"5bff4d736f86be0a41badb91", + "status": "Applied", + "application":{ + "general":{ + "school": "McGill University", + "degree": "Undergraduate", + "fieldOfStudy": "Computer Science", + "graduationYear": "2021", + "jobInterest":"Internship", + "URL":{ + "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", + "github":"https://github.com/abcd", + "dropler":"https://dribbble.com/abcd", + "personal":"https://www.hi.com/", + "linkedIn":"https://linkedin.com/in/abcd", + "other":"https://github.com/hackmcgill/hackerAPI/issues/168" + }, + }, + "shortAnswer": { + "skills":["Javascript","Typescript"], + "question1": "I love McHacks", + "question2":"Pls accept me", + "comments":"hi!", + }, + "other:" { + "gender": "male", + "ethnicity": "Asian or Pacific Islander", + "privacyPolicy": true, + "codeOfConduct": true, + } + "accomodation": { + "travel": 0 + }, + } + } + * } + * @apiError {string} message Error message + * @apiError {object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Error while updating hacker", "data": {}} + */ + hackerRouter.route("/:id").patch( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + + Middleware.Validator.Hacker.updateHackerValidator, + + Middleware.parseBody.middleware, + Middleware.Hacker.parsePatch, + Middleware.Hacker.validateConfirmedStatusFromHackerId, + + Middleware.Hacker.updateHacker, + Middleware.Hacker.updateStatusIfApplicationCompleted, + Controllers.Hacker.updatedHacker + ); + + /** + * @api {get} /hacker/:id get a hacker's information + * @apiName getHacker + * @apiGroup Hacker + * @apiVersion 0.0.8 + * + * @apiParam (param) {String} id a hacker's unique mongoID + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Successfully retrieved hacker information", + "data": { + "id":"5bff4d736f86be0a41badb91", + "status": "Applied", + "application":{ + "general":{ + "school": "McGill University", + "degree": "Undergraduate", + "fieldOfStudy": "Computer Science", + "graduationYear": "2021", + "jobInterest":"Internship", + "URL":{ + "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", + "github":"https://github.com/abcd", + "dropler":"https://dribbble.com/abcd", + "personal":"https://www.hi.com/", + "linkedIn":"https://linkedin.com/in/abcd", + "other":"https://github.com/hackmcgill/hackerAPI/issues/168" + }, + }, + "shortAnswer": { + "skills":["Javascript","Typescript"], + "question1": "I love McHacks", + "question2":"Pls accept me", + "comments":"hi!", + }, + "other:" { + "gender": "male", + "ethnicity": "Asian or Pacific Islander", + "privacyPolicy": true, + "codeOfConduct": true, + } + "accomodation": { + "travel": 0 + }, + } + } + } + + * @apiError {String} message Error message + * @apiError {Object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Hacker not found", "data": {}} + */ + hackerRouter.route("/:id").get( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + + Middleware.parseBody.middleware, + + Middleware.Hacker.findById, + Controllers.Hacker.showHacker + ); + + /** + * @api {get} /hacker/email/:email get a hacker's information + * @apiName getHacker + * @apiGroup Hacker + * @apiVersion 0.0.8 + * + * @apiParam (param) {String} email a hacker's unique email + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Successfully retrieved hacker information", + "data": { + "id":"5bff4d736f86be0a41badb91", + "status": "Applied", + "application":{ + "general":{ + "school": "McGill University", + "degree": "Undergraduate", + "fieldOfStudy": "Computer Science", + "graduationYear": "2021", + "jobInterest":"Internship", + "URL":{ + "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", + "github":"https://github.com/abcd", + "dropler":"https://dribbble.com/abcd", + "personal":"https://www.hi.com/", + "linkedIn":"https://linkedin.com/in/abcd", + "other":"https://github.com/hackmcgill/hackerAPI/issues/168" + }, + }, + "shortAnswer": { + "skills":["Javascript","Typescript"], + "question1": "I love McHacks", + "question2":"Pls accept me", + "comments":"hi!", + }, + "other:" { + "gender": "male", + "ethnicity": "Asian or Pacific Islander", + "privacyPolicy": true, + "codeOfConduct": true, + } + "accomodation": { + "travel": 0 + }, + } + } + } + + * @apiError {String} message Error message + * @apiError {Object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Hacker not found", "data": {}} + */ + hackerRouter.route("/email/:email").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Account.findByEmail]), + + Middleware.Validator.RouteParam.emailValidator, + Middleware.parseBody.middleware, + + Middleware.Hacker.findByEmail, + Controllers.Hacker.showHacker + ); + + hackerRouter + .route("/resume/:id") + /** + * @api {post} /hacker/resume/:id upload or update resume for a hacker. + * @apiName postHackerResume + * @apiGroup Hacker + * @apiVersion 0.0.8 + * @apiDescription NOTE: This must be sent via multipart/form-data POST request + * + * @apiParam (param) {ObjectId} id Hacker id + * @apiParam (body) {File} resume The uploaded file. + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Location in the bucket that the file was stored. + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * message: "Uploaded resume", + * data: { + * filename: "resumes/1535032624768-507f191e810c19729de860ea" + * } + * } + * + * @apiPermission Must be logged in, and the account id must be linked to the hacker. + */ + .post( + //TODO: authenticate middleware + Middleware.Validator.Hacker.uploadResumeValidator, + Middleware.parseBody.middleware, + //verify that the hacker entity contains the account id + Middleware.Hacker.ensureAccountLinkedToHacker, + //load resume into memory + Middleware.Util.Multer.single("resume"), + //upload resume to storage and update hacker profile + Middleware.Hacker.uploadResume, + //controller response + Controllers.Hacker.uploadedResume + ) + /** + * @api {get} /hacker/resume:id get the resume for a hacker. + * @apiName getHackerResume + * @apiGroup Hacker + * @apiVersion 0.0.8 + * + * @apiParam (param) {ObjectId} id Hacker id + * + * @apiSuccess {String} message Success message + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * message: "Downloaded resume", + * data: { + * id: "507f191e810c19729de860ea", + * resume: [Buffer] + * } + * } + * @apiError {String} message "Resume does not exist" + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 404 + * { + * message: "Resume not found", + * data: {} + * } + * @apiSampleRequest off + * @apiPermission Must be logged in, and the account id must be linked to the hacker. + */ + .get( + //TODO: authenticate middleware + Middleware.Validator.Hacker.downloadResumeValidator, + Middleware.parseBody.middleware, + Middleware.Hacker.downloadResume, + Controllers.Hacker.downloadedResume + ); + + /** + * @api {patch} /hacker/confirmation/:id + * Allows confirmation of hacker attendence if they are accepted. Also allows change from 'confirmed' to 'withdrawn'. + * @apiName patchHackerConfirmed + * @apiGroup Hacker + * @apiVersion 0.0.9 + * + * @apiParam (body) {string} [status] The new status of the hacker. "Accepted", "Confirmed", or "Withdrawn" + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Changed hacker information", + * "data": { + * "status": "Confirmed" + * } + * } + * @apiPermission Administrator + * @apiPermission Hacker + */ + hackerRouter.route("/confirmation/:id").patch( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + + Middleware.Validator.Hacker.updateConfirmationValidator, + Middleware.parseBody.middleware, + Middleware.Hacker.parsePatch, + Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Hacker.checkStatus([ + CONSTANTS.HACKER_STATUS_ACCEPTED, + CONSTANTS.HACKER_STATUS_CONFIRMED, + CONSTANTS.HACKER_STATUS_WITHDRAWN + ]), + + Middleware.Hacker.parseConfirmation, + Middleware.Hacker.updateHacker, + + Middleware.Hacker.sendStatusUpdateEmail, + Controllers.Hacker.updatedHacker + ); + + /** + * @api {post} /hacker/email/weekOf/:id + * @apiDescription Sends a hacker the week-of email, along with the HackPass QR code to view their hacker profile (for checkin purposes). Hackers must be either confirmed, or checked in. + * @apiName postHackerSendWeekOfEmail + * @apiGroup Hacker + * @apiVersion 0.0.9 + * + * @apiParam (param) {string} [status] The hacker ID + * @apiSuccess {string} message Success message + * @apiSuccess {object} data empty + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Hacker week-of email sent.", + * "data": {} + * } + * @apiPermission Administrator + */ + hackerRouter.route("/email/weekOf/:id").post( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + + Middleware.parseBody.middleware, + Middleware.Hacker.findById, + + Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Hacker.checkStatus([ + CONSTANTS.HACKER_STATUS_CONFIRMED, + CONSTANTS.HACKER_STATUS_CHECKED_IN + ]), + + Middleware.Hacker.sendWeekOfEmail, + Controllers.Hacker.sentWeekOfEmail + ); + + /** + * @api {post} /hacker/email/dayOf/:id + * @apiDescription Sends a hacker the day-of email, along with the HackPass QR code to view their hacker profile (for checkin purposes). Hackers must be either confirmed, or checked in. + * @apiName postHackerSendDayOfEmail + * @apiGroup Hacker + * @apiVersion 0.0.9 + * + * @apiParam (param) {string} [status] The hacker ID + * @apiSuccess {string} message Success message + * @apiSuccess {object} data empty + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Hacker day-of email sent.", + * "data": {} + * } + * @apiPermission Administrator + */ + hackerRouter.route("/email/dayOf/:id").post( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + + Middleware.parseBody.middleware, + Middleware.Hacker.findById, + Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Hacker.checkStatus([CONSTANTS.HACKER_STATUS_CHECKED_IN]), + Middleware.Hacker.sendDayOfEmail, + Controllers.Hacker.sentDayOfEmail + ); + + /** + * @api {post} /hacker/email/weekOf/:id + * @apiDescription Sends a hacker the week-of email, along with the HackPass QR code to view their hacker profile (for checkin purposes). Hackers must be eitherconfirmed, or checked in. + * @apiName postHackerSendWeekOfEmail + * @apiGroup Hacker + * @apiVersion 0.0.9 + * + * @apiParam (param) {string} [status] The hacker ID + * @apiSuccess {string} message Success message + * @apiSuccess {object} data empty + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Hacker week-of email sent.", + * "data": {} + * } + * @apiPermission Administrator + */ + hackerRouter.route("/email/dayOf/:id").post( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + + Middleware.Validator.RouteParam.idValidator, + Middleware.parseBody.middleware, + Middleware.Hacker.findById, + Middleware.Hacker.checkStatus([CONSTANTS.HACKER_STATUS_CHECKED_IN]), + Middleware.Hacker.sendDayOfEmail, + Controllers.Hacker.sentDayOfEmail + ); + + apiRouter.use("/hacker", hackerRouter); + } +}; diff --git a/services/hacker.service.js b/services/hacker.service.js index 8e0ea501..989f5e21 100644 --- a/services/hacker.service.js +++ b/services/hacker.service.js @@ -194,7 +194,7 @@ function getStats(hackers) { stats.graduationYear[hacker.application.general.graduationYear] = stats .graduationYear[hacker.application.general.graduationYear] ? stats.graduationYear[hacker.application.general.graduationYear] + - 1 + 1 : 1; for (const dietaryRestrictions of hacker.accountId .dietaryRestrictions) { diff --git a/services/travel.service.js b/services/travel.service.js new file mode 100644 index 00000000..5a3e0a6c --- /dev/null +++ b/services/travel.service.js @@ -0,0 +1,112 @@ +"use strict"; +const Travel = require("../models/travel.model"); +const logger = require("./logger.service"); + +// const Constants = require("../constants/general.constant"); + + +/** + * @function createTravel + * @param {{_id: ObjectId, accountId: ObjectId, status: enum of Constants.TRAVEL_STATUSES, request: Number, offer?: number}} travelDetails + * @return {Promise} The promise will resolve to a travel object if save is successful. + * @description Adds a new travel to database. + */ +function createHacker(travelDetails) { + const TAG = `[Travel Service # createTravel]:`; + + const travel = new Travel(travelDetails); + + return travel.save(); +} + +/** + * @function updateOne + * @param {ObjectId} id + * @param {{_id?: ObjectId, accountId?: ObjectId, status?: enum of Constants.TRAVEL_STATUSES, request?: Number, offer?: number}} travelDetails + * @return {DocumentQuery} The document query will resolve to travel or null. + * @description Update an travel specified by its mongoId with information specified by travelDetails. + */ +function updateOne(id, travelDetails) { + const TAG = `[Travel Service # update ]:`; + + const query = { + _id: id + }; + + return Travel.findOneAndUpdate( + query, + travelDetails, + logger.updateCallbackFactory(TAG, "travel") + ); +} + +/** + * @function findById + * @param {ObjectId} id + * @return {DocumentQuery} The document query will resolve to travel or null. + * @description Finds an travel by the id, which is the mongoId. + */ +function findById(id) { + const TAG = `[Travel Service # findById ]:`; + + return Travel.findById(id, logger.queryCallbackFactory(TAG, "travel", id)); +} + +/** + * @async + * @function findOne + * @param {JSON} query + * @return {Travel | null} either travel or null + * @description Finds an travel by some query. + */ +async function findIds(queries) { + const TAG = `[Travel Service # findIds ]:`; + let ids = []; + + for (const query of queries) { + let currId = await Travel.findOne( + query, + "_id", + logger.queryCallbackFactory(TAG, "travel", query) + ); + ids.push(currId); + } + return ids; +} + +/** + * @function findByAccountId + * @param {ObjectId} accountId + * @return {DocumentQuery} A travel document queried by accountId + */ +function findByAccountId(accountId) { + const TAG = `[ Travel Service # findByAccountId ]:`; + const query = { + accountId: accountId + }; + + return Travel.findOne(query, logger.updateCallbackFactory(TAG, "travel")); +} + +/** + * @function findByHackerId + * @param {ObjectId} travelId + * @return {DocumentQuery} A travel document queried by hackerId + */ +function findByAccountId(accountId) { + const TAG = `[ Travel Service # findByAccountId ]:`; + const query = { + hackerId: hackerId + }; + + return Travel.findOne(query, logger.updateCallbackFactory(TAG, "travel")); +} + +module.exports = { + createTravel: createTravel, + findById: findById, + updateOne: updateOne, + findIds: findIds, + findByAccountId: findByAccountId, + findByHackerId: findByHackerId +}; From 3ac0b29e4cd4c9306e9800f797dca0c7acb8f8f9 Mon Sep 17 00:00:00 2001 From: logan-r Date: Mon, 30 Dec 2019 23:43:46 -0500 Subject: [PATCH 03/14] Create controller for travel --- constants/success.constant.js | 4 +++ controllers/travel.controller.js | 59 ++++++++++++++++++++++++++++++++ middlewares/travel.middleware.js | 1 + 3 files changed, 64 insertions(+) create mode 100644 controllers/travel.controller.js diff --git a/constants/success.constant.js b/constants/success.constant.js index 8fda8b91..c752174e 100644 --- a/constants/success.constant.js +++ b/constants/success.constant.js @@ -27,6 +27,8 @@ const HACKER_SENT_DAY_OF = "Hacker day-of email sent."; const RESUME_UPLOAD = "Resume upload successful."; const RESUME_DOWNLOAD = "Resume download successful."; +const TRAVEL_READ = "Travel retrieval successful."; + const ROLE_CREATE = "Role creation successful."; const SEARCH_QUERY = "Query search successful. Returning results."; @@ -78,6 +80,8 @@ module.exports = { RESUME_UPLOAD: RESUME_UPLOAD, RESUME_DOWNLOAD: RESUME_DOWNLOAD, + TRAVEL_READ: TRAVEL_READ, + ROLE_CREATE: ROLE_CREATE, SEARCH_QUERY: SEARCH_QUERY, diff --git a/controllers/travel.controller.js b/controllers/travel.controller.js new file mode 100644 index 00000000..c2a78543 --- /dev/null +++ b/controllers/travel.controller.js @@ -0,0 +1,59 @@ +"use strict"; +const Constants = { + Success: require("../constants/success.constant"), + Error: require("../constants/error.constant") +}; + +/** + * @function showTravel + * @param {{body: {travel: Object}}} req + * @param {*} res + * @return {JSON} Success status and travel object + * @description Returns the JSON of travel object located in req.body.travel + */ +function showTravel(req, res) { + return res.status(200).json({ + message: Constants.Success.TRAVEL_READ, + data: req.body.travel.toJSON() + }); +} + +/** + * @function createTravel + * @param {{body: {travel: {_id: ObjectId, accountId: ObjectId, hackerId: objectId, status: string, request: number, offer: number}}}} req + * @param {*} res + * @return {JSON} Success status + * @description + * Create a travel's record based off information stored in req.body.travel + * Returns a 200 status for the created travel. + */ +function createdTravel(req, res) { + return res.status(200).json({ + message: Constants.Success.TRAVEL_CREATE, + data: req.body.travel.toJSON() + }); +} + +/** + * @function updateHacker + * @param {{params: {id: ObjectId}, body: {Object}}} req + * @param {*} res + * @return {JSON} Success or error status + * @description + * Change a travel's information based on the trave;'s mongoID specified in req.params.id. + * The id is moved to req.body.id from req.params.id by validation. + * Returns a 200 status for an updated travel. + * The new information is located in req.body. + */ +function updatedTravel(req, res) { + return res.status(200).json({ + message: Constants.Success.TRAVEL_UPLOAD, + data: req.body + }); +} + +module.exports = { + showTravel: showTravel, + updatedTravel: updatedTravel, + createdTravel: createdTravel +}; diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index ba4f819a..a9698a6c 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -3,6 +3,7 @@ const TAG = `[ TRAVEL.MIDDLEWARE.js ]`; const mongoose = require("mongoose"); const Services = { + Travel: require("../services/travel.service") //Hacker: require("../services/hacker.service"), //Storage: require("../services/storage.service"), //Email: require("../services/email.service"), From 2848f427c59df700b6ced07313e7a36479ea661d Mon Sep 17 00:00:00 2001 From: logan-r Date: Tue, 31 Dec 2019 12:39:33 -0500 Subject: [PATCH 04/14] Implement POST /travel/ endpoint --- constants/error.constant.js | 5 +- middlewares/hacker.middleware.js | 10 +- middlewares/travel.middleware.js | 89 ++++++++++- middlewares/validators/travel.validator.js | 18 +++ routes/api/travel.js | 167 +++------------------ services/travel.service.js | 4 +- 6 files changed, 138 insertions(+), 155 deletions(-) create mode 100644 middlewares/validators/travel.validator.js diff --git a/constants/error.constant.js b/constants/error.constant.js index 4c398b73..60ac33ca 100644 --- a/constants/error.constant.js +++ b/constants/error.constant.js @@ -45,6 +45,7 @@ const EMAIL_500_MESSAGE = "Error while generating email"; const GENERIC_500_MESSAGE = "Internal error"; const LOGIN_500_MESSAGE = "Error while logging in"; const ROLE_CREATE_500_MESSAGE = "Error while creating role"; +const TRAVEL_CREATE_500_MESSAGE = "Error while creating travel"; module.exports = { ACCOUNT_404_MESSAGE: ACCOUNT_404_MESSAGE, @@ -84,5 +85,7 @@ module.exports = { TEAM_READ_500_MESSAGE: TEAM_READ_500_MESSAGE, VOLUNTEER_404_MESSAGE: VOLUNTEER_404_MESSAGE, SPONSOR_UPDATE_500_MESSAGE: SPONSOR_UPDATE_500_MESSAGE, - SETTINGS_404_MESSAGE: SETTINGS_404_MESSAGE + SETTINGS_404_MESSAGE: SETTINGS_404_MESSAGE, + TRAVEL_404_MESSAGE: TRAVEL_404_MESSAGE, + TRAVEL_CREATE_500_MESSAGE: TRAVEL_CREATE_500_MESSAGE }; diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index affede09..444e9c65 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -158,9 +158,9 @@ async function validateConfirmedStatusFromHackerId(req, res, next) { const hacker = await Services.Hacker.findById(req.params.id); if (hacker == null) { return next({ - status: 404, - message: Constants.Error.HACKER_404_MESSAGE, - data: req.body.hackerId + status: 404, + message: Constants.Error.HACKER_404_MESSAGE, + data: req.body.hackerId }); } const account = await Services.Account.findById(hacker.accountId); @@ -235,7 +235,7 @@ function ensureAccountLinkedToHacker(req, res, next) { hacker && req.user && String.toString(hacker.accountId) === - String.toString(req.user.id) + String.toString(req.user.id) ) { return next(); } else { @@ -536,7 +536,7 @@ function parseAccept(req, res, next) { /** - * @function createhacker + * @function createHacker * @param {{body: {hackerDetails: object}}} req * @param {*} res * @param {(err?)=>void} next diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index a9698a6c..fa28a6e9 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -3,8 +3,8 @@ const TAG = `[ TRAVEL.MIDDLEWARE.js ]`; const mongoose = require("mongoose"); const Services = { - Travel: require("../services/travel.service") - //Hacker: require("../services/hacker.service"), + Travel: require("../services/travel.service"), + Hacker: require("../services/hacker.service"), //Storage: require("../services/storage.service"), //Email: require("../services/email.service"), //Account: require("../services/account.service"), @@ -18,6 +18,88 @@ const Constants = { Error: require("../constants/error.constant") }; +/** + * @function parseTravel + * @param {{body: {accountId: ObjectId, hackerId: ObjectId, request: number, authorization: string}}} req + * @param {*} res + * @param {(err?)=>void} next + * @return {void} + * @description + * Moves accountId, hackerId & request from req.body to req.body.travelId. + * Adds _id to hackerDetails. + */ +function parseTravel(req, res, next) { + const travelDetails = { + _id: mongoose.Types.ObjectId(), + accountId: req.body.accountId, + hackerId: req.body.hackerId, + request: req.body.request + }; + req.body.token = req.body.authorization; + + delete req.body.accountId; + delete req.body.hackerId; + delete req.body.request; + + req.body.travelDetails = travelDetails; + + return next(); +} + +/** + * @function addDefaultStatusAndOffer + * @param {{body: {travelDetails: {status: String, offer: Number}}}} req + * @param {JSON} res + * @param {(err?)=>void} next + * @return {void} + * @description Adds default status and offer to travelDetails. + */ +function addDefaultStatusAndOffer(req, res, next) { + req.body.travelDetails.status = "None"; + req.body.travelDetails.offer = 0; + return next(); +} + +/** + * @function createTravel + * @param {{body: {hackerTravel: object}}} req + * @param {*} res + * @param {(err?)=>void} next + * @return {void} + * @description + * Creates travel document after making sure there is no other hacker with the same linked accountId or hackerId + */ +async function createTravel(req, res, next) { + const travelDetails = req.body.travelDetails; + + const exists = await Services.Hacker.findByAccountId( + travelDetails.accountId + ) || Services.Hacker.findById( + travelDetails.hackerId + ); + + if (exists) { + return next({ + status: 422, + message: Constants.Error.ACCOUNT_DUPLICATE_422_MESSAGE, + data: { + id: travelDetails.accountId + } + }); + } + const travel = await Services.Travel.createTravel(travelDetails); + if (!!hacker) { + req.body.travel = travel; + return next(); + } else { + return next({ + status: 500, + message: Constants.Error.TRAVEL_CREATE_500_MESSAGE, + data: {} + }); + } +} + /** * Finds the travel information of the logged in user * and places that information in req.body.travel @@ -56,5 +138,8 @@ async function findSelf(req, res, next) { } module.exports = { + parseTravel: parseTravel, + addDefaultStatusAndOffer: addDefaultStatusAndOffer, + createTravel: Middleware.Util.asyncMiddleware(createTravel), findSelf: Middleware.Util.asyncMiddleware(findSelf) }; diff --git a/middlewares/validators/travel.validator.js b/middlewares/validators/travel.validator.js new file mode 100644 index 00000000..be97ccf2 --- /dev/null +++ b/middlewares/validators/travel.validator.js @@ -0,0 +1,18 @@ +"use strict"; +const VALIDATOR = require("./validator.helper"); +const Constants = require("../../constants/general.constant"); + +module.exports = { + newTravelValidator: [ + VALIDATOR.integerValidator("body", "request", false, 0, 3000), + VALIDATOR.jwtValidator( + "header", + "token", + process.env.JWT_CONFIRM_ACC_SECRET, + true + ) + ], + updateTravelValidator: [ + VALIDATOR.integerValidator("body", "request", false, 0, 3000) + ] +}; diff --git a/routes/api/travel.js b/routes/api/travel.js index 7c23a08c..1e41d4b8 100644 --- a/routes/api/travel.js +++ b/routes/api/travel.js @@ -18,7 +18,7 @@ const Middleware = { }; const Services = { Travel: require('../../services/travel.service'), - Hacker: require("../../services/hacker.service") + //Hacker: require("../../services/hacker.service") }; const CONSTANTS = require("../../constants/general.constant"); @@ -54,178 +54,55 @@ module.exports = { Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized(), - Middleware.Hacker.findSelf, - Controllers.Hacker.showHacker + Middleware.Travel.findSelf, + Controllers.Travel.showTfravel ); /** - * @api {post} /hacker/ create a new hacker - * @apiName createHacker - * @apiGroup Hacker - * @apiVersion 0.0.8 + * @api {post} /travel/ create a new travel + * @apiName createTravel + * @apiGroup Travel + * @apiVersion 2.0.1 * * @apiParam (body) {MongoID} accountId ObjectID of the respective account - * @apiParam (body) {String} school Name of the school the hacker goes to - * @apiParam (body) {String} gender Gender of the hacker - * @apiParam (body) {Number} travel Whether the hacker requires a bus for transportation - * @apiParam (body) {String[]} ethnicity the ethnicities of the hacker - * @apiParam (body) {String[]} major the major of the hacker - * @apiParam (body) {Number} graduationYear the graduation year of the hacker - * @apiParam (body) {Boolean} codeOfConduct acceptance of the code of conduct - * @apiParam (body) {Json} application The hacker's application. Resume and jobInterest fields are required. - * @apiParamExample {Json} application: - * { - "application":{ - "general":{ - "school": "McGill University", - "degree": "Undergraduate", - "fieldOfStudy": "Computer Science", - "graduationYear": "2021", - "jobInterest":"Internship", - "URL":{ - "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", - "github":"https://github.com/abcd", - "dropler":"https://dribbble.com/abcd", - "personal":"https://www.hi.com/", - "linkedIn":"https://linkedin.com/in/abcd", - "other":"https://github.com/hackmcgill/hackerAPI/issues/168" - }, - }, - "shortAnswer": { - "skills":["Javascript","Typescript"], - "question1": "I love McHacks", - "question2":"Pls accept me", - "comments":"hi!", - }, - "other:" { - "gender": "male", - "ethnicity": "Asian or Pacific Islander", - "privacyPolicy": true, - "codeOfConduct": true, - } - "accomodation": { - "travel": 0 - }, - } - - * } + * @apiParam (body) {MongoID} hackerId ObjectID of the respective hacker + * @apiParam (body) {Number} request The amount of money the traveller wants for travel * * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object + * @apiSuccess {object} data Travel object * @apiSuccessExample {object} Success-Response: * { - * "message": "Hacker creation successful", + * "message": "Travel creation successful", * "data": { - "id":"5bff4d736f86be0a41badb91", - "application":{ - "general":{ - "school": "McGill University", - "degree": "Undergraduate", - "fieldOfStudy": "Computer Science", - "graduationYear": "2021", - "jobInterest":"Internship", - "URL":{ - "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", - "github":"https://github.com/abcd", - "dropler":"https://dribbble.com/abcd", - "personal":"https://www.hi.com/", - "linkedIn":"https://linkedin.com/in/abcd", - "other":"https://github.com/hackmcgill/hackerAPI/issues/168" - }, - }, - "shortAnswer": { - "skills":["Javascript","Typescript"], - "question1": "I love McHacks", - "question2":"Pls accept me", - "comments":"hi!", - }, - "other:" { - "gender": "male", - "ethnicity": "Asian or Pacific Islander", - "privacyPolicy": true, - "codeOfConduct": true, - } - "accomodation": { - "travel": 0 - }, - } + * "id":"5bff4d736f86be0a41badb91", + * "status": "None", + * "request": 50, + * "offer": 0 + * } * } * @apiError {string} message Error message * @apiError {object} data empty * @apiErrorExample {object} Error-Response: - * {"message": "Error while creating hacker", "data": {}} + * {"message": "Error while creating travel", "data": {}} */ hackerRouter.route("/").post( Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized(), - Middleware.Validator.Hacker.newHackerValidator, + Middleware.Validator.Travel.newTravelValidator, Middleware.parseBody.middleware, // validate type Middleware.Hacker.validateConfirmedStatusFromAccountId, - // validate that the accountId is not being used for any other thing - Middleware.Hacker.checkDuplicateAccountLinks, - Middleware.Hacker.parseHacker, + Middleware.Travel.parseHacker, - Middleware.Hacker.addDefaultStatus, - Middleware.Auth.createRoleBindings(CONSTANTS.HACKER), - Middleware.Hacker.createHacker, - Middleware.Hacker.sendAppliedStatusEmail, + Middleware.Travel.addDefaultStatusAndOffer, + Middleware.Travel.createTravel, - Controllers.Hacker.createdHacker + Controllers.Travel.createdTravel ); - /** - * @api {get} /hacker/stats - * Gets the stats of all of the hackers who have applied. - * @apiName getHackerStats - * @apiGroup Hacker - * @apiVersion 0.0.9 - * - * @apiParam (query) {String} model the model to be searched (Only hacker supported) - * @apiParam (query) {Array} q the query to be executed. For more information on how to format this, please see https://docs.mchacks.ca/architecture/ - * - * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object - * @apiSuccessExample {object} Success-Response: - * { - * "message": "Retrieved stats", - * "data": { - * "stats" : { - * "total": 10, - "status": { "Applied": 10 }, - "school": { "McGill University": 3, "Harvard University": 7 }, - degree: { "Undergraduate": 10 }, - gender: { "Male": 1, "Female": 9 }, - travel: { "true": 7, "false": 3 }, - ethnicity: { "White": 10, }, - jobInterest: { "Internship": 10 }, - major: { "Computer Science": 10 }, - graduationYear: { "2019": 10 }, - dietaryRestrictions: { "None": 10 }, - shirtSize: { "M": 3, "XL": 7 }, - age: { "22": 10 } - } - * } - * } - * - */ - hackerRouter - .route("/stats") - .get( - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized(), - Middleware.Validator.Hacker.statsValidator, - Middleware.parseBody.middleware, - Middleware.Search.setExpandTrue, - Middleware.Search.parseQuery, - Middleware.Search.executeQuery, - Middleware.Hacker.getStats, - Controllers.Hacker.gotStats - ); - /** * @api {patch} /hacker/status/:id update a hacker's status * @apiName patchHackerStatus diff --git a/services/travel.service.js b/services/travel.service.js index 5a3e0a6c..540c9c5b 100644 --- a/services/travel.service.js +++ b/services/travel.service.js @@ -11,7 +11,7 @@ const logger = require("./logger.service"); * @return {Promise} The promise will resolve to a travel object if save is successful. * @description Adds a new travel to database. */ -function createHacker(travelDetails) { +function createTravel(travelDetails) { const TAG = `[Travel Service # createTravel]:`; const travel = new Travel(travelDetails); @@ -93,7 +93,7 @@ function findByAccountId(accountId) { * @param {ObjectId} travelId * @return {DocumentQuery} A travel document queried by hackerId */ -function findByAccountId(accountId) { +function findByHackerId(accountId) { const TAG = `[ Travel Service # findByAccountId ]:`; const query = { hackerId: hackerId From e9b5f227a1a2e61a113e8ed8c4f9dd82a27c0187 Mon Sep 17 00:00:00 2001 From: logan-r Date: Tue, 31 Dec 2019 13:18:40 -0500 Subject: [PATCH 05/14] Implement PATCH /travel/status/:id and PATCH /travel/offer/:id endpoints --- constants/success.constant.js | 4 + controllers/travel.controller.js | 18 +- middlewares/travel.middleware.js | 21 +++ middlewares/validators/travel.validator.js | 17 ++ routes/api/travel.js | 206 +++------------------ 5 files changed, 85 insertions(+), 181 deletions(-) diff --git a/constants/success.constant.js b/constants/success.constant.js index c752174e..e908f651 100644 --- a/constants/success.constant.js +++ b/constants/success.constant.js @@ -28,6 +28,8 @@ const RESUME_UPLOAD = "Resume upload successful."; const RESUME_DOWNLOAD = "Resume download successful."; const TRAVEL_READ = "Travel retrieval successful."; +const TRAVEL_CREATE = "Travel creation successful."; +const TRAVEL_UPDATE = "Travel update successful."; const ROLE_CREATE = "Role creation successful."; @@ -81,6 +83,8 @@ module.exports = { RESUME_DOWNLOAD: RESUME_DOWNLOAD, TRAVEL_READ: TRAVEL_READ, + TRAVEL_CREATE: TRAVEL_CREATE, + TRAVE_UPDATE: TRAVEL_UPDATE, ROLE_CREATE: ROLE_CREATE, diff --git a/controllers/travel.controller.js b/controllers/travel.controller.js index c2a78543..27120305 100644 --- a/controllers/travel.controller.js +++ b/controllers/travel.controller.js @@ -4,6 +4,19 @@ const Constants = { Error: require("../constants/error.constant") }; +/** + * @function parsePatch + * @param {body: {id: ObjectId}} req + * @param {*} res + * @param {(err?) => void} next + * @return {void} + * @description Delete the req.body.id that was added by the validation of route parameter. + */ +function parsePatch(req, res, next) { + delete req.body.id; + return next(); +} + /** * @function showTravel * @param {{body: {travel: Object}}} req @@ -35,7 +48,7 @@ function createdTravel(req, res) { } /** - * @function updateHacker + * @function updatedTravel * @param {{params: {id: ObjectId}, body: {Object}}} req * @param {*} res * @return {JSON} Success or error status @@ -47,12 +60,13 @@ function createdTravel(req, res) { */ function updatedTravel(req, res) { return res.status(200).json({ - message: Constants.Success.TRAVEL_UPLOAD, + message: Constants.Success.TRAVEL_UPDATE, data: req.body }); } module.exports = { + parsePatch: parsePatch, showTravel: showTravel, updatedTravel: updatedTravel, createdTravel: createdTravel diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index fa28a6e9..0572d379 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -100,6 +100,27 @@ async function createTravel(req, res, next) { } } +/** + * Updates a travel that is specified by req.params.id + * @param {{params:{id: string}, body: *}} req + * @param {*} res + * @param {*} next + */ +async function updateTravel(req, res, next) { + const travel = await Services.Travel.updateOne(req.params.id, req.body); + if (travel) { + return next(); + } else { + return next({ + status: 404, + message: Constants.Error.TRAVEL_404_MESSAGE, + data: { + id: req.params.id + } + }); + } +} + /** * Finds the travel information of the logged in user * and places that information in req.body.travel diff --git a/middlewares/validators/travel.validator.js b/middlewares/validators/travel.validator.js index be97ccf2..9f9c768c 100644 --- a/middlewares/validators/travel.validator.js +++ b/middlewares/validators/travel.validator.js @@ -14,5 +14,22 @@ module.exports = { ], updateTravelValidator: [ VALIDATOR.integerValidator("body", "request", false, 0, 3000) + ], + updateStatusValidator: [ + VALIDATOR.enumValidator( + "body", + "status", + Constants.TRAVEL_STATUSES, + false + ) + ], + updateOfferValidator: [ + VALIDATOR.integerValidator( + "body", + "offer", + false, + 0, + 3000 + ) ] }; diff --git a/routes/api/travel.js b/routes/api/travel.js index 1e41d4b8..a3e920d4 100644 --- a/routes/api/travel.js +++ b/routes/api/travel.js @@ -104,17 +104,17 @@ module.exports = { ); /** - * @api {patch} /hacker/status/:id update a hacker's status - * @apiName patchHackerStatus - * @apiGroup Hacker - * @apiVersion 0.0.9 + * @api {patch} /travel/status/:id update a traveler's status + * @apiName patchTravelStatus + * @apiGroup Travel + * @apiVersion 2.0.1 * - * @apiParam (body) {string} [status] Status of the hacker's application ("None"|"Applied"|"Accepted"|"Declined"|"Waitlisted"|"Confirmed"|"Withdrawn"|"Checked-in") + * @apiParam (body) {string} [status] Status of the travel's reimbursement ("None"|"Bus"|"Offered"|"Valid"|"Invalid"|"Claimed") * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object + * @apiSuccess {object} data Travel object * @apiSuccessExample {object} Success-Response: * { - * "message": "Changed hacker information", + * "message": "Changed travel information", * "data": { * "status": "Accepted" * } @@ -124,199 +124,47 @@ module.exports = { hackerRouter.route("/status/:id").patch( Middleware.Validator.RouteParam.idValidator, Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - Middleware.Validator.Hacker.updateStatusValidator, + Middleware.Auth.ensureAuthorized([Services.Travel.findById]), + Middleware.Validator.Travel.updateStatusValidator, Middleware.parseBody.middleware, - Middleware.Hacker.parsePatch, - Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Travel.parsePatch, - Middleware.Hacker.updateHacker, - Middleware.Hacker.sendStatusUpdateEmail, - Controllers.Hacker.updatedHacker + Middleware.Travel.updateTravel, + Controllers.Travel.updateTravel ); /** - * @api {patch} /hacker/accept/:id accept a Hacker - * @apiName acceptHacker - * @apiGroup Hacker - * @apiVersion 2.0.0 + * @api {patch} /travel/offer/:id update a traveler's offer + * @apiName patchTravelOffer + * @apiGroup Travel + * @apiVersion 2.0.1 * + * @apiParam (body) {number} [offer] Amount of money offered for travel * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object - * @apiSuccessExample {object} Success-Response: - * { - * "message": "Changed hacker information", - * "data": { - * "status": "Accepted" - * } - * } - * @apiPermission Administrator - */ - hackerRouter - .route("/accept/:id") - .patch( - Middleware.Validator.RouteParam.idValidator, - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - Middleware.Hacker.validateConfirmedStatusFromHackerId, - Middleware.Hacker.parseAccept, - Middleware.Hacker.updateHacker, - Middleware.Hacker.sendStatusUpdateEmail, - Controllers.Hacker.updatedHacker - ); - - /** - * @api {patch} /hacker/checkin/:id update a hacker's status to be 'Checked-in'. Note that the Hacker must eitehr be Accepted or Confirmed. - * @apiName checkinHacker - * @apiGroup Hacker - * @apiVersion 0.0.9 - * @apiParam (body) {string} [status] Check-in status. "Checked-in" - * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object + * @apiSuccess {object} data Travel object * @apiSuccessExample {object} Success-Response: * { - * "message": "Changed hacker information", + * "message": "Changed travel information", * "data": { - * "status": "Checked-in" + * "offer": 75 * } * } * @apiPermission Administrator - * @apiPermission Volunteer - */ - hackerRouter.route("/checkin/:id").patch( - Middleware.Validator.RouteParam.idValidator, - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - - Middleware.parseBody.middleware, - Middleware.Hacker.parsePatch, - Middleware.Hacker.validateConfirmedStatusFromHackerId, - Middleware.Hacker.checkStatus([ - CONSTANTS.HACKER_STATUS_ACCEPTED, - CONSTANTS.HACKER_STATUS_CONFIRMED - ]), - Middleware.Hacker.parseCheckIn, - Middleware.Hacker.updateHacker, - - Middleware.Hacker.sendStatusUpdateEmail, - Controllers.Hacker.updatedHacker - ); - - /** - * @api {patch} /hacker/:id update a hacker's information. - * @apiDescription This route only contains the ability to update a subset of a hacker's information. If you want to update a status, you must have Admin priviledges and use PATCH /hacker/status/:id. - * @apiName patchHacker - * @apiGroup Hacker - * @apiVersion 0.0.8 - * - * @apiParam (body) {String} [school] Name of the school the hacker goes to - * @apiParam (body) {String} [gender] Gender of the hacker - * @apiParam (body) {Number} [travel] How much the hacker requires a bus for transportation - * @apiParam (body) {String[]} [ethnicity] the ethnicities of the hacker - * @apiParam (body) {String[]} [major] the major of the hacker - * @apiParam (body) {Number} [graduationYear] the graduation year of the hacker - * @apiParam (body) {Json} [application] The hacker's application - * @apiParamExample {Json} application: - * { - "application":{ - "general":{ - "school": "McGill University", - "degree": "Undergraduate", - "fieldOfStudy": "Computer Science", - "graduationYear": "2021", - "jobInterest":"Internship", - "URL":{ - "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", - "github":"https://github.com/abcd", - "dropler":"https://dribbble.com/abcd", - "personal":"https://www.hi.com/", - "linkedIn":"https://linkedin.com/in/abcd", - "other":"https://github.com/hackmcgill/hackerAPI/issues/168" - }, - }, - "shortAnswer": { - "skills":["Javascript","Typescript"], - "question1": "I love McHacks", - "question2":"Pls accept me", - "comments":"hi!", - }, - "other:" { - "gender": "male", - "ethnicity": "Asian or Pacific Islander", - "privacyPolicy": true, - "codeOfConduct": true, - } - "accomodation": { - "travel": 0 - }, - } - } - * - * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object - * @apiSuccessExample {object} Success-Response: - * { - * "message": "Changed hacker information", - * "data": { - "id":"5bff4d736f86be0a41badb91", - "status": "Applied", - "application":{ - "general":{ - "school": "McGill University", - "degree": "Undergraduate", - "fieldOfStudy": "Computer Science", - "graduationYear": "2021", - "jobInterest":"Internship", - "URL":{ - "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", - "github":"https://github.com/abcd", - "dropler":"https://dribbble.com/abcd", - "personal":"https://www.hi.com/", - "linkedIn":"https://linkedin.com/in/abcd", - "other":"https://github.com/hackmcgill/hackerAPI/issues/168" - }, - }, - "shortAnswer": { - "skills":["Javascript","Typescript"], - "question1": "I love McHacks", - "question2":"Pls accept me", - "comments":"hi!", - }, - "other:" { - "gender": "male", - "ethnicity": "Asian or Pacific Islander", - "privacyPolicy": true, - "codeOfConduct": true, - } - "accomodation": { - "travel": 0 - }, - } - } - * } - * @apiError {string} message Error message - * @apiError {object} data empty - * @apiErrorExample {object} Error-Response: - * {"message": "Error while updating hacker", "data": {}} */ - hackerRouter.route("/:id").patch( + hackerRouter.route("/offer/:id").patch( Middleware.Validator.RouteParam.idValidator, Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - - Middleware.Validator.Hacker.updateHackerValidator, - + Middleware.Auth.ensureAuthorized([Services.Travel.findById]), + Middleware.Validator.Travel.updateOfferValidator, Middleware.parseBody.middleware, - Middleware.Hacker.parsePatch, - Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Travel.parsePatch, - Middleware.Hacker.updateHacker, - Middleware.Hacker.updateStatusIfApplicationCompleted, - Controllers.Hacker.updatedHacker + Middleware.Travel.updateTravel, + Controllers.Travel.updateTravel ); /** - * @api {get} /hacker/:id get a hacker's information + * @api {get} /travel/:id get a hacker's information * @apiName getHacker * @apiGroup Hacker * @apiVersion 0.0.8 From 0c3d7ebb4ae3b8b6ab418e48e0e9c93dad90f841 Mon Sep 17 00:00:00 2001 From: logan-r Date: Tue, 31 Dec 2019 13:34:18 -0500 Subject: [PATCH 06/14] Implement GET /travel/:id and GET /travel/email/:email endpoints --- middlewares/travel.middleware.js | 51 ++++- routes/api/travel.js | 338 ++++--------------------------- services/travel.service.js | 2 +- 3 files changed, 82 insertions(+), 309 deletions(-) diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index 0572d379..d59113d9 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -5,10 +5,7 @@ const mongoose = require("mongoose"); const Services = { Travel: require("../services/travel.service"), Hacker: require("../services/hacker.service"), - //Storage: require("../services/storage.service"), - //Email: require("../services/email.service"), - //Account: require("../services/account.service"), - //Env: require("../services/env.service") + Account: require("../services/account.service"), }; const Middleware = { Util: require("./util.middleware") @@ -121,6 +118,49 @@ async function updateTravel(req, res, next) { } } +/** + * @async + * @function findById + * @param {{body: {id: ObjectId}}} req + * @param {*} res + * @description Retrieves a travel's information via req.body.id, moving result to req.body.travel if succesful. + */ +async function findById(req, res, next) { + const hacker = await Services.Travel.findById(req.body.id); + + if (!hacker) { + return next({ + status: 404, + message: Constants.Error.TRAVEL_404_MESSAGE + }); + } + + req.body.travel = travel; + next(); +} + +async function findByEmail(req, res, next) { + const account = await Services.Account.findByEmail(req.body.email); + if (!account) { + return next({ + status: 404, + message: Constants.Error.ACCOUNT_404_MESSAGE, + error: {} + }); + } + const travel = await Services.Travel.findByAccountId(account._id); + if (!travel) { + return next({ + status: 404, + message: Constants.Error.TRAVEL_404_MESSAGE, + error: {} + }); + } + + req.body.travel = travel; + next(); +} + /** * Finds the travel information of the logged in user * and places that information in req.body.travel @@ -162,5 +202,8 @@ module.exports = { parseTravel: parseTravel, addDefaultStatusAndOffer: addDefaultStatusAndOffer, createTravel: Middleware.Util.asyncMiddleware(createTravel), + updateTravel: Middleware.Util.asyncMiddleware(updateTravel), + findById: Middleware.Util.asyncMiddleware(findById), + findByEmail: Middleware.Util.asyncMiddleware(findByEmail), findSelf: Middleware.Util.asyncMiddleware(findSelf) }; diff --git a/routes/api/travel.js b/routes/api/travel.js index a3e920d4..87d8c6a5 100644 --- a/routes/api/travel.js +++ b/routes/api/travel.js @@ -50,7 +50,7 @@ module.exports = { * @apiErrorExample {object} Error-Response: * {"message": "Travel not found", "data": {}} */ - hackerRouter.route("/self").get( + travelRouter.route("/self").get( Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized(), @@ -86,7 +86,7 @@ module.exports = { * @apiErrorExample {object} Error-Response: * {"message": "Error while creating travel", "data": {}} */ - hackerRouter.route("/").post( + travelRouter.route("/").post( Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized(), Middleware.Validator.Travel.newTravelValidator, @@ -121,7 +121,7 @@ module.exports = { * } * @apiPermission Administrator */ - hackerRouter.route("/status/:id").patch( + travelRouter.route("/status/:id").patch( Middleware.Validator.RouteParam.idValidator, Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized([Services.Travel.findById]), @@ -151,7 +151,7 @@ module.exports = { * } * @apiPermission Administrator */ - hackerRouter.route("/offer/:id").patch( + travelRouter.route("/offer/:id").patch( Middleware.Validator.RouteParam.idValidator, Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized([Services.Travel.findById]), @@ -164,349 +164,79 @@ module.exports = { ); /** - * @api {get} /travel/:id get a hacker's information - * @apiName getHacker - * @apiGroup Hacker - * @apiVersion 0.0.8 + * @api {get} /travel/:id get a traveler's information + * @apiName getTravel + * @apiGroup Travel + * @apiVersion 2.0.1 * - * @apiParam (param) {String} id a hacker's unique mongoID + * @apiParam (param) {String} id a travel's unique mongoID * * @apiSuccess {String} message Success message - * @apiSuccess {Object} data Hacker object + * @apiSuccess {Object} data Travel object * @apiSuccessExample {object} Success-Response: * { - "message": "Successfully retrieved hacker information", + "message": "Successfully retrieved travel information", "data": { "id":"5bff4d736f86be0a41badb91", - "status": "Applied", - "application":{ - "general":{ - "school": "McGill University", - "degree": "Undergraduate", - "fieldOfStudy": "Computer Science", - "graduationYear": "2021", - "jobInterest":"Internship", - "URL":{ - "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", - "github":"https://github.com/abcd", - "dropler":"https://dribbble.com/abcd", - "personal":"https://www.hi.com/", - "linkedIn":"https://linkedin.com/in/abcd", - "other":"https://github.com/hackmcgill/hackerAPI/issues/168" - }, - }, - "shortAnswer": { - "skills":["Javascript","Typescript"], - "question1": "I love McHacks", - "question2":"Pls accept me", - "comments":"hi!", - }, - "other:" { - "gender": "male", - "ethnicity": "Asian or Pacific Islander", - "privacyPolicy": true, - "codeOfConduct": true, - } - "accomodation": { - "travel": 0 - }, - } + "status": "Valid", + "request": 100, + "offer": 50 } } * @apiError {String} message Error message * @apiError {Object} data empty * @apiErrorExample {object} Error-Response: - * {"message": "Hacker not found", "data": {}} + * {"message": "Travel not found", "data": {}} */ - hackerRouter.route("/:id").get( + travelRouter.route("/:id").get( Middleware.Validator.RouteParam.idValidator, Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), Middleware.parseBody.middleware, - Middleware.Hacker.findById, - Controllers.Hacker.showHacker + Middleware.Travel.findById, + Controllers.Travel.showHacker ); /** - * @api {get} /hacker/email/:email get a hacker's information - * @apiName getHacker - * @apiGroup Hacker - * @apiVersion 0.0.8 + * @api {get} /travel/email/:email get a travel's information + * @apiName getTravel + * @apiGroup Travel + * @apiVersion 2.0.1 * - * @apiParam (param) {String} email a hacker's unique email + * @apiParam (param) {String} email a travel's unique email * * @apiSuccess {String} message Success message - * @apiSuccess {Object} data Hacker object + * @apiSuccess {Object} data Travel object * @apiSuccessExample {object} Success-Response: - * { - "message": "Successfully retrieved hacker information", + * { + "message": "Successfully retrieved travel information", "data": { "id":"5bff4d736f86be0a41badb91", - "status": "Applied", - "application":{ - "general":{ - "school": "McGill University", - "degree": "Undergraduate", - "fieldOfStudy": "Computer Science", - "graduationYear": "2021", - "jobInterest":"Internship", - "URL":{ - "resume":"resumes/1543458163426-5bff4d736f86be0a41badb91", - "github":"https://github.com/abcd", - "dropler":"https://dribbble.com/abcd", - "personal":"https://www.hi.com/", - "linkedIn":"https://linkedin.com/in/abcd", - "other":"https://github.com/hackmcgill/hackerAPI/issues/168" - }, - }, - "shortAnswer": { - "skills":["Javascript","Typescript"], - "question1": "I love McHacks", - "question2":"Pls accept me", - "comments":"hi!", - }, - "other:" { - "gender": "male", - "ethnicity": "Asian or Pacific Islander", - "privacyPolicy": true, - "codeOfConduct": true, - } - "accomodation": { - "travel": 0 - }, - } + "status": "Valid", + "request": 100, + "offer": 50 } } * @apiError {String} message Error message * @apiError {Object} data empty * @apiErrorExample {object} Error-Response: - * {"message": "Hacker not found", "data": {}} + * {"message": "Travel not found", "data": {}} */ - hackerRouter.route("/email/:email").get( + travelRouter.route("/email/:email").get( Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized([Services.Account.findByEmail]), Middleware.Validator.RouteParam.emailValidator, Middleware.parseBody.middleware, - Middleware.Hacker.findByEmail, - Controllers.Hacker.showHacker - ); - - hackerRouter - .route("/resume/:id") - /** - * @api {post} /hacker/resume/:id upload or update resume for a hacker. - * @apiName postHackerResume - * @apiGroup Hacker - * @apiVersion 0.0.8 - * @apiDescription NOTE: This must be sent via multipart/form-data POST request - * - * @apiParam (param) {ObjectId} id Hacker id - * @apiParam (body) {File} resume The uploaded file. - * - * @apiSuccess {String} message Success message - * @apiSuccess {Object} data Location in the bucket that the file was stored. - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * message: "Uploaded resume", - * data: { - * filename: "resumes/1535032624768-507f191e810c19729de860ea" - * } - * } - * - * @apiPermission Must be logged in, and the account id must be linked to the hacker. - */ - .post( - //TODO: authenticate middleware - Middleware.Validator.Hacker.uploadResumeValidator, - Middleware.parseBody.middleware, - //verify that the hacker entity contains the account id - Middleware.Hacker.ensureAccountLinkedToHacker, - //load resume into memory - Middleware.Util.Multer.single("resume"), - //upload resume to storage and update hacker profile - Middleware.Hacker.uploadResume, - //controller response - Controllers.Hacker.uploadedResume - ) - /** - * @api {get} /hacker/resume:id get the resume for a hacker. - * @apiName getHackerResume - * @apiGroup Hacker - * @apiVersion 0.0.8 - * - * @apiParam (param) {ObjectId} id Hacker id - * - * @apiSuccess {String} message Success message - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * message: "Downloaded resume", - * data: { - * id: "507f191e810c19729de860ea", - * resume: [Buffer] - * } - * } - * @apiError {String} message "Resume does not exist" - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 404 - * { - * message: "Resume not found", - * data: {} - * } - * @apiSampleRequest off - * @apiPermission Must be logged in, and the account id must be linked to the hacker. - */ - .get( - //TODO: authenticate middleware - Middleware.Validator.Hacker.downloadResumeValidator, - Middleware.parseBody.middleware, - Middleware.Hacker.downloadResume, - Controllers.Hacker.downloadedResume - ); - - /** - * @api {patch} /hacker/confirmation/:id - * Allows confirmation of hacker attendence if they are accepted. Also allows change from 'confirmed' to 'withdrawn'. - * @apiName patchHackerConfirmed - * @apiGroup Hacker - * @apiVersion 0.0.9 - * - * @apiParam (body) {string} [status] The new status of the hacker. "Accepted", "Confirmed", or "Withdrawn" - * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object - * @apiSuccessExample {object} Success-Response: - * { - * "message": "Changed hacker information", - * "data": { - * "status": "Confirmed" - * } - * } - * @apiPermission Administrator - * @apiPermission Hacker - */ - hackerRouter.route("/confirmation/:id").patch( - Middleware.Validator.RouteParam.idValidator, - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - - Middleware.Validator.Hacker.updateConfirmationValidator, - Middleware.parseBody.middleware, - Middleware.Hacker.parsePatch, - Middleware.Hacker.validateConfirmedStatusFromHackerId, - Middleware.Hacker.checkStatus([ - CONSTANTS.HACKER_STATUS_ACCEPTED, - CONSTANTS.HACKER_STATUS_CONFIRMED, - CONSTANTS.HACKER_STATUS_WITHDRAWN - ]), - - Middleware.Hacker.parseConfirmation, - Middleware.Hacker.updateHacker, - - Middleware.Hacker.sendStatusUpdateEmail, - Controllers.Hacker.updatedHacker - ); - - /** - * @api {post} /hacker/email/weekOf/:id - * @apiDescription Sends a hacker the week-of email, along with the HackPass QR code to view their hacker profile (for checkin purposes). Hackers must be either confirmed, or checked in. - * @apiName postHackerSendWeekOfEmail - * @apiGroup Hacker - * @apiVersion 0.0.9 - * - * @apiParam (param) {string} [status] The hacker ID - * @apiSuccess {string} message Success message - * @apiSuccess {object} data empty - * @apiSuccessExample {object} Success-Response: - * { - * "message": "Hacker week-of email sent.", - * "data": {} - * } - * @apiPermission Administrator - */ - hackerRouter.route("/email/weekOf/:id").post( - Middleware.Validator.RouteParam.idValidator, - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - - Middleware.parseBody.middleware, - Middleware.Hacker.findById, - - Middleware.Hacker.validateConfirmedStatusFromHackerId, - Middleware.Hacker.checkStatus([ - CONSTANTS.HACKER_STATUS_CONFIRMED, - CONSTANTS.HACKER_STATUS_CHECKED_IN - ]), - - Middleware.Hacker.sendWeekOfEmail, - Controllers.Hacker.sentWeekOfEmail - ); - - /** - * @api {post} /hacker/email/dayOf/:id - * @apiDescription Sends a hacker the day-of email, along with the HackPass QR code to view their hacker profile (for checkin purposes). Hackers must be either confirmed, or checked in. - * @apiName postHackerSendDayOfEmail - * @apiGroup Hacker - * @apiVersion 0.0.9 - * - * @apiParam (param) {string} [status] The hacker ID - * @apiSuccess {string} message Success message - * @apiSuccess {object} data empty - * @apiSuccessExample {object} Success-Response: - * { - * "message": "Hacker day-of email sent.", - * "data": {} - * } - * @apiPermission Administrator - */ - hackerRouter.route("/email/dayOf/:id").post( - Middleware.Validator.RouteParam.idValidator, - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - - Middleware.parseBody.middleware, - Middleware.Hacker.findById, - Middleware.Hacker.validateConfirmedStatusFromHackerId, - Middleware.Hacker.checkStatus([CONSTANTS.HACKER_STATUS_CHECKED_IN]), - Middleware.Hacker.sendDayOfEmail, - Controllers.Hacker.sentDayOfEmail - ); - - /** - * @api {post} /hacker/email/weekOf/:id - * @apiDescription Sends a hacker the week-of email, along with the HackPass QR code to view their hacker profile (for checkin purposes). Hackers must be eitherconfirmed, or checked in. - * @apiName postHackerSendWeekOfEmail - * @apiGroup Hacker - * @apiVersion 0.0.9 - * - * @apiParam (param) {string} [status] The hacker ID - * @apiSuccess {string} message Success message - * @apiSuccess {object} data empty - * @apiSuccessExample {object} Success-Response: - * { - * "message": "Hacker week-of email sent.", - * "data": {} - * } - * @apiPermission Administrator - */ - hackerRouter.route("/email/dayOf/:id").post( - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - - Middleware.Validator.RouteParam.idValidator, - Middleware.parseBody.middleware, - Middleware.Hacker.findById, - Middleware.Hacker.checkStatus([CONSTANTS.HACKER_STATUS_CHECKED_IN]), - Middleware.Hacker.sendDayOfEmail, - Controllers.Hacker.sentDayOfEmail + Middleware.Travel.findByEmail, + Controllers.Travel.showHacker ); - apiRouter.use("/hacker", hackerRouter); + apiRouter.use("/travel", travelRouter); } }; diff --git a/services/travel.service.js b/services/travel.service.js index 540c9c5b..e9a18c6f 100644 --- a/services/travel.service.js +++ b/services/travel.service.js @@ -93,7 +93,7 @@ function findByAccountId(accountId) { * @param {ObjectId} travelId * @return {DocumentQuery} A travel document queried by hackerId */ -function findByHackerId(accountId) { +function findByHackerId(hackerId) { const TAG = `[ Travel Service # findByAccountId ]:`; const query = { hackerId: hackerId From 99450d72d7ddd1817241c024c3249d37a594ec5c Mon Sep 17 00:00:00 2001 From: logan-r Date: Wed, 1 Jan 2020 10:08:48 -0500 Subject: [PATCH 07/14] Fix bugs in travel endpoints --- app.js | 3 + controllers/travel.controller.js | 14 --- middlewares/travel.middleware.js | 15 +++ routes/api/travel.js | 162 ++++++++++++++++--------------- 4 files changed, 100 insertions(+), 94 deletions(-) diff --git a/app.js b/app.js index 4de92284..2c6359a5 100755 --- a/app.js +++ b/app.js @@ -25,6 +25,7 @@ const accountRouter = require("./routes/api/account"); const authRouter = require("./routes/api/auth"); const hackerRouter = require("./routes/api/hacker"); const teamRouter = require("./routes/api/team"); +const travelRouter = require("./routes/api/travel"); const sponsorRouter = require("./routes/api/sponsor"); const searchRouter = require("./routes/api/search"); const settingsRouter = require("./routes/api/settings"); @@ -87,6 +88,8 @@ hackerRouter.activate(apiRouter); Services.log.info("Hacker router activated"); teamRouter.activate(apiRouter); Services.log.info("Team router activated"); +travelRouter.activate(apiRouter); +Services.log.info("Travel router activated") sponsorRouter.activate(apiRouter); Services.log.info("Sponsor router activated"); volunteerRouter.activate(apiRouter); diff --git a/controllers/travel.controller.js b/controllers/travel.controller.js index 27120305..b33e55b2 100644 --- a/controllers/travel.controller.js +++ b/controllers/travel.controller.js @@ -4,19 +4,6 @@ const Constants = { Error: require("../constants/error.constant") }; -/** - * @function parsePatch - * @param {body: {id: ObjectId}} req - * @param {*} res - * @param {(err?) => void} next - * @return {void} - * @description Delete the req.body.id that was added by the validation of route parameter. - */ -function parsePatch(req, res, next) { - delete req.body.id; - return next(); -} - /** * @function showTravel * @param {{body: {travel: Object}}} req @@ -66,7 +53,6 @@ function updatedTravel(req, res) { } module.exports = { - parsePatch: parsePatch, showTravel: showTravel, updatedTravel: updatedTravel, createdTravel: createdTravel diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index d59113d9..b57f576c 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -15,6 +15,20 @@ const Constants = { Error: require("../constants/error.constant") }; +/** + * @function parsePatch + * @param {body: {id: ObjectId}} req + * @param {*} res + * @param {(err?) => void} next + * @return {void} + * @description Delete the req.body.id that was added by the validation of route parameter. + */ +function parsePatch(req, res, next) { + delete req.body.id; + return next(); +} + + /** * @function parseTravel * @param {{body: {accountId: ObjectId, hackerId: ObjectId, request: number, authorization: string}}} req @@ -199,6 +213,7 @@ async function findSelf(req, res, next) { } module.exports = { + parsePatch: parsePatch, parseTravel: parseTravel, addDefaultStatusAndOffer: addDefaultStatusAndOffer, createTravel: Middleware.Util.asyncMiddleware(createTravel), diff --git a/routes/api/travel.js b/routes/api/travel.js index 87d8c6a5..f2b5e7a1 100644 --- a/routes/api/travel.js +++ b/routes/api/travel.js @@ -13,12 +13,14 @@ const Middleware = { parseBody: require("../../middlewares/parse-body.middleware"), Util: require("../../middlewares/util.middleware"), Travel: require("../../middlewares/travel.middleware"), + Hacker: require("../../middlewares/hacker.middleware"), Auth: require("../../middlewares/auth.middleware"), - Search: require("../../middlewares/search.middleware") + //Search: require("../../middlewares/search.middleware") }; const Services = { Travel: require('../../services/travel.service'), - //Hacker: require("../../services/hacker.service") + Hacker: require("../../services/hacker.service"), + Account: require("../../services/account.service") }; const CONSTANTS = require("../../constants/general.constant"); @@ -26,6 +28,80 @@ module.exports = { activate: function (apiRouter) { const travelRouter = express.Router(); + /** + * @api {get} /travel/:id get a traveler's information + * @apiName getTravel + * @apiGroup Travel + * @apiVersion 2.0.1 + * + * @apiParam (param) {String} id a travel's unique mongoID + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Travel object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Successfully retrieved travel information", + "data": { + "id":"5bff4d736f86be0a41badb91", + "status": "Valid", + "request": 100, + "offer": 50 + } + } + + * @apiError {String} message Error message + * @apiError {Object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Travel not found", "data": {}} + */ + travelRouter.route("/:id").get( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findByAccountId]), + + Middleware.parseBody.middleware, + + Middleware.Travel.findById, + Controllers.Travel.showTravel + ); + + /** + * @api {get} /travel/email/:email get a travel's information + * @apiName getTravel + * @apiGroup Travel + * @apiVersion 2.0.1 + * + * @apiParam (param) {String} email a travel's unique email + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Travel object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Successfully retrieved travel information", + "data": { + "id":"5bff4d736f86be0a41badb91", + "status": "Valid", + "request": 100, + "offer": 50 + } + } + + * @apiError {String} message Error message + * @apiError {Object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Travel not found", "data": {}} + */ + travelRouter.route("/email/:email").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Account.findByEmail]), + + Middleware.Validator.RouteParam.emailValidator, + Middleware.parseBody.middleware, + + Middleware.Travel.findByEmail, + Controllers.Travel.showTravel + ); + /** * @api {get} /travel/self get information about own hacker's travel * @apiName self @@ -55,7 +131,7 @@ module.exports = { Middleware.Auth.ensureAuthorized(), Middleware.Travel.findSelf, - Controllers.Travel.showTfravel + Controllers.Travel.showTravel ); /** @@ -95,7 +171,7 @@ module.exports = { // validate type Middleware.Hacker.validateConfirmedStatusFromAccountId, - Middleware.Travel.parseHacker, + Middleware.Travel.parseTravel, Middleware.Travel.addDefaultStatusAndOffer, Middleware.Travel.createTravel, @@ -130,7 +206,7 @@ module.exports = { Middleware.Travel.parsePatch, Middleware.Travel.updateTravel, - Controllers.Travel.updateTravel + Controllers.Travel.updatedTravel ); /** @@ -160,81 +236,7 @@ module.exports = { Middleware.Travel.parsePatch, Middleware.Travel.updateTravel, - Controllers.Travel.updateTravel - ); - - /** - * @api {get} /travel/:id get a traveler's information - * @apiName getTravel - * @apiGroup Travel - * @apiVersion 2.0.1 - * - * @apiParam (param) {String} id a travel's unique mongoID - * - * @apiSuccess {String} message Success message - * @apiSuccess {Object} data Travel object - * @apiSuccessExample {object} Success-Response: - * { - "message": "Successfully retrieved travel information", - "data": { - "id":"5bff4d736f86be0a41badb91", - "status": "Valid", - "request": 100, - "offer": 50 - } - } - - * @apiError {String} message Error message - * @apiError {Object} data empty - * @apiErrorExample {object} Error-Response: - * {"message": "Travel not found", "data": {}} - */ - travelRouter.route("/:id").get( - Middleware.Validator.RouteParam.idValidator, - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - - Middleware.parseBody.middleware, - - Middleware.Travel.findById, - Controllers.Travel.showHacker - ); - - /** - * @api {get} /travel/email/:email get a travel's information - * @apiName getTravel - * @apiGroup Travel - * @apiVersion 2.0.1 - * - * @apiParam (param) {String} email a travel's unique email - * - * @apiSuccess {String} message Success message - * @apiSuccess {Object} data Travel object - * @apiSuccessExample {object} Success-Response: - * { - "message": "Successfully retrieved travel information", - "data": { - "id":"5bff4d736f86be0a41badb91", - "status": "Valid", - "request": 100, - "offer": 50 - } - } - - * @apiError {String} message Error message - * @apiError {Object} data empty - * @apiErrorExample {object} Error-Response: - * {"message": "Travel not found", "data": {}} - */ - travelRouter.route("/email/:email").get( - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Account.findByEmail]), - - Middleware.Validator.RouteParam.emailValidator, - Middleware.parseBody.middleware, - - Middleware.Travel.findByEmail, - Controllers.Travel.showHacker + Controllers.Travel.updatedTravel ); apiRouter.use("/travel", travelRouter); From 4e48321e1d938fb36bd779711caabae7b5333e3f Mon Sep 17 00:00:00 2001 From: logan-r Date: Wed, 1 Jan 2020 12:09:04 -0500 Subject: [PATCH 08/14] Load request from travel --- middlewares/travel.middleware.js | 32 +++++++++++++++++++++++++++----- routes/api/travel.js | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index b57f576c..44e2e4db 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -31,32 +31,53 @@ function parsePatch(req, res, next) { /** * @function parseTravel - * @param {{body: {accountId: ObjectId, hackerId: ObjectId, request: number, authorization: string}}} req + * @param {{body: {accountId: ObjectId, hackerId: ObjectId, authorization: string}}} req * @param {*} res * @param {(err?)=>void} next * @return {void} * @description - * Moves accountId, hackerId & request from req.body to req.body.travelId. + * Moves accountId & hackerId from req.body to req.body.travelId. * Adds _id to hackerDetails. */ function parseTravel(req, res, next) { const travelDetails = { _id: mongoose.Types.ObjectId(), accountId: req.body.accountId, - hackerId: req.body.hackerId, - request: req.body.request + hackerId: req.body.hackerId }; req.body.token = req.body.authorization; delete req.body.accountId; delete req.body.hackerId; - delete req.body.request; req.body.travelDetails = travelDetails; return next(); } +/** + * @function addRequestFromHacker + * @param {{body: {travelDetails: {request: Number}}}} req + * @param {JSON} res + * @param {(err?)=>void} next + * @return {void} + * @description Load travel request from hacker application and add it to + */ +async function addRequestFromHacker(req, res, next) { + const hacker = await Services.Hacker.findById(req.body.travelDetails.accountId); + if (!hacker) { + return next({ + status: 500, + message: Constants.Error.HACKER_UPDATE_500_MESSAGE, + data: { + hackerId: hacker.id, + accountId: hacker.accountId + } + }); + } + req.body.travelDetails.request = hacker.application.accommodation.travel; +} + /** * @function addDefaultStatusAndOffer * @param {{body: {travelDetails: {status: String, offer: Number}}}} req @@ -216,6 +237,7 @@ module.exports = { parsePatch: parsePatch, parseTravel: parseTravel, addDefaultStatusAndOffer: addDefaultStatusAndOffer, + addRequestFromHacker: Middleware.Util.asyncMiddleware(addRequestFromHacker), createTravel: Middleware.Util.asyncMiddleware(createTravel), updateTravel: Middleware.Util.asyncMiddleware(updateTravel), findById: Middleware.Util.asyncMiddleware(findById), diff --git a/routes/api/travel.js b/routes/api/travel.js index f2b5e7a1..5a0681ea 100644 --- a/routes/api/travel.js +++ b/routes/api/travel.js @@ -142,7 +142,6 @@ module.exports = { * * @apiParam (body) {MongoID} accountId ObjectID of the respective account * @apiParam (body) {MongoID} hackerId ObjectID of the respective hacker - * @apiParam (body) {Number} request The amount of money the traveller wants for travel * * @apiSuccess {string} message Success message * @apiSuccess {object} data Travel object @@ -173,6 +172,7 @@ module.exports = { Middleware.Travel.parseTravel, + Middleware.Travel.addRequestFromHacker, Middleware.Travel.addDefaultStatusAndOffer, Middleware.Travel.createTravel, From 6b13751aed2f60999345f4f44e88c20376ec85b6 Mon Sep 17 00:00:00 2001 From: logan-r Date: Wed, 1 Jan 2020 14:10:47 -0500 Subject: [PATCH 09/14] Bind Hacker.application.accommondaton.travel to Travel.request --- middlewares/hacker.middleware.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index 444e9c65..aede117b 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -7,6 +7,7 @@ const Services = { Storage: require("../services/storage.service"), Email: require("../services/email.service"), Account: require("../services/account.service"), + Travel: require("../services/travel.service"), Env: require("../services/env.service") }; const Middleware = { @@ -511,6 +512,12 @@ async function updateHacker(req, res, next) { }); } req.email = acct.email; + + // If this hacker has a travel account associated with it, then update request to reflect amount wanted for travel + const travel = await Services.Travel.findByHackerId(hacker.id); + if (travel) { + await Services.Travel.updateOne(travel.id, { "request": hacker.application.accommodation.travel }); + } return next(); } else { return next({ From c203958747636b449521c83f656f6a2ac39cc800 Mon Sep 17 00:00:00 2001 From: logan-r Date: Mon, 13 Jan 2020 15:22:41 -0500 Subject: [PATCH 10/14] Get travel api working --- constants/role.constant.js | 8 ++++ constants/routes.constant.js | 37 +++++++++++++++++ controllers/travel.controller.js | 7 ++++ middlewares/travel.middleware.js | 4 +- routes/api/travel.js | 69 +++++++++++++++++--------------- scripts/batch_update_travel.js | 10 +++++ 6 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 scripts/batch_update_travel.js diff --git a/constants/role.constant.js b/constants/role.constant.js index b2f00147..f9917247 100644 --- a/constants/role.constant.js +++ b/constants/role.constant.js @@ -38,6 +38,14 @@ const hackerRole = { Constants.Routes.hackerRoutes.patchSelfConfirmationById, Constants.Routes.hackerRoutes.getSelf, + Constants.Routes.travelRoutes.getSelf, + Constants.Routes.travelRoutes.getSelfById, + Constants.Routes.travelRoutes.getAnyById, + Constants.Routes.travelRoutes.getSelfByEmail, + Constants.Routes.travelRoutes.getAnyByEmail, + Constants.Routes.travelRoutes.patchAnyStatusById, + Constants.Routes.travelRoutes.patchAnyOfferById, + Constants.Routes.teamRoutes.join, Constants.Routes.teamRoutes.patchSelfById, Constants.Routes.teamRoutes.post, diff --git a/constants/routes.constant.js b/constants/routes.constant.js index e22e1d28..8dafc726 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -146,6 +146,41 @@ const hackerRoutes = { } }; +const travelRoutes = { + getSelf: { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/travel/self/" + }, + getSelfById: { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/travel/" + Constants.ROLE_CATEGORIES.SELF + }, + getAnyById: { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/travel/" + Constants.ROLE_CATEGORIES.ALL + }, + getSelfByEmail: { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/travel/email/" + Constants.ROLE_CATEGORIES.SELF + }, + getAnyByEmail: { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/travel/email/" + Constants.ROLE_CATEGORIES.ALL + }, + post: { + requestType: Constants.REQUEST_TYPES.POST, + uri: "/api/travel/" + }, + patchAnyStatusById: { + requestType: Constants.REQUEST_TYPES.PATCH, + uri: "/api/travel/status/" + Constants.ROLE_CATEGORIES.ALL + }, + patchAnyOfferById: { + requestType: Constants.REQUEST_TYPES.PATCH, + uri: "/api/travel/offer/" + Constants.ROLE_CATEGORIES.ALL + } +} + const sponsorRoutes = { getSelf: { requestType: Constants.REQUEST_TYPES.GET, @@ -259,6 +294,7 @@ const allRoutes = { Auth: authRoutes, Account: accountRoutes, Hacker: hackerRoutes, + Travel: travelRoutes, Sponsor: sponsorRoutes, Team: teamRoutes, Volunteer: volunteerRoutes, @@ -298,6 +334,7 @@ module.exports = { authRoutes: authRoutes, accountRoutes: accountRoutes, hackerRoutes: hackerRoutes, + travelRoutes: travelRoutes, sponsorRoutes: sponsorRoutes, teamRoutes: teamRoutes, volunteerRoutes: volunteerRoutes, diff --git a/controllers/travel.controller.js b/controllers/travel.controller.js index b33e55b2..d69a50a7 100644 --- a/controllers/travel.controller.js +++ b/controllers/travel.controller.js @@ -4,6 +4,12 @@ const Constants = { Error: require("../constants/error.constant") }; +function okay(req, res) { + return res.status(200).json({ + message: "good" + }); +} + /** * @function showTravel * @param {{body: {travel: Object}}} req @@ -53,6 +59,7 @@ function updatedTravel(req, res) { } module.exports = { + okay: okay, showTravel: showTravel, updatedTravel: updatedTravel, createdTravel: createdTravel diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index 44e2e4db..b9a28bee 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -161,9 +161,9 @@ async function updateTravel(req, res, next) { * @description Retrieves a travel's information via req.body.id, moving result to req.body.travel if succesful. */ async function findById(req, res, next) { - const hacker = await Services.Travel.findById(req.body.id); + const travel = await Services.Travel.findById(req.body.id); - if (!hacker) { + if (!travel) { return next({ status: 404, message: Constants.Error.TRAVEL_404_MESSAGE diff --git a/routes/api/travel.js b/routes/api/travel.js index 5a0681ea..715b8660 100644 --- a/routes/api/travel.js +++ b/routes/api/travel.js @@ -28,6 +28,43 @@ module.exports = { activate: function (apiRouter) { const travelRouter = express.Router(); + + travelRouter.route("/").get( + Controllers.Travel.okay + ) + + /** + * @api {get} /travel/self get information about own hacker's travel + * @apiName self + * @apiGroup Travel + * @apiVersion 2.0.1 + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Travel object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Travel found by logged in account id", + "data": { + "id":"5bff4d736f86be0a41badb91", + "status": "Claimed" + "request": 90, + "offer": 80 + } + } + + * @apiError {string} message Error message + * @apiError {object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Travel not found", "data": {}} + */ + travelRouter.route("/self").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), + + Middleware.Travel.findSelf, + Controllers.Travel.showTravel + ); + /** * @api {get} /travel/:id get a traveler's information * @apiName getTravel @@ -102,38 +139,6 @@ module.exports = { Controllers.Travel.showTravel ); - /** - * @api {get} /travel/self get information about own hacker's travel - * @apiName self - * @apiGroup Travel - * @apiVersion 2.0.1 - * - * @apiSuccess {string} message Success message - * @apiSuccess {object} data Travel object - * @apiSuccessExample {object} Success-Response: - * { - "message": "Travel found by logged in account id", - "data": { - "id":"5bff4d736f86be0a41badb91", - "status": "Claimed" - "request": 90, - "offer": 80 - } - } - - * @apiError {string} message Error message - * @apiError {object} data empty - * @apiErrorExample {object} Error-Response: - * {"message": "Travel not found", "data": {}} - */ - travelRouter.route("/self").get( - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized(), - - Middleware.Travel.findSelf, - Controllers.Travel.showTravel - ); - /** * @api {post} /travel/ create a new travel * @apiName createTravel diff --git a/scripts/batch_update_travel.js b/scripts/batch_update_travel.js new file mode 100644 index 00000000..e727e5e3 --- /dev/null +++ b/scripts/batch_update_travel.js @@ -0,0 +1,10 @@ +use hackboard - dev; // Change to product for actual update + +// Create a travel document for every hacker document +db.hackers.find().forEach(hacker => { + let request = 0; + if (hacker.application && hacker.application.accommodation && hacker.application.accommodation.travel) { + request = hacker.application.accommodation.travel; + } + db.travels.insert({ hackerId: hacker._id, accountId: hacker.accountId, request: request, offer: 0, status: "None" }) +}); From 04a5cef36553f8ffe9c32df47b263b28b6ebf885 Mon Sep 17 00:00:00 2001 From: logan-r Date: Tue, 14 Jan 2020 11:52:10 -0500 Subject: [PATCH 11/14] Fix typos --- middlewares/travel.middleware.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index b9a28bee..7e7ae1f8 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -61,7 +61,9 @@ function parseTravel(req, res, next) { * @param {JSON} res * @param {(err?)=>void} next * @return {void} - * @description Load travel request from hacker application and add it to + * @description + * Load travel request from hacker application and add it to + * req.body.travelDetails */ async function addRequestFromHacker(req, res, next) { const hacker = await Services.Hacker.findById(req.body.travelDetails.accountId); @@ -76,6 +78,7 @@ async function addRequestFromHacker(req, res, next) { }); } req.body.travelDetails.request = hacker.application.accommodation.travel; + return next(); } /** From 2b3d2ac441dffd6bc0796f90afafd0a3175218d8 Mon Sep 17 00:00:00 2001 From: logan-r Date: Tue, 14 Jan 2020 11:56:35 -0500 Subject: [PATCH 12/14] Update changelog and fix merge conflicts --- .github/CHANGELOG.md | 7 +++++++ constants/general.constant.js | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 093a4222..ea809a07 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Added travel model +- Added route to create travel +- Added routes to look up travel by id, email or self +- Added routes to status or offer of an existing travel + ## [2.2.0](https://github.com/hackmcgill/hackerapi/tree/2.2.0) - 2020-01-12 ### Added diff --git a/constants/general.constant.js b/constants/general.constant.js index 5c9f219c..2d5ee959 100644 --- a/constants/general.constant.js +++ b/constants/general.constant.js @@ -175,7 +175,6 @@ module.exports = { HACKER_STATUS_WITHDRAWN: HACKER_STATUS_WITHDRAWN, HACKER_STATUS_CHECKED_IN: HACKER_STATUS_CHECKED_IN, HACKER_STATUSES: HACKER_STATUSES, -<<<<<<< HEAD TRAVEL_STATUS_NONE: TRAVEL_STATUS_NONE, TRAVEL_STATUS_BUS: TRAVEL_STATUS_BUS, TRAVEL_STATUS_OFFERED: TRAVEL_STATUS_OFFERED, @@ -183,9 +182,7 @@ module.exports = { TRAVEL_STATUS_INVALID: TRAVEL_STATUS_INVALID, TRAVEL_STATUS_CLAIMED: TRAVEL_STATUS_CLAIMED, TRAVEL_STATUSES: TRAVEL_STATUSES, -======= APPLICATION_CLOSE_TIME: APPLICATION_CLOSE_TIME, ->>>>>>> 050ba0fb617749f0c36fa0ee159e4d3e3ff65868 REQUEST_TYPES: REQUEST_TYPES, JOB_INTERESTS: JOB_INTERESTS, SHIRT_SIZES: SHIRT_SIZES, From 77c191852422f668e17db2bdc18a1ab2735f811f Mon Sep 17 00:00:00 2001 From: logan-r Date: Tue, 14 Jan 2020 12:00:26 -0500 Subject: [PATCH 13/14] Ensure travel is uniquely associated with accounts --- middlewares/travel.middleware.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/middlewares/travel.middleware.js b/middlewares/travel.middleware.js index 7e7ae1f8..ad268b3f 100644 --- a/middlewares/travel.middleware.js +++ b/middlewares/travel.middleware.js @@ -107,10 +107,8 @@ function addDefaultStatusAndOffer(req, res, next) { async function createTravel(req, res, next) { const travelDetails = req.body.travelDetails; - const exists = await Services.Hacker.findByAccountId( + const exists = await Services.Travel.findByAccountId( travelDetails.accountId - ) || Services.Hacker.findById( - travelDetails.hackerId ); if (exists) { @@ -123,7 +121,7 @@ async function createTravel(req, res, next) { }); } const travel = await Services.Travel.createTravel(travelDetails); - if (!!hacker) { + if (!!travel) { req.body.travel = travel; return next(); } else { From 627cd0a9d1a2e3f99c430aba5c2816176d928d96 Mon Sep 17 00:00:00 2001 From: logan-r Date: Tue, 14 Jan 2020 12:35:51 -0500 Subject: [PATCH 14/14] Add support for travel policy agreement travel state --- constants/general.constant.js | 3 +++ routes/api/travel.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/constants/general.constant.js b/constants/general.constant.js index 2d5ee959..cb0c4c06 100644 --- a/constants/general.constant.js +++ b/constants/general.constant.js @@ -30,6 +30,7 @@ const APPLICATION_CLOSE_TIME = 1578286800000; const TRAVEL_STATUS_NONE = "None"; // Hacker has not been offered compensation for travelling const TRAVEL_STATUS_BUS = "Bus"; // Hacker is taking bus to hackathon +const TRAVEL_STATUS_POLICY = "Policy"; // Hacker has been offer some reimbursement, but we are waiting for hacker to accept travel policy first const TRAVEL_STATUS_OFFERED = "Offered"; // Hacker has been offered some amount of compensation for travelling, but we have not verified their reciepts yet const TRAVEL_STATUS_VALID = "Valid"; // Hacker has been offered some amount of compensation for travelling and have uploaded reciepts which we have confirmed to be an approprate amount const TRAVEL_STATUS_INVALID = "Invalid"; // Hacker has been offered some amount of compensation for travelling but have uploaded reciepts which we have confirmed to be an inapproprate amount @@ -37,6 +38,7 @@ const TRAVEL_STATUS_CLAIMED = "Claimed"; // Hacker has been offered some amount const TRAVEL_STATUSES = [ TRAVEL_STATUS_NONE, TRAVEL_STATUS_BUS, + TRAVEL_STATUS_POLICY, TRAVEL_STATUS_OFFERED, TRAVEL_STATUS_VALID, TRAVEL_STATUS_INVALID, @@ -177,6 +179,7 @@ module.exports = { HACKER_STATUSES: HACKER_STATUSES, TRAVEL_STATUS_NONE: TRAVEL_STATUS_NONE, TRAVEL_STATUS_BUS: TRAVEL_STATUS_BUS, + TRAVEL_STATUS_POLICY: TRAVEL_STATUS_POLICY, TRAVEL_STATUS_OFFERED: TRAVEL_STATUS_OFFERED, TRAVEL_STATUS_VALID: TRAVEL_STATUS_VALID, TRAVEL_STATUS_INVALID: TRAVEL_STATUS_INVALID, diff --git a/routes/api/travel.js b/routes/api/travel.js index 715b8660..41a8994c 100644 --- a/routes/api/travel.js +++ b/routes/api/travel.js @@ -113,7 +113,7 @@ module.exports = { * @apiSuccess {String} message Success message * @apiSuccess {Object} data Travel object * @apiSuccessExample {object} Success-Response: - * { + * { "message": "Successfully retrieved travel information", "data": { "id":"5bff4d736f86be0a41badb91",