mirror of https://github.com/xfarrow/blink
Compare commits
2 Commits
50a5f4f2c6
...
3d4299da6f
Author | SHA1 | Date |
---|---|---|
xfarrow | 3d4299da6f | |
xfarrow | 18d1babe33 |
|
@ -11,6 +11,7 @@
|
||||||
IN THE SOFTWARE.
|
IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
const knex = require('../utils/knex_config');
|
const knex = require('../utils/knex_config');
|
||||||
|
const OrganizationAdmin = require('../models/organization_admin_model');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a new JobApplication.
|
* Inserts a new JobApplication.
|
||||||
|
@ -29,26 +30,27 @@ async function insert(personId, jobOfferId) {
|
||||||
.returning("*");
|
.returning("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function userAlreadyApplicated(personId, jobOfferId){
|
async function userAlreadyApplicated(personId, jobOfferId) {
|
||||||
const person = await knex('JobApplication')
|
const person = await knex('JobApplication')
|
||||||
.where('person_id', personId)
|
.where('person_id', personId)
|
||||||
.where('job_offer_id', jobOfferId)
|
.where('job_offer_id', jobOfferId)
|
||||||
.select('*')
|
.select('*')
|
||||||
.first();
|
.first();
|
||||||
if(person){
|
if (person) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all the applications of the specified Person, includinf data from
|
* Retrieves all the applications of the specified Person, including data from
|
||||||
* JobOffer and Organization
|
* JobOffer and Organization. It is useful when a person wants to retrieve
|
||||||
|
* their applications
|
||||||
* @param {*} personId
|
* @param {*} personId
|
||||||
* @returns All the applications of the specified Person, throws an exception
|
* @returns All the applications of the specified Person, throws an exception
|
||||||
* otherwise
|
* otherwise
|
||||||
*/
|
*/
|
||||||
async function getApplications(personId){
|
async function getMyApplications(personId) {
|
||||||
return await knex('JobApplication')
|
return await knex('JobApplication')
|
||||||
.where('person_id', personId)
|
.where('person_id', personId)
|
||||||
.join('JobOffer', 'JobOffer.id', 'JobApplication.job_offer_id')
|
.join('JobOffer', 'JobOffer.id', 'JobApplication.job_offer_id')
|
||||||
|
@ -56,8 +58,95 @@ async function getApplications(personId){
|
||||||
.select('JobApplication.id', 'JobOffer.title', 'JobOffer.description', 'Organization.name', 'Organization.location');
|
.select('JobApplication.id', 'JobOffer.title', 'JobOffer.description', 'Organization.name', 'Organization.location');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all the applicants of the specified JobOffer
|
||||||
|
* under the form of a subset of properties of the Person
|
||||||
|
* object
|
||||||
|
*
|
||||||
|
* @param {*} organizationId
|
||||||
|
*/
|
||||||
|
async function getApplicantsByJobOffer(jobOfferId) {
|
||||||
|
return await knex('JobApplication')
|
||||||
|
.where('job_offer_id', jobOfferId)
|
||||||
|
.join('Person', 'Person.id', 'JobApplication.person_id')
|
||||||
|
.select('Person.id AS person_id', 'Person.display_name', 'Person.qualification', 'Person.about_me');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all the applicants and the relative JobOffer they
|
||||||
|
* applied to
|
||||||
|
*
|
||||||
|
* @param {*} organizationId
|
||||||
|
* @returns An array of Person and JobOffer objects. Throws an exception if an error occurs
|
||||||
|
*/
|
||||||
|
async function getApplicansByOrganization(organizationId) {
|
||||||
|
const applicants = await knex('JobApplication')
|
||||||
|
.join('Person', 'Person.id', 'JobApplication.person_id')
|
||||||
|
.join('JobOffer', 'JobOffer.id', 'JobApplication.job_offer_id')
|
||||||
|
.join('Organization', 'Organization.id', 'JobOffer.organization_id')
|
||||||
|
.where('Organization.id', organizationId)
|
||||||
|
.select('Person.id AS person_id', 'Person.display_name', 'Person.qualification', 'Person.about_me', 'JobOffer.id AS job_offer_id', 'JobOffer.title', 'JobOffer.description');
|
||||||
|
|
||||||
|
return applicants.map(applicant => ({
|
||||||
|
Person: {
|
||||||
|
person_id: applicant.person_id,
|
||||||
|
display_name: applicant.display_name,
|
||||||
|
qualification: applicant.qualification,
|
||||||
|
about_me: applicant.about_me,
|
||||||
|
},
|
||||||
|
JobOffer: {
|
||||||
|
job_offer_id: applicant.job_offer_id,
|
||||||
|
title: applicant.title,
|
||||||
|
description: applicant.description,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function find(jobApplicationId) {
|
||||||
|
return await knex('JobApplication')
|
||||||
|
.where('id', jobApplicationId)
|
||||||
|
.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(jobApplicationId) {
|
||||||
|
return await knex('JobApplication')
|
||||||
|
.where('id', jobApplicationId)
|
||||||
|
.del();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setStatus(jobApplicationId, status) {
|
||||||
|
return await knex('JobApplication')
|
||||||
|
.where('id', jobApplicationId)
|
||||||
|
.update('application_status', status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only Organization Administrators can change the status of a JobApplication
|
||||||
|
* @param {*} jobApplicationId
|
||||||
|
* @param {*} personId
|
||||||
|
*/
|
||||||
|
async function canPersonSetStatus(jobApplicationId, personId) {
|
||||||
|
const organization = await knex('JobApplication')
|
||||||
|
.where('JobApplication.id', jobApplicationId)
|
||||||
|
.join('JobOffer', 'JobOffer.id', 'JobApplication.job_offer_id')
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
insert,
|
insert,
|
||||||
userAlreadyApplicated,
|
userAlreadyApplicated,
|
||||||
getApplications
|
getMyApplications,
|
||||||
|
getApplicantsByJobOffer,
|
||||||
|
getApplicansByOrganization,
|
||||||
|
find,
|
||||||
|
remove,
|
||||||
|
setStatus,
|
||||||
|
canPersonSetStatus
|
||||||
}
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
// TODO: I don't like the routes. It should be /api/organizations/idOrganization/joboffers/idJobOffer/
|
||||||
|
// TODO: Create a validator
|
||||||
/*
|
/*
|
||||||
This code is part of Blink
|
This code is part of Blink
|
||||||
licensed under GPLv3
|
licensed under GPLv3
|
||||||
|
@ -13,16 +15,14 @@
|
||||||
|
|
||||||
const Application = require('../models/job_application_model');
|
const Application = require('../models/job_application_model');
|
||||||
const JobOffer = require('../models/job_offer_model');
|
const JobOffer = require('../models/job_offer_model');
|
||||||
|
const OrganizationAdmin = require('../models/organization_admin_model');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const jwtUtils = require('../utils/jwt_utils');
|
const jwtUtils = require('../utils/jwt_utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST Request
|
* **POST** Request
|
||||||
*
|
*
|
||||||
* Inserts a new job application
|
* Inserts a new job application
|
||||||
* @param {*} req
|
|
||||||
* @param {*} res
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
async function insert(req, res) {
|
async function insert(req, res) {
|
||||||
try {
|
try {
|
||||||
|
@ -35,7 +35,7 @@ async function insert(req, res) {
|
||||||
|
|
||||||
// Check if the user has already applied for this position
|
// Check if the user has already applied for this position
|
||||||
if (await Application.userAlreadyApplicated(req.jwt.person_id, req.body.jobOfferId)) {
|
if (await Application.userAlreadyApplicated(req.jwt.person_id, req.body.jobOfferId)) {
|
||||||
return res.status(401).json({
|
return res.status(400).json({
|
||||||
error: 'User has already applied to this job'
|
error: 'User has already applied to this job'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -52,17 +52,14 @@ async function insert(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET Request
|
* **GET** Request
|
||||||
*
|
*
|
||||||
* Retrieves all the job applications of the logged in user
|
* Retrieves all the job applications of the logged in user
|
||||||
* @param {*} req
|
|
||||||
* @param {*} res
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
async function myApplications(req, res) {
|
async function myApplications(req, res) {
|
||||||
try {
|
try {
|
||||||
const applications = await Application.getApplications(req.jwt.person_id);
|
const applications = await Application.getMyApplications(req.jwt.person_id);
|
||||||
return res.status(201).send(applications);
|
return res.status(200).json(applications);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error in function ${myApplications.name}: ${error}`);
|
console.error(`Error in function ${myApplications.name}: ${error}`);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
|
@ -71,9 +68,112 @@ async function myApplications(req, res) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **GET** Request
|
||||||
|
*
|
||||||
|
* Retrieve all the applicants who applicated to a job offer.
|
||||||
|
* Only an organization administrator is allowed to perform this action.
|
||||||
|
*/
|
||||||
|
async function getApplicantsByJobOffer(req, res) {
|
||||||
|
try {
|
||||||
|
const isAdmin = await OrganizationAdmin.isAdmin(req.jwt.person_id, req.body.organizationId);
|
||||||
|
if (!isAdmin) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'Forbidden'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const applicants = await Application.getApplicantsByJobOffer(req.body.jobOfferId);
|
||||||
|
return res.status(200).json(applicants);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in function ${getApplicantsByJobOffer.name}: ${error}`);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **GET** Request
|
||||||
|
*
|
||||||
|
* Retrieve all the applicants who applicated to a job offer created
|
||||||
|
* by the specific organization.
|
||||||
|
* Only an organization administrator is allowed to perform this action.
|
||||||
|
*/
|
||||||
|
async function getApplicantsByOrganization(req, res) {
|
||||||
|
try {
|
||||||
|
const isAdmin = await OrganizationAdmin.isAdmin(req.jwt.person_id, req.body.organizationId);
|
||||||
|
if (!isAdmin) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'Forbidden'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const applicants = await Application.getApplicansByOrganization(req.body.organizationId);
|
||||||
|
return res.status(200).json(applicants);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in function ${getApplicantsByOrganization.name}: ${error}`);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **DELETE** Request
|
||||||
|
*
|
||||||
|
* Removes a job application. Only the applicant is allowed to do that.
|
||||||
|
*/
|
||||||
|
async function remove(req, res) {
|
||||||
|
try {
|
||||||
|
const jobApplication = await Application.find(req.body.jobApplicationId);
|
||||||
|
if (jobApplication == null) {
|
||||||
|
return res.status(404).send();
|
||||||
|
}
|
||||||
|
if (jobApplication.person_id !== req.jwt.person_id) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'Forbidden'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await Application.remove(req.body.jobApplicationId);
|
||||||
|
return res.status(200).send();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in function ${remove.name}: ${error}`);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **PATCH** Request
|
||||||
|
*
|
||||||
|
* Sets a new status to a JobApplication. Only an Organization Administrator
|
||||||
|
* can perform this action
|
||||||
|
*/
|
||||||
|
async function setStatus(req, res) {
|
||||||
|
try {
|
||||||
|
const canPersonSetStatus = Application.canPersonSetStatus(req.body.jobApplication, req.jwt.person_id);
|
||||||
|
if (!canPersonSetStatus) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'Forbidden'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await Application.setStatus(req.body.jobApplication, req.body.status);
|
||||||
|
return res.status(204).send();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in function ${remove.name}: ${error}`);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const routes = express.Router();
|
const routes = express.Router();
|
||||||
routes.post('/', jwtUtils.extractToken, insert);
|
routes.post('/', jwtUtils.extractToken, insert);
|
||||||
routes.get('/myapplications', jwtUtils.extractToken, myApplications);
|
routes.get('/myapplications', jwtUtils.extractToken, myApplications);
|
||||||
|
routes.get('/applicantsbyjoboffer', jwtUtils.extractToken, getApplicantsByJobOffer);
|
||||||
|
routes.get('/applicantsbyorganization', jwtUtils.extractToken, getApplicantsByOrganization);
|
||||||
|
routes.delete('/', jwtUtils.extractToken, remove);
|
||||||
|
routes.patch('/', jwtUtils.extractToken, setStatus);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
routes
|
routes
|
||||||
|
|
|
@ -5,6 +5,7 @@ CREATE TYPE ContractType as ENUM ('FULL-TIME','PART-TIME','INTERNSHIP','CONTRACT
|
||||||
CREATE TYPE ExperienceType as ENUM ('EDUCATION', 'WORK', 'VOLOUNTEER');
|
CREATE TYPE ExperienceType as ENUM ('EDUCATION', 'WORK', 'VOLOUNTEER');
|
||||||
CREATE TYPE SalaryFrequency as ENUM ('HOURLY', 'WEEKLY', 'YEARLY');
|
CREATE TYPE SalaryFrequency as ENUM ('HOURLY', 'WEEKLY', 'YEARLY');
|
||||||
CREATE TYPE InfoType AS ENUM ('EMAIL', 'PHONE', 'WEBSITE', 'GIT', 'MASTODON', 'YOUTUBE', 'FACEBOOK', 'INSTAGRAM', 'OTHER');
|
CREATE TYPE InfoType AS ENUM ('EMAIL', 'PHONE', 'WEBSITE', 'GIT', 'MASTODON', 'YOUTUBE', 'FACEBOOK', 'INSTAGRAM', 'OTHER');
|
||||||
|
CREATE TYPE ApplicationStatus AS ENUM ('PENDING', 'ACCEPTED', 'REJECTED');
|
||||||
|
|
||||||
-- Table: Person
|
-- Table: Person
|
||||||
CREATE TABLE IF NOT EXISTS "Person" (
|
CREATE TABLE IF NOT EXISTS "Person" (
|
||||||
|
@ -81,6 +82,7 @@ CREATE TABLE IF NOT EXISTS "JobApplication" (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
person_id INTEGER NOT NULL,
|
person_id INTEGER NOT NULL,
|
||||||
job_offer_id INTEGER NOT NULL,
|
job_offer_id INTEGER NOT NULL,
|
||||||
|
application_status ApplicationStatus NOT NULL DEFAULT 'PENDING',
|
||||||
CONSTRAINT "PersonFk" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE,
|
CONSTRAINT "PersonFk" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
CONSTRAINT "JobOfferFk" FOREIGN KEY (job_offer_id) REFERENCES "JobOffer" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
|
CONSTRAINT "JobOfferFk" FOREIGN KEY (job_offer_id) REFERENCES "JobOffer" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 180 KiB |
Loading…
Reference in New Issue