diff --git a/backend/apis/nodejs/src/app.js b/backend/apis/nodejs/src/app.js index 927d386..f669499 100644 --- a/backend/apis/nodejs/src/app.js +++ b/backend/apis/nodejs/src/app.js @@ -12,14 +12,17 @@ */ // Importing modules +// TODO: clean up +require('dotenv').config(); const express = require('express'); const cors = require('cors'); const rateLimit = require('express-rate-limit'); -const apiController = require('./controllers/api_controller.js'); // todo refactor const personRoutes = require('./routes/person_routes.js'); const organizationRoutes = require('./routes/organization_routes.js'); +const organizationAdminRoutes = require('./routes/organization_admin_routes.js'); +const organizationPostRoutes = require('./routes/organization_post_routes.js') const jwt_utils = require('./utils/jwt_utils.js'); -require('dotenv').config(); + // Application configuration const app = express(); @@ -41,14 +44,14 @@ protectedRoutes.get('/person/myself', personRoutes.getMyself); protectedRoutes.get('/person/:id', personRoutes.getPerson); protectedRoutes.put('/person/:id', personRoutes.updatePerson); protectedRoutes.delete('/person/delete', personRoutes.deletePerson); -protectedRoutes.post('/organization/admin', apiController.addOrganizationAdmin); -protectedRoutes.delete('/organization/removeadmin', apiController.removeOrganizationAdmin); +protectedRoutes.post('/organization/admin', organizationAdminRoutes.addOrganizationAdmin); +protectedRoutes.delete('/organization/removeadmin', organizationAdminRoutes.removeOrganizationAdmin); protectedRoutes.post('/organization', organizationRoutes.createOrganization); protectedRoutes.get('/organization/:id', organizationRoutes.getOrganization); protectedRoutes.put('/organization/:id', organizationRoutes.updateOrganization); protectedRoutes.delete('/organization/:id', organizationRoutes.deleteOrganization); -protectedRoutes.post('/organization/post', apiController.createOrganizationPost); -protectedRoutes.delete('/organization/post/:id', apiController.deleteOrganizationPost); +protectedRoutes.post('/organization/post', organizationPostRoutes.createOrganizationPost); +protectedRoutes.delete('/organization/post/:id', organizationPostRoutes.deleteOrganizationPost); // Mounting routes app.use('/api', publicRoutes); // Routes not requiring token diff --git a/backend/apis/nodejs/src/routes/organization_admin_routes.js b/backend/apis/nodejs/src/routes/organization_admin_routes.js new file mode 100644 index 0000000..9f59c85 --- /dev/null +++ b/backend/apis/nodejs/src/routes/organization_admin_routes.js @@ -0,0 +1,107 @@ +/* + 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'); + +/** + * POST Method + * + * Add an Administrator to an Organization. Allowed only if the + * logged user is an Administrator themselves. + * + * Required field(s): organization_id, person_id + */ +async function addOrganizationAdmin(req, res){ + + // Ensure that the required fields are present before proceeding + if (!req.body.organization_id || !req.body.person_id) { + return res.status(400).json({ error : "Invalid request"}); + } + + try { + const isPersonAdmin = await knex('OrganizationAdministrator') + .where('id_person', req.jwt.person_id) + .where('id_organization', req.body.organization_id) + .select('*') + .first(); + + if(!isPersonAdmin){ + return res.status(401).json({error : "Forbidden"}); + } + + await knex('OrganizationAdministrator') + .insert({ + id_person: req.body.person_id, + id_organization: req.body.organization_id + }); + return res.status(200).json({success : true}); + } + catch (error) { + console.error('Error while adding organization admin: ' + error); + res.status(500).json({error : "Internal server error"}); + } + } + + /** + * DELETE Request + * + * Deletes a Person from the list of Administrators of an Organization. + * The logged user can only remove themselves. If no more Administrators + * are left, the Organization is removed. + * + * Required field(s): organization_id + */ + async function removeOrganizationAdmin(req, res){ + + // Ensure that the required fields are present before proceeding + if (!req.body.organization_id) { + return res.status(400).json({ error : "Invalid request"}); + } + + try{ + const transaction = await knex.transaction(); + + // We lock the table to ensure that we won't have concurrency issues + // while checking remainingAdministrators. + // TODO: Understand whether a lock on the table is necessary + await transaction.raw('LOCK TABLE "OrganizationAdministrator" IN SHARE MODE'); + + await transaction('OrganizationAdministrator') + .where('id_person', req.jwt.person_id) + .where('id_organization', req.body.organization_id) + .del(); + + // TODO: If the user instead deletes their entire profile, the organization will not be deleted. Fix. (database schema) + const remainingAdministrators = await transaction('OrganizationAdministrator') + .where({ id_organization: req.body.organization_id }); + + if (remainingAdministrators.length === 0) { + // If no more users, delete the organization + await transaction('Organization') + .where('id', req.body.organization_id) + .del(); + } + + await transaction.commit(); + return res.status(200).json({success : true}); + } + catch (error){ + console.error(error); + return res.status(500).json({ error: "Internal server error"}); + } + } + + module.exports = { + addOrganizationAdmin, + removeOrganizationAdmin +}; \ No newline at end of file diff --git a/backend/apis/nodejs/src/controllers/api_controller.js b/backend/apis/nodejs/src/routes/organization_post_routes.js similarity index 50% rename from backend/apis/nodejs/src/controllers/api_controller.js rename to backend/apis/nodejs/src/routes/organization_post_routes.js index d8e6f99..98ae01c 100644 --- a/backend/apis/nodejs/src/controllers/api_controller.js +++ b/backend/apis/nodejs/src/routes/organization_post_routes.js @@ -10,13 +10,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// todo this file shall be deleted -require('dotenv').config(); + const knex = require('../utils/knex_config'); -// ======== BEGIN API ENDPOINTS ======== - - /** +/** * POST Request * * Creates a Post belonging to an organization @@ -24,8 +21,8 @@ const knex = require('../utils/knex_config'); * Required field(s): organization_id, content * @returns the inserted Post */ - async function createOrganizationPost(req, res){ - +async function createOrganizationPost(req, res){ + // Ensure that the required fields are present before proceeding if (!req.body.organization_id || !req.body.content) { return res.status(400).json({ error : "Invalid request"}); @@ -55,7 +52,7 @@ const knex = require('../utils/knex_config'); return res.status(200).json(organizationPost[0]); } catch (error) { - console.log(error); + console.log("Error while creating Organization Post: " + error); return res.status(500).json({error : "Internal server error"}); } } @@ -97,102 +94,10 @@ async function deleteOrganizationPost(req, res){ } } -/** - * POST Method - * - * Add an Administrator to an Organization. Allowed only if the - * logged user is an Administrator themselves. - * - * Required field(s): organization_id, person_id - */ -async function addOrganizationAdmin(req, res){ - - // Ensure that the required fields are present before proceeding - if (!req.body.organization_id || !req.body.person_id) { - return res.status(400).json({ error : "Invalid request"}); - } - - try { - const isPersonAdmin = await knex('OrganizationAdministrator') - .where('id_person', req.jwt.person_id) - .where('id_organization', req.body.organization_id) - .select('*') - .first(); - - if(!isPersonAdmin){ - return res.status(401).json({error : "Forbidden"}); - } - - await knex('OrganizationAdministrator') - .insert({ - id_person: req.body.person_id, - id_organization: req.body.organization_id - }); - return res.status(200).json({success : true}); - } - catch (error) { - console.error('Error while adding organization admin: ' + error); - res.status(500).json({error : "Internal server error"}); - } -} - -/** - * DELETE Request - * - * Deletes a Person from the list of Administrators of an Organization. - * The logged user can only remove themselves. If no more Administrators - * are left, the Organization is removed. - * - * Required field(s): organization_id - */ -async function removeOrganizationAdmin(req, res){ - - // Ensure that the required fields are present before proceeding - if (!req.body.organization_id) { - return res.status(400).json({ error : "Invalid request"}); - } - - try{ - const transaction = await knex.transaction(); - - // We lock the table to ensure that we won't have concurrency issues - // while checking remainingAdministrators. - // TODO: Understand whether a lock on the table is necessary - await transaction.raw('LOCK TABLE "OrganizationAdministrator" IN SHARE MODE'); - - await transaction('OrganizationAdministrator') - .where('id_person', req.jwt.person_id) - .where('id_organization', req.body.organization_id) - .del(); - - // TODO: If the user instead deletes their entire profile, the organization will not be deleted. Fix. (database schema) - const remainingAdministrators = await transaction('OrganizationAdministrator') - .where({ id_organization: req.body.organization_id }); - - if (remainingAdministrators.length === 0) { - // If no more users, delete the organization - await transaction('Organization') - .where('id', req.body.organization_id) - .del(); - } - - await transaction.commit(); - return res.status(200).json({success : true}); - } - catch (error){ - console.error(error); - return res.status(500).json({ error: "Internal server error"}); - } -} - -// ======== END API ENDPOINTS ======== - // Exporting a function // means making a JavaScript function defined in one // module available for use in another module. module.exports = { createOrganizationPost, - deleteOrganizationPost, - addOrganizationAdmin, - removeOrganizationAdmin + deleteOrganizationPost }; \ No newline at end of file