diff --git a/backend/apis/nodejs/src/models/job_application_model.js b/backend/apis/nodejs/src/models/job_application_model.js index e067f61..945ebf0 100644 --- a/backend/apis/nodejs/src/models/job_application_model.js +++ b/backend/apis/nodejs/src/models/job_application_model.js @@ -55,13 +55,25 @@ async function getMyApplications(personId, organizationId) { .where('person_id', personId) .join('JobOffer', 'JobOffer.id', 'JobApplication.job_offer_id') .join('Organization', 'Organization.id', 'JobOffer.organization_id') - .select('JobApplication.id', 'JobOffer.title', 'JobOffer.description', 'Organization.name', 'Organization.location'); + .select('JobApplication.id', 'JobOffer.id AS JobOfferId', 'JobOffer.title', 'JobOffer.description', 'Organization.id AS OrganizationId', 'Organization.name', 'Organization.location'); if (organizationId != null) { query.where('Organization.id', organizationId); } - return await query; + return (await query).map(myApplication => ({ + id: myApplication.id, + JobOffer: { + id: myApplication.JobOfferId, + title: myApplication.title, + description: myApplication.description + }, + Organization: { + id: myApplication.OrganizationId, + name: myApplication.name, + location: myApplication.location + } + })); } /** @@ -85,7 +97,7 @@ async function getApplicantsByJobOffer(jobOfferId) { * @param {*} organizationId * @returns An array of Person and JobOffer objects. Throws an exception if an error occurs */ -async function getApplicansByOrganization(organizationId) { +async function getApplicantsByOrganization(organizationId) { const applicants = await knex('JobApplication') .join('Person', 'Person.id', 'JobApplication.person_id') .join('JobOffer', 'JobOffer.id', 'JobApplication.job_offer_id') @@ -127,11 +139,12 @@ async function setStatus(jobApplicationId, status) { } /** - * Only Organization Administrators can change the status of a JobApplication + * Determines whether the submitted personId represents an administrator + * of the company which created the joboffer of the current application * @param {*} jobApplicationId * @param {*} personId */ -async function canPersonSetStatus(jobApplicationId, personId) { +async function isPersonJobApplicationAdministrator(jobApplicationId, personId) { const organization = await knex('JobApplication') .where('JobApplication.id', jobApplicationId) .join('JobOffer', 'JobOffer.id', 'JobApplication.job_offer_id') @@ -150,9 +163,9 @@ module.exports = { userAlreadyApplicated, getMyApplications, getApplicantsByJobOffer, - getApplicansByOrganization, + getApplicantsByOrganization, find, remove, setStatus, - canPersonSetStatus + isPersonJobApplicationAdministrator } \ No newline at end of file diff --git a/backend/apis/nodejs/src/models/job_offer_model.js b/backend/apis/nodejs/src/models/job_offer_model.js index bf54ac6..d7da55f 100644 --- a/backend/apis/nodejs/src/models/job_offer_model.js +++ b/backend/apis/nodejs/src/models/job_offer_model.js @@ -97,6 +97,19 @@ async function findByOrganizationId(organizationId) { return result; } +async function isPersonJobOfferAdministrator(personId, jobOfferId) { + const organization = await knex('JobOffer') + .where('JobOffer.id', jobOfferId) + .join('Organization', 'Organization.id', 'JobOffer.organization_id') + .first() + .select('Organization.id'); + if (organization == null) { + return false; + } + const isAdmin = await OrganizationAdmin.isAdmin(personId, organization.id); + return isAdmin; +} + // test async function filter(title, description, requirements, salary, salaryOperator, salaryFrequency, location, tags) { let query = knex('JobOffer'); @@ -132,5 +145,6 @@ module.exports = { insert, remove, findByOrganizationId, - findById + findById, + isPersonJobOfferAdministrator } \ No newline at end of file diff --git a/backend/apis/nodejs/src/routes/job_application_routes.js b/backend/apis/nodejs/src/routes/job_application_routes.js index 8fd7794..8a2519d 100644 --- a/backend/apis/nodejs/src/routes/job_application_routes.js +++ b/backend/apis/nodejs/src/routes/job_application_routes.js @@ -17,6 +17,7 @@ const JobOffer = require('../models/job_offer_model'); const OrganizationAdmin = require('../models/organization_admin_model'); const express = require('express'); const jwtUtils = require('../utils/jwt_utils'); +const jobApplicationValidator = require('../utils/validators/job_application_validator'); /** * **POST** Request @@ -25,6 +26,13 @@ const jwtUtils = require('../utils/jwt_utils'); */ async function insert(req, res) { try { + const errors = jobApplicationValidator.validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }); + } + // Check if the job offer exists if (await JobOffer.findById(req.params.idJobOffer) == null) { return res.status(404).json({ @@ -50,7 +58,6 @@ async function insert(req, res) { } } -// TODO /** * **GET** Request * @@ -60,13 +67,32 @@ async function insert(req, res) { * @param {*} req * @param {*} res */ -async function find(req, res){ +async function find(req, res) { try { + const errors = jobApplicationValidator.validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }); + } + const jobApplication = await Application.find(req.params.idApplication); if (jobApplication == null) { return res.status(404).send(); } - + // Case in which the user themselves requested it + if (jobApplication.person_id == req.jwt.person_id) { + return res.status(200).json(jobApplication) + } else { + const isPersonOrganizationAdmin = await Application.isPersonJobApplicationAdministrator(jobApplication.id, req.jwt.person_id); + if (isPersonOrganizationAdmin === true) { + return res.status(200).json(jobApplication) + } else { + return res.status(401).json({ + error: 'Forbidden' + }); + } + } } catch (error) { console.error(`Error in function ${find.name}: ${error}`); res.status(500).json({ @@ -82,6 +108,13 @@ async function find(req, res){ */ async function myApplications(req, res) { try { + const errors = jobApplicationValidator.validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }); + } + const applications = await Application.getMyApplications(req.jwt.person_id, req.body.organizationId); return res.status(200).json(applications); } catch (error) { @@ -100,7 +133,14 @@ async function myApplications(req, res) { */ async function getApplicationsByJobOffer(req, res) { try { - const isAdmin = await OrganizationAdmin.isAdmin(req.jwt.person_id, req.params.idJobOffer); //todo error! It's not idJobOffer + const errors = jobApplicationValidator.validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + errors: errors.array() + }); + } + + const isAdmin = await JobOffer.isPersonJobOfferAdministrator(req.jwt.person_id, req.params.idJobOffer); if (!isAdmin) { return res.status(401).json({ error: 'Forbidden' @@ -131,7 +171,7 @@ async function getApplicationsByOrganization(req, res) { error: 'Forbidden' }); } - const applicants = await Application.getApplicansByOrganization(req.params.idOrganization); + const applicants = await Application.getApplicantsByOrganization(req.params.idOrganization); return res.status(200).json(applicants); } catch (error) { console.error(`Error in function ${getApplicationsByOrganization.name}: ${error}`); @@ -175,7 +215,7 @@ async function remove(req, res) { */ async function setStatus(req, res) { try { - const canPersonSetStatus = await Application.canPersonSetStatus(req.params.idApplication, req.jwt.person_id); + const canPersonSetStatus = await Application.isPersonJobApplicationAdministrator(req.params.idApplication, req.jwt.person_id); if (!canPersonSetStatus) { return res.status(401).json({ error: 'Forbidden' @@ -192,7 +232,7 @@ async function setStatus(req, res) { } const routes = express.Router(); -routes.post('/joboffers/:idJobOffer/applications', jwtUtils.extractToken, insert); +routes.post('/joboffers/:idJobOffer/applications', jwtUtils.extractToken, jobApplicationValidator.insertValidator, insert); routes.get('/joboffers/:idJobOffer/applications/:idApplication', jwtUtils.extractToken, find); routes.get('/joboffers/applications/mine', jwtUtils.extractToken, myApplications); routes.get('/joboffers/:idJobOffer/applications', jwtUtils.extractToken, getApplicationsByJobOffer); diff --git a/backend/apis/nodejs/src/utils/validators/job_application_validator.js b/backend/apis/nodejs/src/utils/validators/job_application_validator.js new file mode 100644 index 0000000..38276bf --- /dev/null +++ b/backend/apis/nodejs/src/utils/validators/job_application_validator.js @@ -0,0 +1,45 @@ +/* + This code is part of Blink + licensed under GPLv3 + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ + +const { + check, + validationResult + } = require("express-validator"); // This is the destructuring part. It specifies which properties of the imported object (express-validator) you want to extract. + + const { + escape +} = require('validator'); + +const insertValidator = [ + check('idJobOffer').trim().escape().isInt() +]; + +const findValidator = [ + check('idApplication').trim().escape().isInt() +]; + +const myApplicationsValidator = [ + check('organizationId').optional().trim().escape().isInt() +]; + +const getApplicationsByJobOfferValidator = [ + check('idJobOffer').optional().trim().escape().isInt() +]; + +module.exports = { + insertValidator, + findValidator, + myApplicationsValidator, + getApplicationsByJobOfferValidator, + validationResult +} \ No newline at end of file