-
Notifications
You must be signed in to change notification settings - Fork 56
feat(PM-1168): Added API to assign an opportunity with an applicant #805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
aeb7b48
af846ab
709601f
4543e5e
9f7dc48
7f40cdb
8e2881d
2ee4f9b
2fd1652
f181533
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
module.exports = { | ||
up: async (queryInterface, Sequelize) => { | ||
await queryInterface.addColumn('copilot_applications', 'status', { | ||
type: Sequelize.STRING(16), | ||
allowNull: true, | ||
hentrymartin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
|
||
await queryInterface.sequelize.query( | ||
'UPDATE copilot_applications SET status = \'pending\' WHERE status IS NULL', | ||
); | ||
|
||
await queryInterface.changeColumn('copilot_applications', 'status', { | ||
type: Sequelize.STRING(16), | ||
allowNull: false, | ||
}); | ||
}, | ||
|
||
down: async (queryInterface) => { | ||
await queryInterface.removeColumn('copilot_applications', 'status'); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import _ from 'lodash'; | ||
import validate from 'express-validation'; | ||
import Joi from 'joi'; | ||
|
||
import models from '../../models'; | ||
import util from '../../util'; | ||
import { PERMISSION } from '../../permissions/constants'; | ||
import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS } from '../../constants'; | ||
|
||
const assignCopilotOpportunityValidations = { | ||
body: Joi.object().keys({ | ||
applicationId: Joi.string(), | ||
}), | ||
}; | ||
|
||
module.exports = [ | ||
validate(assignCopilotOpportunityValidations), | ||
async (req, res, next) => { | ||
const { applicationId } = req.body; | ||
const copilotOpportunityId = _.parseInt(req.params.id); | ||
if (!util.hasPermissionByReq(PERMISSION.ASSIGN_COPILOT_OPPORTUNITY, req)) { | ||
const err = new Error('Unable to assign copilot opportunity'); | ||
_.assign(err, { | ||
details: JSON.stringify({ message: 'You do not have permission to assign a copilot opportunity' }), | ||
status: 403, | ||
}); | ||
return next(err); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||
} | ||
|
||
return models.sequelize.transaction(async (t) => { | ||
const opportunity = await models.CopilotOpportunity.findOne({ | ||
where: { id: copilotOpportunityId }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding error handling for the database transaction itself to ensure that any issues during the transaction are properly caught and handled. |
||
transaction: t, | ||
}); | ||
|
||
if (!opportunity) { | ||
const err = new Error('No opportunity found'); | ||
err.status = 404; | ||
throw err; | ||
} | ||
|
||
if (opportunity.status !== COPILOT_OPPORTUNITY_STATUS.ACTIVE) { | ||
const err = new Error('Opportunity is not active'); | ||
err.status = 400; | ||
throw err; | ||
} | ||
|
||
const application = await models.CopilotApplication.findOne({ | ||
where: { id: applicationId }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
transaction: t, | ||
}); | ||
|
||
if (!application) { | ||
const err = new Error('No such application available'); | ||
err.status = 400; | ||
throw err; | ||
} | ||
|
||
if (application.status === COPILOT_APPLICATION_STATUS.ACCEPTED) { | ||
const err = new Error('Application already accepted'); | ||
err.status = 400; | ||
throw err; | ||
} | ||
|
||
const projectId = opportunity.projectId; | ||
const userId = application.userId; | ||
const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId); | ||
|
||
const existingUser = activeMembers.find(item => item.userId === userId); | ||
if (existingUser && existingUser.role === 'copilot') { | ||
const err = new Error(`User is already a copilot of this project`); | ||
err.status = 400; | ||
throw err; | ||
} | ||
|
||
await models.CopilotRequest.update( | ||
{ status: COPILOT_REQUEST_STATUS.FULFILLED }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that the |
||
{ where: { id: opportunity.copilotRequestId }, transaction: t }, | ||
); | ||
|
||
await opportunity.update( | ||
{ status: COPILOT_OPPORTUNITY_STATUS.COMPLETED }, | ||
{ transaction: t }, | ||
); | ||
|
||
await models.CopilotApplication.update( | ||
{ status: COPILOT_APPLICATION_STATUS.ACCEPTED }, | ||
{ where: { id: applicationId }, transaction: t }, | ||
); | ||
|
||
hentrymartin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
res.status(200).send({ id: applicationId }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding a final |
||
}).catch(err => next(err)); | ||
}, | ||
]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,16 +63,16 @@ module.exports = [ | |
res.status(200).json(existingApplication); | ||
return Promise.resolve(); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unnecessary whitespace change to keep the code clean and consistent. |
||
return models.CopilotApplication.create(data) | ||
.then((result) => { | ||
res.status(201).json(result); | ||
return Promise.resolve(); | ||
}) | ||
.catch((err) => { | ||
util.handleError('Error creating copilot application', err, req, next); | ||
return next(err); | ||
}); | ||
util.handleError('Error creating copilot application', err, req, next); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure consistent indentation for the |
||
return next(err); | ||
}); | ||
}).catch((e) => { | ||
util.handleError('Error applying for copilot opportunity', e, req, next); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -199,7 +199,7 @@ const buildCreateInvitePromises = (req, inviteEmails, inviteUserIds, invites, da | |
}; | ||
|
||
const sendInviteEmail = (req, projectId, invite) => { | ||
req.log.debug(`Sending invite email: ${JSON.stringify(req.body)}, ${projectId}, ${JSON.stringify(invite)}`) | ||
req.log.debug(`Sending invite email: ${JSON.stringify(req.body)}, ${projectId}, ${JSON.stringify(invite)}`); | ||
req.log.debug(req.authUser); | ||
const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; | ||
const promises = [ | ||
|
@@ -295,13 +295,12 @@ module.exports = [ | |
// whom we are inviting, because Member Service has a loose search logic and may return | ||
// users with handles whom we didn't search for | ||
.then((foundUsers) => { | ||
if(invite.handles) { | ||
if (invite.handles) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be a space between |
||
const lowerCaseHandles = invite.handles.map(handle => handle.toLowerCase()); | ||
return foundUsers.filter(foundUser => _.includes(lowerCaseHandles, foundUser.handleLower)); | ||
} | ||
else { | ||
return [] | ||
} | ||
|
||
return []; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure consistent use of semicolons. The semicolon at the end of the |
||
}) | ||
.then((inviteUsers) => { | ||
const members = req.context.currentProjectMembers; | ||
|
@@ -414,7 +413,7 @@ module.exports = [ | |
RESOURCES.PROJECT_MEMBER_INVITE, | ||
v.toJSON()); | ||
|
||
req.log.debug(`V: ${JSON.stringify(v)}`) | ||
req.log.debug(`V: ${JSON.stringify(v)}`); | ||
// send email invite (async) | ||
if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { | ||
sendInviteEmail(req, projectId, v); | ||
|
@@ -443,7 +442,7 @@ module.exports = [ | |
} | ||
}); | ||
}).catch((err) => { | ||
console.log(err) | ||
console.log(err); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
if (failed.length) { | ||
res.status(403).json(_.assign({}, { success: [] }, { failed })); | ||
} else next(err); | ||
|
Uh oh!
There was an error while loading. Please reload this page.