diff --git a/backend/apis/nodejs/src/models/job_offer_model.js b/backend/apis/nodejs/src/models/job_offer_model.js index c4b9d6d..b2f5de8 100644 --- a/backend/apis/nodejs/src/models/job_offer_model.js +++ b/backend/apis/nodejs/src/models/job_offer_model.js @@ -14,19 +14,20 @@ const knex = require('../utils/knex_config'); const OrganizationAdmin = require('../models/organization_admin_model'); -async function insert(requester, organizationId, title, description, requirements, salary, salaryFrequency, salaryCurrency, location, tags) { +async function insert(requester, organizationId, title, description, salaryMin, salaryMax, salaryFrequency, salaryCurrency, location, remote, contractType, tags) { const isAdmin = await OrganizationAdmin.isAdmin(requester, organizationId); if (isAdmin) { return await knex.transaction(async (tr) => { const jobOffer = await tr('JobOffer').insert({ + organization_id: organizationId, title, description, - requirements, - salary, + salary: salaryMin != null ? knex.raw(`int4range('[${salaryMin}, ${salaryMax}]')`) : null, salary_frequency: salaryFrequency, + salary_currency: salaryCurrency, location, - organization_id: organizationId, - salary_currency: salaryCurrency + remote, + contract_type: contractType }) .returning('*'); diff --git a/backend/apis/nodejs/src/models/tags_model.js b/backend/apis/nodejs/src/models/tags_model.js index 3958565..d478eaf 100644 --- a/backend/apis/nodejs/src/models/tags_model.js +++ b/backend/apis/nodejs/src/models/tags_model.js @@ -13,6 +13,13 @@ const knex = require('../utils/knex_config'); +/** + * Given an array of strings, return an array of + * database tags + * + * @param {*} tags + * @returns + */ async function findByTags(tags) { if(!tags){ return null; diff --git a/backend/apis/nodejs/src/routes/job_offer_routes.js b/backend/apis/nodejs/src/routes/job_offer_routes.js index d9e303d..01734d9 100644 --- a/backend/apis/nodejs/src/routes/job_offer_routes.js +++ b/backend/apis/nodejs/src/routes/job_offer_routes.js @@ -35,16 +35,30 @@ async function insert(req, res) { } const tags = await Tag.findByTags(req.body.tags); + + let salaryMin = null; + let salaryMax = null; + if (req.body.salary != null && req.body.salary.length >= 1) { + salaryMin = req.body.salary[0]; + if (req.body.salary.length == 2) { + salaryMax = req.body.salary[1]; + } else { + salaryMax = salaryMin; + } + } + const insertedJobOffer = await JobOffer.insert( req.jwt.person_id, req.params.id, // organization id req.body.title, req.body.description, - req.body.requirements, - req.body.salary, - req.body.salary_frequency, - req.body.salary_currency, + salaryMin, + salaryMax, + req.body.salaryFrequency, + req.body.salaryCurrency, req.body.location, + req.body.remote, + req.body.contractType, tags); if (insertedJobOffer) { @@ -109,7 +123,7 @@ async function findByOrganizationId(req, res) { errors: errors.array() }); } - + const result = await JobOffer.findByOrganizationId(req.params.id); return res.status(200).send(result); } catch (error) { diff --git a/backend/apis/nodejs/src/routes/organization_admin_routes.js b/backend/apis/nodejs/src/routes/organization_admin_routes.js index 406ec8e..d3575e0 100644 --- a/backend/apis/nodejs/src/routes/organization_admin_routes.js +++ b/backend/apis/nodejs/src/routes/organization_admin_routes.js @@ -32,7 +32,7 @@ async function addOrganizationAdmin(req, res) { errors: errors.array() }); } - const success = await organizationAdmin.insert(req.body.person_id, req.params.organizationId, req.jwt.person_id); + const success = await organizationAdmin.insert(req.body.personId, req.params.organizationId, req.jwt.person_id); if (success) { return res.status(204).send(); } diff --git a/backend/apis/nodejs/src/utils/validators/job_offer_validator.js b/backend/apis/nodejs/src/utils/validators/job_offer_validator.js index 8988815..a18db13 100644 --- a/backend/apis/nodejs/src/utils/validators/job_offer_validator.js +++ b/backend/apis/nodejs/src/utils/validators/job_offer_validator.js @@ -24,23 +24,34 @@ const insertValidator = [ check('title').trim().notEmpty().escape().isLength({ max: 2048 }), - check('description').trim().escape().isLength({ + check('description').optional().trim().escape().isLength({ max: 4096 }), - check('requirements').trim().escape().isLength({ - max: 4096 - }), - check('salary').trim().notEmpty().escape().isCurrency(), - check('salary_frequency').trim().notEmpty().escape().isLength({ + check('salary').optional().isArray().withMessage('Salary must be an array').custom((value) => { + if (value.length < 1 || value.length > 2) { + throw new Error('Salary array must have between 1 and 2 elements'); + } + if (value !== null && !value.every((element) => typeof element === 'number')) { + throw new Error('Salary array elements must be numbers'); + } + return true; + }), + check('salaryFrequency').trim().notEmpty().escape().isLength({ max: 64 }), - check('salary_currency').trim().notEmpty().escape().isLength({ + check('salaryCurrency').optional().trim().notEmpty().escape().isLength({ max: 64 }), - check('location').trim().escape().isLength({ + check('location').optional().trim().escape().isLength({ max: 256 }), - check('tags').custom(tags => { + check('remote').optional().trim().escape().isLength({ + max: 256 + }), + check('contractType').trim().escape().isLength({ + max: 256 + }), + check('tags').optional().custom(tags => { if (!Array.isArray(tags)) { throw new Error('tags must be an array'); } diff --git a/backend/apis/nodejs/src/utils/validators/organization_admin_validator.js b/backend/apis/nodejs/src/utils/validators/organization_admin_validator.js index 522f881..d0eaac9 100644 --- a/backend/apis/nodejs/src/utils/validators/organization_admin_validator.js +++ b/backend/apis/nodejs/src/utils/validators/organization_admin_validator.js @@ -17,7 +17,7 @@ const { } = require("express-validator"); const addOrganizationAdminValidator = [ - check('person_id').trim().notEmpty().escape(), + check('personId').trim().notEmpty().escape(), check('organizationId').trim().notEmpty().escape() ]; diff --git a/backend/apis/nodejs/src/utils/validators/organization_validator.js b/backend/apis/nodejs/src/utils/validators/organization_validator.js index d38cddf..4206ebd 100644 --- a/backend/apis/nodejs/src/utils/validators/organization_validator.js +++ b/backend/apis/nodejs/src/utils/validators/organization_validator.js @@ -23,8 +23,7 @@ const createOrganizationValidator = [ check('location').trim().escape().isLength({ max: 256 }), - check('description').optional().trim().escape(), - check('isHiring').optional().isBoolean() + check('description').optional().trim().escape() ]; const updateOrganizationValidator = [ @@ -34,8 +33,7 @@ const updateOrganizationValidator = [ check('location').trim().escape().isLength({ max: 256 }), - check('description').optional().trim().escape(), - check('isHiring').optional().isBoolean() + check('description').optional().trim().escape() ]; const deleteOrGetOrganizationValidator = [ diff --git a/backend/sql/1-create_tables.sql b/backend/sql/1-create_tables.sql index 48662e2..11e8d8f 100644 --- a/backend/sql/1-create_tables.sql +++ b/backend/sql/1-create_tables.sql @@ -3,7 +3,7 @@ CREATE TYPE SalaryCurrency as ENUM ('EUR', 'USD', 'GBP'); CREATE TYPE RemotePosition as ENUM ('YES', 'NO', 'NOT_SPECIFIED', 'PARTIALLY'); CREATE TYPE ContractType as ENUM ('FULL-TIME','PART-TIME','INTERNSHIP','CONTRACT','FREELANCE','TEMPORARY','SEASONAL','APPRENTICESHIP','VOLUNTEER','ZERO-HOURS','FIXED-TERM','CASUAL','PROBATIONARY','SECONDMENT','JOB-SHARING'); CREATE TYPE ExperienceType as ENUM ('EDUCATION', 'WORK', 'VOLOUNTEER'); -CREATE TYPE SalaryFrequency as ENUM ('WEEKLY', 'YEARLY'); +CREATE TYPE SalaryFrequency as ENUM ('HOURLY', 'WEEKLY', 'YEARLY'); CREATE TYPE InfoType AS ENUM ('EMAIL', 'PHONE', 'WEBSITE', 'GIT', 'MASTODON', 'YOUTUBE', 'FACEBOOK', 'INSTAGRAM', 'OTHER'); -- Table: Person