From 2e6a82de39d01a220db7ee93bbcdd05f30bd95e2 Mon Sep 17 00:00:00 2001 From: Jonathan Reynolds Date: Tue, 4 Oct 2016 10:09:10 -0500 Subject: [PATCH 01/38] Mentor and project schema --- .../V20161004_0917__createMentors.sql | 34 +++++++++++++++++++ .../V20161004_0917__createMentors.revert.sql | 2 ++ 2 files changed, 36 insertions(+) create mode 100644 database/migration/V20161004_0917__createMentors.sql create mode 100644 database/revert/V20161004_0917__createMentors.revert.sql diff --git a/database/migration/V20161004_0917__createMentors.sql b/database/migration/V20161004_0917__createMentors.sql new file mode 100644 index 0000000..76226f4 --- /dev/null +++ b/database/migration/V20161004_0917__createMentors.sql @@ -0,0 +1,34 @@ +CREATE TABLE `mentors` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `user_id` INT UNSIGNED NOT NULL, + `first_name` VARCHAR(255) NOT NULL, + `last_name` VARCHAR(255) NOT NULL, + `shirt_size` ENUM('S', 'M', 'L', 'XL') NOT NULL, + `github` VARCHAR(50) NULL, + `location` VARCHAR(255) NOT NULL, + `summary` VARCHAR(255) NOT NULL, + `occupation` VARCHAR(100) NOT NULL, + `status` ENUM('ACCEPTED', 'WAITLISTED', 'REJECTED', 'PENDING') NOT NULL DEFAULT 'PENDING', + PRIMARY KEY (`id`), + INDEX `fk_mentors_user_id_idx` (`user_id` ASC), + CONSTRAINT `fk_mentors_user_id` + FOREIGN KEY (`user_id`) + REFERENCES `users` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION +); + +CREATE TABLE `mentor_project_ideas` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `mentor_id` INT UNSIGNED NOT NULL, + `link` VARCHAR(255) NOT NULL, + `contributions` VARCHAR(255) NOT NULL, + `ideas` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`), + INDEX `fk_mentor_project_ideas_mentor_id_idx` (`mentor_id` ASC), + CONSTRAINT `fk_mentor_project_ideas_mentor_id` + FOREIGN KEY (`mentor_id`) + REFERENCES `mentors` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION +); diff --git a/database/revert/V20161004_0917__createMentors.revert.sql b/database/revert/V20161004_0917__createMentors.revert.sql new file mode 100644 index 0000000..810db6c --- /dev/null +++ b/database/revert/V20161004_0917__createMentors.revert.sql @@ -0,0 +1,2 @@ +DROP TABLE `mentor_project_ideas`; +DROP TABLE `mentors`; From de5feb61d5ab7b4630d836cab2269fd9f174a430 Mon Sep 17 00:00:00 2001 From: Jonathan Reynolds Date: Tue, 4 Oct 2016 21:50:22 -0500 Subject: [PATCH 02/38] Mentor models, registration service and controller, and modifications to User and UserRole --- api/v1/controllers/RegistrationController.js | 37 ++++++++++++++++++ api/v1/controllers/UserController.js | 6 +-- api/v1/controllers/index.js | 3 +- api/v1/endpoints.js | 5 ++- api/v1/models/Mentor.js | 28 ++++++++++++++ api/v1/models/MentorProjectIdea.js | 12 ++++++ api/v1/models/User.js | 12 +++++- api/v1/models/UserRole.js | 35 ++++++++++------- api/v1/services/RegistrationService.js | 40 ++++++++++++++++++++ api/v1/utils/registration.js | 35 +++++++++++++++++ 10 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 api/v1/controllers/RegistrationController.js create mode 100644 api/v1/models/Mentor.js create mode 100644 api/v1/models/MentorProjectIdea.js create mode 100644 api/v1/services/RegistrationService.js create mode 100644 api/v1/utils/registration.js diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js new file mode 100644 index 0000000..9fa838e --- /dev/null +++ b/api/v1/controllers/RegistrationController.js @@ -0,0 +1,37 @@ +var bodyParser = require('body-parser'); + +var errors = require('../errors'); +var services = require('../services'); +var config = require('../../config'); +var middleware = require('../middleware'); + +var roles = require('../utils/roles'); + +var router = require('express').Router(); + +router.use(bodyParser.json()); +router.use(middleware.auth); +router.use(middleware.request); + +function createMentor(req, res, next) { + services.RegistrationService.createMentor(req.body['mentor'], req.user) + .then(function (mentor) { + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + +router.post('/mentor', createMentor); + +router.use(middleware.response); +router.use(middleware.errors); + +module.exports.createAttendee = createAttendee; +module.exports.createAccreditedUser = createAccreditedUser; +module.exports.router = router; diff --git a/api/v1/controllers/UserController.js b/api/v1/controllers/UserController.js index 888cbad..b721b75 100644 --- a/api/v1/controllers/UserController.js +++ b/api/v1/controllers/UserController.js @@ -21,9 +21,9 @@ function isRequester(req) { return req.user.get('id') == req.params.id; } -function createAttendee (req, res, next) { +function createUser (req, res, next) { services.UserService - .createUser(req.body.email, req.body.password, roles.ATTENDEE) + .createUser(req.body.email, req.body.password) .then(function (user) { return services.AuthService.issueForUser(user); }) @@ -103,7 +103,7 @@ router.use(bodyParser.json()); router.use(middleware.auth); router.use(middleware.request); -router.post('/attendee', createAttendee); +router.post('/', createUser); router.post('/accredited', middleware.permission(roles.ORGANIZERS), createAccreditedUser); router.post('/reset', requestPasswordReset); router.get('/:id', middleware.permission(roles.ORGANIZERS, isRequester), getUser); diff --git a/api/v1/controllers/index.js b/api/v1/controllers/index.js index 73adcd1..827e40c 100644 --- a/api/v1/controllers/index.js +++ b/api/v1/controllers/index.js @@ -1,5 +1,6 @@ module.exports = { AuthController: require('./AuthController.js'), UploadController: require('./UploadController.js'), - UserController: require('./UserController.js') + UserController: require('./UserController.js'), + RegistrationController: require('./RegistrationController.js') }; diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 96fa10e..663882c 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -2,7 +2,7 @@ var requests = require('./requests'); var endpoints = {}; -endpoints['/v1/user/attendee'] = { +endpoints['/v1/user/'] = { POST: requests.BasicAuthRequest }; endpoints['/v1/user/accredited'] = { @@ -11,6 +11,9 @@ endpoints['/v1/user/accredited'] = { endpoints['/v1/user/reset'] = { POST: requests.ResetTokenRequest }; +endpoints['/v1/registration/mentor'] = { + POST: requests.BasicAuthRequest +}; endpoints['/v1/auth/reset'] = { POST: requests.ResetPasswordRequest }; diff --git a/api/v1/models/Mentor.js b/api/v1/models/Mentor.js new file mode 100644 index 0000000..369e11d --- /dev/null +++ b/api/v1/models/Mentor.js @@ -0,0 +1,28 @@ +var registration = require('../utils/registration'); + +var Model = require('./Model'); +var Mentor = Model.extend({ + tableName: 'mentors', + idAttribute: 'id', + validations: { + first_name: ['required', 'string', 'maxLength:255'], + last_name: ['required', 'string', 'maxLength:255'], + shirt_size: ['required', 'string', registration.verifyTshirtSize], + github: ['string', 'maxLength:50'], + summary: ['required', 'string', 'maxLength:255'], + occupation: ['required', 'string', 'maxLength:255'], + status: ['required', 'string', registration.verifyStatus] + } +}); + + +/** + * Finds a mentor by its relational user's id + * @param {Number|String} id the ID of the user with the appropriate type + * @return {Promise} a Promise resolving to the resulting mentor or null + */ +Mentor.findByUserId = function (userId) { + return Mentor.where({ user_id: userId }).fetch(); +}; + +module.exports = Mentor; diff --git a/api/v1/models/MentorProjectIdea.js b/api/v1/models/MentorProjectIdea.js new file mode 100644 index 0000000..f54d745 --- /dev/null +++ b/api/v1/models/MentorProjectIdea.js @@ -0,0 +1,12 @@ +var Model = require('./Model'); +var MentorProjectIdea = Model.extend({ + tableName: 'mentor_project_ideas', + idAttribute: 'id', + validations: { + link: ['required', 'url', 'maxLength:255'], + contributions: ['required', 'string', 'maxLength:255'], + ideas: ['required', 'string', 'maxLength:255'] + } +}); + +module.exports = MentorProjectIdea; diff --git a/api/v1/models/User.js b/api/v1/models/User.js index d724934..91e7ee1 100644 --- a/api/v1/models/User.js +++ b/api/v1/models/User.js @@ -45,13 +45,21 @@ User.findByEmail = function (email) { * set to active if user creation succeeds. Validation is performed on-save only * @param {String} email the user's email * @param {String} password the user's raw password - * @param {String} role the string representation of a role from utils.roles - * @return {Promise} the User object with the related roles joined-in + * @param {String} role the string representation of a role from utils.roles (optional) + * @return {Promise} the User object with the related roles joined-in (if any) */ User.create = function (email, password, role) { var user = User.forge({ email: email }); var userRole = UserRole.forge({ role: role, active: true }); + if(!role){ + // No roles were provided, so create the User + return user.setPassword(password) + .then(function(result){ + return result.save(); + }); + } + return User .transaction(function (t) { return user.setPassword(password) diff --git a/api/v1/models/UserRole.js b/api/v1/models/UserRole.js index 300799c..f499488 100644 --- a/api/v1/models/UserRole.js +++ b/api/v1/models/UserRole.js @@ -11,6 +11,20 @@ var UserRole = Model.extend({ role: ['required', 'string', roles.verifyRole] } }); +/** + * Saves a forged user role using the passed transaction + */ +function _addRole (userRole, active, t) { + return userRole + .fetch({ transacting: t }) + .then(function (result) { + if (result) { + return _Promise.resolve(result); + } + userRole.set({ active: (_.isUndefined(active) || active) }); + return userRole.save(null, { transacting: t }); + }); +} /** * Adds a role to the specified user. If the role already exists, it is returned @@ -18,22 +32,17 @@ var UserRole = Model.extend({ * @param {User} user the target user * @param {String} role the string representation of the role from utils.roles * @param {Boolean} active whether or not the role should be activated (defaults to true) + * @param {Transaction} t pending transaction (optional) * @returns {Promise} the result of the addititon */ -UserRole.addRole = function (user, role, active) { +UserRole.addRole = function (user, role, active, t) { var userRole = UserRole.forge({ user_id: user.id, role: role }); - return UserRole - .transaction(function (t) { - return userRole - .fetch({ transacting: t }) - .then(function (result) { - if (result) { - return _Promise.resolve(result); - } - userRole.set({ active: (_.isUndefined(active) || active) }); - return userRole.save(null, { transacting: t }); - }); - }); + if (t) { + return _addRole(userRole, active, t); + } + return UserRole.transaction(function(t){ + return _addRole(userRole, active, t); + }); }; /** diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js new file mode 100644 index 0000000..5544bd0 --- /dev/null +++ b/api/v1/services/RegistrationService.js @@ -0,0 +1,40 @@ +var Checkit = require('checkit'); +var _Promise = require('bluebird'); +var _ = require('lodash'); + +var Mentor = require('../models/Mentor'); +var errors = require('../errors'); +var utils = require('../utils'); + +/** + * Registers a mentor for the given user with the specified attributes. + * @param {Object} attributes the attributes for this mentor registration + * @param {Object} user the user for which a mentor will be registered + * @return {Promise} resolving to the newly-created user + * @throws InvalidParameterError when a mentor exists for the specified user + */ +module.exports.createMentor = function (attributes, user) { + var userId = user.get('id'); + attributes['userId'] = userId; + var mentor = Mentor.forge(attributes); + + return mentor + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + if (user.hasRole(utils.roles.MENTOR, false)) { + var message = "The given user has already registered as a mentor"; + var source = "userId"; + throw new errors.InvalidParameterError(message, source); + } + return Mentor.transaction( function (t) { + return UserRole.addRole(user, utils.roles.MENTOR, false, t) + .then(function (result) { + return mentor.save(null, { transacting: t }); + }); + }); + }) + .then(function (result) { + return _Promise.resolve(result); + }); +}; diff --git a/api/v1/utils/registration.js b/api/v1/utils/registration.js new file mode 100644 index 0000000..47df36d --- /dev/null +++ b/api/v1/utils/registration.js @@ -0,0 +1,35 @@ +var _ = require('lodash'); + +var TSHIRT_SIZES = ['S', 'M', 'L', 'XL']; +var STATUSES = ['ACCEPTED', 'WAITLISTED', 'REJECTED', 'PENDING']; + + +/** + * Ensures that the provided tshirt-size is in the list + * of valid size options + * @param {String} size the value to check + * @return {Boolean} true when the size is valid + * @throws TypeError when the size is invalid + */ +module.exports.verifyTshirtSize = function (size) { + if (!_.includes(TSHIRT_SIZES, size)) { + throw new TypeError(size + " is not a valid size"); + } + + return true; +}; + +/** + * Ensures that the provided status is in the list + * of valid status options + * @param {String} size the value to check + * @return {Boolean} true when the status is valid + * @throws TypeError when the status is invalid + */ +module.exports.verifyStatus = function (status) { + if (!_.includes(STATUSES, status)) { + throw new TypeError(status + " is not a valid status"); + } + + return true; +}; From 7a1604fec0ed24489a3bd55a8bccfd9fab44a7a1 Mon Sep 17 00:00:00 2001 From: Jonathan Reynolds Date: Thu, 6 Oct 2016 09:20:43 -0500 Subject: [PATCH 03/38] POST, GET, PUT routes defined and service functions implemented for them --- api/v1/controllers/RegistrationController.js | 68 ++++- api/v1/models/Mentor.js | 34 ++- api/v1/services/RegistrationService.js | 253 +++++++++++++++++-- 3 files changed, 313 insertions(+), 42 deletions(-) diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js index 9fa838e..71b3312 100644 --- a/api/v1/controllers/RegistrationController.js +++ b/api/v1/controllers/RegistrationController.js @@ -14,12 +14,68 @@ router.use(middleware.auth); router.use(middleware.request); function createMentor(req, res, next) { - services.RegistrationService.createMentor(req.body['mentor'], req.user) + services.RegistrationService.createMentor(req.body, req.user) .then(function (mentor) { + res.body = mentor.toJSON(); //TODO: change this to reflect what's returned + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + +function fetchMentorByUser(req, res, next) { + services.RegistrationService.findMentorByUserId(req.user.get('id')) + .then(function(mentor){ res.body = mentor.toJSON(); - - next(); - return null; + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + +function fetchMentorById(req, res, next) { + services.RegistrationService.findMentorById(req.params.id) + .then(function(mentor){ + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + +function updateMentorByUser(req, res, next) { + services.RegistrationService.updateMentorByUser(req.body, req.user) + .then(function(mentor){ + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + +function updateMentorById(req, res, next) { + services.RegistrationService.updateMentorById(req.body, req.params.id) + .then(function(mentor){ + res.body = mentor.toJSON(); + + next(); + return null; }) .catch(function (error) { next(error); @@ -28,6 +84,10 @@ function createMentor(req, res, next) { } router.post('/mentor', createMentor); +router.get('/mentor', middleware.permission(roles.MENTOR), fetchMentorByUser); +router.get('/mentor/:id', middleware.permission(roles.ORGANIZERS), fetchMentorById); +router.put('/mentor', middleware.permission(roles.MENTOR), updateMentorByUser); +router.put('/mentor/:id', middleware.permission(roles.ORGANIZERS), updateMentorById); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/models/Mentor.js b/api/v1/models/Mentor.js index 369e11d..5dad8a9 100644 --- a/api/v1/models/Mentor.js +++ b/api/v1/models/Mentor.js @@ -5,24 +5,36 @@ var Mentor = Model.extend({ tableName: 'mentors', idAttribute: 'id', validations: { - first_name: ['required', 'string', 'maxLength:255'], - last_name: ['required', 'string', 'maxLength:255'], + first_name: ['required', 'string', 'maxLength:255'], + last_name: ['required', 'string', 'maxLength:255'], shirt_size: ['required', 'string', registration.verifyTshirtSize], - github: ['string', 'maxLength:50'], - summary: ['required', 'string', 'maxLength:255'], - occupation: ['required', 'string', 'maxLength:255'], - status: ['required', 'string', registration.verifyStatus] + github: ['string', 'maxLength:50'], + summary: ['required', 'string', 'maxLength:255'], + occupation: ['required', 'string', 'maxLength:255'], + status: ['required', 'string', registration.verifyStatus] + }, + ideas: function () { + return this.hasMany(MentorProjectIdea); } }); /** - * Finds a mentor by its relational user's id - * @param {Number|String} id the ID of the user with the appropriate type - * @return {Promise} a Promise resolving to the resulting mentor or null - */ +* Finds a mentor by its relational user's id, joining in its related project ideas +* @param {Number|String} id the ID of the user with the appropriate type +* @return {Promise} a Promise resolving to the resulting mentor or null +*/ Mentor.findByUserId = function (userId) { - return Mentor.where({ user_id: userId }).fetch(); + return Mentor.where({ user_id: userId }).fetch({ withRelated: ['ideas'] }); +}; + +/** +* Finds a mentor by its ID, joining in its related project ideas +* @param {Number|String} id the ID of the model with the appropriate type +* @return {Promise} a Promise resolving to the resulting model or null +*/ +Mentor.findById = function (id) { + return Mentor.where({ id: id }).fetch({ withRelated: ['ideas'] }); }; module.exports = Mentor; diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 5544bd0..4249a0b 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -3,38 +3,237 @@ var _Promise = require('bluebird'); var _ = require('lodash'); var Mentor = require('../models/Mentor'); +var MentorProjectIdea = require('../models/MentorProjectIdea'); var errors = require('../errors'); var utils = require('../utils'); /** - * Registers a mentor for the given user with the specified attributes. - * @param {Object} attributes the attributes for this mentor registration - * @param {Object} user the user for which a mentor will be registered - * @return {Promise} resolving to the newly-created user - * @throws InvalidParameterError when a mentor exists for the specified user - */ -module.exports.createMentor = function (attributes, user) { +* Registers a mentor and their project ideas for the given user +* @param {Object} mentorObject a JSON object holding the mentor registration +* @param {Object} user the user for which a mentor will be registered +* @return {Promise} resolving to an object in the same format as mentorObject, holding the saved models +* @throws InvalidParameterError when a mentor exists for the specified user +*/ +module.exports.createMentor = function (mentorObject, user) { var userId = user.get('id'); - attributes['userId'] = userId; - var mentor = Mentor.forge(attributes); - - return mentor - .validate() - .catch(Checkit.Error, utils.errors.handleValidationError) - .then(function (validated) { - if (user.hasRole(utils.roles.MENTOR, false)) { - var message = "The given user has already registered as a mentor"; - var source = "userId"; - throw new errors.InvalidParameterError(message, source); - } - return Mentor.transaction( function (t) { - return UserRole.addRole(user, utils.roles.MENTOR, false, t) + var mentorAttributes = mentorObject['mentor']; + var mentorIdeas = mentorObject['ideas']; + mentorAttributes['userId'] = userId; + if (!user.hasRoles(utils.roles.ORGANIZERS, false)) { + delete mentorAttributes['status']; + } + var mentor = Mentor.forge(mentorAttributes); + + return mentor + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + if (user.hasRole(utils.roles.MENTOR, false)) { + var message = "The given user has already registered as a mentor"; + var source = "userId"; + throw new errors.InvalidParameterError(message, source); + } + return Mentor.transaction( function (t) { + return UserRole.addRole(user, utils.roles.MENTOR, false, t) + .then(function (result) { + return mentor.save(null, { transacting: t }); + }) + .then(function (result) { + var mentorAndIdeas = { + 'mentor': result, + 'ideas': [] + }; + var mentorId = result.get('id'); + return _Promise.all( + _.map(mentorIdeas, function(ideaAttributes) { + ideaAttributes['mentorId'] = mentorId; + var projectIdea = MentorProjectIdea.forge(ideaAttributes); + return projectIdea.save(null, { transacting: t }) + .then( function(idea) { + mentorAndIdeas['ideas'].push(idea); + return idea; + }); + }); + ) .then(function (result) { - return mentor.save(null, { transacting: t }); + return mentorAndIdeas; + }); + }); + }); + }) + .then(function (result) { + return _Promise.resolve(result); + }); +}; + +/** +* Finds a mentor by querying for the user_id +* @param {Number} id the User ID to query +* @return {Promise} resolving to the associated Mentor model +* @throws {NotFoundError} when the requested mentor cannot be found +*/ +module.exports.findMentorByUserId = function (user_id) { + return Mentor + .findByUserId(user_id) + .then(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given user ID cannot be found"; + var source = "userId"; + throw new errors.NotFoundError(message, source); + } + + return _Promise.resolve(result); + }); +}; + +/** +* Finds a mentor by querying for the given ID +* @param {Number} id the ID to query +* @return {Promise} resolving to the associated Mentor model +* @throws {NotFoundError} when the requested mentor cannot be found +*/ +module.exports.findMentorById = function (id) { + return Mentor + .findById(id) + .then(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given ID cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } + + return _Promise.resolve(result); + }); +}; + +/** +* Updates a mentor and their project ideas by relational user +* @param {Object} mentorObject a JSON object holding the mentor registration +* @param {Object} user the relational user of the mentor to be updated +* @return {Promise} resolving to an object in the same format as mentorObject, holding the saved models +* @throws InvalidParameterError when a mentor doesn't exist for the specified user +*/ +module.exports.updateMentorbyUser = function (mentorObject, user) { + var userId = user.get('id'); + var mentorAttributes = mentorObject['mentor']; + var mentorIdeas = mentorObject['ideas']; + if (!user.hasRoles(utils.roles.ORGANIZERS, false)) { + delete mentorAttributes['status']; + } + return Mentor + .findByUserId(user.get('id')) + .then(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given user ID cannot be found"; + var source = "userId"; + throw new errors.NotFoundError(message, source); + } + return _Promise.all( + _.map(result.related('ideas'), function(idea) { + return idea.destroy(); + }); + ) + .then(function(){ + return result; + }); + }) + .then(mentor) { + mentor.set(mentorAttributes); + return mentor + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + return Mentor.transaction( function(t) { + return mentor.save(null, { transacting: t }) + .then( function(result) { + var mentorAndIdeas = { + 'mentor': result, + 'ideas': [] + }; + var mentorId = result.get('id'); + return _Promise.all( + _.map(mentorIdeas, function(ideaAttributes) { + ideaAttributes['mentorId'] = mentorId; + var projectIdea = MentorProjectIdea.forge(ideaAttributes); + return projectIdea.save(null, { transacting: t }) + .then( function(idea) { + mentorAndIdeas['ideas'].push(idea); + return idea; + }); + }); + ) + .then(function (result) { + return mentorAndIdeas; + }); + }); + }); + }); + }) + .then(function (result) { + return _Promise.resolve(result); + }); +}; + +/** +* Updates a mentor and their project ideas by id +* @param {Object} mentorObject a JSON object holding the mentor registration +* @param {Object} id the id of the mentor to be updated +* @return {Promise} resolving to an object in the same format as mentorObject, holding the saved models +* @throws InvalidParameterError when a mentor doesn't exist with the specified id +*/ +module.exports.updateMentorbyId = function (mentorObject, id) { + var mentorAttributes = mentorObject['mentor']; + var mentorIdeas = mentorObject['ideas']; + return Mentor + .findById(id) + .then(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given ID cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } + return _Promise.all( + _.map(result.related('ideas'), function(idea) { + return idea.destroy(); + }); + ) + .then(function(){ + return result; + }); + }) + .then(mentor) { + mentor.set(mentorAttributes); + return mentor + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + return Mentor.transaction( function(t) { + return mentor.save(null, { transacting: t }) + .then( function(result) { + var mentorAndIdeas = { + 'mentor': result, + 'ideas': [] + }; + var mentorId = result.get('id'); + return _Promise.all( + _.map(mentorIdeas, function(ideaAttributes) { + ideaAttributes['mentorId'] = mentorId; + var projectIdea = MentorProjectIdea.forge(ideaAttributes); + return projectIdea.save(null, { transacting: t }) + .then( function(idea) { + mentorAndIdeas['ideas'].push(idea); + return idea; + }); + }); + ) + .then(function (result) { + return mentorAndIdeas; + }); }); - }); - }) - .then(function (result) { - return _Promise.resolve(result); - }); + }); + }); + }) + .then(function (result) { + return _Promise.resolve(result); + }); }; From 0cb0f226b5c98dc0b2102aaef32bd3bc077ad44e Mon Sep 17 00:00:00 2001 From: Jonathan Reynolds Date: Fri, 7 Oct 2016 14:39:24 -0500 Subject: [PATCH 04/38] Needs MentorRegistrationRequest implementation --- api/v1/controllers/RegistrationController.js | 2 +- api/v1/endpoints.js | 6 +++++- api/v1/index.js | 1 + api/v1/requests/MentorRegistrationRequest.js | 17 +++++++++++++++++ api/v1/services/RegistrationService.js | 18 +++++++++++------- api/v1/services/index.js | 3 ++- 6 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 api/v1/requests/MentorRegistrationRequest.js diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js index 71b3312..361c6b7 100644 --- a/api/v1/controllers/RegistrationController.js +++ b/api/v1/controllers/RegistrationController.js @@ -16,7 +16,7 @@ router.use(middleware.request); function createMentor(req, res, next) { services.RegistrationService.createMentor(req.body, req.user) .then(function (mentor) { - res.body = mentor.toJSON(); //TODO: change this to reflect what's returned + res.body = mentor.toJSON(); next(); return null; diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 663882c..7a07001 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -12,7 +12,11 @@ endpoints['/v1/user/reset'] = { POST: requests.ResetTokenRequest }; endpoints['/v1/registration/mentor'] = { - POST: requests.BasicAuthRequest + POST: requests.MentorRegistrationRequest, + PUT: requests.MentorRegistrationRequest +}; +endpoints['/v1/registration/mentor/:id'] = { + PUT: requests.MentorRegistrationRequest }; endpoints['/v1/auth/reset'] = { POST: requests.ResetPasswordRequest diff --git a/api/v1/index.js b/api/v1/index.js index fbd7bc7..87fbda3 100644 --- a/api/v1/index.js +++ b/api/v1/index.js @@ -15,5 +15,6 @@ var controllers = require('./controllers'); v1.use('/auth', controllers.AuthController.router); v1.use('/user', controllers.UserController.router); v1.use('/upload', controllers.UploadController.router); +v1.use('/registration', controllers.RegistrationController.router); module.exports = v1; diff --git a/api/v1/requests/MentorRegistrationRequest.js b/api/v1/requests/MentorRegistrationRequest.js new file mode 100644 index 0000000..8fdde3c --- /dev/null +++ b/api/v1/requests/MentorRegistrationRequest.js @@ -0,0 +1,17 @@ +var Request = require('./Request'); + +var bodyRequired = []; +var bodyValidations = { +}; + +function MentorRegistrationRequest(headers, body) { + Request.call(this, headers, body); + + this.bodyRequired = bodyRequired; + this.bodyValidations = bodyValidations; +} + +MentorRegistrationRequest.prototype = Object.create(Request.prototype); +MentorRegistrationRequest.prototype.constructor = MentorRegistrationRequest; + +module.exports = MentorRegistrationRequest; diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 4249a0b..fdd6554 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -81,8 +81,11 @@ module.exports.findMentorByUserId = function (user_id) { var source = "userId"; throw new errors.NotFoundError(message, source); } - - return _Promise.resolve(result); + var mentorAndIdeas = { + 'mentor': result, + 'ideas': result.related('ideas') + }; + return _Promise.resolve(mentorAndIdeas); }); }; @@ -101,8 +104,11 @@ module.exports.findMentorById = function (id) { var source = "id"; throw new errors.NotFoundError(message, source); } - - return _Promise.resolve(result); + var mentorAndIdeas = { + 'mentor': result, + 'ideas': result.related('ideas') + }; + return _Promise.resolve(mentorAndIdeas); }); }; @@ -117,9 +123,7 @@ module.exports.updateMentorbyUser = function (mentorObject, user) { var userId = user.get('id'); var mentorAttributes = mentorObject['mentor']; var mentorIdeas = mentorObject['ideas']; - if (!user.hasRoles(utils.roles.ORGANIZERS, false)) { - delete mentorAttributes['status']; - } + delete mentorAttributes['status']; return Mentor .findByUserId(user.get('id')) .then(function (result) { diff --git a/api/v1/services/index.js b/api/v1/services/index.js index 1fb983c..33f0ff3 100644 --- a/api/v1/services/index.js +++ b/api/v1/services/index.js @@ -4,5 +4,6 @@ module.exports = { PermissionService: require('./PermissionService'), StorageService: require('./StorageService'), UserService: require('./UserService'), - TokenService: require('./TokenService') + TokenService: require('./TokenService'), + RegistrationService: require('./TokenService') }; From 2867f17aa1b13a0692534dbf58322d02cc835e70 Mon Sep 17 00:00:00 2001 From: Jonathan Reynolds Date: Sat, 15 Oct 2016 17:14:09 -0500 Subject: [PATCH 05/38] Custom request validators for nested and array args --- api/v1/controllers/RegistrationController.js | 2 -- api/v1/controllers/UserController.js | 2 +- api/v1/models/Mentor.js | 3 ++- api/v1/models/MentorProjectIdea.js | 3 ++- api/v1/models/index.js | 4 +++- api/v1/requests/MentorRegistrationRequest.js | 21 +++++++++++++++++++- api/v1/requests/index.js | 3 ++- api/v1/services/RegistrationService.js | 16 +++++++-------- api/v1/services/index.js | 2 +- api/v1/utils/index.js | 4 +++- api/v1/utils/validator.js | 18 +++++++++++++++++ 11 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 api/v1/utils/validator.js diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js index 361c6b7..1e52d6a 100644 --- a/api/v1/controllers/RegistrationController.js +++ b/api/v1/controllers/RegistrationController.js @@ -92,6 +92,4 @@ router.put('/mentor/:id', middleware.permission(roles.ORGANIZERS), updateMentorB router.use(middleware.response); router.use(middleware.errors); -module.exports.createAttendee = createAttendee; -module.exports.createAccreditedUser = createAccreditedUser; module.exports.router = router; diff --git a/api/v1/controllers/UserController.js b/api/v1/controllers/UserController.js index b721b75..5ca8e9a 100644 --- a/api/v1/controllers/UserController.js +++ b/api/v1/controllers/UserController.js @@ -111,6 +111,6 @@ router.get('/:id', middleware.permission(roles.ORGANIZERS, isRequester), getUser router.use(middleware.response); router.use(middleware.errors); -module.exports.createAttendee = createAttendee; +module.exports.createUser = createUser; module.exports.createAccreditedUser = createAccreditedUser; module.exports.router = router; diff --git a/api/v1/models/Mentor.js b/api/v1/models/Mentor.js index 5dad8a9..3dfd3f4 100644 --- a/api/v1/models/Mentor.js +++ b/api/v1/models/Mentor.js @@ -9,9 +9,10 @@ var Mentor = Model.extend({ last_name: ['required', 'string', 'maxLength:255'], shirt_size: ['required', 'string', registration.verifyTshirtSize], github: ['string', 'maxLength:50'], + location: ['required', 'string', 'maxLength:255'], summary: ['required', 'string', 'maxLength:255'], occupation: ['required', 'string', 'maxLength:255'], - status: ['required', 'string', registration.verifyStatus] + user_id: ['required', 'integer'] }, ideas: function () { return this.hasMany(MentorProjectIdea); diff --git a/api/v1/models/MentorProjectIdea.js b/api/v1/models/MentorProjectIdea.js index f54d745..3474840 100644 --- a/api/v1/models/MentorProjectIdea.js +++ b/api/v1/models/MentorProjectIdea.js @@ -5,7 +5,8 @@ var MentorProjectIdea = Model.extend({ validations: { link: ['required', 'url', 'maxLength:255'], contributions: ['required', 'string', 'maxLength:255'], - ideas: ['required', 'string', 'maxLength:255'] + ideas: ['required', 'string', 'maxLength:255'], + mentor_id: ['required', 'integer'] } }); diff --git a/api/v1/models/index.js b/api/v1/models/index.js index 1e33d32..ad436ed 100644 --- a/api/v1/models/index.js +++ b/api/v1/models/index.js @@ -3,5 +3,7 @@ module.exports = { MailingListUser: require('./MailingListUser'), User: require('./User'), UserRole: require('./UserRole'), - Token: require('./Token') + Token: require('./Token'), + Mentor: require('./Mentor'), + MentorProjectIdea: require('./MentorProjectIdea') }; diff --git a/api/v1/requests/MentorRegistrationRequest.js b/api/v1/requests/MentorRegistrationRequest.js index 8fdde3c..fa72123 100644 --- a/api/v1/requests/MentorRegistrationRequest.js +++ b/api/v1/requests/MentorRegistrationRequest.js @@ -1,7 +1,26 @@ var Request = require('./Request'); +var validator = require('../utils/validator'); +var registration = require('../utils/registration'); -var bodyRequired = []; + +var mentorValidations = { + first_name: ['required', 'string', 'maxLength:255'], + last_name: ['required', 'string', 'maxLength:255'], + shirt_size: ['required', 'string', registration.verifyTshirtSize], + github: ['string', 'maxLength:50'], + location: ['required', 'string', 'maxLength:255'], + summary: ['required', 'string', 'maxLength:255'], + occupation: ['required', 'string', 'maxLength:255'], +}; +var ideaValidations = { + link: ['required', 'url', 'maxLength:255'], + contributions: ['required', 'string', 'maxLength:255'], + ideas: ['required', 'string', 'maxLength:255'] +}; +var bodyRequired = ['mentor', 'ideas']; var bodyValidations = { + 'mentor': ['object', validator.nestedValidator(mentorValidations)], + 'ideas': ['array', validator.arrayValidator(validator.nestedValidator(ideaValidations))] }; function MentorRegistrationRequest(headers, body) { diff --git a/api/v1/requests/index.js b/api/v1/requests/index.js index 4c2c586..70f873c 100644 --- a/api/v1/requests/index.js +++ b/api/v1/requests/index.js @@ -3,5 +3,6 @@ module.exports = { BasicAuthRequest: require('./BasicAuthRequest'), ResetTokenRequest: require('./ResetTokenRequest'), ResetPasswordRequest: require('./ResetPasswordRequest'), - UploadRequest: require('./UploadRequest') + UploadRequest: require('./UploadRequest'), + MentorRegistrationRequest: require('./MentorRegistrationRequest') }; diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index fdd6554..dce0814 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -18,7 +18,7 @@ module.exports.createMentor = function (mentorObject, user) { var userId = user.get('id'); var mentorAttributes = mentorObject['mentor']; var mentorIdeas = mentorObject['ideas']; - mentorAttributes['userId'] = userId; + mentorAttributes['user_id'] = userId; if (!user.hasRoles(utils.roles.ORGANIZERS, false)) { delete mentorAttributes['status']; } @@ -53,7 +53,7 @@ module.exports.createMentor = function (mentorObject, user) { mentorAndIdeas['ideas'].push(idea); return idea; }); - }); + }) ) .then(function (result) { return mentorAndIdeas; @@ -135,13 +135,13 @@ module.exports.updateMentorbyUser = function (mentorObject, user) { return _Promise.all( _.map(result.related('ideas'), function(idea) { return idea.destroy(); - }); + }) ) .then(function(){ return result; }); }) - .then(mentor) { + .then(function (mentor) { mentor.set(mentorAttributes); return mentor .validate() @@ -164,7 +164,7 @@ module.exports.updateMentorbyUser = function (mentorObject, user) { mentorAndIdeas['ideas'].push(idea); return idea; }); - }); + }) ) .then(function (result) { return mentorAndIdeas; @@ -199,13 +199,13 @@ module.exports.updateMentorbyId = function (mentorObject, id) { return _Promise.all( _.map(result.related('ideas'), function(idea) { return idea.destroy(); - }); + }) ) .then(function(){ return result; }); }) - .then(mentor) { + .then(function (mentor) { mentor.set(mentorAttributes); return mentor .validate() @@ -228,7 +228,7 @@ module.exports.updateMentorbyId = function (mentorObject, id) { mentorAndIdeas['ideas'].push(idea); return idea; }); - }); + }) ) .then(function (result) { return mentorAndIdeas; diff --git a/api/v1/services/index.js b/api/v1/services/index.js index 33f0ff3..0641f14 100644 --- a/api/v1/services/index.js +++ b/api/v1/services/index.js @@ -5,5 +5,5 @@ module.exports = { StorageService: require('./StorageService'), UserService: require('./UserService'), TokenService: require('./TokenService'), - RegistrationService: require('./TokenService') + RegistrationService: require('./RegistrationService') }; diff --git a/api/v1/utils/index.js b/api/v1/utils/index.js index 9732dee..3084c98 100644 --- a/api/v1/utils/index.js +++ b/api/v1/utils/index.js @@ -5,5 +5,7 @@ module.exports = { roles: require('./roles.js'), scopes: require('./scopes.js'), storage: require('./storage.js'), - time: require('./time.js') + time: require('./time.js'), + registration: require('./registration.js'), + validator: require('./validator.js') }; diff --git a/api/v1/utils/validator.js b/api/v1/utils/validator.js new file mode 100644 index 0000000..4125f88 --- /dev/null +++ b/api/v1/utils/validator.js @@ -0,0 +1,18 @@ +var checkit = require('checkit') +var _ = require('lodash') +var _Promise = require('bluebird') + +module.exports.nestedValidator = function(validations){ + return function(value){ + return checkit(validations).run(value); + }; +} + +module.exports.arrayValidator = function(validator){ + return function(value){ + return _Promise.all(_.map(value, validator)) + .then(function(value){ + return true; + }); + } +} From 9da45d5f8f72895c027f6b9563bb259064af9474 Mon Sep 17 00:00:00 2001 From: Nick Magerko Date: Tue, 18 Oct 2016 17:28:35 -0500 Subject: [PATCH 06/38] working on testing mentor service --- api/v1/controllers/RegistrationController.js | 120 +++--- api/v1/endpoints.js | 6 +- api/v1/models/Mentor.js | 8 +- api/v1/models/MentorProjectIdea.js | 8 +- api/v1/requests/MentorCreationRequest.js | 38 ++ api/v1/requests/MentorRegistrationRequest.js | 36 -- api/v1/requests/MentorUpdateRequest.js | 21 ++ api/v1/requests/index.js | 5 +- api/v1/services/RegistrationService.js | 364 +++++++++---------- api/v1/utils/errors.js | 12 +- api/v1/utils/index.js | 2 +- api/v1/utils/validator.js | 18 - api/v1/utils/validators.js | 24 ++ 13 files changed, 357 insertions(+), 305 deletions(-) create mode 100644 api/v1/requests/MentorCreationRequest.js delete mode 100644 api/v1/requests/MentorRegistrationRequest.js create mode 100644 api/v1/requests/MentorUpdateRequest.js delete mode 100644 api/v1/utils/validator.js create mode 100644 api/v1/utils/validators.js diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js index 1e52d6a..50b5c7b 100644 --- a/api/v1/controllers/RegistrationController.js +++ b/api/v1/controllers/RegistrationController.js @@ -9,81 +9,95 @@ var roles = require('../utils/roles'); var router = require('express').Router(); -router.use(bodyParser.json()); -router.use(middleware.auth); -router.use(middleware.request); +function _isAuthenticated (req) { + return req.auth && (req.user !== undefined); +} function createMentor(req, res, next) { - services.RegistrationService.createMentor(req.body, req.user) - .then(function (mentor) { - res.body = mentor.toJSON(); - - next(); - return null; - }) - .catch(function (error) { - next(error); - return null; - }); + delete req.body.status; + + services.RegistrationService.createMentor(req.body, req.user) + .then(function (mentor) { + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); } function fetchMentorByUser(req, res, next) { services.RegistrationService.findMentorByUserId(req.user.get('id')) .then(function(mentor){ - res.body = mentor.toJSON(); + res.body = mentor.toJSON(); - next(); - return null; + next(); + return null; }) .catch(function (error) { - next(error); - return null; + next(error); + return null; }); } function fetchMentorById(req, res, next) { - services.RegistrationService.findMentorById(req.params.id) - .then(function(mentor){ - res.body = mentor.toJSON(); - - next(); - return null; - }) - .catch(function (error) { - next(error); - return null; - }); + services.RegistrationService.findMentorById(req.params.id) + .then(function(mentor){ + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); } function updateMentorByUser(req, res, next) { - services.RegistrationService.updateMentorByUser(req.body, req.user) - .then(function(mentor){ - res.body = mentor.toJSON(); - - next(); - return null; - }) - .catch(function (error) { - next(error); - return null; - }); + if (!req.user.hasRoles(utils.roles.ORGANIZERS)) { + delete req.body.status; + } + + services.RegistrationService.updateMentorByUser(req.body, req.user) + .then(function(mentor){ + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); } function updateMentorById(req, res, next) { - services.RegistrationService.updateMentorById(req.body, req.params.id) - .then(function(mentor){ - res.body = mentor.toJSON(); - - next(); - return null; - }) - .catch(function (error) { - next(error); - return null; - }); + if (!req.user.hasRoles(utils.roles.ORGANIZERS)) { + delete req.body.status; + } + + services.RegistrationService.updateMentorById(req.body, req.params.id) + .then(function(mentor){ + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); } -router.post('/mentor', createMentor); +router.use(bodyParser.json()); +router.use(middleware.auth); +router.use(middleware.request); + +router.post('/mentor', middleware.permission(roles.NONE, _isAuthenticated), createMentor); router.get('/mentor', middleware.permission(roles.MENTOR), fetchMentorByUser); router.get('/mentor/:id', middleware.permission(roles.ORGANIZERS), fetchMentorById); router.put('/mentor', middleware.permission(roles.MENTOR), updateMentorByUser); diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 7a07001..0840d7f 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -12,11 +12,11 @@ endpoints['/v1/user/reset'] = { POST: requests.ResetTokenRequest }; endpoints['/v1/registration/mentor'] = { - POST: requests.MentorRegistrationRequest, - PUT: requests.MentorRegistrationRequest + POST: requests.MentorCreationRequest, + PUT: requests.MentorUpdateRequest }; endpoints['/v1/registration/mentor/:id'] = { - PUT: requests.MentorRegistrationRequest + PUT: requests.MentorUpdateRequest }; endpoints['/v1/auth/reset'] = { POST: requests.ResetPasswordRequest diff --git a/api/v1/models/Mentor.js b/api/v1/models/Mentor.js index 3dfd3f4..89f6a1e 100644 --- a/api/v1/models/Mentor.js +++ b/api/v1/models/Mentor.js @@ -5,14 +5,14 @@ var Mentor = Model.extend({ tableName: 'mentors', idAttribute: 'id', validations: { - first_name: ['required', 'string', 'maxLength:255'], - last_name: ['required', 'string', 'maxLength:255'], - shirt_size: ['required', 'string', registration.verifyTshirtSize], + firstName: ['required', 'string', 'maxLength:255'], + lastName: ['required', 'string', 'maxLength:255'], + shirtSize: ['required', 'string', registration.verifyTshirtSize], github: ['string', 'maxLength:50'], location: ['required', 'string', 'maxLength:255'], summary: ['required', 'string', 'maxLength:255'], occupation: ['required', 'string', 'maxLength:255'], - user_id: ['required', 'integer'] + userId: ['required', 'integer'] }, ideas: function () { return this.hasMany(MentorProjectIdea); diff --git a/api/v1/models/MentorProjectIdea.js b/api/v1/models/MentorProjectIdea.js index 3474840..c24de7b 100644 --- a/api/v1/models/MentorProjectIdea.js +++ b/api/v1/models/MentorProjectIdea.js @@ -3,10 +3,10 @@ var MentorProjectIdea = Model.extend({ tableName: 'mentor_project_ideas', idAttribute: 'id', validations: { - link: ['required', 'url', 'maxLength:255'], - contributions: ['required', 'string', 'maxLength:255'], - ideas: ['required', 'string', 'maxLength:255'], - mentor_id: ['required', 'integer'] + link: ['required', 'url', 'maxLength:255'], + contributions: ['required', 'string', 'maxLength:255'], + ideas: ['required', 'string', 'maxLength:255'], + mentorId: ['required', 'integer'] } }); diff --git a/api/v1/requests/MentorCreationRequest.js b/api/v1/requests/MentorCreationRequest.js new file mode 100644 index 0000000..58b54a5 --- /dev/null +++ b/api/v1/requests/MentorCreationRequest.js @@ -0,0 +1,38 @@ +var Request = require('./Request'); +var validators = require('../utils/validators'); +var registration = require('../utils/registration'); + +var mentorValidations = { + firstName: ['required', 'string', 'maxLength:255'], + lastName: ['required', 'string', 'maxLength:255'], + shirtSize: ['required', 'string', registration.verifyTshirtSize], + github: ['required', 'string', 'maxLength:50'], + location: ['required', 'string', 'maxLength:255'], + summary: ['required', 'string', 'maxLength:255'], + occupation: ['required', 'string', 'maxLength:255'], +}; +var ideaValidations = { + link: ['required', 'url', 'maxLength:255'], + contributions: ['required', 'string', 'maxLength:255'], + ideas: ['required', 'string', 'maxLength:255'] +}; +var bodyRequired = ['mentor', 'ideas']; +var bodyValidations = { + 'mentor': ['required', 'plainObject', validators.nested(mentorValidations, 'mentor')], + 'ideas': ['required', 'array', 'minLength:1', 'maxLength:5', validators.array(validators.nested(ideaValidations, 'ideas'))] +}; + +function MentorCreationRequest(headers, body) { + Request.call(this, headers, body); + + this.bodyRequired = bodyRequired; + this.bodyValidations = bodyValidations; +} + +MentorCreationRequest._mentorValidations = mentorValidations; +MentorCreationRequest._ideaValidations = ideaValidations; + +MentorCreationRequest.prototype = Object.create(Request.prototype); +MentorCreationRequest.prototype.constructor = MentorCreationRequest; + +module.exports = MentorCreationRequest; diff --git a/api/v1/requests/MentorRegistrationRequest.js b/api/v1/requests/MentorRegistrationRequest.js deleted file mode 100644 index fa72123..0000000 --- a/api/v1/requests/MentorRegistrationRequest.js +++ /dev/null @@ -1,36 +0,0 @@ -var Request = require('./Request'); -var validator = require('../utils/validator'); -var registration = require('../utils/registration'); - - -var mentorValidations = { - first_name: ['required', 'string', 'maxLength:255'], - last_name: ['required', 'string', 'maxLength:255'], - shirt_size: ['required', 'string', registration.verifyTshirtSize], - github: ['string', 'maxLength:50'], - location: ['required', 'string', 'maxLength:255'], - summary: ['required', 'string', 'maxLength:255'], - occupation: ['required', 'string', 'maxLength:255'], -}; -var ideaValidations = { - link: ['required', 'url', 'maxLength:255'], - contributions: ['required', 'string', 'maxLength:255'], - ideas: ['required', 'string', 'maxLength:255'] -}; -var bodyRequired = ['mentor', 'ideas']; -var bodyValidations = { - 'mentor': ['object', validator.nestedValidator(mentorValidations)], - 'ideas': ['array', validator.arrayValidator(validator.nestedValidator(ideaValidations))] -}; - -function MentorRegistrationRequest(headers, body) { - Request.call(this, headers, body); - - this.bodyRequired = bodyRequired; - this.bodyValidations = bodyValidations; -} - -MentorRegistrationRequest.prototype = Object.create(Request.prototype); -MentorRegistrationRequest.prototype.constructor = MentorRegistrationRequest; - -module.exports = MentorRegistrationRequest; diff --git a/api/v1/requests/MentorUpdateRequest.js b/api/v1/requests/MentorUpdateRequest.js new file mode 100644 index 0000000..6f7625b --- /dev/null +++ b/api/v1/requests/MentorUpdateRequest.js @@ -0,0 +1,21 @@ +var Request = require('./Request'); +var MentorCreationRequest = require('./MentorCreationRequest'); +var validators = require('../utils/validators'); + +var bodyRequired = ['mentor']; +var bodyValidations = { + 'mentor': ['required', 'plainObject', validators.nested(MentorCreationRequest._mentorValidations, 'mentor')], + 'ideas': ['array', 'maxLength:5', validators.array(validators.nested(MentorCreationRequest._ideaValidations, 'ideas'))] +}; + +function MentorUpdateRequest(headers, body) { + Request.call(this, headers, body); + + this.bodyRequired = bodyRequired; + this.bodyValidations = bodyValidations; +} + +MentorUpdateRequest.prototype = Object.create(Request.prototype); +MentorUpdateRequest.prototype.constructor = MentorUpdateRequest; + +module.exports = MentorUpdateRequest; diff --git a/api/v1/requests/index.js b/api/v1/requests/index.js index 70f873c..f7f67b4 100644 --- a/api/v1/requests/index.js +++ b/api/v1/requests/index.js @@ -1,8 +1,9 @@ module.exports = { AccreditedUserCreationRequest: require('./AccreditedUserCreationRequest'), BasicAuthRequest: require('./BasicAuthRequest'), + MentorCreationRequest: require('./MentorCreationRequest'), + MentorUpdateRequest: require('./MentorUpdateRequest'), ResetTokenRequest: require('./ResetTokenRequest'), ResetPasswordRequest: require('./ResetPasswordRequest'), - UploadRequest: require('./UploadRequest'), - MentorRegistrationRequest: require('./MentorRegistrationRequest') + UploadRequest: require('./UploadRequest') }; diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index dce0814..6e1ebc3 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -4,6 +4,7 @@ var _ = require('lodash'); var Mentor = require('../models/Mentor'); var MentorProjectIdea = require('../models/MentorProjectIdea'); +var UserRole = require('../models/UserRole'); var errors = require('../errors'); var utils = require('../utils'); @@ -15,78 +16,78 @@ var utils = require('../utils'); * @throws InvalidParameterError when a mentor exists for the specified user */ module.exports.createMentor = function (mentorObject, user) { - var userId = user.get('id'); - var mentorAttributes = mentorObject['mentor']; - var mentorIdeas = mentorObject['ideas']; - mentorAttributes['user_id'] = userId; - if (!user.hasRoles(utils.roles.ORGANIZERS, false)) { - delete mentorAttributes['status']; - } - var mentor = Mentor.forge(mentorAttributes); - - return mentor - .validate() - .catch(Checkit.Error, utils.errors.handleValidationError) - .then(function (validated) { - if (user.hasRole(utils.roles.MENTOR, false)) { - var message = "The given user has already registered as a mentor"; - var source = "userId"; - throw new errors.InvalidParameterError(message, source); - } - return Mentor.transaction( function (t) { - return UserRole.addRole(user, utils.roles.MENTOR, false, t) - .then(function (result) { - return mentor.save(null, { transacting: t }); - }) - .then(function (result) { - var mentorAndIdeas = { - 'mentor': result, - 'ideas': [] - }; - var mentorId = result.get('id'); - return _Promise.all( - _.map(mentorIdeas, function(ideaAttributes) { - ideaAttributes['mentorId'] = mentorId; - var projectIdea = MentorProjectIdea.forge(ideaAttributes); - return projectIdea.save(null, { transacting: t }) - .then( function(idea) { - mentorAndIdeas['ideas'].push(idea); - return idea; - }); - }) - ) - .then(function (result) { - return mentorAndIdeas; - }); - }); - }); - }) - .then(function (result) { - return _Promise.resolve(result); - }); -}; + var userId = user.get('id'); + var mentorAttributes = mentorObject.mentor; + var mentorIdeas = mentorObject.ideas; + + mentorAttributes.userId = userId; + var mentor = Mentor.forge(mentorAttributes); + + return mentor + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + if (user.hasRole(utils.roles.MENTOR, false)) { + var message = "The given user has already registered as a mentor"; + var source = "userId"; + throw new errors.InvalidParameterError(message, source); + } + + return Mentor.transaction(function (t) { + return UserRole.addRole(user, utils.roles.MENTOR, false, t) + .then(function (result) { + return mentor.save(null, { transacting: t }); + }) + .then(function (result) { + var mentorAndIdeas = { + 'mentor': result, + 'ideas': [] + }; + + var mentorId = result.get('id'); + return _Promise.all(_.map(mentorIdeas, function(ideaAttributes) { + ideaAttributes.mentorId = mentorId; + var projectIdea = MentorProjectIdea.forge(ideaAttributes); + + return projectIdea.save(null, { transacting: t }) + .then(function(idea) { + mentorAndIdeas.ideas.push(idea); + return idea; + }); + })) + .then(function (result) { + return mentorAndIdeas; + }); + }); + }); + }) + .then(function (result) { + return _Promise.resolve(result); + }); + }; /** -* Finds a mentor by querying for the user_id +* Finds a mentor by querying for the userId * @param {Number} id the User ID to query * @return {Promise} resolving to the associated Mentor model * @throws {NotFoundError} when the requested mentor cannot be found */ -module.exports.findMentorByUserId = function (user_id) { - return Mentor - .findByUserId(user_id) - .then(function (result) { - if (_.isNull(result)) { - var message = "A mentor with the given user ID cannot be found"; - var source = "userId"; - throw new errors.NotFoundError(message, source); - } - var mentorAndIdeas = { - 'mentor': result, - 'ideas': result.related('ideas') - }; - return _Promise.resolve(mentorAndIdeas); - }); +module.exports.findMentorByUserId = function (userId) { + return Mentor + .findByUserId(user_id) + .then(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given user ID cannot be found"; + var source = "userId"; + throw new errors.NotFoundError(message, source); + } + + var mentorAndIdeas = { + 'mentor': result, + 'ideas': result.related('ideas') + }; + return _Promise.resolve(mentorAndIdeas); + }); }; /** @@ -96,19 +97,20 @@ module.exports.findMentorByUserId = function (user_id) { * @throws {NotFoundError} when the requested mentor cannot be found */ module.exports.findMentorById = function (id) { - return Mentor - .findById(id) - .then(function (result) { - if (_.isNull(result)) { - var message = "A mentor with the given ID cannot be found"; - var source = "id"; - throw new errors.NotFoundError(message, source); - } - var mentorAndIdeas = { - 'mentor': result, - 'ideas': result.related('ideas') - }; - return _Promise.resolve(mentorAndIdeas); + return Mentor + .findById(id) + .then(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given ID cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } + + var mentorAndIdeas = { + 'mentor': result, + 'ideas': result.related('ideas') + }; + return _Promise.resolve(mentorAndIdeas); }); }; @@ -120,63 +122,61 @@ module.exports.findMentorById = function (id) { * @throws InvalidParameterError when a mentor doesn't exist for the specified user */ module.exports.updateMentorbyUser = function (mentorObject, user) { - var userId = user.get('id'); - var mentorAttributes = mentorObject['mentor']; - var mentorIdeas = mentorObject['ideas']; - delete mentorAttributes['status']; - return Mentor - .findByUserId(user.get('id')) - .then(function (result) { - if (_.isNull(result)) { - var message = "A mentor with the given user ID cannot be found"; - var source = "userId"; - throw new errors.NotFoundError(message, source); - } - return _Promise.all( - _.map(result.related('ideas'), function(idea) { - return idea.destroy(); - }) - ) - .then(function(){ - return result; - }); - }) - .then(function (mentor) { - mentor.set(mentorAttributes); - return mentor - .validate() - .catch(Checkit.Error, utils.errors.handleValidationError) - .then(function (validated) { - return Mentor.transaction( function(t) { - return mentor.save(null, { transacting: t }) - .then( function(result) { - var mentorAndIdeas = { - 'mentor': result, - 'ideas': [] - }; - var mentorId = result.get('id'); - return _Promise.all( - _.map(mentorIdeas, function(ideaAttributes) { - ideaAttributes['mentorId'] = mentorId; - var projectIdea = MentorProjectIdea.forge(ideaAttributes); - return projectIdea.save(null, { transacting: t }) - .then( function(idea) { - mentorAndIdeas['ideas'].push(idea); - return idea; - }); - }) - ) - .then(function (result) { - return mentorAndIdeas; - }); - }); - }); - }); - }) - .then(function (result) { - return _Promise.resolve(result); - }); -}; + var userId = user.get('id'); + var mentorAttributes = mentorObject.mentor; + var mentorIdeas = mentorObject.ideas; + + return Mentor + .findByUserId(user.get('id')) + .then(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given user ID cannot be found"; + var source = "userId"; + throw new errors.NotFoundError(message, source); + } + + return _Promise.all(_.map(result.related('ideas'), function(idea) { + return idea.destroy(); + })) + .then(function(){ + return result; + }); + }) + .then(function (mentor) { + mentor.set(mentorAttributes); + return mentor + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + return Mentor.transaction( function(t) { + return mentor.save(null, { transacting: t }) + .then(function(result) { + var mentorAndIdeas = { + 'mentor': result, + 'ideas': [] + }; + var mentorId = result.get('id'); + return _Promise.all(_.map(mentorIdeas, function(ideaAttributes) { + ideaAttributes.mentorId = mentorId; + + var projectIdea = MentorProjectIdea.forge(ideaAttributes); + return projectIdea.save(null, { transacting: t }) + .then(function(idea) { + mentorAndIdeas.ideas.push(idea); + return idea; + }); + })) + .then(function (result) { + return mentorAndIdeas; + }); + }); + }); + }); + }) + .then(function (result) { + return _Promise.resolve(result); + }); + }; /** * Updates a mentor and their project ideas by id @@ -186,58 +186,58 @@ module.exports.updateMentorbyUser = function (mentorObject, user) { * @throws InvalidParameterError when a mentor doesn't exist with the specified id */ module.exports.updateMentorbyId = function (mentorObject, id) { - var mentorAttributes = mentorObject['mentor']; - var mentorIdeas = mentorObject['ideas']; + var mentorAttributes = mentorObject.mentor; + var mentorIdeas = mentorObject.ideas; return Mentor .findById(id) .then(function (result) { - if (_.isNull(result)) { - var message = "A mentor with the given ID cannot be found"; - var source = "id"; - throw new errors.NotFoundError(message, source); - } - return _Promise.all( - _.map(result.related('ideas'), function(idea) { - return idea.destroy(); - }) - ) - .then(function(){ - return result; - }); + if (_.isNull(result)) { + var message = "A mentor with the given ID cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } + return _Promise.all( + _.map(result.related('ideas'), function(idea) { + return idea.destroy(); + }) + ) + .then(function(){ + return result; + }); }) .then(function (mentor) { - mentor.set(mentorAttributes); - return mentor - .validate() - .catch(Checkit.Error, utils.errors.handleValidationError) - .then(function (validated) { - return Mentor.transaction( function(t) { - return mentor.save(null, { transacting: t }) - .then( function(result) { - var mentorAndIdeas = { - 'mentor': result, - 'ideas': [] - }; - var mentorId = result.get('id'); - return _Promise.all( - _.map(mentorIdeas, function(ideaAttributes) { - ideaAttributes['mentorId'] = mentorId; - var projectIdea = MentorProjectIdea.forge(ideaAttributes); - return projectIdea.save(null, { transacting: t }) - .then( function(idea) { - mentorAndIdeas['ideas'].push(idea); - return idea; - }); - }) - ) - .then(function (result) { - return mentorAndIdeas; - }); - }); - }); - }); + mentor.set(mentorAttributes); + return mentor + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + return Mentor.transaction( function(t) { + return mentor.save(null, { transacting: t }) + .then( function(result) { + var mentorAndIdeas = { + 'mentor': result, + 'ideas': [] + }; + var mentorId = result.get('id'); + return _Promise.all( + _.map(mentorIdeas, function(ideaAttributes) { + ideaAttributes.mentorId = mentorId; + var projectIdea = MentorProjectIdea.forge(ideaAttributes); + return projectIdea.save(null, { transacting: t }) + .then( function(idea) { + mentorAndIdeas.ideas.push(idea); + return idea; + }); + }) + ) + .then(function (result) { + return mentorAndIdeas; + }); + }); + }); + }); }) .then(function (result) { - return _Promise.resolve(result); + return _Promise.resolve(result); }); }; diff --git a/api/v1/utils/errors.js b/api/v1/utils/errors.js index 284b9c6..7079ea9 100644 --- a/api/v1/utils/errors.js +++ b/api/v1/utils/errors.js @@ -6,8 +6,16 @@ var InvalidParameterError = require('../errors/InvalidParameterError'); * @throws {InvalidParameterError} the re-thrown error */ module.exports.handleValidationError = function (error) { - var errorSource = error.keys()[0]; - var errorDetail = error.errors[errorSource].message; + var errorKey = error.keys()[0]; + var specificError = error.errors[errorKey]; + + var errorDetail = specificError.message; + var errorSource; + while (specificError.key) { + // find the most-complete error source in the error stack + errorSource = specificError.key; + specificError = (specificError.errors) ? specificError.errors[0] : undefined; + } throw new InvalidParameterError(errorDetail, errorSource); }; diff --git a/api/v1/utils/index.js b/api/v1/utils/index.js index 3084c98..ac053c3 100644 --- a/api/v1/utils/index.js +++ b/api/v1/utils/index.js @@ -7,5 +7,5 @@ module.exports = { storage: require('./storage.js'), time: require('./time.js'), registration: require('./registration.js'), - validator: require('./validator.js') + validators: require('./validators.js') }; diff --git a/api/v1/utils/validator.js b/api/v1/utils/validator.js deleted file mode 100644 index 4125f88..0000000 --- a/api/v1/utils/validator.js +++ /dev/null @@ -1,18 +0,0 @@ -var checkit = require('checkit') -var _ = require('lodash') -var _Promise = require('bluebird') - -module.exports.nestedValidator = function(validations){ - return function(value){ - return checkit(validations).run(value); - }; -} - -module.exports.arrayValidator = function(validator){ - return function(value){ - return _Promise.all(_.map(value, validator)) - .then(function(value){ - return true; - }); - } -} diff --git a/api/v1/utils/validators.js b/api/v1/utils/validators.js new file mode 100644 index 0000000..e9d974d --- /dev/null +++ b/api/v1/utils/validators.js @@ -0,0 +1,24 @@ +var checkit = require('checkit'); +var _ = require('lodash'); +var _Promise = require('bluebird'); + +module.exports.nested = function(validations, parentName){ + return function(value){ + return checkit(validations).run(value) + .catch(checkit.Error, function (error) { + var specificError = error.errors[error.keys()[0]]; + specificError.key = parentName + '.' + specificError.key; + + throw specificError; + }); + }; +}; + +module.exports.array = function(validator, parentName){ + return function(value){ + return _Promise.all(_.map(value, validator)) + .then(function(value){ + return true; + }); + }; +}; From 3c6b9760393731f374c238b54120da8d5a402f32 Mon Sep 17 00:00:00 2001 From: Nick Magerko Date: Fri, 21 Oct 2016 00:57:48 -0500 Subject: [PATCH 07/38] finished minimal functionality; need to prevent total deletions on update --- api/v1/controllers/RegistrationController.js | 46 ++-- api/v1/endpoints.js | 6 +- api/v1/models/Mentor.js | 2 + api/v1/models/User.js | 7 +- ...torCreationRequest.js => MentorRequest.js} | 0 api/v1/requests/MentorUpdateRequest.js | 21 -- api/v1/requests/index.js | 3 +- api/v1/services/RegistrationService.js | 256 +++++------------- 8 files changed, 106 insertions(+), 235 deletions(-) rename api/v1/requests/{MentorCreationRequest.js => MentorRequest.js} (100%) delete mode 100644 api/v1/requests/MentorUpdateRequest.js diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js index 50b5c7b..2adb0c2 100644 --- a/api/v1/controllers/RegistrationController.js +++ b/api/v1/controllers/RegistrationController.js @@ -1,10 +1,7 @@ var bodyParser = require('body-parser'); -var errors = require('../errors'); var services = require('../services'); -var config = require('../../config'); var middleware = require('../middleware'); - var roles = require('../utils/roles'); var router = require('express').Router(); @@ -16,7 +13,7 @@ function _isAuthenticated (req) { function createMentor(req, res, next) { delete req.body.status; - services.RegistrationService.createMentor(req.body, req.user) + services.RegistrationService.createMentor(req.user, req.body) .then(function (mentor) { res.body = mentor.toJSON(); @@ -30,17 +27,18 @@ function createMentor(req, res, next) { } function fetchMentorByUser(req, res, next) { - services.RegistrationService.findMentorByUserId(req.user.get('id')) - .then(function(mentor){ - res.body = mentor.toJSON(); - - next(); - return null; - }) - .catch(function (error) { - next(error); - return null; - }); + services.RegistrationService + .findMentorByUser(req.user) + .then(function(mentor){ + res.body = mentor.toJSON(); + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); } function fetchMentorById(req, res, next) { @@ -58,11 +56,15 @@ function fetchMentorById(req, res, next) { } function updateMentorByUser(req, res, next) { - if (!req.user.hasRoles(utils.roles.ORGANIZERS)) { + if (!req.user.hasRoles(roles.ORGANIZERS)) { delete req.body.status; } - services.RegistrationService.updateMentorByUser(req.body, req.user) + services.RegistrationService + .findMentorByUser(req.user) + .then(function (mentor) { + return services.RegistrationService.updateMentor(mentor, req.body); + }) .then(function(mentor){ res.body = mentor.toJSON(); @@ -76,12 +78,16 @@ function updateMentorByUser(req, res, next) { } function updateMentorById(req, res, next) { - if (!req.user.hasRoles(utils.roles.ORGANIZERS)) { + if (!req.user.hasRoles(roles.ORGANIZERS)) { delete req.body.status; } - services.RegistrationService.updateMentorById(req.body, req.params.id) - .then(function(mentor){ + services.RegistrationService + .findMentorById(req.params.id) + .then (function (mentor) { + return services.RegistrationService.updateMentor(mentor, req.body); + }) + .then(function (mentor) { res.body = mentor.toJSON(); next(); diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 0840d7f..70bd770 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -12,11 +12,11 @@ endpoints['/v1/user/reset'] = { POST: requests.ResetTokenRequest }; endpoints['/v1/registration/mentor'] = { - POST: requests.MentorCreationRequest, - PUT: requests.MentorUpdateRequest + POST: requests.MentorRequest, + PUT: requests.MentorRequest }; endpoints['/v1/registration/mentor/:id'] = { - PUT: requests.MentorUpdateRequest + PUT: requests.MentorRequest }; endpoints['/v1/auth/reset'] = { POST: requests.ResetPasswordRequest diff --git a/api/v1/models/Mentor.js b/api/v1/models/Mentor.js index 89f6a1e..57cb959 100644 --- a/api/v1/models/Mentor.js +++ b/api/v1/models/Mentor.js @@ -1,6 +1,8 @@ +var _ = require('lodash'); var registration = require('../utils/registration'); var Model = require('./Model'); +var MentorProjectIdea = require('./MentorProjectIdea'); var Mentor = Model.extend({ tableName: 'mentors', idAttribute: 'id', diff --git a/api/v1/models/User.js b/api/v1/models/User.js index 91e7ee1..7e2e873 100644 --- a/api/v1/models/User.js +++ b/api/v1/models/User.js @@ -158,12 +158,7 @@ User.prototype.hasPassword = function (password) { * @return {Object} the serialized form of this User */ User.prototype.serialize = function () { - var serialized = _.omit(this.attributes, ['password']); - - var roles = this.related('roles'); - serialized.roles = (_.isUndefined(roles)) ? null : roles.serialize(); - - return serialized; + return _.omit(this.attributes, ['password']); }; module.exports = User; diff --git a/api/v1/requests/MentorCreationRequest.js b/api/v1/requests/MentorRequest.js similarity index 100% rename from api/v1/requests/MentorCreationRequest.js rename to api/v1/requests/MentorRequest.js diff --git a/api/v1/requests/MentorUpdateRequest.js b/api/v1/requests/MentorUpdateRequest.js deleted file mode 100644 index 6f7625b..0000000 --- a/api/v1/requests/MentorUpdateRequest.js +++ /dev/null @@ -1,21 +0,0 @@ -var Request = require('./Request'); -var MentorCreationRequest = require('./MentorCreationRequest'); -var validators = require('../utils/validators'); - -var bodyRequired = ['mentor']; -var bodyValidations = { - 'mentor': ['required', 'plainObject', validators.nested(MentorCreationRequest._mentorValidations, 'mentor')], - 'ideas': ['array', 'maxLength:5', validators.array(validators.nested(MentorCreationRequest._ideaValidations, 'ideas'))] -}; - -function MentorUpdateRequest(headers, body) { - Request.call(this, headers, body); - - this.bodyRequired = bodyRequired; - this.bodyValidations = bodyValidations; -} - -MentorUpdateRequest.prototype = Object.create(Request.prototype); -MentorUpdateRequest.prototype.constructor = MentorUpdateRequest; - -module.exports = MentorUpdateRequest; diff --git a/api/v1/requests/index.js b/api/v1/requests/index.js index f7f67b4..c0ced33 100644 --- a/api/v1/requests/index.js +++ b/api/v1/requests/index.js @@ -1,8 +1,7 @@ module.exports = { AccreditedUserCreationRequest: require('./AccreditedUserCreationRequest'), BasicAuthRequest: require('./BasicAuthRequest'), - MentorCreationRequest: require('./MentorCreationRequest'), - MentorUpdateRequest: require('./MentorUpdateRequest'), + MentorRequest: require('./MentorRequest'), ResetTokenRequest: require('./ResetTokenRequest'), ResetPasswordRequest: require('./ResetPasswordRequest'), UploadRequest: require('./UploadRequest') diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 6e1ebc3..77e7e0a 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -1,4 +1,6 @@ -var Checkit = require('checkit'); +/* jshint esversion: 6 */ + +var CheckitError = require('checkit').Error; var _Promise = require('bluebird'); var _ = require('lodash'); @@ -8,236 +10,124 @@ var UserRole = require('../models/UserRole'); var errors = require('../errors'); var utils = require('../utils'); +/** + * Persists a mentor and its ideas + * @param {Mentor} mentor a mentor object to be created/updated + * @param {Array} ideas an array of raw mentor attributes + * @param {Transaction} t a pending transaction + * @return {Promise} the mentor with related ideas + */ +function _saveMentorAndIdeas(mentor, ideas, t) { + return mentor + .save(null, { transacting: t }) + .then(function (mentor) { + return _Promise.map(ideas, function (idea) { + return mentor.related('ideas').create(idea, { transacting: t }); + }).return(mentor); + }); +} + /** * Registers a mentor and their project ideas for the given user -* @param {Object} mentorObject a JSON object holding the mentor registration * @param {Object} user the user for which a mentor will be registered -* @return {Promise} resolving to an object in the same format as mentorObject, holding the saved models -* @throws InvalidParameterError when a mentor exists for the specified user +* @param {Object} attributes a JSON object holding the mentor attributes +* @return {Promise} the mentor with related ideas +* @throws {InvalidParameterError} when a mentor exists for the specified user */ -module.exports.createMentor = function (mentorObject, user) { - var userId = user.get('id'); - var mentorAttributes = mentorObject.mentor; - var mentorIdeas = mentorObject.ideas; +var createMentor = function (user, attributes) { + var mentorAttributes = attributes.mentor; + var mentorIdeas = attributes.ideas; - mentorAttributes.userId = userId; + mentorAttributes.userId = user.get('id'); var mentor = Mentor.forge(mentorAttributes); return mentor .validate() - .catch(Checkit.Error, utils.errors.handleValidationError) + .catch(CheckitError, utils.errors.handleValidationError) .then(function (validated) { if (user.hasRole(utils.roles.MENTOR, false)) { - var message = "The given user has already registered as a mentor"; + var message = "The given user has already registered as a mentor"; var source = "userId"; throw new errors.InvalidParameterError(message, source); } return Mentor.transaction(function (t) { - return UserRole.addRole(user, utils.roles.MENTOR, false, t) - .then(function (result) { - return mentor.save(null, { transacting: t }); - }) + return UserRole + .addRole(user, utils.roles.MENTOR, false, t) .then(function (result) { - var mentorAndIdeas = { - 'mentor': result, - 'ideas': [] - }; - - var mentorId = result.get('id'); - return _Promise.all(_.map(mentorIdeas, function(ideaAttributes) { - ideaAttributes.mentorId = mentorId; - var projectIdea = MentorProjectIdea.forge(ideaAttributes); - - return projectIdea.save(null, { transacting: t }) - .then(function(idea) { - mentorAndIdeas.ideas.push(idea); - return idea; - }); - })) - .then(function (result) { - return mentorAndIdeas; - }); - }); + return _saveMentorAndIdeas(mentor, mentorIdeas); }); - }) - .then(function (result) { - return _Promise.resolve(result); }); - }; + }); +}; /** -* Finds a mentor by querying for the userId -* @param {Number} id the User ID to query -* @return {Promise} resolving to the associated Mentor model +* Finds a mentor by querying on a user's ID +* @param {User} user the user expected to be associated with a mentor +* @return {Promise} resolving to the associated Mentor model * @throws {NotFoundError} when the requested mentor cannot be found */ -module.exports.findMentorByUserId = function (userId) { +var findMentorByUser = function (user) { return Mentor - .findByUserId(user_id) - .then(function (result) { + .findByUserId(user.get('id')) + .tap(function (result) { if (_.isNull(result)) { var message = "A mentor with the given user ID cannot be found"; var source = "userId"; throw new errors.NotFoundError(message, source); } - - var mentorAndIdeas = { - 'mentor': result, - 'ideas': result.related('ideas') - }; - return _Promise.resolve(mentorAndIdeas); }); }; /** * Finds a mentor by querying for the given ID * @param {Number} id the ID to query -* @return {Promise} resolving to the associated Mentor model +* @return {Promise} resolving to the associated Mentor model * @throws {NotFoundError} when the requested mentor cannot be found */ -module.exports.findMentorById = function (id) { +var findMentorById = function (id) { return Mentor - .findById(id) - .then(function (result) { - if (_.isNull(result)) { - var message = "A mentor with the given ID cannot be found"; - var source = "id"; - throw new errors.NotFoundError(message, source); - } - - var mentorAndIdeas = { - 'mentor': result, - 'ideas': result.related('ideas') - }; - return _Promise.resolve(mentorAndIdeas); - }); + .findById(id) + .tap(function (result) { + if (_.isNull(result)) { + var message = "A mentor with the given ID cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } + }); }; /** * Updates a mentor and their project ideas by relational user -* @param {Object} mentorObject a JSON object holding the mentor registration -* @param {Object} user the relational user of the mentor to be updated -* @return {Promise} resolving to an object in the same format as mentorObject, holding the saved models -* @throws InvalidParameterError when a mentor doesn't exist for the specified user +* @param {Mentor} mentor the mentor to be updated +* @param {Object} attributes a JSON object holding the mentor registration attributes +* @return {Promise} resolving to an object in the same format as attributes, holding the saved models +* @throws {InvalidParameterError} when a mentor doesn't exist for the specified user */ -module.exports.updateMentorbyUser = function (mentorObject, user) { - var userId = user.get('id'); - var mentorAttributes = mentorObject.mentor; - var mentorIdeas = mentorObject.ideas; +var updateMentor = function (mentor, attributes) { + var mentorAttributes = attributes.mentor; + var mentorIdeas = attributes.ideas; - return Mentor - .findByUserId(user.get('id')) - .then(function (result) { - if (_.isNull(result)) { - var message = "A mentor with the given user ID cannot be found"; - var source = "userId"; - throw new errors.NotFoundError(message, source); - } + mentor.set(mentorAttributes); + mentor.related('ideas').reset(); - return _Promise.all(_.map(result.related('ideas'), function(idea) { - return idea.destroy(); - })) - .then(function(){ - return result; - }); - }) - .then(function (mentor) { - mentor.set(mentorAttributes); - return mentor + return mentor .validate() - .catch(Checkit.Error, utils.errors.handleValidationError) + .catch(CheckitError, utils.errors.handleValidationError) .then(function (validated) { - return Mentor.transaction( function(t) { - return mentor.save(null, { transacting: t }) - .then(function(result) { - var mentorAndIdeas = { - 'mentor': result, - 'ideas': [] - }; - var mentorId = result.get('id'); - return _Promise.all(_.map(mentorIdeas, function(ideaAttributes) { - ideaAttributes.mentorId = mentorId; - - var projectIdea = MentorProjectIdea.forge(ideaAttributes); - return projectIdea.save(null, { transacting: t }) - .then(function(idea) { - mentorAndIdeas.ideas.push(idea); - return idea; - }); - })) - .then(function (result) { - return mentorAndIdeas; - }); - }); + return Mentor.transaction(function (t) { + return mentor.related('ideas') + .invokeThen('destroy', { transacting: t }) + .then(function () { + return _saveMentorAndIdeas(mentor, mentorIdeas); }); - }); - }) - .then(function (result) { - return _Promise.resolve(result); }); - }; - -/** -* Updates a mentor and their project ideas by id -* @param {Object} mentorObject a JSON object holding the mentor registration -* @param {Object} id the id of the mentor to be updated -* @return {Promise} resolving to an object in the same format as mentorObject, holding the saved models -* @throws InvalidParameterError when a mentor doesn't exist with the specified id -*/ -module.exports.updateMentorbyId = function (mentorObject, id) { - var mentorAttributes = mentorObject.mentor; - var mentorIdeas = mentorObject.ideas; - return Mentor - .findById(id) - .then(function (result) { - if (_.isNull(result)) { - var message = "A mentor with the given ID cannot be found"; - var source = "id"; - throw new errors.NotFoundError(message, source); - } - return _Promise.all( - _.map(result.related('ideas'), function(idea) { - return idea.destroy(); - }) - ) - .then(function(){ - return result; - }); - }) - .then(function (mentor) { - mentor.set(mentorAttributes); - return mentor - .validate() - .catch(Checkit.Error, utils.errors.handleValidationError) - .then(function (validated) { - return Mentor.transaction( function(t) { - return mentor.save(null, { transacting: t }) - .then( function(result) { - var mentorAndIdeas = { - 'mentor': result, - 'ideas': [] - }; - var mentorId = result.get('id'); - return _Promise.all( - _.map(mentorIdeas, function(ideaAttributes) { - ideaAttributes.mentorId = mentorId; - var projectIdea = MentorProjectIdea.forge(ideaAttributes); - return projectIdea.save(null, { transacting: t }) - .then( function(idea) { - mentorAndIdeas.ideas.push(idea); - return idea; - }); - }) - ) - .then(function (result) { - return mentorAndIdeas; - }); }); - }); - }); - }) - .then(function (result) { - return _Promise.resolve(result); - }); +}; + +module.exports = { + createMentor: createMentor, + findMentorByUser: findMentorByUser, + findMentorById: findMentorById, + updateMentor: updateMentor }; From 6f071005a41ac5d6e9373fd82b67b7213c47eaa7 Mon Sep 17 00:00:00 2001 From: Nick Magerko Date: Mon, 24 Oct 2016 00:25:38 -0500 Subject: [PATCH 08/38] fixed mentor update to update ideas appropriately; need to clean up --- api/v1/services/RegistrationService.js | 38 +++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 77e7e0a..5f8014d 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -108,21 +108,51 @@ var updateMentor = function (mentor, attributes) { var mentorAttributes = attributes.mentor; var mentorIdeas = attributes.ideas; + // TODO clean this up!! mentor.set(mentorAttributes); - mentor.related('ideas').reset(); return mentor .validate() .catch(CheckitError, utils.errors.handleValidationError) .then(function (validated) { + var newIdeas = []; + var updatedIdeas = []; + var updatedIdeaIds = []; + + _.forEach(mentorIdeas, function (idea) { + if (!_.has(idea, 'id')) { + newIdeas.push(idea); + } else if (_.isUndefined(mentor.related('ideas').get(idea.id))) { + const MESSAGE = "A MentorProjectIdea with the given ID does not exist"; + const SOURCE = "idea.id"; + throw new errors.NotFoundError(MESSAGE, SOURCE); + } else { + updatedIdeas.push(mentor.related('ideas').get(idea.id).set(idea)); + updatedIdeaIds.push(idea.id); + } + }); + + return _Promise.all([newIdeas, updatedIdeas, updatedIdeaIds]); + }) + .spread(function (newIdeas, updatedIdeas, updatedIdeaIds){ return Mentor.transaction(function (t) { return mentor.related('ideas') - .invokeThen('destroy', { transacting: t }) + .query().transacting(t) + .whereNotIn('id', updatedIdeaIds) + .delete() + .catch(Mentor.NoRowsDeletedError, function () { return null; }) .then(function () { - return _saveMentorAndIdeas(mentor, mentorIdeas); + mentor.related('ideas').reset(); + return _Promise.map(updatedIdeas, function (idea) { + mentor.related('ideas').add(idea); + return idea.save(null, { transacting: t, require: false }); + }); + }) + .then(function () { + return _saveMentorAndIdeas(mentor, newIdeas, t); }); + }); }); - }); }; module.exports = { From 108f530c107081a0fb13f4045409b6e0e027ea56 Mon Sep 17 00:00:00 2001 From: Nick Magerko Date: Mon, 24 Oct 2016 12:00:33 -0500 Subject: [PATCH 09/38] refactored mentor update method --- api/v1/services/RegistrationService.js | 94 +++++++++++++++++--------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 5f8014d..0b4c877 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -27,6 +27,65 @@ function _saveMentorAndIdeas(mentor, ideas, t) { }); } +/** + * Determines which ideas are new and which are + * existing ones that need to be updated + * @param {Mentor} mentor the Mentor with whom the ideas are associated + * @param {Array} mentorIdeas the list of MentorProjectIdea objects/attributes + * @return {Array} containing the new ideas, updated ideas, and ids of updated ideas + */ +function _extractMentorIdeas(mentor, mentorIdeas) { + var newIdeas = []; + var updatedIdeas = []; + var updatedIdeaIds = []; + + _.forEach(mentorIdeas, function (idea) { + var MESSAGE, SOURCE; + if (!_.has(idea, 'id')) { + newIdeas.push(idea); + } else if (_.isUndefined(mentor.related('ideas').get(idea.id))) { + MESSAGE = "A MentorProjectIdea with the given ID does not exist"; + SOURCE = "idea.id"; + throw new errors.NotFoundError(MESSAGE, SOURCE); + } else if (mentor.related('ideas').get(idea.id).get('mentorId') !== mentor.get('id')){ + MESSAGE = "A MentorProjectIdea that does not belong to this mentor cannot be updated here"; + throw new errors.UnauthorizedError(MESSAGE); + } else { + // TODO remove this once Request validator can marshal recursively + idea.mentorId = mentor.get('id'); + + updatedIdeas.push(mentor.related('ideas').get(idea.id).set(idea)); + updatedIdeaIds.push(idea.id); + } + }); + + return _Promise.all([newIdeas, updatedIdeas, updatedIdeaIds]); +} + +/** + * Removes unwanted ideas and updates desired ideas + * @param {Mentor} mentor the Mentor with whom the ideas are associated + * @param {Array} updatedIdeas a list of related MentorProjectIdeas with new attributes + * @param {Array} updatedIdeaIds a list of the ids contained in the updatedIdeas + * @param {Transaction} t a pending transaction + * @return {Promise<>} a promise indicating all changes have been added to the transaction + */ +function _adjustMentorIdeas(mentor, updatedIdeas, updatedIdeaIds, t) { + return mentor.related('ideas') + .query().transacting(t) + .whereNotIn('id', updatedIdeaIds) + .delete() + .catch(Mentor.NoRowsDeletedError, function () { return null; }) + .then(function () { + mentor.related('ideas').reset(); + + return _Promise.map(updatedIdeas, function (idea) { + mentor.related('ideas').add(idea); + return idea.save(null, { transacting: t, require: false }); + }); + }); +} + /** * Registers a mentor and their project ideas for the given user * @param {Object} user the user for which a mentor will be registered @@ -108,46 +167,17 @@ var updateMentor = function (mentor, attributes) { var mentorAttributes = attributes.mentor; var mentorIdeas = attributes.ideas; - // TODO clean this up!! mentor.set(mentorAttributes); return mentor .validate() .catch(CheckitError, utils.errors.handleValidationError) - .then(function (validated) { - var newIdeas = []; - var updatedIdeas = []; - var updatedIdeaIds = []; - - _.forEach(mentorIdeas, function (idea) { - if (!_.has(idea, 'id')) { - newIdeas.push(idea); - } else if (_.isUndefined(mentor.related('ideas').get(idea.id))) { - const MESSAGE = "A MentorProjectIdea with the given ID does not exist"; - const SOURCE = "idea.id"; - throw new errors.NotFoundError(MESSAGE, SOURCE); - } else { - updatedIdeas.push(mentor.related('ideas').get(idea.id).set(idea)); - updatedIdeaIds.push(idea.id); - } - }); - - return _Promise.all([newIdeas, updatedIdeas, updatedIdeaIds]); + .then(function (){ + return _extractMentorIdeas(mentor, mentorIdeas); }) .spread(function (newIdeas, updatedIdeas, updatedIdeaIds){ return Mentor.transaction(function (t) { - return mentor.related('ideas') - .query().transacting(t) - .whereNotIn('id', updatedIdeaIds) - .delete() - .catch(Mentor.NoRowsDeletedError, function () { return null; }) - .then(function () { - mentor.related('ideas').reset(); - return _Promise.map(updatedIdeas, function (idea) { - mentor.related('ideas').add(idea); - return idea.save(null, { transacting: t, require: false }); - }); - }) + return _adjustMentorIdeas(mentor, updatedIdeas, updatedIdeaIds, t) .then(function () { return _saveMentorAndIdeas(mentor, newIdeas, t); }); From 0457a89ff01415cd803353e9cb47b860b7a79c41 Mon Sep 17 00:00:00 2001 From: Nick Magerko Date: Thu, 27 Oct 2016 23:27:51 -0500 Subject: [PATCH 10/38] added gitter badge to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4715ec8..5f52b03 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/HackIllinois/api-2017?utm_source=badge&utm_medium=badge) + # HackIllinois API (2017) The back-end services supporting HackIllinois 2017 are stored here. Looking to From ce11c631ca6a331a8b95dcb41a8f10ea78ec6ba3 Mon Sep 17 00:00:00 2001 From: Jonathan Reynolds Date: Thu, 3 Nov 2016 20:07:38 -0500 Subject: [PATCH 11/38] Fix endpoints and status checking --- api/v1/endpoints.js | 2 +- api/v1/models/Mentor.js | 1 + api/v1/requests/MentorRequest.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 70bd770..85bdfaf 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -2,7 +2,7 @@ var requests = require('./requests'); var endpoints = {}; -endpoints['/v1/user/'] = { +endpoints['/v1/user'] = { POST: requests.BasicAuthRequest }; endpoints['/v1/user/accredited'] = { diff --git a/api/v1/models/Mentor.js b/api/v1/models/Mentor.js index 57cb959..4db2189 100644 --- a/api/v1/models/Mentor.js +++ b/api/v1/models/Mentor.js @@ -10,6 +10,7 @@ var Mentor = Model.extend({ firstName: ['required', 'string', 'maxLength:255'], lastName: ['required', 'string', 'maxLength:255'], shirtSize: ['required', 'string', registration.verifyTshirtSize], + status: ['string', registration.verifyStatus], github: ['string', 'maxLength:50'], location: ['required', 'string', 'maxLength:255'], summary: ['required', 'string', 'maxLength:255'], diff --git a/api/v1/requests/MentorRequest.js b/api/v1/requests/MentorRequest.js index 58b54a5..b1b10d4 100644 --- a/api/v1/requests/MentorRequest.js +++ b/api/v1/requests/MentorRequest.js @@ -6,6 +6,7 @@ var mentorValidations = { firstName: ['required', 'string', 'maxLength:255'], lastName: ['required', 'string', 'maxLength:255'], shirtSize: ['required', 'string', registration.verifyTshirtSize], + status: ['string', registration.verifyStatus], github: ['required', 'string', 'maxLength:50'], location: ['required', 'string', 'maxLength:255'], summary: ['required', 'string', 'maxLength:255'], From 6cbbda38a3d42688ef47e2aa693027f90d97ef61 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sat, 5 Nov 2016 20:37:49 -0500 Subject: [PATCH 12/38] Add Projects model --- api/v1/models/Project.js | 14 ++++++++++++++ .../migration/V20161105_1750__createProjects.sql | 7 +++++++ 2 files changed, 21 insertions(+) create mode 100644 api/v1/models/Project.js create mode 100644 database/migration/V20161105_1750__createProjects.sql diff --git a/api/v1/models/Project.js b/api/v1/models/Project.js new file mode 100644 index 0000000..5c1b7d4 --- /dev/null +++ b/api/v1/models/Project.js @@ -0,0 +1,14 @@ +var Model = require('./Model'); + +var Project = Model.extend({ + tableName: 'projects', + idAttribute: 'id', + validations: { + name: ['required', 'string', 'maxLength:100'], + description: ['required', 'string', 'maxLength:200'], + repo: ['string', 'maxLength:255'], + is_published: ['required', 'boolean'] + } +}); + +module.exports Project; \ No newline at end of file diff --git a/database/migration/V20161105_1750__createProjects.sql b/database/migration/V20161105_1750__createProjects.sql new file mode 100644 index 0000000..971d1eb --- /dev/null +++ b/database/migration/V20161105_1750__createProjects.sql @@ -0,0 +1,7 @@ +CREATE TABLE `projects` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(100) NOT NULL, + `description` VARCHAR(200) NOT NULL, + `repo` VARCHAR(255) NULL, + `is_published` TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`)); \ No newline at end of file From a5c0228eb75ea6398bb5616f6940c12c846ceeb7 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sat, 5 Nov 2016 21:00:32 -0500 Subject: [PATCH 13/38] Add project mentors model --- api/v1/models/ProjectMentor.js | 14 ++++++++++++++ .../V20161105_1750__createProjects.sql | 3 ++- .../V20161105_2059__createProjectMentors.sql | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 api/v1/models/ProjectMentor.js create mode 100644 database/migration/V20161105_2059__createProjectMentors.sql diff --git a/api/v1/models/ProjectMentor.js b/api/v1/models/ProjectMentor.js new file mode 100644 index 0000000..df9577f --- /dev/null +++ b/api/v1/models/ProjectMentor.js @@ -0,0 +1,14 @@ +var Model = require('./Model'); +var Project = require('./Project'); +var Mentor = require('./Mentor'); + +var ProjectMentor = Model.extend({ + tableName: 'project_mentors', + idAttribute: 'id', + project : function () { + return this.belongsTo(Project, 'project_id'); + }, + mentor: function () { + return this.belongsTo(Mentor, 'mentor_id'); + } +}); \ No newline at end of file diff --git a/database/migration/V20161105_1750__createProjects.sql b/database/migration/V20161105_1750__createProjects.sql index 971d1eb..cb8b633 100644 --- a/database/migration/V20161105_1750__createProjects.sql +++ b/database/migration/V20161105_1750__createProjects.sql @@ -4,4 +4,5 @@ CREATE TABLE `projects` ( `description` VARCHAR(200) NOT NULL, `repo` VARCHAR(255) NULL, `is_published` TINYINT(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`)); \ No newline at end of file + PRIMARY KEY (`id`) +); \ No newline at end of file diff --git a/database/migration/V20161105_2059__createProjectMentors.sql b/database/migration/V20161105_2059__createProjectMentors.sql new file mode 100644 index 0000000..80e713d --- /dev/null +++ b/database/migration/V20161105_2059__createProjectMentors.sql @@ -0,0 +1,18 @@ +CREATE TABLE `project_mentors` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `project_id` INT UNSIGNED NOT NULL, + `mentor_id` INT UNSIGNED NOT NULL, + PRIMARY KEY (`id`), + INDEX `fk_project_mentors_projects_id_idx` (`project_id` ASC), + INDEX `fk_project_mentors_mentors_id_idx` (`mentor_id` ASC), + CONSTRAINT `project_id` + FOREIGN KEY (`mentor_id`) + REFERENCES `projects` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `mentor_id` + FOREIGN KEY (`mentor_id`) + REFERENCES `mentors` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION +); From 450cecd6a916dc6440375e49db66cecbb279c114 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sat, 5 Nov 2016 23:40:31 -0500 Subject: [PATCH 14/38] Added basic project creation --- .gitignore | 2 ++ api/v1/controllers/ProjectController.js | 42 +++++++++++++++++++++++++ api/v1/controllers/index.js | 3 +- api/v1/index.js | 1 + api/v1/models/Project.js | 4 +-- api/v1/services/ProjectService.js | 30 ++++++++++++++++++ api/v1/services/index.js | 1 + 7 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 api/v1/controllers/ProjectController.js create mode 100644 api/v1/services/ProjectService.js diff --git a/.gitignore b/.gitignore index 234336b..e2f842a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ node_modules temp *.config + +dump.rdb \ No newline at end of file diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js new file mode 100644 index 0000000..c2327d7 --- /dev/null +++ b/api/v1/controllers/ProjectController.js @@ -0,0 +1,42 @@ +var bodyParser = require('body-parser'); +var middleware = require('../middleware'); +var router = require('express').Router(); + +var errors = require('../errors'); +var config = require('../../config'); + +var ProjectService = require('../services/ProjectService'); + +//TODO: Add project creation validation + +function createProject (req, res, next) { + attributes = {}; + attributes.name = req.body.name; + attributes.description = req.body.description; + attributes.repo = req.body.repo; + attributes.isPublished = req.body.isPublished; + + ProjectService + .createProject(attributes) + .then(function (newProject) { + res.body = newProject.toJSON(); + + next(); + return null; + }) + .catch(function (error){ + next(error); + return null; + }); +} + +router.use(bodyParser.json()); +router.use(middleware.auth); +router.use(middleware.request); + +router.post('/', createProject); + +router.use(middleware.response); +router.use(middleware.errors); + +module.exports.router = router; \ No newline at end of file diff --git a/api/v1/controllers/index.js b/api/v1/controllers/index.js index 827e40c..75f3f45 100644 --- a/api/v1/controllers/index.js +++ b/api/v1/controllers/index.js @@ -2,5 +2,6 @@ module.exports = { AuthController: require('./AuthController.js'), UploadController: require('./UploadController.js'), UserController: require('./UserController.js'), - RegistrationController: require('./RegistrationController.js') + RegistrationController: require('./RegistrationController.js'), + ProjectController: require('./ProjectController.js') }; diff --git a/api/v1/index.js b/api/v1/index.js index 87fbda3..7e9d3cd 100644 --- a/api/v1/index.js +++ b/api/v1/index.js @@ -16,5 +16,6 @@ v1.use('/auth', controllers.AuthController.router); v1.use('/user', controllers.UserController.router); v1.use('/upload', controllers.UploadController.router); v1.use('/registration', controllers.RegistrationController.router); +v1.use('/project', controllers.ProjectController.router); module.exports = v1; diff --git a/api/v1/models/Project.js b/api/v1/models/Project.js index 5c1b7d4..c534afe 100644 --- a/api/v1/models/Project.js +++ b/api/v1/models/Project.js @@ -7,8 +7,8 @@ var Project = Model.extend({ name: ['required', 'string', 'maxLength:100'], description: ['required', 'string', 'maxLength:200'], repo: ['string', 'maxLength:255'], - is_published: ['required', 'boolean'] + is_published: ['boolean'] } }); -module.exports Project; \ No newline at end of file +module.exports = Project; \ No newline at end of file diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js new file mode 100644 index 0000000..12e33b9 --- /dev/null +++ b/api/v1/services/ProjectService.js @@ -0,0 +1,30 @@ +var Checkit = require('checkit'); +var _Promise = require('bluebird'); +var _ = require('lodash'); + +var Project = require('../models/Project'); +var ProjectMentor = require('../models/ProjectMentor'); +var errors = require('../errors'); +var utils = require('../utils'); + + +module.exports.createProject = function (attributes) { + if(typeof attributes.repo === 'undefined'){ + attributes.description = ''; + } + if(typeof attributes.isPublished === 'undefined'){ + attributes.isPublished = 0; //false + } + + var project = Project.forge(attributes); + return project + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + return project + .save() + .then(function (project) { + return project; + }); + }); +} diff --git a/api/v1/services/index.js b/api/v1/services/index.js index 0641f14..1c63f9d 100644 --- a/api/v1/services/index.js +++ b/api/v1/services/index.js @@ -2,6 +2,7 @@ module.exports = { AuthService: require('./AuthService'), MailService: require('./MailService'), PermissionService: require('./PermissionService'), + ProjectService: require('./ProjectService'), StorageService: require('./StorageService'), UserService: require('./UserService'), TokenService: require('./TokenService'), From cc550f688501e396abacacde21160b49d5156d44 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sun, 6 Nov 2016 00:34:50 -0500 Subject: [PATCH 15/38] Implementing request validation --- api/v1/controllers/ProjectController.js | 10 ++++++++-- api/v1/endpoints.js | 3 +++ api/v1/requests/ProjectCreationRequest.js | 22 ++++++++++++++++++++++ api/v1/requests/index.js | 1 + api/v1/services/ProjectService.js | 10 ++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 api/v1/requests/ProjectCreationRequest.js diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index c2327d7..903d803 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -4,6 +4,7 @@ var router = require('express').Router(); var errors = require('../errors'); var config = require('../../config'); +var roles = require('../utils/roles'); var ProjectService = require('../services/ProjectService'); @@ -16,8 +17,13 @@ function createProject (req, res, next) { attributes.repo = req.body.repo; attributes.isPublished = req.body.isPublished; + console.log(attributes); + ProjectService - .createProject(attributes) + .canCreateProject(req.user) + .then(function (isAuthed) { + return ProjectService.createProject(attributes); + }) .then(function (newProject) { res.body = newProject.toJSON(); @@ -34,7 +40,7 @@ router.use(bodyParser.json()); router.use(middleware.auth); router.use(middleware.request); -router.post('/', createProject); +router.post('/', middleware.permission(roles.ORGANIZERS), createProject); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 85bdfaf..0533f20 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -30,5 +30,8 @@ endpoints['/v1/upload/resume'] = { endpoints['/v1/upload/resume/:id'] = { PUT: requests.UploadRequest }; +endpoints['/v1/project'] = { + POST: requests.ProjectCreationRequest +}; module.exports = endpoints; diff --git a/api/v1/requests/ProjectCreationRequest.js b/api/v1/requests/ProjectCreationRequest.js new file mode 100644 index 0000000..9318c30 --- /dev/null +++ b/api/v1/requests/ProjectCreationRequest.js @@ -0,0 +1,22 @@ +var roles = require('../utils/roles'); +var Request = require('./Request'); + +var bodyRequired = ['name', 'description']; +var bodyValidations = { + 'name': ['string', 'required'], + 'description': ['string', 'required'], + 'repo': ['string', 'maxLength:255'], + 'is_published': ['boolean'] +}; + +function ProjectCreationRequest(headers, body) { + Request.call(this, headers, body); + + this.bodyRequired = bodyRequired; + this.bodyValidations = bodyValidations; +} + +ProjectCreationRequest.prototype = Object.create(Request.prototype); +ProjectCreationRequest.prototype.constructor = ProjectCreationRequest; + +module.exports = ProjectCreationRequest; \ No newline at end of file diff --git a/api/v1/requests/index.js b/api/v1/requests/index.js index c0ced33..8542442 100644 --- a/api/v1/requests/index.js +++ b/api/v1/requests/index.js @@ -2,6 +2,7 @@ module.exports = { AccreditedUserCreationRequest: require('./AccreditedUserCreationRequest'), BasicAuthRequest: require('./BasicAuthRequest'), MentorRequest: require('./MentorRequest'), + ProjectCreationRequest: require('./ProjectCreationRequest'), ResetTokenRequest: require('./ResetTokenRequest'), ResetPasswordRequest: require('./ResetPasswordRequest'), UploadRequest: require('./UploadRequest') diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 12e33b9..d61eccb 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -6,8 +6,18 @@ var Project = require('../models/Project'); var ProjectMentor = require('../models/ProjectMentor'); var errors = require('../errors'); var utils = require('../utils'); +var roles = require('../utils/roles'); +module.exports.canCreateProject = function (creator) { + if(creator.hasRole(roles.SUPERUSER) || creator.hasRole(roles.ORGANIZERS)){ + return _Promise.resolve(true); + } + + var message = "A project cannot be created with the provided credentials"; + return _Promise.reject(new errors.UnauthorizedError(message)); +} + module.exports.createProject = function (attributes) { if(typeof attributes.repo === 'undefined'){ attributes.description = ''; From 214dc112c1fb1466b364ff3387f3b583fc1549ca Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sun, 6 Nov 2016 00:56:55 -0500 Subject: [PATCH 16/38] Finished project creation validation --- api/v1/controllers/ProjectController.js | 6 ++---- api/v1/requests/ProjectCreationRequest.js | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index 903d803..d0796fc 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -15,9 +15,7 @@ function createProject (req, res, next) { attributes.name = req.body.name; attributes.description = req.body.description; attributes.repo = req.body.repo; - attributes.isPublished = req.body.isPublished; - - console.log(attributes); + attributes.is_published = req.body.is_published; ProjectService .canCreateProject(req.user) @@ -40,7 +38,7 @@ router.use(bodyParser.json()); router.use(middleware.auth); router.use(middleware.request); -router.post('/', middleware.permission(roles.ORGANIZERS), createProject); +router.post('/', middleware.permission(roles.ALL), createProject); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/requests/ProjectCreationRequest.js b/api/v1/requests/ProjectCreationRequest.js index 9318c30..f626b67 100644 --- a/api/v1/requests/ProjectCreationRequest.js +++ b/api/v1/requests/ProjectCreationRequest.js @@ -1,7 +1,7 @@ var roles = require('../utils/roles'); var Request = require('./Request'); -var bodyRequired = ['name', 'description']; +var bodyRequired = ['name', 'description', 'repo', 'is_published']; var bodyValidations = { 'name': ['string', 'required'], 'description': ['string', 'required'], From 565f2601647d154bff93ae7e1db8d5a3ec0dd47a Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sun, 6 Nov 2016 11:20:53 -0600 Subject: [PATCH 17/38] Added project retrieval --- api/v1/controllers/ProjectController.js | 23 +++++++++++++++++++-- api/v1/models/Project.js | 9 +++++++++ api/v1/services/ProjectService.js | 27 +++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index d0796fc..fad0791 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -8,7 +8,7 @@ var roles = require('../utils/roles'); var ProjectService = require('../services/ProjectService'); -//TODO: Add project creation validation + function createProject (req, res, next) { attributes = {}; @@ -34,11 +34,30 @@ function createProject (req, res, next) { }); } +function getProject (req, res, next) { + var id = req.params.id; + + ProjectService + .findProjectById(id) + .then(function (project) { + res.body = project.toJSON(); + res.body.test = "test"; + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + router.use(bodyParser.json()); router.use(middleware.auth); router.use(middleware.request); -router.post('/', middleware.permission(roles.ALL), createProject); +router.post('/', middleware.permission(roles.ORGANIZERS), createProject); +router.get('/:id', middleware.permission(roles.ALL), getProject); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/models/Project.js b/api/v1/models/Project.js index c534afe..66211da 100644 --- a/api/v1/models/Project.js +++ b/api/v1/models/Project.js @@ -11,4 +11,13 @@ var Project = Model.extend({ } }); +Project.findByName = function (name) { + name = name.toLowerCase(); + return Project.where({ name:name }).fetch(); +} + +Project.findById = function (id) { + return Project.where({ id:id }).fetch(); +} + module.exports = Project; \ No newline at end of file diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index d61eccb..506fe85 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -26,15 +26,42 @@ module.exports.createProject = function (attributes) { attributes.isPublished = 0; //false } + attributes.name = attributes.name.toLowerCase(); + var project = Project.forge(attributes); return project .validate() .catch(Checkit.Error, utils.errors.handleValidationError) .then(function (validated) { + return Project.findByName(attributes.name); + }) + .then(function (result){ + if (!_.isNull(result)) { + var message = "A project with the given name already exists"; + var source = "name"; + throw new errors.InvalidParameterError(message, source); + } + return project .save() .then(function (project) { return project; }); + }) +} + +module.exports.findProjectById = function (id) { + return Project + .findById(id) + .then(function (result){ + if(_.isNull(result)){ + var message = "A project with the given ID cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } + + return _Promise.resolve(result); }); } + + From 91b101a74fcced32177c8b9b2175684781cfa670 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sun, 6 Nov 2016 12:09:29 -0600 Subject: [PATCH 18/38] Added in project updating --- api/v1/controllers/ProjectController.js | 24 +++++++++++++++++++++++- api/v1/services/ProjectService.js | 5 ++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index fad0791..ad2c227 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -41,7 +41,28 @@ function getProject (req, res, next) { .findProjectById(id) .then(function (project) { res.body = project.toJSON(); - res.body.test = "test"; + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + +function updateProject (req, res, next) { + var id = req.params.id; + var key = req.body.key; + var value = req.body.value; + + ProjectService + .findProjectById(id) + .then(function (project) { + return ProjectService.updateProject(project, key, value); + }) + .then(function (project) { + res.body = project.toJSON(); next(); return null; @@ -58,6 +79,7 @@ router.use(middleware.request); router.post('/', middleware.permission(roles.ORGANIZERS), createProject); router.get('/:id', middleware.permission(roles.ALL), getProject); +router.put('/:id', middleware.permission(roles.ORGANIZERS), updateProject); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 506fe85..6a76fda 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -53,7 +53,7 @@ module.exports.createProject = function (attributes) { module.exports.findProjectById = function (id) { return Project .findById(id) - .then(function (result){ + .then(function (result) { if(_.isNull(result)){ var message = "A project with the given ID cannot be found"; var source = "id"; @@ -64,4 +64,7 @@ module.exports.findProjectById = function (id) { }); } +module.exports.updateProject = function (project, key, value) { + return project.set(key, value).save(); +} From b81820d8f18003f65cee5bc2053d668629206fd5 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Thu, 10 Nov 2016 12:03:49 -0600 Subject: [PATCH 19/38] Add initial functionality --- api/v1/controllers/ProjectController.js | 2 ++ api/v1/models/ProjectMentor.js | 3 +++ api/v1/services/ProjectService.js | 34 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index ad2c227..695674d 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -51,6 +51,8 @@ function getProject (req, res, next) { }); } + +// TODO: Make this better function updateProject (req, res, next) { var id = req.params.id; var key = req.body.key; diff --git a/api/v1/models/ProjectMentor.js b/api/v1/models/ProjectMentor.js index df9577f..c09f04d 100644 --- a/api/v1/models/ProjectMentor.js +++ b/api/v1/models/ProjectMentor.js @@ -1,3 +1,6 @@ +var _Promise = require('bluebird'); +var _ = require('lodash'); + var Model = require('./Model'); var Project = require('./Project'); var Mentor = require('./Mentor'); diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 6a76fda..ef20cf0 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -18,6 +18,7 @@ module.exports.canCreateProject = function (creator) { return _Promise.reject(new errors.UnauthorizedError(message)); } +//TODO: make the name handling better module.exports.createProject = function (attributes) { if(typeof attributes.repo === 'undefined'){ attributes.description = ''; @@ -68,3 +69,36 @@ module.exports.updateProject = function (project, key, value) { return project.set(key, value).save(); } + +module.exports.deleteProjectMentor = function (project, mentor) { + return ProjectMentor + .where({ project_id: project.id, mentor_id: mentor.id }).fetch() + .then(function(oldProjectMentor) { + return oldProjectMentor.destroy(); + }); +} + + +module.exports.addProjectMentor = function (project, mentor) { + var projectMentor = ProjectMentor.forge({ project_id: project.id, mentor_id: mentor.id }); + + return ProjectMentor + .findByProjectId(project.id) + .then(function (result) { + if (!_.isNull(result)) { + deleteProjectMentor(project, mentor); + } + + return projectMentor + .save() + .then(function (projectMentor) { + return projectMentor; + }); + }); +} + + + + + + From 0ef32986e66ff3aebbff0a99faf542478ca49ef2 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Thu, 10 Nov 2016 20:43:44 -0600 Subject: [PATCH 20/38] Basic functionality done --- api/v1/controllers/ProjectController.js | 38 +++++++++++++ api/v1/models/ProjectMentor.js | 8 ++- api/v1/services/ProjectService.js | 71 ++++++++++++++++++++++--- 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index 695674d..05a33a1 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -75,6 +75,42 @@ function updateProject (req, res, next) { }); } +function addProjectMentor (req, res, next) { + var project_id = req.body.project_id; + var mentor_id = req.body.mentor_id; + + ProjectService + .addProjectMentor(project_id, mentor_id) + .then(function (projectMentor) { + res.body = projectMentor.toJSON(); + + next(); + return null; + }) + .catch( function (error) { + next(error); + return null; + }); +} + +function deleteProjectMentor (req, res, next) { + var project_id = req.body.project_id; + var mentor_id = req.body.mentor_id; + + ProjectService + .deleteProjectMentor(project_id, mentor_id) + .then(function () { + res.body = {} + + next(); + return null; + }) + .catch( function (error) { + next(error); + return null; + }); +} + router.use(bodyParser.json()); router.use(middleware.auth); router.use(middleware.request); @@ -82,6 +118,8 @@ router.use(middleware.request); router.post('/', middleware.permission(roles.ORGANIZERS), createProject); router.get('/:id', middleware.permission(roles.ALL), getProject); router.put('/:id', middleware.permission(roles.ORGANIZERS), updateProject); +router.post('/mentor', addProjectMentor); +router.delete('/mentor', deleteProjectMentor); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/models/ProjectMentor.js b/api/v1/models/ProjectMentor.js index c09f04d..d0e5000 100644 --- a/api/v1/models/ProjectMentor.js +++ b/api/v1/models/ProjectMentor.js @@ -14,4 +14,10 @@ var ProjectMentor = Model.extend({ mentor: function () { return this.belongsTo(Mentor, 'mentor_id'); } -}); \ No newline at end of file +}); + +ProjectMentor.findByProjectId = function (project_id) { + return ProjectMentor.where({ project_id: project_id }).fetch(); +} + +module.exports = ProjectMentor; \ No newline at end of file diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index ef20cf0..a1baaa7 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -2,8 +2,10 @@ var Checkit = require('checkit'); var _Promise = require('bluebird'); var _ = require('lodash'); +var Mentor = require('../models/Mentor'); var Project = require('../models/Project'); var ProjectMentor = require('../models/ProjectMentor'); + var errors = require('../errors'); var utils = require('../utils'); var roles = require('../utils/roles'); @@ -70,23 +72,63 @@ module.exports.updateProject = function (project, key, value) { } -module.exports.deleteProjectMentor = function (project, mentor) { +_isProjectMentorValid = function (project_id, mentor_id) { + return Project + .findById(project_id) + .then(function (result) { + if(!_.isNull(result)) { + return _Promise.resolve(true); + }else{ + return _Promise.resolve(false); + } + }) + .then(function (isValidSoFar) { + if(isValidSoFar){ + return Mentor + .findById(mentor_id) + .then(function (res) { + if(!_.isNull(res)) { + return _Promise.resolve(true); + } + }) + } + return _Promise.resolve(false); + }); +} + +_deleteProjectMentor = function (project_id) { return ProjectMentor - .where({ project_id: project.id, mentor_id: mentor.id }).fetch() + .where({ project_id: project_id }).fetch() .then(function(oldProjectMentor) { return oldProjectMentor.destroy(); }); } -module.exports.addProjectMentor = function (project, mentor) { - var projectMentor = ProjectMentor.forge({ project_id: project.id, mentor_id: mentor.id }); +module.exports.addProjectMentor = function (project_id, mentor_id) { + var projectMentor = ProjectMentor.forge({ project_id: project_id, mentor_id: mentor_id }); - return ProjectMentor - .findByProjectId(project.id) + return _isProjectMentorValid(project_id, mentor_id) + .then(function (isValid) { + if(!isValid){ + var message = "A project or mentor with the given IDs cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } + + return null; + }) + .then(function () { + return ProjectMentor.findByProjectId(project_id); + }) .then(function (result) { if (!_.isNull(result)) { - deleteProjectMentor(project, mentor); + if(result.attributes.mentorId != mentor_id){ + _deleteProjectMentor(project_id); + }else{ + //There already exists the project mentor + return _Promise.resolve(result); + } } return projectMentor @@ -98,7 +140,20 @@ module.exports.addProjectMentor = function (project, mentor) { } +module.exports.deleteProjectMentor = function (project_id, mentor_id) { + return _isProjectMentorValid(project_id, mentor_id) + .then(function (isValid) { + if(!isValid){ + var message = "A project or mentor with the given IDs cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } - + return null; + }) + .then(function () { + return _deleteProjectMentor(project_id, mentor_id); + }); +} From d81b00657b810ec2fa354392abe79ade217ff617 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sun, 13 Nov 2016 10:45:42 -0800 Subject: [PATCH 21/38] Add null check for invalid project ids --- api/v1/services/ProjectService.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index a1baaa7..429ae06 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -100,6 +100,11 @@ _deleteProjectMentor = function (project_id) { return ProjectMentor .where({ project_id: project_id }).fetch() .then(function(oldProjectMentor) { + if(_.isNull(oldProjectMentor)) { + var message = "A project with the given ID cannot be found"; + var source = "id"; + throw new errors.NotFoundError(message, source); + } return oldProjectMentor.destroy(); }); } From 7b42a6f067590f0c9918cf6241b564f03a8bd228 Mon Sep 17 00:00:00 2001 From: Yash Sharma Date: Wed, 16 Nov 2016 02:19:30 -0600 Subject: [PATCH 22/38] initial add of health check endpoit --- .idea/api-2017.iml | 9 + .idea/compiler.xml | 22 + .idea/copyright/profiles_settings.xml | 3 + .idea/jsLibraryMappings.xml | 6 + .idea/misc.xml | 52 ++ .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 1129 ++++++++++++++++++++++++ api/v1/controllers/HealthController.js | 16 + api/v1/controllers/index.js | 3 +- api/v1/index.js | 1 + dump.rdb | 1 + 12 files changed, 1255 insertions(+), 1 deletion(-) create mode 100644 .idea/api-2017.iml create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/jsLibraryMappings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 api/v1/controllers/HealthController.js create mode 100644 dump.rdb diff --git a/.idea/api-2017.iml b/.idea/api-2017.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/api-2017.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..d123dd6 --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8d501ac --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..36a7f0d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..8ac5bd4 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,1129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + project + + + true + + + + DIRECTORY + + false + + + + + + + + + + + + + + + + + + 1477014460070 + + + 1478737311856 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/v1/controllers/HealthController.js b/api/v1/controllers/HealthController.js new file mode 100644 index 0000000..be8c26d --- /dev/null +++ b/api/v1/controllers/HealthController.js @@ -0,0 +1,16 @@ +var bodyParser = require('body-parser'); +var middleware = require('../middleware'); + +var router = require('express').Router(); + +router.use(bodyParser.json()); +router.use(middleware.request); + +router.get('/', function(req, res) { + res.send(); +}); + +router.use(middleware.response); +router.use(middleware.errors); + +module.exports.router = router; \ No newline at end of file diff --git a/api/v1/controllers/index.js b/api/v1/controllers/index.js index 827e40c..cdb905e 100644 --- a/api/v1/controllers/index.js +++ b/api/v1/controllers/index.js @@ -2,5 +2,6 @@ module.exports = { AuthController: require('./AuthController.js'), UploadController: require('./UploadController.js'), UserController: require('./UserController.js'), - RegistrationController: require('./RegistrationController.js') + RegistrationController: require('./RegistrationController.js'), + HealthController: require('./HealthController.js') }; diff --git a/api/v1/index.js b/api/v1/index.js index 87fbda3..573fd18 100644 --- a/api/v1/index.js +++ b/api/v1/index.js @@ -16,5 +16,6 @@ v1.use('/auth', controllers.AuthController.router); v1.use('/user', controllers.UserController.router); v1.use('/upload', controllers.UploadController.router); v1.use('/registration', controllers.RegistrationController.router); +v1.use('/health', controllers.HealthController.router); module.exports = v1; diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000..56af04e --- /dev/null +++ b/dump.rdb @@ -0,0 +1 @@ +REDIS0006ÿܳCðZÜòV \ No newline at end of file From 2f5db0b0a6bfe8d98f7c2b5adbedb59ecd2a70e0 Mon Sep 17 00:00:00 2001 From: Yash Sharma Date: Wed, 16 Nov 2016 02:28:02 -0600 Subject: [PATCH 23/38] some refactoring --- .idea/workspace.xml | 59 +++++++++++++++----------- api/v1/controllers/HealthController.js | 11 +++-- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8ac5bd4..e32302d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,9 +2,7 @@ - - - + @@ -28,8 +26,18 @@ - - + + + + + + + + + + + + @@ -689,7 +697,7 @@ - + 1478737311856 @@ -702,7 +710,7 @@ - @@ -963,13 +971,6 @@ - - - - - - - @@ -1077,14 +1078,6 @@ - - - - - - - - @@ -1109,9 +1102,25 @@ + + + + + + + + + + + + + + + + - + @@ -1119,8 +1128,8 @@ - - + + diff --git a/api/v1/controllers/HealthController.js b/api/v1/controllers/HealthController.js index be8c26d..fe300a6 100644 --- a/api/v1/controllers/HealthController.js +++ b/api/v1/controllers/HealthController.js @@ -3,13 +3,16 @@ var middleware = require('../middleware'); var router = require('express').Router(); +function healthCheck(req, res, next) { + next(); + return null; +} + router.use(bodyParser.json()); +router.use(middleware.auth); router.use(middleware.request); -router.get('/', function(req, res) { - res.send(); -}); - +router.get('', healthCheck); router.use(middleware.response); router.use(middleware.errors); From 1a44ca2b37ff55cf1525d7e75dfd42c5def613c9 Mon Sep 17 00:00:00 2001 From: Yash Sharma Date: Wed, 16 Nov 2016 02:34:35 -0600 Subject: [PATCH 24/38] updated gitignore --- .gitignore | 4 +- .idea/api-2017.iml | 9 - .idea/compiler.xml | 22 - .idea/copyright/profiles_settings.xml | 3 - .idea/jsLibraryMappings.xml | 6 - .idea/misc.xml | 52 -- .idea/modules.xml | 8 - .idea/vcs.xml | 6 - .idea/workspace.xml | 1138 ------------------------- dump.rdb | 1 - 10 files changed, 3 insertions(+), 1246 deletions(-) delete mode 100644 .idea/api-2017.iml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/jsLibraryMappings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml delete mode 100644 dump.rdb diff --git a/.gitignore b/.gitignore index 234336b..6e3e984 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,13 @@ npm-debug.log* pids *.pid *.seed +dump.rdb node_modules .npm +.idea .DS_Store temp -*.config +*.config \ No newline at end of file diff --git a/.idea/api-2017.iml b/.idea/api-2017.iml deleted file mode 100644 index d6ebd48..0000000 --- a/.idea/api-2017.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43e..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml deleted file mode 100644 index d123dd6..0000000 --- a/.idea/jsLibraryMappings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 8d501ac..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 36a7f0d..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index e32302d..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,1138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - DEFINITION_ORDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - project - - - - - - - - - - - - - - - - project - - - true - - - - DIRECTORY - - false - - - - - - - - - - - - - - - - - - 1477014460070 - - - 1478737311856 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dump.rdb b/dump.rdb deleted file mode 100644 index 56af04e..0000000 --- a/dump.rdb +++ /dev/null @@ -1 +0,0 @@ -REDIS0006ÿܳCðZÜòV \ No newline at end of file From 781e83cd2fbd9df2fe8151f78214441fc44b413a Mon Sep 17 00:00:00 2001 From: Yash Sharma Date: Wed, 16 Nov 2016 03:05:56 -0600 Subject: [PATCH 25/38] removed unneccessary middleware, added documentation --- api/v1/controllers/HealthController.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/v1/controllers/HealthController.js b/api/v1/controllers/HealthController.js index fe300a6..563c087 100644 --- a/api/v1/controllers/HealthController.js +++ b/api/v1/controllers/HealthController.js @@ -1,4 +1,3 @@ -var bodyParser = require('body-parser'); var middleware = require('../middleware'); var router = require('express').Router(); @@ -8,8 +7,6 @@ function healthCheck(req, res, next) { return null; } -router.use(bodyParser.json()); -router.use(middleware.auth); router.use(middleware.request); router.get('', healthCheck); From a206092b1cfcced18424f8e5230c31dc1d53c996 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Fri, 18 Nov 2016 02:04:43 -0600 Subject: [PATCH 26/38] Add doc strings --- api/v1/services/ProjectService.js | 45 ++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 429ae06..5d038a9 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -5,12 +5,18 @@ var _ = require('lodash'); var Mentor = require('../models/Mentor'); var Project = require('../models/Project'); var ProjectMentor = require('../models/ProjectMentor'); - +, var errors = require('../errors'); var utils = require('../utils'); var roles = require('../utils/roles'); +/** + * Checks to see if a requestor valid permissions to create a new project + * @param {User} user creating the new project + * @return {Promise} resolving to true if the user is an organizer + * @throws InvalidParameterError when a user does not have correct permissions + */ module.exports.canCreateProject = function (creator) { if(creator.hasRole(roles.SUPERUSER) || creator.hasRole(roles.ORGANIZERS)){ return _Promise.resolve(true); @@ -20,7 +26,13 @@ module.exports.canCreateProject = function (creator) { return _Promise.reject(new errors.UnauthorizedError(message)); } -//TODO: make the name handling better + +/** + * Creates a project with the specificed attributes + * @param {Object} Contains name, description, repo, and is_published + * @return {Promise} resolving to the newly-created project + * @throws InvalidParameterError when a project exists with the specified name + */ module.exports.createProject = function (attributes) { if(typeof attributes.repo === 'undefined'){ attributes.description = ''; @@ -53,6 +65,13 @@ module.exports.createProject = function (attributes) { }) } + +/** + * Returns a project with the specified project id + * @param {int} ID of the project + * @return {Promise} resolving to the project + * @throws InvalidParameterError when a project doesn't exist with the specified ID + */ module.exports.findProjectById = function (id) { return Project .findById(id) @@ -67,6 +86,13 @@ module.exports.findProjectById = function (id) { }); } +/** + * Update a key value pair in a project + * @param {Project} Project that will be updated + * @param {String} Key is the name of the attribute + * @param {String} Value is the new value of the attribute + * @return {Promise} resolving to the updated project + */ module.exports.updateProject = function (project, key, value) { return project.set(key, value).save(); } @@ -110,6 +136,13 @@ _deleteProjectMentor = function (project_id) { } +/** + * Add a new project-mentor relationship + * @param {Int} ID of the project assigned to the mentor + * @param {Int} ID of the mentor assigned to the project + * @return {Promise} resolving to the new relationship + * @throws InvalidParameterError when a project or mentor doesn't exist with the specified ID + */ module.exports.addProjectMentor = function (project_id, mentor_id) { var projectMentor = ProjectMentor.forge({ project_id: project_id, mentor_id: mentor_id }); @@ -144,7 +177,13 @@ module.exports.addProjectMentor = function (project_id, mentor_id) { }); } - +/** + * Deletes a project-mentor relationship + * @param {Int} ID of the project in question + * @param {Int} ID of the mentor in question + * @return {Promise} resolving to the deleted relationship + * @throws InvalidParameterError when a project or mentor doesn't exist with the specified ID + */ module.exports.deleteProjectMentor = function (project_id, mentor_id) { return _isProjectMentorValid(project_id, mentor_id) .then(function (isValid) { From a3d5f53147543c8a8aef4a538c6c9f41fd832b2f Mon Sep 17 00:00:00 2001 From: tommypacker Date: Fri, 18 Nov 2016 20:30:45 -0600 Subject: [PATCH 27/38] Tightened up project validations --- api/v1/services/ProjectService.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 5d038a9..07955e5 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -5,11 +5,12 @@ var _ = require('lodash'); var Mentor = require('../models/Mentor'); var Project = require('../models/Project'); var ProjectMentor = require('../models/ProjectMentor'); -, + var errors = require('../errors'); var utils = require('../utils'); var roles = require('../utils/roles'); +const projectKeys = ["name", "description", "repo", "isPublished"]; /** * Checks to see if a requestor valid permissions to create a new project @@ -41,14 +42,14 @@ module.exports.createProject = function (attributes) { attributes.isPublished = 0; //false } - attributes.name = attributes.name.toLowerCase(); + validationName = attributes.name.toLowerCase(); var project = Project.forge(attributes); return project .validate() .catch(Checkit.Error, utils.errors.handleValidationError) .then(function (validated) { - return Project.findByName(attributes.name); + return Project.findByName(validationName); }) .then(function (result){ if (!_.isNull(result)) { @@ -65,7 +66,6 @@ module.exports.createProject = function (attributes) { }) } - /** * Returns a project with the specified project id * @param {int} ID of the project @@ -92,9 +92,21 @@ module.exports.findProjectById = function (id) { * @param {String} Key is the name of the attribute * @param {String} Value is the new value of the attribute * @return {Promise} resolving to the updated project + * @throws InvalidParameterError when the key is not valid */ module.exports.updateProject = function (project, key, value) { - return project.set(key, value).save(); + if(projectKeys.indexOf(key.toLowerCase()) == -1){ + var message = "The given key is invalid"; + var source = "key"; + throw new errors.InvalidParameterError(message, source); + } + return project + .set(key, value) + .validate() + .catch(Checkit.Error, utils.errors.handleValidationError) + .then(function (validated) { + return project.save(); + }); } From a5311b30c4c22ab7c01fde9a2d0d7177fce9ea93 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Fri, 18 Nov 2016 21:03:58 -0600 Subject: [PATCH 28/38] Tightened up project-mentor relationships --- api/v1/controllers/ProjectController.js | 4 +- api/v1/models/ProjectMentor.js | 4 +- api/v1/services/ProjectService.js | 55 +++++++++++-------------- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index 05a33a1..a532bf7 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -118,8 +118,8 @@ router.use(middleware.request); router.post('/', middleware.permission(roles.ORGANIZERS), createProject); router.get('/:id', middleware.permission(roles.ALL), getProject); router.put('/:id', middleware.permission(roles.ORGANIZERS), updateProject); -router.post('/mentor', addProjectMentor); -router.delete('/mentor', deleteProjectMentor); +router.post('/mentor', middleware.permission(roles.ORGANIZERS), addProjectMentor); +router.delete('/mentor', middleware.permission(roles.ORGANIZERS), deleteProjectMentor); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/models/ProjectMentor.js b/api/v1/models/ProjectMentor.js index d0e5000..4549f85 100644 --- a/api/v1/models/ProjectMentor.js +++ b/api/v1/models/ProjectMentor.js @@ -16,8 +16,8 @@ var ProjectMentor = Model.extend({ } }); -ProjectMentor.findByProjectId = function (project_id) { - return ProjectMentor.where({ project_id: project_id }).fetch(); +ProjectMentor.findByProjectAndMentorId = function (project_id, mentor_id) { + return ProjectMentor.where({ project_id: project_id, mentor_id: mentor_id }).fetch(); } module.exports = ProjectMentor; \ No newline at end of file diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 07955e5..460a267 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -109,7 +109,13 @@ module.exports.updateProject = function (project, key, value) { }); } - +/** + * Helper function for determining valid project/mentor ids + * @param {Int} ID of the project assigned to the mentor + * @param {Int} ID of the mentor assigned to the project + * @return {Promise} resolving to whether or not the ids are valid + * @throws InvalidParameterError when a project or mentor doesn't exist with the specified ID + */ _isProjectMentorValid = function (project_id, mentor_id) { return Project .findById(project_id) @@ -128,19 +134,26 @@ _isProjectMentorValid = function (project_id, mentor_id) { if(!_.isNull(res)) { return _Promise.resolve(true); } - }) + }); } return _Promise.resolve(false); }); } -_deleteProjectMentor = function (project_id) { +/** + * Helper function for deleting project-mentor relationships + * @param {Int} ID of the project assigned to the mentor + * @param {Int} ID of the mentor assigned to the project + * @return {Promise} resolving to null + * @throws InvalidParameterError when a project or mentor doesn't exist with the specified ID + */ +_deleteProjectMentor = function (project_id, mentor_id) { return ProjectMentor - .where({ project_id: project_id }).fetch() + .findByProjectAndMentorId(project_id, mentor_id) .then(function(oldProjectMentor) { if(_.isNull(oldProjectMentor)) { - var message = "A project with the given ID cannot be found"; - var source = "id"; + var message = "A project-mentor relationship with the given IDs cannot be found"; + var source = "project_id/mentor_id"; throw new errors.NotFoundError(message, source); } return oldProjectMentor.destroy(); @@ -162,23 +175,16 @@ module.exports.addProjectMentor = function (project_id, mentor_id) { .then(function (isValid) { if(!isValid){ var message = "A project or mentor with the given IDs cannot be found"; - var source = "id"; + var source = "project_id/mentor_id"; throw new errors.NotFoundError(message, source); } - return null; - }) - .then(function () { - return ProjectMentor.findByProjectId(project_id); + return ProjectMentor.findByProjectAndMentorId(project_id, mentor_id); }) .then(function (result) { if (!_.isNull(result)) { - if(result.attributes.mentorId != mentor_id){ - _deleteProjectMentor(project_id); - }else{ - //There already exists the project mentor - return _Promise.resolve(result); - } + //The project mentor relationship already exists + return _Promise.resolve(result); } return projectMentor @@ -197,19 +203,6 @@ module.exports.addProjectMentor = function (project_id, mentor_id) { * @throws InvalidParameterError when a project or mentor doesn't exist with the specified ID */ module.exports.deleteProjectMentor = function (project_id, mentor_id) { - return _isProjectMentorValid(project_id, mentor_id) - .then(function (isValid) { - if(!isValid){ - var message = "A project or mentor with the given IDs cannot be found"; - var source = "id"; - throw new errors.NotFoundError(message, source); - } - - return null; - }) - .then(function () { - return _deleteProjectMentor(project_id, mentor_id); - }); + return _deleteProjectMentor(project_id, mentor_id); } - From e21f7dec8fe3b0a301be50cc3ecf7333013166a0 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Sun, 20 Nov 2016 17:02:54 -0600 Subject: [PATCH 29/38] Small fixes --- api/v1/controllers/ProjectController.js | 12 ++---- api/v1/models/Project.js | 4 +- api/v1/services/PermissionService.js | 16 +++++++ api/v1/services/ProjectService.js | 57 ++++++------------------- 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index a532bf7..04e3c00 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -7,17 +7,13 @@ var config = require('../../config'); var roles = require('../utils/roles'); var ProjectService = require('../services/ProjectService'); - +var PermissionService = require('../services/PermissionService'); function createProject (req, res, next) { - attributes = {}; - attributes.name = req.body.name; - attributes.description = req.body.description; - attributes.repo = req.body.repo; - attributes.is_published = req.body.is_published; + attributes = req.body; - ProjectService + PermissionService .canCreateProject(req.user) .then(function (isAuthed) { return ProjectService.createProject(attributes); @@ -51,8 +47,6 @@ function getProject (req, res, next) { }); } - -// TODO: Make this better function updateProject (req, res, next) { var id = req.params.id; var key = req.body.key; diff --git a/api/v1/models/Project.js b/api/v1/models/Project.js index 66211da..b18fdb5 100644 --- a/api/v1/models/Project.js +++ b/api/v1/models/Project.js @@ -5,8 +5,8 @@ var Project = Model.extend({ idAttribute: 'id', validations: { name: ['required', 'string', 'maxLength:100'], - description: ['required', 'string', 'maxLength:200'], - repo: ['string', 'maxLength:255'], + description: ['required', 'string', 'maxLength:255'], + repo: ['required', 'string', 'maxLength:255'], is_published: ['boolean'] } }); diff --git a/api/v1/services/PermissionService.js b/api/v1/services/PermissionService.js index 58a96cb..0ae6504 100644 --- a/api/v1/services/PermissionService.js +++ b/api/v1/services/PermissionService.js @@ -25,3 +25,19 @@ module.exports.canCreateUser = function (creator, userRole) { var message = "The requested user cannot be created with the provided credentials"; return _Promise.reject(new errors.UnauthorizedError(message)); }; + + +/** + * Checks to see if a requestor valid permissions to create a new project + * @param {User} user creating the new project + * @return {Promise} resolving to true if the user is an organizer + * @throws InvalidParameterError when a user does not have correct permissions + */ +module.exports.canCreateProject = function (creator) { + if(creator.hasRole(roles.SUPERUSER) || creator.hasRole(roles.ORGANIZERS)){ + return _Promise.resolve(true); + } + + var message = "A project cannot be created with the provided credentials"; + return _Promise.reject(new errors.UnauthorizedError(message)); +} \ No newline at end of file diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 460a267..9f5ca3d 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -12,21 +12,6 @@ var roles = require('../utils/roles'); const projectKeys = ["name", "description", "repo", "isPublished"]; -/** - * Checks to see if a requestor valid permissions to create a new project - * @param {User} user creating the new project - * @return {Promise} resolving to true if the user is an organizer - * @throws InvalidParameterError when a user does not have correct permissions - */ -module.exports.canCreateProject = function (creator) { - if(creator.hasRole(roles.SUPERUSER) || creator.hasRole(roles.ORGANIZERS)){ - return _Promise.resolve(true); - } - - var message = "A project cannot be created with the provided credentials"; - return _Promise.reject(new errors.UnauthorizedError(message)); -} - /** * Creates a project with the specificed attributes @@ -35,21 +20,16 @@ module.exports.canCreateProject = function (creator) { * @throws InvalidParameterError when a project exists with the specified name */ module.exports.createProject = function (attributes) { - if(typeof attributes.repo === 'undefined'){ - attributes.description = ''; + if(_.isNull(attributes.isPublished) || _.isUndefined(attributes.isPublished)){ + attributes.isPublished = false; } - if(typeof attributes.isPublished === 'undefined'){ - attributes.isPublished = 0; //false - } - - validationName = attributes.name.toLowerCase(); var project = Project.forge(attributes); return project .validate() .catch(Checkit.Error, utils.errors.handleValidationError) .then(function (validated) { - return Project.findByName(validationName); + return Project.findByName(attributes.name); }) .then(function (result){ if (!_.isNull(result)) { @@ -120,21 +100,18 @@ _isProjectMentorValid = function (project_id, mentor_id) { return Project .findById(project_id) .then(function (result) { - if(!_.isNull(result)) { - return _Promise.resolve(true); - }else{ - return _Promise.resolve(false); + if(_.isNull(result)) { + var message = "The project id is invalid"; + var source = "project_id"; + throw new errors.InvalidParameterError(message, source); } + return Mentor.findById(mentor_id); }) - .then(function (isValidSoFar) { - if(isValidSoFar){ - return Mentor - .findById(mentor_id) - .then(function (res) { - if(!_.isNull(res)) { - return _Promise.resolve(true); - } - }); + .then(function (mentor) { + if(_.isNull(mentor)) { + var message = "The mentor id is invalid"; + var source = "mentor_id"; + throw new errors.InvalidParameterError(message, source); } return _Promise.resolve(false); }); @@ -173,12 +150,6 @@ module.exports.addProjectMentor = function (project_id, mentor_id) { return _isProjectMentorValid(project_id, mentor_id) .then(function (isValid) { - if(!isValid){ - var message = "A project or mentor with the given IDs cannot be found"; - var source = "project_id/mentor_id"; - throw new errors.NotFoundError(message, source); - } - return ProjectMentor.findByProjectAndMentorId(project_id, mentor_id); }) .then(function (result) { @@ -192,7 +163,7 @@ module.exports.addProjectMentor = function (project_id, mentor_id) { .then(function (projectMentor) { return projectMentor; }); - }); + }) } /** From e722548469befc57f8ad8c9c41acbed332f5b85d Mon Sep 17 00:00:00 2001 From: tommypacker Date: Mon, 21 Nov 2016 00:17:50 -0600 Subject: [PATCH 30/38] Better handling of project updates --- api/v1/controllers/ProjectController.js | 5 ++-- api/v1/endpoints.js | 5 +++- ...ctCreationRequest.js => ProjectRequest.js} | 11 ++++---- api/v1/requests/index.js | 2 +- api/v1/services/ProjectService.js | 27 ++++++------------- 5 files changed, 21 insertions(+), 29 deletions(-) rename api/v1/requests/{ProjectCreationRequest.js => ProjectRequest.js} (59%) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index 04e3c00..1e01ea7 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -49,13 +49,12 @@ function getProject (req, res, next) { function updateProject (req, res, next) { var id = req.params.id; - var key = req.body.key; - var value = req.body.value; + var attributes = req.body; ProjectService .findProjectById(id) .then(function (project) { - return ProjectService.updateProject(project, key, value); + return ProjectService.updateProject(project, attributes); }) .then(function (project) { res.body = project.toJSON(); diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 0533f20..dcccf0b 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -30,8 +30,11 @@ endpoints['/v1/upload/resume'] = { endpoints['/v1/upload/resume/:id'] = { PUT: requests.UploadRequest }; +endpoints['/v1/project/:id'] = { + PUT: requests.ProjectRequest +}; endpoints['/v1/project'] = { - POST: requests.ProjectCreationRequest + POST: requests.ProjectRequest }; module.exports = endpoints; diff --git a/api/v1/requests/ProjectCreationRequest.js b/api/v1/requests/ProjectRequest.js similarity index 59% rename from api/v1/requests/ProjectCreationRequest.js rename to api/v1/requests/ProjectRequest.js index f626b67..d4e1d42 100644 --- a/api/v1/requests/ProjectCreationRequest.js +++ b/api/v1/requests/ProjectRequest.js @@ -5,18 +5,19 @@ var bodyRequired = ['name', 'description', 'repo', 'is_published']; var bodyValidations = { 'name': ['string', 'required'], 'description': ['string', 'required'], - 'repo': ['string', 'maxLength:255'], + 'repo': ['required', 'string', 'maxLength:255'], 'is_published': ['boolean'] }; -function ProjectCreationRequest(headers, body) { +function ProjectRequest(headers, body) { + console.log("USING THIS NOW"); Request.call(this, headers, body); this.bodyRequired = bodyRequired; this.bodyValidations = bodyValidations; } -ProjectCreationRequest.prototype = Object.create(Request.prototype); -ProjectCreationRequest.prototype.constructor = ProjectCreationRequest; +ProjectRequest.prototype = Object.create(Request.prototype); +ProjectRequest.prototype.constructor = ProjectRequest; -module.exports = ProjectCreationRequest; \ No newline at end of file +module.exports = ProjectRequest; \ No newline at end of file diff --git a/api/v1/requests/index.js b/api/v1/requests/index.js index 8542442..98a230e 100644 --- a/api/v1/requests/index.js +++ b/api/v1/requests/index.js @@ -2,7 +2,7 @@ module.exports = { AccreditedUserCreationRequest: require('./AccreditedUserCreationRequest'), BasicAuthRequest: require('./BasicAuthRequest'), MentorRequest: require('./MentorRequest'), - ProjectCreationRequest: require('./ProjectCreationRequest'), + ProjectRequest: require('./ProjectRequest'), ResetTokenRequest: require('./ResetTokenRequest'), ResetPasswordRequest: require('./ResetPasswordRequest'), UploadRequest: require('./UploadRequest') diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 9f5ca3d..36d002c 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -10,8 +10,6 @@ var errors = require('../errors'); var utils = require('../utils'); var roles = require('../utils/roles'); -const projectKeys = ["name", "description", "repo", "isPublished"]; - /** * Creates a project with the specificed attributes @@ -20,8 +18,8 @@ const projectKeys = ["name", "description", "repo", "isPublished"]; * @throws InvalidParameterError when a project exists with the specified name */ module.exports.createProject = function (attributes) { - if(_.isNull(attributes.isPublished) || _.isUndefined(attributes.isPublished)){ - attributes.isPublished = false; + if(_.isNull(attributes.is_published) || _.isUndefined(attributes.is_published)){ + attributes.is_published = false; } var project = Project.forge(attributes); @@ -69,19 +67,14 @@ module.exports.findProjectById = function (id) { /** * Update a key value pair in a project * @param {Project} Project that will be updated - * @param {String} Key is the name of the attribute - * @param {String} Value is the new value of the attribute + * @param {Object} JSON representing new project mentor key value pairs * @return {Promise} resolving to the updated project * @throws InvalidParameterError when the key is not valid */ -module.exports.updateProject = function (project, key, value) { - if(projectKeys.indexOf(key.toLowerCase()) == -1){ - var message = "The given key is invalid"; - var source = "key"; - throw new errors.InvalidParameterError(message, source); - } +module.exports.updateProject = function (project, attributes) { + project.set(attributes); + return project - .set(key, value) .validate() .catch(Checkit.Error, utils.errors.handleValidationError) .then(function (validated) { @@ -158,12 +151,8 @@ module.exports.addProjectMentor = function (project_id, mentor_id) { return _Promise.resolve(result); } - return projectMentor - .save() - .then(function (projectMentor) { - return projectMentor; - }); - }) + return projectMentor.save() + }); } /** From ba3eae0ec05f17c8641146d66e1396b480b59ff3 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Mon, 21 Nov 2016 20:50:46 -0600 Subject: [PATCH 31/38] Work on fixing endpoints + renamed is_published to isPublished for consistency --- api/v1/endpoints.js | 10 +++------- api/v1/middleware/request.js | 2 +- api/v1/models/Project.js | 2 +- api/v1/requests/ProjectRequest.js | 5 ++--- api/v1/services/ProjectService.js | 6 +++--- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index dcccf0b..6223361 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -25,16 +25,12 @@ endpoints['/v1/auth'] = { POST: requests.BasicAuthRequest }; endpoints['/v1/upload/resume'] = { - POST: requests.UploadRequest -}; -endpoints['/v1/upload/resume/:id'] = { + POST: requests.UploadRequest, PUT: requests.UploadRequest }; -endpoints['/v1/project/:id'] = { - PUT: requests.ProjectRequest -}; endpoints['/v1/project'] = { - POST: requests.ProjectRequest + POST: requests.ProjectRequest, + PUT: requests.ProjectRequest }; module.exports = endpoints; diff --git a/api/v1/middleware/request.js b/api/v1/middleware/request.js index a220b7f..2097861 100644 --- a/api/v1/middleware/request.js +++ b/api/v1/middleware/request.js @@ -7,7 +7,7 @@ var errorUtils = require('../utils/errors'); module.exports = function(req, res, next) { // we need to find whether or not this endpoint's method has a validating // request object mapped to it - var pathRequests = endpoints[req.originalUrl.replace(/\/+$/, "")]; + var pathRequests = endpoints[req.baseUrl.replace(/\/+$/, "")]; var MethodRequest = (pathRequests) ? pathRequests[req.method] : undefined; // not all methods (or endpoints) define such an object diff --git a/api/v1/models/Project.js b/api/v1/models/Project.js index b18fdb5..5da7746 100644 --- a/api/v1/models/Project.js +++ b/api/v1/models/Project.js @@ -7,7 +7,7 @@ var Project = Model.extend({ name: ['required', 'string', 'maxLength:100'], description: ['required', 'string', 'maxLength:255'], repo: ['required', 'string', 'maxLength:255'], - is_published: ['boolean'] + isPublished: ['boolean'] } }); diff --git a/api/v1/requests/ProjectRequest.js b/api/v1/requests/ProjectRequest.js index d4e1d42..f151b4b 100644 --- a/api/v1/requests/ProjectRequest.js +++ b/api/v1/requests/ProjectRequest.js @@ -1,16 +1,15 @@ var roles = require('../utils/roles'); var Request = require('./Request'); -var bodyRequired = ['name', 'description', 'repo', 'is_published']; +var bodyRequired = ['name', 'description', 'repo', 'isPublished']; var bodyValidations = { 'name': ['string', 'required'], 'description': ['string', 'required'], 'repo': ['required', 'string', 'maxLength:255'], - 'is_published': ['boolean'] + 'isPublished': ['boolean'] }; function ProjectRequest(headers, body) { - console.log("USING THIS NOW"); Request.call(this, headers, body); this.bodyRequired = bodyRequired; diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 36d002c..18dbf71 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -13,13 +13,13 @@ var roles = require('../utils/roles'); /** * Creates a project with the specificed attributes - * @param {Object} Contains name, description, repo, and is_published + * @param {Object} Contains name, description, repo, and isPublished * @return {Promise} resolving to the newly-created project * @throws InvalidParameterError when a project exists with the specified name */ module.exports.createProject = function (attributes) { - if(_.isNull(attributes.is_published) || _.isUndefined(attributes.is_published)){ - attributes.is_published = false; + if(_.isNull(attributes.isPublished) || _.isUndefined(attributes.isPublished)){ + attributes.isPublished = false; } var project = Project.forge(attributes); From 65d09fcf006acb0a0487aee8fa2c9392dc6d4342 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Mon, 21 Nov 2016 21:26:14 -0600 Subject: [PATCH 32/38] Edit endpoints due to issues with :id routes --- api/v1/controllers/RegistrationController.js | 4 ++-- api/v1/endpoints.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js index 2adb0c2..ad586a2 100644 --- a/api/v1/controllers/RegistrationController.js +++ b/api/v1/controllers/RegistrationController.js @@ -104,9 +104,9 @@ router.use(middleware.auth); router.use(middleware.request); router.post('/mentor', middleware.permission(roles.NONE, _isAuthenticated), createMentor); -router.get('/mentor', middleware.permission(roles.MENTOR), fetchMentorByUser); +router.get('/mentor/me', middleware.permission(roles.MENTOR), fetchMentorByUser); router.get('/mentor/:id', middleware.permission(roles.ORGANIZERS), fetchMentorById); -router.put('/mentor', middleware.permission(roles.MENTOR), updateMentorByUser); +router.put('/mentor/me', middleware.permission(roles.MENTOR), updateMentorByUser); router.put('/mentor/:id', middleware.permission(roles.ORGANIZERS), updateMentorById); router.use(middleware.response); diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js index 6223361..7516f4d 100644 --- a/api/v1/endpoints.js +++ b/api/v1/endpoints.js @@ -15,7 +15,7 @@ endpoints['/v1/registration/mentor'] = { POST: requests.MentorRequest, PUT: requests.MentorRequest }; -endpoints['/v1/registration/mentor/:id'] = { +endpoints['/v1/registration/mentor/me'] = { PUT: requests.MentorRequest }; endpoints['/v1/auth/reset'] = { From 1dffd91a573a41b1001325d3f6b4a92167c1de00 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Mon, 21 Nov 2016 23:28:48 -0600 Subject: [PATCH 33/38] Added route for getting all projects --- api/database.js | 1 + api/v1/controllers/ProjectController.js | 42 +++++++++++++++++++++++++ api/v1/services/ProjectService.js | 34 +++++++++++++++----- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/api/database.js b/api/database.js index 1e554c5..0f271b0 100644 --- a/api/database.js +++ b/api/database.js @@ -24,6 +24,7 @@ var KNEX_CONFIG = { function DatabaseManager() { this._knex = Knex(KNEX_CONFIG); this._bookshelf = Bookshelf(this._knex); + this._bookshelf.plugin('pagination') } DatabaseManager.prototype.constructor = DatabaseManager; diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index 1e01ea7..e091a79 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -1,3 +1,4 @@ +var _ = require('lodash'); var bodyParser = require('body-parser'); var middleware = require('../middleware'); var router = require('express').Router(); @@ -10,6 +11,24 @@ var ProjectService = require('../services/ProjectService'); var PermissionService = require('../services/PermissionService'); +function _validGetAllRequest(page, count, published){ + if(_.isNaN(page)){ + var message = "Invalid page parameter"; + var source = "page"; + throw new errors.InvalidParameterError(message, source); + } + if(_.isNaN(count)){ + var message = "Invalid count parameter"; + var source = "count"; + throw new errors.InvalidParameterError(message, source); + } + if(_.isNaN(published) || (published != 0 && published != 1)){ + var message = "Invalid published parameter"; + var source = "published"; + throw new errors.InvalidParameterError(message, source); + } +} + function createProject (req, res, next) { attributes = req.body; @@ -47,6 +66,28 @@ function getProject (req, res, next) { }); } +function getAllProjects (req, res, next) { + var page = parseInt(req.params.page); + var count = parseInt(req.query.count); + var published = parseInt(req.query.published); + + _validGetAllRequest(page, count, published); + + ProjectService + .getAllProjects(page, count, published) + .then(function (results) { + res.body = {}; + res.body.projects = results; + + next(); + return null; + }) + .catch(function (error) { + next(error); + return null; + }); +} + function updateProject (req, res, next) { var id = req.params.id; var attributes = req.body; @@ -111,6 +152,7 @@ router.use(middleware.request); router.post('/', middleware.permission(roles.ORGANIZERS), createProject); router.get('/:id', middleware.permission(roles.ALL), getProject); router.put('/:id', middleware.permission(roles.ORGANIZERS), updateProject); +router.get('/all/:page', middleware.permission(roles.ORGANIZERS), getAllProjects); router.post('/mentor', middleware.permission(roles.ORGANIZERS), addProjectMentor); router.delete('/mentor', middleware.permission(roles.ORGANIZERS), deleteProjectMentor); diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 18dbf71..66bd23d 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -36,12 +36,8 @@ module.exports.createProject = function (attributes) { throw new errors.InvalidParameterError(message, source); } - return project - .save() - .then(function (project) { - return project; - }); - }) + return project.save() + }); } /** @@ -150,7 +146,6 @@ module.exports.addProjectMentor = function (project_id, mentor_id) { //The project mentor relationship already exists return _Promise.resolve(result); } - return projectMentor.save() }); } @@ -166,3 +161,28 @@ module.exports.deleteProjectMentor = function (project_id, mentor_id) { return _deleteProjectMentor(project_id, mentor_id); } + +/** + * Returns a list of all projects + * @param {Int} Page number + * @param {Int} Number of items on the page + * @param {Int} Boolean in int form representing published/unpublished + * @return {Promise} resolving to an array of project objects + */ +module.exports.getAllProjects = function (page, count, isPublished) { + return Project + .query(function (qb){ + qb.groupBy('projects.id'); + qb.where('is_published', '=', String(isPublished)); + }) + .orderBy('-name') + .fetchPage({ + pageSize: count, + page: page + }) + .then(function (results) { + var projects = _.map(results.models, 'attributes'); + return projects; + }); +} + From 82cab79ec0a1b5730be7345d1b3389d5795905f5 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Tue, 22 Nov 2016 11:26:03 -0600 Subject: [PATCH 34/38] Update permissions required for retrieving all projects --- api/v1/controllers/ProjectController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index e091a79..84e7224 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -152,7 +152,7 @@ router.use(middleware.request); router.post('/', middleware.permission(roles.ORGANIZERS), createProject); router.get('/:id', middleware.permission(roles.ALL), getProject); router.put('/:id', middleware.permission(roles.ORGANIZERS), updateProject); -router.get('/all/:page', middleware.permission(roles.ORGANIZERS), getAllProjects); +router.get('/all/:page', middleware.permission(roles.ALL), getAllProjects); router.post('/mentor', middleware.permission(roles.ORGANIZERS), addProjectMentor); router.delete('/mentor', middleware.permission(roles.ORGANIZERS), deleteProjectMentor); From 7f4a45510c9a772023af87c1bbe375f90ee7a22c Mon Sep 17 00:00:00 2001 From: Nick Magerko Date: Tue, 22 Nov 2016 14:52:37 -0600 Subject: [PATCH 35/38] converted request middleware into stand-alone middleware --- api/v1/controllers/AuthController.js | 6 +-- api/v1/controllers/HealthController.js | 8 ++-- api/v1/controllers/RegistrationController.js | 10 +++-- api/v1/controllers/UploadController.js | 8 ++-- api/v1/controllers/UserController.js | 9 ++-- api/v1/endpoints.js | 34 --------------- api/v1/middleware/index.js | 2 +- api/v1/middleware/request.js | 44 ++++++++------------ 8 files changed, 42 insertions(+), 79 deletions(-) delete mode 100644 api/v1/endpoints.js diff --git a/api/v1/controllers/AuthController.js b/api/v1/controllers/AuthController.js index 548b074..b0c4326 100644 --- a/api/v1/controllers/AuthController.js +++ b/api/v1/controllers/AuthController.js @@ -4,6 +4,7 @@ var _Promise = require('bluebird'); var config = require('../../config'); var errors = require('../errors'); var middleware = require('../middleware'); +var requests = require('../requests'); var utils = require('../utils'); var AuthService = require('../services/AuthService'); @@ -102,11 +103,10 @@ function passwordReset(req, res, next) { router.use(bodyParser.json()); router.use(middleware.auth); -router.use(middleware.request); -router.post('', createToken); +router.post('', middleware.request(requests.BasicAuthRequest), createToken); router.get('/refresh', refreshToken); -router.post('/reset', passwordReset); +router.post('/reset', middleware.request(requests.ResetPasswordRequest), passwordReset); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/controllers/HealthController.js b/api/v1/controllers/HealthController.js index 563c087..288e7a8 100644 --- a/api/v1/controllers/HealthController.js +++ b/api/v1/controllers/HealthController.js @@ -3,14 +3,12 @@ var middleware = require('../middleware'); var router = require('express').Router(); function healthCheck(req, res, next) { - next(); - return null; + next(); + return null; } -router.use(middleware.request); - router.get('', healthCheck); router.use(middleware.response); router.use(middleware.errors); -module.exports.router = router; \ No newline at end of file +module.exports.router = router; diff --git a/api/v1/controllers/RegistrationController.js b/api/v1/controllers/RegistrationController.js index 2adb0c2..953b6cb 100644 --- a/api/v1/controllers/RegistrationController.js +++ b/api/v1/controllers/RegistrationController.js @@ -2,6 +2,7 @@ var bodyParser = require('body-parser'); var services = require('../services'); var middleware = require('../middleware'); +var requests = require('../requests'); var roles = require('../utils/roles'); var router = require('express').Router(); @@ -103,11 +104,14 @@ router.use(bodyParser.json()); router.use(middleware.auth); router.use(middleware.request); -router.post('/mentor', middleware.permission(roles.NONE, _isAuthenticated), createMentor); +router.post('/mentor', middleware.request(requests.MentorRequest), + middleware.permission(roles.NONE, _isAuthenticated), createMentor); router.get('/mentor', middleware.permission(roles.MENTOR), fetchMentorByUser); router.get('/mentor/:id', middleware.permission(roles.ORGANIZERS), fetchMentorById); -router.put('/mentor', middleware.permission(roles.MENTOR), updateMentorByUser); -router.put('/mentor/:id', middleware.permission(roles.ORGANIZERS), updateMentorById); +router.put('/mentor', middleware.request(requests.MentorRequest), + middleware.permission(roles.MENTOR), updateMentorByUser); +router.put('/mentor/:id', middleware.request(requests.MentorRequest), + middleware.permission(roles.ORGANIZERS), updateMentorById); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/controllers/UploadController.js b/api/v1/controllers/UploadController.js index da71493..5a5f75a 100644 --- a/api/v1/controllers/UploadController.js +++ b/api/v1/controllers/UploadController.js @@ -15,6 +15,7 @@ var services = require('../services'); var utils = require('../utils'); var Upload = require('../models/Upload'); +var UploadRequest = require('../requests/UploadRequest'); var User = require('../models/User'); const UPLOAD_ALREADY_PRESENT = "An upload has already been associated with this user"; @@ -114,10 +115,11 @@ function getUpload (req, res, next) { // note that the request middleware is added after the body-parser, else there will be no body var resumeRouter = ExpressRouter(); resumeRouter.use(bodyParser.raw({ limit: RESUME_UPLOAD_LIMIT, type: RESUME_UPLOAD_TYPE })); -resumeRouter.use(middleware.request); -resumeRouter.post('/', middleware.upload, middleware.permission(utils.roles.NON_PROFESSIONALS), createResumeUpload); -resumeRouter.put('/:id', middleware.upload, _findUpload, middleware.permission(utils.roles.NONE, _isOwner), replaceResumeUpload); +resumeRouter.post('/', middleware.request(UploadRequest), middleware.upload, + middleware.permission(utils.roles.NON_PROFESSIONALS), createResumeUpload); +resumeRouter.put('/:id', middleware.request(UploadRequest), middleware.upload, + _findUpload, middleware.permission(utils.roles.NONE, _isOwner), replaceResumeUpload); resumeRouter.get('/:id', _findUpload, middleware.permission(utils.roles.ORGANIZERS, _isOwner), getUpload); // set up the primary router with just the auth middleware since the sub-routers diff --git a/api/v1/controllers/UserController.js b/api/v1/controllers/UserController.js index 5ca8e9a..5fb4a18 100644 --- a/api/v1/controllers/UserController.js +++ b/api/v1/controllers/UserController.js @@ -5,6 +5,7 @@ var services = require('../services'); var config = require('../../config'); var middleware = require('../middleware'); +var requests = require('../requests'); var scopes = require('../utils/scopes'); var mail = require('../utils/mail'); var roles = require('../utils/roles'); @@ -101,11 +102,11 @@ function requestPasswordReset (req, res, next) { router.use(bodyParser.json()); router.use(middleware.auth); -router.use(middleware.request); -router.post('/', createUser); -router.post('/accredited', middleware.permission(roles.ORGANIZERS), createAccreditedUser); -router.post('/reset', requestPasswordReset); +router.post('/', middleware.request(requests.BasicAuthRequest), createUser); +router.post('/accredited', middleware.request(requests.AccreditedUserCreationRequest), + middleware.permission(roles.ORGANIZERS), createAccreditedUser); +router.post('/reset', middleware.request(requests.ResetTokenRequest), requestPasswordReset); router.get('/:id', middleware.permission(roles.ORGANIZERS, isRequester), getUser); router.use(middleware.response); diff --git a/api/v1/endpoints.js b/api/v1/endpoints.js deleted file mode 100644 index 85bdfaf..0000000 --- a/api/v1/endpoints.js +++ /dev/null @@ -1,34 +0,0 @@ -var requests = require('./requests'); - -var endpoints = {}; - -endpoints['/v1/user'] = { - POST: requests.BasicAuthRequest -}; -endpoints['/v1/user/accredited'] = { - POST: requests.AccreditedUserCreationRequest -}; -endpoints['/v1/user/reset'] = { - POST: requests.ResetTokenRequest -}; -endpoints['/v1/registration/mentor'] = { - POST: requests.MentorRequest, - PUT: requests.MentorRequest -}; -endpoints['/v1/registration/mentor/:id'] = { - PUT: requests.MentorRequest -}; -endpoints['/v1/auth/reset'] = { - POST: requests.ResetPasswordRequest -}; -endpoints['/v1/auth'] = { - POST: requests.BasicAuthRequest -}; -endpoints['/v1/upload/resume'] = { - POST: requests.UploadRequest -}; -endpoints['/v1/upload/resume/:id'] = { - PUT: requests.UploadRequest -}; - -module.exports = endpoints; diff --git a/api/v1/middleware/index.js b/api/v1/middleware/index.js index b58dec7..06d3ad7 100644 --- a/api/v1/middleware/index.js +++ b/api/v1/middleware/index.js @@ -2,7 +2,7 @@ module.exports = { auth: require('./auth.js'), errors: require('./errors.js'), permission: require('./permission.js'), - request: require('./request.js'), response: require('./response.js'), + request: require('./request.js'), upload: require('./upload.js') }; diff --git a/api/v1/middleware/request.js b/api/v1/middleware/request.js index a220b7f..3521069 100644 --- a/api/v1/middleware/request.js +++ b/api/v1/middleware/request.js @@ -1,34 +1,26 @@ var CheckitError = require('checkit').Error; -var endpoints = require('../endpoints'); var errors = require('../errors'); var errorUtils = require('../utils/errors'); -module.exports = function(req, res, next) { - // we need to find whether or not this endpoint's method has a validating - // request object mapped to it - var pathRequests = endpoints[req.originalUrl.replace(/\/+$/, "")]; - var MethodRequest = (pathRequests) ? pathRequests[req.method] : undefined; +module.exports = function (Request) { + return function (req, res, next) { + if (!Request) { + return next(); + } - // not all methods (or endpoints) define such an object - if (!MethodRequest) { - return next(); - } - - // the request we find is an object type, so we instantiate it - // and then handle any validation errors before continuing - var request = new MethodRequest(req.headers, req.body); - request.validate() - .then(function (validated) { - req.body = request.body(); - - next(); - return null; - }) - .catch(CheckitError, errorUtils.handleValidationError) - .catch(function (error) { - next(error); - return null; - }); + var request = new Request(req.headers, req.body); + request.validate() + .then(function (validated) { + req.body = request.body(); + next(); + return null; + }) + .catch(CheckitError, errorUtils.handleValidationError) + .catch(function (error) { + next(error); + return null; + }); + }; }; From bbd9fdb331e3077864b04b53df10dfecfd1a79b0 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Thu, 24 Nov 2016 13:06:24 -0600 Subject: [PATCH 36/38] Finish merging in updates --- api/v1/controllers/ProjectController.js | 11 +++++++---- api/v1/controllers/index.js | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index ce373bc..b6b15b5 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -5,6 +5,7 @@ var router = require('express').Router(); var errors = require('../errors'); var config = require('../../config'); +var requests = require('../requests'); var roles = require('../utils/roles'); var ProjectService = require('../services/ProjectService'); @@ -147,16 +148,18 @@ function deleteProjectMentor (req, res, next) { router.use(bodyParser.json()); router.use(middleware.auth); -router.use(middleware.request); router.post('/mentor', middleware.permission(roles.ORGANIZERS), addProjectMentor); router.delete('/mentor', middleware.permission(roles.ORGANIZERS), deleteProjectMentor); -router.post('/', middleware.permission(roles.ORGANIZERS), createProject); +router.post('/', middleware.request(requests.ProjectRequest), + middleware.permission(roles.ORGANIZERS), createProject); router.get('/:id', middleware.permission(roles.ALL), getProject); -router.put('/:id', middleware.permission(roles.ORGANIZERS), updateProject); +router.put('/:id', middleware.request(requests.ProjectRequest), + middleware.permission(roles.ORGANIZERS), updateProject); router.get('/all/:page', middleware.permission(roles.ALL), getAllProjects); router.use(middleware.response); router.use(middleware.errors); -module.exports.router = router; \ No newline at end of file +module.exports.router = router; + diff --git a/api/v1/controllers/index.js b/api/v1/controllers/index.js index eee8974..8c8137c 100644 --- a/api/v1/controllers/index.js +++ b/api/v1/controllers/index.js @@ -3,6 +3,6 @@ module.exports = { UploadController: require('./UploadController.js'), UserController: require('./UserController.js'), RegistrationController: require('./RegistrationController.js'), - ProjectController: require('./ProjectController.js') + ProjectController: require('./ProjectController.js'), HealthController: require('./HealthController.js') }; From c5eb6aafdd217b661d53b84e16e0e9bebcd83e23 Mon Sep 17 00:00:00 2001 From: tommypacker Date: Fri, 25 Nov 2016 00:36:21 -0600 Subject: [PATCH 37/38] Tighten up some small issues --- api/v1/controllers/ProjectController.js | 18 ++++++++++++------ api/v1/requests/ProjectMentorRequest.js | 19 +++++++++++++++++++ api/v1/requests/index.js | 1 + api/v1/services/ProjectService.js | 2 +- 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 api/v1/requests/ProjectMentorRequest.js diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index b6b15b5..3ac35d5 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var bodyParser = require('body-parser'); var middleware = require('../middleware'); var router = require('express').Router(); +var _Promise = require('bluebird'); var errors = require('../errors'); var config = require('../../config'); @@ -28,6 +29,7 @@ function _validGetAllRequest(page, count, published){ var source = "published"; throw new errors.InvalidParameterError(message, source); } + return _Promise.resolve(true); } function createProject (req, res, next) { @@ -68,14 +70,16 @@ function getProject (req, res, next) { } function getAllProjects (req, res, next) { + _.defaults(req.params, {'page': 1}); + _.defaults(req.query, {'count': 10, 'published': 1}); var page = parseInt(req.params.page); var count = parseInt(req.query.count); var published = parseInt(req.query.published); - _validGetAllRequest(page, count, published); - - ProjectService - .getAllProjects(page, count, published) + _validGetAllRequest(page, count, published) + .then(function () { + return ProjectService.getAllProjects(page, count, published); + }) .then(function (results) { res.body = {}; res.body.projects = results; @@ -149,8 +153,10 @@ function deleteProjectMentor (req, res, next) { router.use(bodyParser.json()); router.use(middleware.auth); -router.post('/mentor', middleware.permission(roles.ORGANIZERS), addProjectMentor); -router.delete('/mentor', middleware.permission(roles.ORGANIZERS), deleteProjectMentor); +router.post('/mentor', middleware.request(requests.ProjectMentorRequest), + middleware.permission(roles.ORGANIZERS), addProjectMentor); +router.delete('/mentor', middleware.request(requests.ProjectMentorRequest), + middleware.permission(roles.ORGANIZERS), deleteProjectMentor); router.post('/', middleware.request(requests.ProjectRequest), middleware.permission(roles.ORGANIZERS), createProject); router.get('/:id', middleware.permission(roles.ALL), getProject); diff --git a/api/v1/requests/ProjectMentorRequest.js b/api/v1/requests/ProjectMentorRequest.js new file mode 100644 index 0000000..493869a --- /dev/null +++ b/api/v1/requests/ProjectMentorRequest.js @@ -0,0 +1,19 @@ +var Request = require('./Request'); + +var bodyRequired = ['project_id', 'mentor_id']; +var bodyValidations = { + 'project_id': ['integer', 'required'], + 'mentor_id': ['integer', 'required'], +}; + +function ProjectMentorRequest(headers, body) { + Request.call(this, headers, body); + + this.bodyRequired = bodyRequired; + this.bodyValidations = bodyValidations; +} + +ProjectMentorRequest.prototype = Object.create(Request.prototype); +ProjectMentorRequest.prototype.constructor = ProjectMentorRequest; + +module.exports = ProjectMentorRequest; \ No newline at end of file diff --git a/api/v1/requests/index.js b/api/v1/requests/index.js index 98a230e..3e46bed 100644 --- a/api/v1/requests/index.js +++ b/api/v1/requests/index.js @@ -3,6 +3,7 @@ module.exports = { BasicAuthRequest: require('./BasicAuthRequest'), MentorRequest: require('./MentorRequest'), ProjectRequest: require('./ProjectRequest'), + ProjectMentorRequest: require('./ProjectMentorRequest'), ResetTokenRequest: require('./ResetTokenRequest'), ResetPasswordRequest: require('./ResetPasswordRequest'), UploadRequest: require('./UploadRequest') diff --git a/api/v1/services/ProjectService.js b/api/v1/services/ProjectService.js index 66bd23d..52feb55 100644 --- a/api/v1/services/ProjectService.js +++ b/api/v1/services/ProjectService.js @@ -173,7 +173,7 @@ module.exports.getAllProjects = function (page, count, isPublished) { return Project .query(function (qb){ qb.groupBy('projects.id'); - qb.where('is_published', '=', String(isPublished)); + qb.where('is_published', '=', isPublished); }) .orderBy('-name') .fetchPage({ From 3b064c2e951c08857252a57a3abc298707ccca8e Mon Sep 17 00:00:00 2001 From: tommypacker Date: Fri, 25 Nov 2016 00:47:34 -0600 Subject: [PATCH 38/38] Rejected promises --- api/v1/controllers/ProjectController.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/v1/controllers/ProjectController.js b/api/v1/controllers/ProjectController.js index 3ac35d5..23e5fd2 100644 --- a/api/v1/controllers/ProjectController.js +++ b/api/v1/controllers/ProjectController.js @@ -17,17 +17,17 @@ function _validGetAllRequest(page, count, published){ if(_.isNaN(page)){ var message = "Invalid page parameter"; var source = "page"; - throw new errors.InvalidParameterError(message, source); + return _Promise.reject(new errors.InvalidParameterError(message, source)); } if(_.isNaN(count)){ var message = "Invalid count parameter"; var source = "count"; - throw new errors.InvalidParameterError(message, source); + return _Promise.reject(new errors.InvalidParameterError(message, source)); } if(_.isNaN(published) || (published != 0 && published != 1)){ var message = "Invalid published parameter"; var source = "published"; - throw new errors.InvalidParameterError(message, source); + return _Promise.reject(new errors.InvalidParameterError(message, source)); } return _Promise.resolve(true); }