From 7fe83ee2e3df8edfb140bd137c78d7d1c18ae590 Mon Sep 17 00:00:00 2001 From: xfarrow Date: Thu, 22 Feb 2024 11:31:38 +0100 Subject: [PATCH] create organization_model, code refactoring --- .../nodejs/src/models/organization_model.js | 147 +++++++++ .../apis/nodejs/src/models/person_model.js | 4 +- .../nodejs/src/routes/organization_routes.js | 306 +++++++----------- .../apis/nodejs/src/routes/person_routes.js | 2 +- 4 files changed, 263 insertions(+), 196 deletions(-) create mode 100644 backend/apis/nodejs/src/models/organization_model.js diff --git a/backend/apis/nodejs/src/models/organization_model.js b/backend/apis/nodejs/src/models/organization_model.js new file mode 100644 index 0000000..0574565 --- /dev/null +++ b/backend/apis/nodejs/src/models/organization_model.js @@ -0,0 +1,147 @@ +/* + 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 knex = require('../utils/knex_config'); + +/** + * Create Organization object + * @param {*} name + * @param {*} location + * @param {*} description + * @param {*} is_hiring + * @returns + */ +function organization(name, location, description, is_hiring){ + const organization = { + name: name, + location: location, + description: description, + is_hiring: is_hiring + }; + return organization; +} + +/** + * Gets an Organization by its identifier + * @param {*} id + * @returns + */ +async function getOrganizationById(id){ + const organization = await knex('Organization') + .where('id', id) + .select('*') + .first(); + return organization; +} + +/** + * Insert an Organization and its relative Administrator + * @param {*} organization + */ +async function insertOrganization(organization, organizationAdministratorId){ + await knex.transaction(async (trx) => { + // We have to insert either both in Organization and in OrganizationAdministrator + // or in neither + const organizationResult = await trx('Organization') + .insert(organization, '*'); + + // Inserting in the "OrganizationAdministrator" table + await trx('OrganizationAdministrator') + .insert({ + id_person: organizationAdministratorId, + id_organization: organizationResult[0].id, + }); + }); +} + +/** + * Updates an Organization specified by the OrganizationId, if and + * only if the specified personId is one of its Administrator + * @param {*} organization + * @param {*} organizationId + * @param {*} personId + * @returns true if the row was updated, false otherwise + */ +async function updateOrganizationIfAdministrator(organization, organizationId, personId){ + // // const isOrganizationAdmin = await knex('OrganizationAdministrator') + // // .where('id_person', req.jwt.person_id) + // // .where('id_organization', req.params.id) + // // .select('*') + // // .first(); + + // // // This introduces a Time of check Time of use weakeness + // // // which could'have been fixed by either + // // // 1) Using "whereExists", thanks to the "it's easier to ask for + // // // forgiveness than for permission" padarigm. Or, + // // // 2) Using a serializable transaction. + // // // + // // // The undersigned chose not to follow these approaches because + // // // this does not introduces any serious vulnerability. In this + // // // way it seems more readable. + + // // if(!isOrganizationAdmin){ + // // return res.status(403).json({error : "Forbidden"}); + // // } + + // // await knex('Organization') + // // .where('id', req.params.id) + // // .update({ + // // name: req.body.name, + // // location: req.body.location, + // // description: req.body.description, + // // is_hiring: req.body.is_hiring + // // }); + + const numberOfUpdatedRows = await knex('Organization') + .where('id', organizationId) + .whereExists(function(){ + this.select('*') + .from('OrganizationAdministrator') + .where('id_person', personId) + .where('id_organization', organizationId) + }) + .update(organization); + return numberOfUpdatedRows == 1; +} + +/** + * Deletes an Organization if the specified PersonId is + * one of its administrators + * @param {*} organizationId Id of the Organization to delete + * @param {*} personId PersonId of the supposedly administrator + * @returns true if the Organization was successfully deleted, false otherwise + */ +async function deleteOrganizationIfAdmin(organizationId, personId){ + const numberOfDeletedRows = await knex('Organization') + .where({ id: organizationId }) + .whereExists(function(){ + this.select('*') + .from('OrganizationAdministrator') + .where('id_person', personId) + .where('id_organization', organizationId) + }) + .del(); + return numberOfDeletedRows == 1; +} + +// Exporting a function +// means making a JavaScript function defined in one +// module available for use in another module. +module.exports = { + getOrganizationById, + organization, + insertOrganization, + updateOrganizationIfAdministrator, + updateOrganizationIfAdministrator, + deleteOrganizationIfAdmin +}; \ No newline at end of file diff --git a/backend/apis/nodejs/src/models/person_model.js b/backend/apis/nodejs/src/models/person_model.js index f703a6e..1453142 100644 --- a/backend/apis/nodejs/src/models/person_model.js +++ b/backend/apis/nodejs/src/models/person_model.js @@ -25,7 +25,7 @@ const bcrypt = require('bcrypt'); * @param {*} place_of_living * @returns */ -function createPerson(email, password, display_name, date_of_birth, available, enabled, place_of_living) { +function person(email, password, display_name, date_of_birth, available, enabled, place_of_living) { const person = { email: email.toLowerCase(), password: password, @@ -139,7 +139,7 @@ async function deletePerson(person_id){ // means making a JavaScript function defined in one // module available for use in another module. module.exports = { - createPerson, + person, getPersonByEmail, getPersonById, getPersonByEmailAndPassword, diff --git a/backend/apis/nodejs/src/routes/organization_routes.js b/backend/apis/nodejs/src/routes/organization_routes.js index ed380a1..3f64f42 100644 --- a/backend/apis/nodejs/src/routes/organization_routes.js +++ b/backend/apis/nodejs/src/routes/organization_routes.js @@ -11,7 +11,7 @@ IN THE SOFTWARE. */ -const knex = require('../utils/knex_config'); +const organization_model = require('../models/organization_model'); /** * POST Request @@ -23,199 +23,119 @@ const knex = require('../utils/knex_config'); * @returns the inserted organization */ async function createOrganization(req, res){ - - // Ensure that the required fields are present before proceeding - if (!req.body.name) { - return res.status(400).json({ error : "Invalid request"}); - } - - try{ - const insertedOrganization = await knex.transaction(async (trx) => { - - // We have to insert either both in Organization and in OrganizationAdministrator - // or in neither - const organizationResult = await trx('Organization') - .insert({ - name: req.body.name, - location: req.body.location, - description: req.body.description, - is_hiring: req.body.is_hiring, - }, '*'); - - // Inserting in the "OrganizationAdministrator" table - await trx('OrganizationAdministrator') - .insert({ - id_person: req.jwt.person_id, - id_organization: organizationResult[0].id, - }); - return organizationResult[0]; - }); - return res.status(200).json({ Organization: insertedOrganization }); - } - catch (error){ - console.error('Error creating Organization:', error); - res.status(500).json({error : "Internal server error"}); - } - } - - /** - * PUT Request - * Updates an Organization's details - * - * Required field(s): none. - */ - async function updateOrganization(req, res){ - - const updateOrganization = {}; - - if(req.body.name){ - updateOrganization.name = req.body.name; - } - - if(req.body.location){ - updateOrganization.location = req.body.location; - } - - if(req.body.description){ - updateOrganization.description = req.body.description; - } - - if(req.body.is_hiring){ - updateOrganization.is_hiring = req.body.is_hiring; - } - - if (Object.keys(updateOrganization).length === 0) { - return res.status(400).json({ error : "Bad request. No data to update"}); - } - - try { - - // // const isOrganizationAdmin = await knex('OrganizationAdministrator') - // // .where('id_person', req.jwt.person_id) - // // .where('id_organization', req.params.id) - // // .select('*') - // // .first(); - - // // // This introduces a Time of check Time of use weakeness - // // // which could'have been fixed by either - // // // 1) Using "whereExists", thanks to the "it's easier to ask for - // // // forgiveness than for permission" padarigm. Or, - // // // 2) Using a serializable transaction. - // // // - // // // The undersigned chose not to follow these approaches because - // // // this does not introduces any serious vulnerability. In this - // // // way it seems more readable. - - // // if(!isOrganizationAdmin){ - // // return res.status(403).json({error : "Forbidden"}); - // // } - - // // await knex('Organization') - // // .where('id', req.params.id) - // // .update({ - // // name: req.body.name, - // // location: req.body.location, - // // description: req.body.description, - // // is_hiring: req.body.is_hiring - // // }); - - const updatedRows = await knex('Organization') - .where('id', req.params.id) - .whereExists(function(){ - this.select('*') - .from('OrganizationAdministrator') - .where('id_person', req.jwt.person_id) - .where('id_organization', req.params.id) - }) - .update({ - name: req.body.name, - location: req.body.location, - description: req.body.description, - is_hiring: req.body.is_hiring - }); - - if(updatedRows == 1){ - return res.status(200).json({ success : "true"}); - } - else{ - return res.status(404).json({error : "Organization either not found or insufficient permissions"}); - } - } - catch (error) { - console.log(error); - return res.status(500).json({error : "Internal server error"}); - } - } - - /** - * DELETE Request - * - * Deletes the specified organization if the logged user is - * one of its administrator - */ - async function deleteOrganization(req, res){ - const organizationIdToDelete = req.params.id; - - try { - - // Delete organization if admin - const deletedRows = await knex('Organization') - .where({ id: organizationIdToDelete }) - .whereExists(function(){ - this.select('*') - .from('OrganizationAdministrator') - .where('id_person', req.jwt.person_id) - .where('id_organization', organizationIdToDelete) - }) - .del(); - - if(deletedRows == 0){ - return res.status(403).json({error: "Forbidden"}); - } - else{ - return res.status(200).json({success: true}); - } - - } - catch (error) { - console.error(error); - return res.status(500).json({error : "Internal server error"}); - } - } - - /** - * GET Request - * - * Obtains an organization by its identifier. - * - * Required field(s): none. - * - * @returns the organization. - */ - async function getOrganization(req, res){ - const organizationId = req.params.id; - try { - const organization = await knex('Organization') - .where('id', organizationId) - .select('*') - .first(); - if(organization) { - return res.status(200).json(organization); - } - else{ - return res.status(404).json({error : "Not found"}); - } - } - catch (error) { - console.error("Error retrieving an organization: " + error); - return res.status(500).json({error : "Internal server error"}); - } + + // Ensure that the required fields are present before proceeding + if (!req.body.name) { + return res.status(400).json({ error : "Invalid request"}); } - module.exports = { - createOrganization, - getOrganization, - updateOrganization, - deleteOrganization + try{ + const organization = organization_model.organization(req.body.name, req.body.location, req.body.description, req.body.is_hiring); + await organization_model.insertOrganization(organization, req.jwt.person_id); + return res.status(200).json({ Organization: organization }); + } + catch (error){ + console.error('Error creating Organization:', error); + res.status(500).json({error : "Internal server error"}); + } +} + +/** + * PUT Request + * Updates an Organization's details + * + * Required field(s): none. + */ +async function updateOrganization(req, res){ + + const updateOrganization = {}; + + if(req.body.name){ + updateOrganization.name = req.body.name; + } + + if(req.body.location){ + updateOrganization.location = req.body.location; + } + + if(req.body.description){ + updateOrganization.description = req.body.description; + } + + if(req.body.is_hiring){ + updateOrganization.is_hiring = req.body.is_hiring; + } + + if (Object.keys(updateOrganization).length === 0) { + return res.status(400).json({ error : "Bad request. No data to update"}); + } + + try { + const isUpdateSuccessful = organization_model.updateOrganizationIfAdministrator(updateOrganization, req.params.id, req.jwt.person_id); + if(isUpdateSuccessful){ + return res.status(200).json({ success : "true"}); + } + else{ + return res.status(404).json({error : "Organization either not found or insufficient permissions"}); + } + } + catch (error) { + console.log(error); + return res.status(500).json({error : "Internal server error"}); + } +} + +/** + * DELETE Request + * + * Deletes the specified organization if the logged user is + * one of its administrator + */ +async function deleteOrganization(req, res){ + try { + const isDeleteSuccessful = organization_model.deleteOrganizationIfAdmin(req.params.id, req.jwt.person_id); + if(isDeleteSuccessful){ + return res.status(403).json({error: "Forbidden"}); + } + else{ + return res.status(200).json({success: true}); + } + } + catch (error) { + console.error(error); + return res.status(500).json({error : "Internal server error"}); + } +} + +/** + * GET Request + * + * Obtains an organization by its identifier. + * + * Required field(s): none. + * + * @returns the organization. + */ +async function getOrganization(req, res){ + try { + const organization = await organization_model.getOrganizationById(req.params.id); + if(organization) { + return res.status(200).json(organization); + } + else{ + return res.status(404).json({error : "Not found"}); + } + } + catch (error) { + console.error("Error retrieving an organization: " + error); + return res.status(500).json({error : "Internal server error"}); + } +} + +module.exports = { + createOrganization, + getOrganization, + updateOrganization, + deleteOrganization }; diff --git a/backend/apis/nodejs/src/routes/person_routes.js b/backend/apis/nodejs/src/routes/person_routes.js index e0a17de..cd6f270 100644 --- a/backend/apis/nodejs/src/routes/person_routes.js +++ b/backend/apis/nodejs/src/routes/person_routes.js @@ -52,7 +52,7 @@ async function registerPerson(req, res){ if(existingUser){ return res.status(409).json({ error: "E-mail already in use" }); } - const personToInsert = person_model.createPerson( + const personToInsert = person_model.person( req.body.email, await hashPasswordPromise, req.body.display_name,