beautified

This commit is contained in:
xfarrow 2024-03-04 16:49:36 +01:00
parent 3ea41c82d4
commit d9c3f6f55a
18 changed files with 427 additions and 300 deletions

View File

@ -38,7 +38,9 @@ app.use(cors()); // Enable CORS for all routes
app.use(rateLimit({ app.use(rateLimit({
windowMs: process.env.LIMITER_WINDOW, windowMs: process.env.LIMITER_WINDOW,
max: process.env.LIMITER_MAXIMUM_PER_WINDOW, max: process.env.LIMITER_MAXIMUM_PER_WINDOW,
message: { error: 'Too many requests from this IP, please try again later' } message: {
error: 'Too many requests from this IP, please try again later'
}
})); // Apply the rate limiter middleware to all routes })); // Apply the rate limiter middleware to all routes
/* /*
@ -72,4 +74,4 @@ if (process.argv[2] != 'testing') {
} }
// Export the app for testing purposes // Export the app for testing purposes
module.exports = app; module.exports = app;

View File

@ -19,11 +19,11 @@ const knex = require('../utils/knex_config');
* @returns The Person's ID associated with the identifier if present, * @returns The Person's ID associated with the identifier if present,
* null otherwise * null otherwise
*/ */
async function getPersonIdByIdentifier (identifier){ async function getPersonIdByIdentifier(identifier) {
const tuple = await knex('ActivationLink') const tuple = await knex('ActivationLink')
.where('identifier', identifier) .where('identifier', identifier)
.first(); .first();
if(tuple){ if (tuple) {
return tuple.person_id; return tuple.person_id;
} }
return null; return null;

View File

@ -20,7 +20,7 @@ const knex = require('../utils/knex_config');
* @param {*} organizationId * @param {*} organizationId
* @returns true if administrator, false otherwise * @returns true if administrator, false otherwise
*/ */
async function isPersonOrganizationAdministrator (personId, organizationId) { async function isPersonOrganizationAdministrator(personId, organizationId) {
const isPersonAdmin = await knex('OrganizationAdministrator') const isPersonAdmin = await knex('OrganizationAdministrator')
.where('id_person', personId) .where('id_person', personId)
.where('id_organization', organizationId) .where('id_organization', organizationId)
@ -36,15 +36,15 @@ async function isPersonOrganizationAdministrator (personId, organizationId) {
* @param {*} organizationId * @param {*} organizationId
* @param {*} requester Id of the person requesting the addition * @param {*} requester Id of the person requesting the addition
*/ */
async function addOrganizationAdministrator (personId, organizationId, requester) { async function addOrganizationAdministrator(personId, organizationId, requester) {
const isPersonAdmin = await organization_admin_model.isPersonAdmin(requester, organizationId); const isPersonAdmin = await organization_admin_model.isPersonAdmin(requester, organizationId);
if(isPersonAdmin){ if (isPersonAdmin) {
await knex('OrganizationAdministrator') await knex('OrganizationAdministrator')
.insert({ .insert({
id_person: personId, id_person: personId,
id_organization: organizationId id_organization: organizationId
}); });
return true; return true;
} }
return false; return false;
@ -56,7 +56,7 @@ async function addOrganizationAdministrator (personId, organizationId, requester
* @param {*} personId * @param {*} personId
* @param {*} organizationId * @param {*} organizationId
*/ */
async function removeOrganizationAdmin (personId, organizationId) { async function removeOrganizationAdmin(personId, organizationId) {
const transaction = await knex.transaction(); const transaction = await knex.transaction();
// We lock the table to ensure that we won't have concurrency issues // We lock the table to ensure that we won't have concurrency issues
@ -71,7 +71,9 @@ async function removeOrganizationAdmin (personId, organizationId) {
// TODO: If the user instead deletes their entire profile, the organization will not be deleted. Fix. (database schema) // TODO: If the user instead deletes their entire profile, the organization will not be deleted. Fix. (database schema)
const remainingAdministrators = await transaction('OrganizationAdministrator') const remainingAdministrators = await transaction('OrganizationAdministrator')
.where({ id_organization: organizationId }); .where({
id_organization: organizationId
});
if (remainingAdministrators.length === 0) { if (remainingAdministrators.length === 0) {
// If no more users, delete the organization // If no more users, delete the organization
@ -87,4 +89,4 @@ module.exports = {
isPersonOrganizationAdministrator, isPersonOrganizationAdministrator,
addOrganizationAdministrator, addOrganizationAdministrator,
removeOrganizationAdmin removeOrganizationAdmin
}; };

View File

@ -21,7 +21,7 @@ const knex = require('../utils/knex_config');
* @param {*} isHiring * @param {*} isHiring
* @returns * @returns
*/ */
function createOrganization (name, location, description, isHiring) { function createOrganization(name, location, description, isHiring) {
const organization = { const organization = {
name: name, name: name,
location: location, location: location,
@ -36,7 +36,7 @@ function createOrganization (name, location, description, isHiring) {
* @param {*} id * @param {*} id
* @returns the Organization * @returns the Organization
*/ */
async function getOrganizationById (id) { async function getOrganizationById(id) {
const organization = await knex('Organization') const organization = await knex('Organization')
.where('id', id) .where('id', id)
.select('*') .select('*')
@ -50,7 +50,7 @@ async function getOrganizationById (id) {
* *
* @returns The inserted Organization * @returns The inserted Organization
*/ */
async function insertOrganization (organization, organizationAdministratorId) { async function insertOrganization(organization, organizationAdministratorId) {
return await knex.transaction(async (trx) => { return await knex.transaction(async (trx) => {
// We have to insert either both in Organization and in OrganizationAdministrator // We have to insert either both in Organization and in OrganizationAdministrator
// or in neither // or in neither
@ -76,7 +76,7 @@ async function insertOrganization (organization, organizationAdministratorId) {
* @param {*} requester * @param {*} requester
* @returns true if the row was updated, false otherwise * @returns true if the row was updated, false otherwise
*/ */
async function updateOrganization (organization, organizationId, requester) { async function updateOrganization(organization, organizationId, requester) {
const numberOfUpdatedRows = await knex('Organization') const numberOfUpdatedRows = await knex('Organization')
.where('id', organizationId) .where('id', organizationId)
.whereExists(function () { .whereExists(function () {
@ -96,9 +96,11 @@ async function updateOrganization (organization, organizationId, requester) {
* @param {*} requester PersonId of the supposedly administrator * @param {*} requester PersonId of the supposedly administrator
* @returns true if the Organization was successfully deleted, false otherwise * @returns true if the Organization was successfully deleted, false otherwise
*/ */
async function deleteOrganization (organizationId, requester) { async function deleteOrganization(organizationId, requester) {
const numberOfDeletedRows = await knex('Organization') const numberOfDeletedRows = await knex('Organization')
.where({ id: organizationId }) .where({
id: organizationId
})
.whereExists(function () { .whereExists(function () {
this.select('*') this.select('*')
.from('OrganizationAdministrator') .from('OrganizationAdministrator')
@ -118,4 +120,4 @@ module.exports = {
insertOrganization, insertOrganization,
updateOrganization, updateOrganization,
deleteOrganization deleteOrganization
}; };

View File

@ -19,7 +19,7 @@ const knex = require('../utils/knex_config');
* @param {*} content * @param {*} content
* @param {*} originalAuthor * @param {*} originalAuthor
*/ */
function createOrganizationPost (organizationId, content, originalAuthor) { function createOrganizationPost(organizationId, content, originalAuthor) {
const organizationPost = { const organizationPost = {
organization_id: organizationId, organization_id: organizationId,
content, content,
@ -34,7 +34,7 @@ function createOrganizationPost (organizationId, content, originalAuthor) {
* @param {*} organization * @param {*} organization
* @returns the inserted OrganizationPost * @returns the inserted OrganizationPost
*/ */
async function insertOrganizationPost (organization) { async function insertOrganizationPost(organization) {
const isOrganizationAdmin = await knex('OrganizationAdministrator') const isOrganizationAdmin = await knex('OrganizationAdministrator')
.where('id_person', organization.original_author) .where('id_person', organization.original_author)
.where('id_organization', organization.organization_id) .where('id_organization', organization.organization_id)
@ -65,7 +65,7 @@ async function insertOrganizationPost (organization) {
* @param {*} personId * @param {*} personId
* @returns true or false * @returns true or false
*/ */
async function isPersonPostAdministrator (postId, personId) { async function isPersonPostAdministrator(postId, personId) {
return await knex('OrganizationPost') return await knex('OrganizationPost')
.join('OrganizationAdministrator', 'OrganizationPost.organization_id', 'OrganizationAdministrator.id_organization') .join('OrganizationAdministrator', 'OrganizationPost.organization_id', 'OrganizationAdministrator.id_organization')
.where('OrganizationPost.id', postId) .where('OrganizationPost.id', postId)
@ -80,11 +80,11 @@ async function isPersonPostAdministrator (postId, personId) {
* @param {*} postId Id of the Post to delete * @param {*} postId Id of the Post to delete
* @param {*} requester Id of the Person requesting the deletion * @param {*} requester Id of the Person requesting the deletion
*/ */
async function deleteOrganizationPost (postId, requester) { async function deleteOrganizationPost(postId, requester) {
if(await isPersonPostAdministrator(postId, requester)){ if (await isPersonPostAdministrator(postId, requester)) {
return await knex('OrganizationPost') return await knex('OrganizationPost')
.where('id', postId) .where('id', postId)
.del() == 1; .del() == 1;
} }
return false; return false;
} }
@ -94,4 +94,4 @@ module.exports = {
insertOrganizationPost, insertOrganizationPost,
isPersonPostAdministrator, isPersonPostAdministrator,
deleteOrganizationPost deleteOrganizationPost
}; };

View File

@ -25,7 +25,7 @@ const bcrypt = require('bcrypt');
* @param {*} placeOfLiving * @param {*} placeOfLiving
* @returns * @returns
*/ */
function createPerson (email, password, displayName, dateOfBirth, available, enabled, placeOfLiving, aboutMe, qualification) { function createPerson(email, password, displayName, dateOfBirth, available, enabled, placeOfLiving, aboutMe, qualification) {
const person = { const person = {
email: email.toLowerCase(), email: email.toLowerCase(),
password, password,
@ -45,7 +45,7 @@ function createPerson (email, password, displayName, dateOfBirth, available, ena
* @param {*} email email to look the Person for * @param {*} email email to look the Person for
* @returns the Person object * @returns the Person object
*/ */
async function getPersonByEmail (email) { async function getPersonByEmail(email) {
return await knex('Person') return await knex('Person')
.where('email', email.toLowerCase()) .where('email', email.toLowerCase())
.first(); .first();
@ -56,10 +56,12 @@ async function getPersonByEmail (email) {
* @param {*} id - The id to look the person for * @param {*} id - The id to look the person for
* @returns * @returns
*/ */
async function getPersonById (id) { async function getPersonById(id) {
return await knex('Person') return await knex('Person')
.select('*') .select('*')
.where({ id }) .where({
id
})
.first(); .first();
} }
@ -69,7 +71,7 @@ async function getPersonById (id) {
* @param {*} person A Person object * @param {*} person A Person object
* @param {*} activationLink the activationLink identifier * @param {*} activationLink the activationLink identifier
*/ */
async function registerPerson (person, activationLink) { async function registerPerson(person, activationLink) {
// We need to insert either both in the "Person" table // We need to insert either both in the "Person" table
// and in the "ActivationLink" one, or in neither // and in the "ActivationLink" one, or in neither
await knex.transaction(async (tr) => { await knex.transaction(async (tr) => {
@ -91,7 +93,7 @@ async function registerPerson (person, activationLink) {
* @param {*} password * @param {*} password
* @returns * @returns
*/ */
async function getPersonByEmailAndPassword (email, password) { async function getPersonByEmailAndPassword(email, password) {
const person = await knex('Person') const person = await knex('Person')
.where('email', email.toLowerCase()) .where('email', email.toLowerCase())
.where('enabled', true) .where('enabled', true)
@ -112,7 +114,7 @@ async function getPersonByEmailAndPassword (email, password) {
* @param {*} person The Person to update * @param {*} person The Person to update
* @param {*} person_id The database id of the Person to update * @param {*} person_id The database id of the Person to update
*/ */
async function updatePerson (person, person_id) { async function updatePerson(person, person_id) {
await knex('Person') await knex('Person')
.where('id', person_id) .where('id', person_id)
.update(person); .update(person);
@ -122,21 +124,25 @@ async function updatePerson (person, person_id) {
* Deletes a Person specified by its database id. * Deletes a Person specified by its database id.
* @param {*} person_id * @param {*} person_id
*/ */
async function deletePerson (personId) { async function deletePerson(personId) {
await knex('Person') await knex('Person')
.where({ id: personId }) .where({
id: personId
})
.del(); .del();
} }
async function confirmActivation (personId) { async function confirmActivation(personId) {
await knex.transaction(async (tr) => { await knex.transaction(async (tr) => {
await knex('Person') await knex('Person')
.where('id', personId) .where('id', personId)
.update({enabled: true}); .update({
enabled: true
});
await tr('ActivationLink') await tr('ActivationLink')
.where('person_id', personId) .where('person_id', personId)
.del(); .del();
}); });
} }
@ -153,4 +159,4 @@ module.exports = {
updatePerson, updatePerson,
deletePerson, deletePerson,
confirmActivation confirmActivation
}; };

View File

@ -23,45 +23,59 @@ const jwtUtils = require('../utils/middleware_utils');
* *
* Required field(s): organization_id, person_id * Required field(s): organization_id, person_id
*/ */
async function addOrganizationAdmin (req, res) { async function addOrganizationAdmin(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id || !req.body.person_id) { if (!req.body.organization_id || !req.body.person_id) {
return res.status(400).json({ error: 'Invalid request' }); return res.status(400).json({
error: 'Invalid request'
});
} }
try { try {
const success = await organizationAdminModel.addOrganizationAdministrator(req.body.person_id, req.body.organization_id, req.jwt.person_id); const success = await organizationAdminModel.addOrganizationAdministrator(req.body.person_id, req.body.organization_id, req.jwt.person_id);
if(success){ if (success) {
return res.status(200).json({ success: true }); return res.status(200).json({
success: true
});
} }
return res.status(403).json({ error: 'Forbidden' }); return res.status(403).json({
error: 'Forbidden'
});
} catch (error) { } catch (error) {
console.error(`Error in function ${addOrganizationAdmin.name}: ${error}`); console.error(`Error in function ${addOrganizationAdmin.name}: ${error}`);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({
error: 'Internal server error'
});
} }
} }
/** /**
* DELETE Request * DELETE Request
* *
* Deletes a Person from the list of Administrators of an Organization. * Deletes a Person from the list of Administrators of an Organization.
* The logged user can only remove themselves. If no more Administrators * The logged user can only remove themselves. If no more Administrators
* are left, the Organization is removed. * are left, the Organization is removed.
* *
* Required field(s): organization_id * Required field(s): organization_id
*/ */
async function removeOrganizationAdmin (req, res) { async function removeOrganizationAdmin(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id) { if (!req.body.organization_id) {
return res.status(400).json({ error: 'Invalid request' }); return res.status(400).json({
error: 'Invalid request'
});
} }
try { try {
await organizationAdminModel.removeOrganizationAdmin(req.jwt.person_id, req.body.organization_id); await organizationAdminModel.removeOrganizationAdmin(req.jwt.person_id, req.body.organization_id);
return res.status(200).json({ success: true }); return res.status(200).json({
success: true
});
} catch (error) { } catch (error) {
console.error(`Error in function ${removeOrganizationAdmin.name}: ${error}`); console.error(`Error in function ${removeOrganizationAdmin.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -72,4 +86,4 @@ protectedRoutes.delete('/organization/admin', removeOrganizationAdmin);
module.exports = { module.exports = {
protectedRoutes protectedRoutes
}; };

View File

@ -16,17 +16,19 @@ const express = require('express');
const jwtUtils = require('../utils/middleware_utils'); const jwtUtils = require('../utils/middleware_utils');
/** /**
* POST Request * POST Request
* *
* Creates a Post belonging to an organization * Creates a Post belonging to an organization
* *
* Required field(s): organization_id, content * Required field(s): organization_id, content
* @returns the inserted Post * @returns the inserted Post
*/ */
async function createOrganizationPost (req, res) { async function createOrganizationPost(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id || !req.body.content) { if (!req.body.organization_id || !req.body.content) {
return res.status(400).json({ error: 'Invalid request' }); return res.status(400).json({
error: 'Invalid request'
});
} }
const organization = organizationPostModel.createOrganizationPost( const organization = organizationPostModel.createOrganizationPost(
@ -39,7 +41,9 @@ async function createOrganizationPost (req, res) {
return res.status(200).json(insertedOrganization); return res.status(200).json(insertedOrganization);
} catch (error) { } catch (error) {
console.error(`Error in function ${createOrganizationPost.name}: ${error}`); console.error(`Error in function ${createOrganizationPost.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -51,18 +55,24 @@ async function createOrganizationPost (req, res) {
* *
* Required field(s): none. * Required field(s): none.
*/ */
async function deleteOrganizationPost (req, res) { async function deleteOrganizationPost(req, res) {
try { try {
const success = await organizationPostModel.deleteOrganizationPost(req.params.id, req.jwt.person_id); const success = await organizationPostModel.deleteOrganizationPost(req.params.id, req.jwt.person_id);
if (success) {
return res.status(200).json({
success: true
});
}
return res.status(401).json({
error: 'Forbidden'
});
if(success){
return res.status(200).json({ success: true });
}
return res.status(401).json({ error: 'Forbidden' });
} catch (error) { } catch (error) {
console.error(`Error in function ${deleteOrganizationPost.name}: ${error}`); console.error(`Error in function ${deleteOrganizationPost.name}: ${error}`);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -76,4 +86,4 @@ protectedRoutes.delete('/organization/post/:id', deleteOrganizationPost);
// module available for use in another module. // module available for use in another module.
module.exports = { module.exports = {
protectedRoutes protectedRoutes
}; };

View File

@ -24,10 +24,12 @@ const jwtUtils = require('../utils/middleware_utils');
* *
* @returns the inserted organization * @returns the inserted organization
*/ */
async function createOrganization (req, res) { async function createOrganization(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.name) { if (!req.body.name) {
return res.status(400).json({ error: 'Invalid request' }); return res.status(400).json({
error: 'Invalid request'
});
} }
try { try {
@ -36,7 +38,9 @@ async function createOrganization (req, res) {
return res.status(200).json(insertedOrganization); return res.status(200).json(insertedOrganization);
} catch (error) { } catch (error) {
console.error(`Error in function ${createOrganization.name}: ${error}`); console.error(`Error in function ${createOrganization.name}: ${error}`);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -46,7 +50,7 @@ async function createOrganization (req, res) {
* *
* Required field(s): none. * Required field(s): none.
*/ */
async function updateOrganization (req, res) { async function updateOrganization(req, res) {
const updateOrganization = {}; const updateOrganization = {};
if (req.body.name) { if (req.body.name) {
@ -66,19 +70,27 @@ async function updateOrganization (req, res) {
} }
if (Object.keys(updateOrganization).length === 0) { if (Object.keys(updateOrganization).length === 0) {
return res.status(400).json({ error: 'Bad request. No data to update' }); return res.status(400).json({
error: 'Bad request. No data to update'
});
} }
try { try {
const isUpdateSuccessful = organizationModel.updateOrganization(updateOrganization, req.params.id, req.jwt.person_id); const isUpdateSuccessful = organizationModel.updateOrganization(updateOrganization, req.params.id, req.jwt.person_id);
if (isUpdateSuccessful) { if (isUpdateSuccessful) {
return res.status(200).json({ success: 'true' }); return res.status(200).json({
success: 'true'
});
} else { } else {
return res.status(404).json({ error: 'Organization either not found or insufficient permissions' }); return res.status(404).json({
error: 'Organization either not found or insufficient permissions'
});
} }
} catch (error) { } catch (error) {
console.error(`Error in function ${updateOrganization.name}: ${error}`); console.error(`Error in function ${updateOrganization.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -88,16 +100,22 @@ async function updateOrganization (req, res) {
* Deletes the specified organization if the logged user is * Deletes the specified organization if the logged user is
* one of its administrator * one of its administrator
*/ */
async function deleteOrganization (req, res) { async function deleteOrganization(req, res) {
try { try {
const isDeleteSuccessful = await organizationModel.deleteOrganization(req.params.id, req.jwt.person_id); const isDeleteSuccessful = await organizationModel.deleteOrganization(req.params.id, req.jwt.person_id);
if (isDeleteSuccessful) { if (isDeleteSuccessful) {
return res.status(200).json({ success: true }); return res.status(200).json({
success: true
});
} }
return res.status(403).json({ error: 'Forbidden' }); return res.status(403).json({
error: 'Forbidden'
});
} catch (error) { } catch (error) {
console.error(`Error in function ${deleteOrganization.name}: ${error}`); console.error(`Error in function ${deleteOrganization.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -110,17 +128,21 @@ async function deleteOrganization (req, res) {
* *
* @returns the organization. * @returns the organization.
*/ */
async function getOrganization (req, res) { async function getOrganization(req, res) {
try { try {
const organization = await organizationModel.getOrganizationById(req.params.id); const organization = await organizationModel.getOrganizationById(req.params.id);
if (organization) { if (organization) {
return res.status(200).json(organization); return res.status(200).json(organization);
} else { } else {
return res.status(404).json({ error: 'Not found' }); return res.status(404).json({
error: 'Not found'
});
} }
} catch (error) { } catch (error) {
console.error(`Error in function ${getOrganization.name}: ${error}`); console.error(`Error in function ${getOrganization.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -136,4 +158,4 @@ protectedRoutes.delete('/organization/:id', deleteOrganization);
module.exports = { module.exports = {
publicRoutes, publicRoutes,
protectedRoutes protectedRoutes
}; };

View File

@ -28,17 +28,23 @@ const express = require('express');
* *
* @returns The activationlink identifier * @returns The activationlink identifier
*/ */
async function registerPerson (req, res) { async function registerPerson(req, res) {
// Does this server allow users to register? // Does this server allow users to register?
if (process.env.ALLOW_USER_REGISTRATION === 'false') { if (process.env.ALLOW_USER_REGISTRATION === 'false') {
return res.status(403).json({ error: 'Users cannot register on this server' }); return res.status(403).json({
error: 'Users cannot register on this server'
});
} }
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.display_name || !req.body.email || !req.body.password) { if (!req.body.display_name || !req.body.email || !req.body.password) {
return res.status(400).json({ error: 'Some or all required fields are missing' }); return res.status(400).json({
error: 'Some or all required fields are missing'
});
} }
if (!validator.validateEmail(req.body.email)) { if (!validator.validateEmail(req.body.email)) {
return res.status(400).json({ error: 'The email is not in a valid format' }); return res.status(400).json({
error: 'The email is not in a valid format'
});
} }
// Generate activation link token // Generate activation link token
@ -50,7 +56,9 @@ async function registerPerson (req, res) {
// Check whether e-mail exists already (enforced by database constraints) // Check whether e-mail exists already (enforced by database constraints)
const existingUser = await personModel.getPersonByEmail(req.body.email); const existingUser = await personModel.getPersonByEmail(req.body.email);
if (existingUser) { if (existingUser) {
return res.status(409).json({ error: 'E-mail already in use' }); return res.status(409).json({
error: 'E-mail already in use'
});
} }
const personToInsert = personModel.createPerson( const personToInsert = personModel.createPerson(
req.body.email, req.body.email,
@ -63,10 +71,14 @@ async function registerPerson (req, res) {
req.body.about_me, req.body.about_me,
req.body.qualification); req.body.qualification);
await personModel.registerPerson(personToInsert, activationLink); await personModel.registerPerson(personToInsert, activationLink);
return res.status(200).json({ activationLink }); return res.status(200).json({
activationLink
});
} catch (error) { } catch (error) {
console.error(`Error in function ${registerPerson.name}: ${error}`); console.error(`Error in function ${registerPerson.name}: ${error}`);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -80,23 +92,31 @@ async function registerPerson (req, res) {
* *
* @returns The token * @returns The token
*/ */
async function login (req, res) { async function login(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.email || !req.body.password) { if (!req.body.email || !req.body.password) {
return res.status(400).json({ error: 'Invalid request' }); return res.status(400).json({
error: 'Invalid request'
});
} }
try { try {
const person = await personModel.getPersonByEmailAndPassword(req.body.email, req.body.password); const person = await personModel.getPersonByEmailAndPassword(req.body.email, req.body.password);
if (person) { if (person) {
const token = jwtUtils.generateToken(person.id); const token = jwtUtils.generateToken(person.id);
return res.status(200).json({ token }); return res.status(200).json({
token
});
} else { } else {
return res.status(401).json({ error: 'Invalid credentials' }); return res.status(401).json({
error: 'Invalid credentials'
});
} }
} catch (error) { } catch (error) {
console.error(`Error in function ${login.name}: ${error}`); console.error(`Error in function ${login.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -109,17 +129,21 @@ async function login (req, res) {
* *
* @returns The Person * @returns The Person
*/ */
async function getPerson (req, res) { async function getPerson(req, res) {
try { try {
const person = await personModel.getPersonById(req.params.id); const person = await personModel.getPersonById(req.params.id);
if (person && person.enabled) { if (person && person.enabled) {
delete person.password; // remove password field for security reasons delete person.password; // remove password field for security reasons
return res.status(200).send(person); return res.status(200).send(person);
} }
return res.status(404).json({ error: 'Not found' }); return res.status(404).json({
error: 'Not found'
});
} catch (error) { } catch (error) {
console.error(`Error in function ${getPerson.name}: ${error}`); console.error(`Error in function ${getPerson.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -131,17 +155,21 @@ async function getPerson (req, res) {
* *
* @returns Person's details * @returns Person's details
*/ */
async function getMyself (req, res) { async function getMyself(req, res) {
try { try {
const person = await personModel.getPersonById(req.jwt.person_id); const person = await personModel.getPersonById(req.jwt.person_id);
if (person) { if (person) {
delete person.password; delete person.password;
return res.status(200).send(person); return res.status(200).send(person);
} }
return res.status(404).json({ error: 'Not found' }); return res.status(404).json({
error: 'Not found'
});
} catch (error) { } catch (error) {
console.error(`Error in function ${getMyself.name}: ${error}`); console.error(`Error in function ${getMyself.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -156,7 +184,7 @@ async function getMyself (req, res) {
* new_password if updating the password. * new_password if updating the password.
* *
*/ */
async function updatePerson (req, res) { async function updatePerson(req, res) {
const updatePerson = {}; const updatePerson = {};
if (req.body.display_name) { if (req.body.display_name) {
@ -167,7 +195,9 @@ async function updatePerson (req, res) {
if (validator.isPostgresDateFormatValid(req.body.date_of_birth)) { if (validator.isPostgresDateFormatValid(req.body.date_of_birth)) {
updatePerson.date_of_birth = req.body.date_of_birth; updatePerson.date_of_birth = req.body.date_of_birth;
} else { } else {
return res.status(400).json({ error: 'Date of birth format not valid. Please specify a YYYY-MM-DD date' }); return res.status(400).json({
error: 'Date of birth format not valid. Please specify a YYYY-MM-DD date'
});
} }
} }
@ -179,41 +209,53 @@ async function updatePerson (req, res) {
updatePerson.place_of_living = req.body.place_of_living; updatePerson.place_of_living = req.body.place_of_living;
} }
if(req.body.about_me) { if (req.body.about_me) {
updatePerson.about_me = req.body.about_me; updatePerson.about_me = req.body.about_me;
} }
if(req.body.qualification) { if (req.body.qualification) {
updatePerson.qualification = req.body.qualification; updatePerson.qualification = req.body.qualification;
} }
// If we are tying to change password, the old password must be provided // If we are tying to change password, the old password must be provided
if (req.body.old_password || req.body.new_password) { if (req.body.old_password || req.body.new_password) {
if(!req.body.old_password){ if (!req.body.old_password) {
return res.status(401).json({ error: 'The old password must be specified' }); return res.status(401).json({
error: 'The old password must be specified'
});
} }
if(!req.body.new_password){ if (!req.body.new_password) {
return res.status(401).json({ error: 'The new password must be specified' }); return res.status(401).json({
error: 'The new password must be specified'
});
} }
const user = await personModel.getPersonById(req.jwt.person_id); const user = await personModel.getPersonById(req.jwt.person_id);
const passwordMatches = await bcrypt.compare(req.body.old_password, user.password); const passwordMatches = await bcrypt.compare(req.body.old_password, user.password);
if (passwordMatches) { if (passwordMatches) {
updatePerson.password = await bcrypt.hash(req.body.new_password, 10); updatePerson.password = await bcrypt.hash(req.body.new_password, 10);
} else { } else {
return res.status(401).json({ error: 'Password verification failed' }); return res.status(401).json({
error: 'Password verification failed'
});
} }
} }
if (Object.keys(updatePerson).length === 0) { if (Object.keys(updatePerson).length === 0) {
return res.status(400).json({ error: 'Bad request. No data to update' }); return res.status(400).json({
error: 'Bad request. No data to update'
});
} }
try { try {
await personModel.updatePerson(updatePerson, req.jwt.person_id); await personModel.updatePerson(updatePerson, req.jwt.person_id);
return res.status(200).json({ success: 'true' }); return res.status(200).json({
success: 'true'
});
} catch (error) { } catch (error) {
console.error(`Error in function ${updatePerson.name}: ${error}`); console.error(`Error in function ${updatePerson.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -226,14 +268,18 @@ async function updatePerson (req, res) {
* Required field(s): none * Required field(s): none
* *
*/ */
async function deletePerson (req, res) { async function deletePerson(req, res) {
// TODO: Delete Organization if this user was its only administrator // TODO: Delete Organization if this user was its only administrator
try { try {
await personModel.deletePerson(req.jwt.person_id); await personModel.deletePerson(req.jwt.person_id);
return res.status(200).json({ success: true }); return res.status(200).json({
success: true
});
} catch (error) { } catch (error) {
console.error(`Error in function ${deletePerson.name}: ${error}`); console.error(`Error in function ${deletePerson.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -245,17 +291,23 @@ async function deletePerson (req, res) {
* *
* Required field(s): identifier * Required field(s): identifier
*/ */
async function confirmActivation(req, res){ async function confirmActivation(req, res) {
try { try {
const personId = await activationModel.getPersonIdByIdentifier(req.query.q); const personId = await activationModel.getPersonIdByIdentifier(req.query.q);
if(!personId){ if (!personId) {
return res.status(401).json({error: 'Activation Link either not valid or expired'}); return res.status(401).json({
error: 'Activation Link either not valid or expired'
});
} }
await personModel.confirmActivation(personId); await personModel.confirmActivation(personId);
return res.status(200).json({ success: true }); return res.status(200).json({
success: true
});
} catch (error) { } catch (error) {
console.error(`Error in function ${confirmActivation.name}: ${error}`); console.error(`Error in function ${confirmActivation.name}: ${error}`);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({
error: 'Internal server error'
});
} }
} }
@ -277,4 +329,4 @@ protectedRoutes.delete('/person/delete', deletePerson);
module.exports = { module.exports = {
publicRoutes, publicRoutes,
protectedRoutes protectedRoutes
}; };

View File

@ -22,4 +22,4 @@ const knexInstance = require('knex')({
} }
}); });
module.exports = knexInstance; module.exports = knexInstance;

View File

@ -13,7 +13,7 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
function generateToken (person_id) { function generateToken(person_id) {
// The payload the JWT will carry within itself // The payload the JWT will carry within itself
const payload = { const payload = {
person_id person_id
@ -26,16 +26,20 @@ function generateToken (person_id) {
} }
// Middlware // Middlware
function verifyToken (req, res, next) { function verifyToken(req, res, next) {
const token = req.headers.authorization; const token = req.headers.authorization;
if (!token) { if (!token) {
return res.status(401).send({ error: 'No token provided' }); return res.status(401).send({
error: 'No token provided'
});
} }
jwt.verify(token, process.env.JWT_SECRET_KEY, (err, decoded) => { jwt.verify(token, process.env.JWT_SECRET_KEY, (err, decoded) => {
if (err) { if (err) {
return res.status(401).send({ error: 'Failed to authenticate token' }); return res.status(401).send({
error: 'Failed to authenticate token'
});
} }
// If the token is valid, store the decoded data in the request object // If the token is valid, store the decoded data in the request object
@ -48,4 +52,4 @@ function verifyToken (req, res, next) {
module.exports = { module.exports = {
generateToken, generateToken,
verifyToken verifyToken
}; };

View File

@ -16,7 +16,7 @@
* @param {*} email email to validate * @param {*} email email to validate
* @returns true or false * @returns true or false
*/ */
function validateEmail (email) { function validateEmail(email) {
const regex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/; const regex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
return regex.test(email); return regex.test(email);
} }
@ -27,7 +27,7 @@ function validateEmail (email) {
* @param {*} dateString the date to validate * @param {*} dateString the date to validate
* @returns true or false * @returns true or false
*/ */
function isPostgresDateFormatValid (dateString) { function isPostgresDateFormatValid(dateString) {
const regex = /^\d{4}-\d{2}-\d{2}$/; const regex = /^\d{4}-\d{2}-\d{2}$/;
return regex.test(dateString); return regex.test(dateString);
} }
@ -35,4 +35,4 @@ function isPostgresDateFormatValid (dateString) {
module.exports = { module.exports = {
validateEmail, validateEmail,
isPostgresDateFormatValid isPostgresDateFormatValid
}; };

View File

@ -1,65 +1,71 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" > <html lang="en">
<head>
<meta charset="UTF-8">
<title>Log in - Blink</title>
<link rel="stylesheet" href="../css/login-register.css">
</head>
<body>
<!-- partial:index.partial.html -->
<div id="login-form-wrap">
<h2>Login</h2>
<form id="login-form" method="POST">
<p> <head>
<input type="email" id="email" name="email" placeholder="Email Address" required><i class="validation"><span></span><span></span></i> <meta charset="UTF-8">
</p> <title>Log in - Blink</title>
<link rel="stylesheet" href="../css/login-register.css">
</head>
<p> <body>
<input type="password" id="password" name="password" placeholder="Password" required><i class="validation"><span></span><span></span></i> <!-- partial:index.partial.html -->
</p> <div id="login-form-wrap">
<h2>Login</h2>
<form id="login-form" method="POST">
<p>
<input type="email" id="email" name="email" placeholder="Email Address" required><i
class="validation"><span></span><span></span></i>
</p>
<p>
<input type="password" id="password" name="password" placeholder="Password" required><i
class="validation"><span></span><span></span></i>
</p>
<p>
<button type="button" onclick="login()">Login</button>
</p>
</form>
<div id="create-account-wrap">
<p>Not a member? <a href="./register.html">Create Account</a>
<p> <p>
<button type="button" onclick="login()">Login</button>
</p>
</form>
<div id="create-account-wrap">
<p>Not a member? <a href="./register.html">Create Account</a><p>
</div>
</div> </div>
</div>
<script src="../js/constants.js"></script> <script src="../js/constants.js"></script>
<script> <script>
async function login() { async function login() {
const email = document.getElementById("email").value; const email = document.getElementById("email").value;
const password = document.getElementById("password").value; const password = document.getElementById("password").value;
if(!email || !password){ if (!email || !password) {
alert('Please fill in all fields'); alert('Please fill in all fields');
return; return;
}
const response = await fetch(`${API_URL}/login`, {
method: "POST",
body: JSON.stringify({
email: email,
password: password }),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
});
const data = await response.json();
if(response.ok){
console.log(`Login was successful. Token is ${data.token}`);
document.cookie = `token=${data.token};`;
window.location.href = 'userprofile.html?id=myself';
}
else{
alert(data.error);
}
} }
</script> const response = await fetch(`${API_URL}/login`, {
method: "POST",
body: JSON.stringify({
email: email,
password: password
}),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
});
const data = await response.json();
if (response.ok) {
console.log(`Login was successful. Token is ${data.token}`);
document.cookie = `token=${data.token};`;
window.location.href = 'userprofile.html?id=myself';
} else {
alert(data.error);
}
}
</script>
</body>
</body>
</html> </html>

View File

@ -1,11 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title> <title>Title</title>
<link rel="stylesheet" href="../css/organization.css"> <link rel="stylesheet" href="../css/organization.css">
</head> </head>
<body style="display: none;"> <body style="display: none;">
<div class="container"> <div class="container">
<div class="hiring-badge" style="display: none;" id="isHiringBadge">Now Hiring</div> <div class="hiring-badge" style="display: none;" id="isHiringBadge">Now Hiring</div>
@ -26,50 +28,47 @@
<script src="../js/utils.js"></script> <script src="../js/utils.js"></script>
<script> <script>
window.addEventListener("load", async function() { window.addEventListener("load", async function () {
loadOrganization(); loadOrganization();
}); });
async function loadOrganization (){ async function loadOrganization() {
const idToDisplay = new URLSearchParams(window.location.search).get('id'); const idToDisplay = new URLSearchParams(window.location.search).get('id');
if(!idToDisplay){ if (!idToDisplay) {
alert("Invalid URL."); alert("Invalid URL.");
return; return;
} }
const response = await fetch(`${API_URL}/organization/${idToDisplay}`, { const response = await fetch(`${API_URL}/organization/${idToDisplay}`, {
headers: { headers: {
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
} }
}); });
const data = await response.json(); const data = await response.json();
if(response.ok) { if (response.ok) {
populateFields(data.name, data.location, data.description, data.is_hiring); populateFields(data.name, data.location, data.description, data.is_hiring);
document.body.style.display = "block"; // Show page document.body.style.display = "block"; // Show page
} } else {
else {
alert(data.error); alert(data.error);
} }
} }
function populateFields (name, location, description, isHiring) { function populateFields(name, location, description, isHiring) {
document.getElementById('organizationName').textContent = name; document.getElementById('organizationName').textContent = name;
document.title = `${name} - Blink` document.title = `${name} - Blink`
document.getElementById('location').textContent = location; document.getElementById('location').textContent = location;
document.getElementById('description').textContent = description; document.getElementById('description').textContent = description;
if(isHiring === true) { if (isHiring === true) {
document.getElementById('isHiring').textContent = 'Yes'; document.getElementById('isHiring').textContent = 'Yes';
document.getElementById('isHiringBadge').style.display = 'block'; document.getElementById('isHiringBadge').style.display = 'block';
} } else if (isHiring === false) {
else if (isHiring === false) {
document.getElementById('isHiring').textContent = 'No'; document.getElementById('isHiring').textContent = 'No';
} } else {
else {
document.getElementById('isHiring').textContent = 'Not specified'; document.getElementById('isHiring').textContent = 'Not specified';
} }
} }
</script> </script>
</body> </body>
</html>
</html>

View File

@ -1,76 +1,86 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" > <html lang="en">
<head>
<meta charset="UTF-8">
<title>Sign Up to Blink</title>
<link rel="stylesheet" href="../css/login-register.css">
<script src=""></script>
</head>
<body>
<!-- partial:index.partial.html -->
<div id="login-form-wrap">
<h2>Sign Up</h2>
<form id="login-form">
<p> <head>
<input type="text" id="displayname" name="displayname" placeholder="Your name" required><i class="validation"><span></span><span></span></i> <meta charset="UTF-8">
</p> <title>Sign Up to Blink</title>
<link rel="stylesheet" href="../css/login-register.css">
<script src=""></script>
</head>
<p> <body>
<input type="email" id="email" name="email" placeholder="Email Address" required><i class="validation"><span></span><span></span></i> <!-- partial:index.partial.html -->
</p> <div id="login-form-wrap">
<h2>Sign Up</h2>
<form id="login-form">
<p> <p>
<input type="password" id="password" name="password" placeholder="Password" required><i class="validation"><span></span><span></span></i> <input type="text" id="displayname" name="displayname" placeholder="Your name" required><i
</p> class="validation"><span></span><span></span></i>
</p>
<p> <p>
<button type="button" onclick="register()">Register</button> <input type="email" id="email" name="email" placeholder="Email Address" required><i
</p> class="validation"><span></span><span></span></i>
</form> </p>
<div id="create-account-wrap">
<p>Already a member? <a href="./login.html">Login</a><p>
</div>
</div>
<script src="../js/constants.js"></script> <p>
<script src="../js/utils.js"></script> <input type="password" id="password" name="password" placeholder="Password" required><i
class="validation"><span></span><span></span></i>
</p>
<script> <p>
<button type="button" onclick="register()">Register</button>
</p>
</form>
<div id="create-account-wrap">
<p>Already a member? <a href="./login.html">Login</a>
<p>
</div>
</div>
function register(){ <script src="../js/constants.js"></script>
const display_name = document.getElementById('displayname').value; <script src="../js/utils.js"></script>
const email = document.getElementById('email').value;
const password = document.getElementById('password').value; <script>
function register() {
if(!display_name || !email || !password){ const display_name = document.getElementById('displayname').value;
alert('Please fill in all fields'); const email = document.getElementById('email').value;
return; const password = document.getElementById('password').value;
if (!display_name || !email || !password) {
alert('Please fill in all fields');
return;
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
display_name,
email,
password
}),
};
fetch(`${API_URL}/register`, options)
.then(response => {
if (response.ok) {
alert("Congratulations! You've successfully registered to Blink." +
" Please click on the e-mail we sent you to confirm your account");
window.location.href = '/login.html';
} }
})
.catch(err => {
alert("An error has occurred :-( please try again later")
console.error(err);
});
}
</script>
const options = { </body>
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ display_name, email, password }),
};
fetch(`${API_URL}/register`, options) </html>
.then(response => {
if (response.ok) {
alert("Congratulations! You've successfully registered to Blink." +
" Please click on the e-mail we sent you to confirm your account");
window.location.href = '/login.html';
}
})
.catch(err => {
alert("An error has occurred :-( please try again later")
console.error(err);
});
}
</script>
</body>
</html>

View File

@ -1,11 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Title</title> <title>Page Title</title>
<link rel="stylesheet" href="../css/profile.css"> <link rel="stylesheet" href="../css/profile.css">
</head> </head>
<body style="display: none;"> <body style="display: none;">
<div class="container"> <div class="container">
<div class="edit-badge" style="display: none;" id="editBadge" onclick="editProfile()">Edit</div> <div class="edit-badge" style="display: none;" id="editBadge" onclick="editProfile()">Edit</div>
@ -46,21 +48,21 @@
<script src="../js/utils.js"></script> <script src="../js/utils.js"></script>
<script> <script>
window.addEventListener("load", async function() { window.addEventListener("load", async function () {
loadProfile(); loadProfile();
}); });
async function loadProfile (){ async function loadProfile() {
const idToDisplay = new URLSearchParams(window.location.search).get('id'); const idToDisplay = new URLSearchParams(window.location.search).get('id');
let response; let response;
// Retrieving the logged in user's profile // Retrieving the logged in user's profile
if(!idToDisplay || idToDisplay === 'myself'){ if (!idToDisplay || idToDisplay === 'myself') {
document.getElementById('editBadge').style.display = 'block'; // show edit button document.getElementById('editBadge').style.display = 'block'; // show edit button
const token = getCookie('token'); const token = getCookie('token');
// Check whether the token exists // Check whether the token exists
if(!token){ if (!token) {
window.location.href = 'login.html'; window.location.href = 'login.html';
} }
response = await fetch(`${API_URL}/person/myself`, { response = await fetch(`${API_URL}/person/myself`, {
@ -69,8 +71,7 @@
"authorization": token "authorization": token
} }
}); });
} } else {
else {
response = await fetch(`${API_URL}/person/${idToDisplay}/details`, { response = await fetch(`${API_URL}/person/${idToDisplay}/details`, {
headers: { headers: {
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
@ -79,19 +80,17 @@
} }
const data = await response.json(); const data = await response.json();
if (response.ok){ if (response.ok) {
populateFields(data.display_name, data.email, data.about_me, data.qualification); populateFields(data.display_name, data.email, data.about_me, data.qualification);
document.body.style.display = 'block'; // Show page document.body.style.display = 'block'; // Show page
} } else if (response.status == 401) {
else if (response.status == 401){
window.location.href = 'login.html'; window.location.href = 'login.html';
} } else {
else{
alert(`Unable to load profile. Error: ${data.error}`); alert(`Unable to load profile. Error: ${data.error}`);
} }
} }
function populateFields (displayName, email, aboutMe, qualification) { function populateFields(displayName, email, aboutMe, qualification) {
document.getElementById('displayName').textContent = displayName; document.getElementById('displayName').textContent = displayName;
document.title = `${displayName} - Blink` document.title = `${displayName} - Blink`
document.getElementById('email').textContent = email; document.getElementById('email').textContent = email;
@ -99,12 +98,11 @@
document.getElementById('qualification').textContent = qualification; document.getElementById('qualification').textContent = qualification;
} }
function editProfile () { function editProfile() {
alert('Editing'); alert('Editing');
} }
</script> </script>
</body> </body>
</html>
</html>

View File

@ -1 +1 @@
const API_URL = 'http://localhost:3000/api'; const API_URL = 'http://localhost:3000/api';