mirror of https://github.com/xfarrow/blink
Compare commits
4 Commits
9fe5879554
...
6023ee56d0
Author | SHA1 | Date |
---|---|---|
xfarrow | 6023ee56d0 | |
xfarrow | 10aa3460a3 | |
xfarrow | ca5e201dc4 | |
xfarrow | 5835bfbc16 |
|
@ -23,7 +23,6 @@ const helmet = require('helmet')
|
||||||
const personRoutes = require('./routes/person_routes.js');
|
const personRoutes = require('./routes/person_routes.js');
|
||||||
const organizationRoutes = require('./routes/organization_routes.js');
|
const organizationRoutes = require('./routes/organization_routes.js');
|
||||||
const organizationAdminRoutes = require('./routes/organization_admin_routes.js');
|
const organizationAdminRoutes = require('./routes/organization_admin_routes.js');
|
||||||
const organizationPostRoutes = require('./routes/organization_post_routes.js');
|
|
||||||
const jobOffersRoutes = require('./routes/job_offer_routes.js');
|
const jobOffersRoutes = require('./routes/job_offer_routes.js');
|
||||||
const serverRoutes = require('./routes/server_routes.js');
|
const serverRoutes = require('./routes/server_routes.js');
|
||||||
const resetPasswordRoutes = require('./routes/reset_password_routes.js');
|
const resetPasswordRoutes = require('./routes/reset_password_routes.js');
|
||||||
|
@ -55,7 +54,6 @@ app.use('/api/persons', personRoutes.protectedRoutes);
|
||||||
app.use('/api/organizations', organizationRoutes.routes);
|
app.use('/api/organizations', organizationRoutes.routes);
|
||||||
app.use('/api/organizations', jobOffersRoutes.routes);
|
app.use('/api/organizations', jobOffersRoutes.routes);
|
||||||
app.use('/api/organizations', organizationAdminRoutes.routes);
|
app.use('/api/organizations', organizationAdminRoutes.routes);
|
||||||
app.use('/api/organizations', organizationPostRoutes.routes);
|
|
||||||
app.use('/api/resetpassword', resetPasswordRoutes.routes);
|
app.use('/api/resetpassword', resetPasswordRoutes.routes);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
* ⚠ WARNING ⚠
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Posts are now scheduled to be developed at a later stage in the development
|
|
||||||
* process, with the possibility that it may not be developed at all.
|
|
||||||
* I am unsure whether it is a good thing or it'll only be used to
|
|
||||||
* flood timelines with low-effort content, like other competing platforms.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
const knex = require('../utils/knex_config');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create OrganizationPost object
|
|
||||||
* @param {*} organizationId
|
|
||||||
* @param {*} content
|
|
||||||
* @param {*} originalAuthor
|
|
||||||
*/
|
|
||||||
function createOrganizationPost(organizationId, content, originalAuthor) {
|
|
||||||
const organizationPost = {
|
|
||||||
organization_id: organizationId,
|
|
||||||
content,
|
|
||||||
original_author: originalAuthor
|
|
||||||
};
|
|
||||||
return organizationPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert an OrganizationPost if and only if the author is
|
|
||||||
* one of the Organization's administrators.
|
|
||||||
* @param {*} organization
|
|
||||||
* @returns the inserted OrganizationPost
|
|
||||||
*/
|
|
||||||
async function insertOrganizationPost(organization) {
|
|
||||||
const isOrganizationAdmin = await knex('OrganizationAdministrator')
|
|
||||||
.where('id_person', organization.original_author)
|
|
||||||
.where('id_organization', organization.organization_id)
|
|
||||||
.select('*')
|
|
||||||
.first();
|
|
||||||
|
|
||||||
// Non-exploitable TOC/TOU weakness
|
|
||||||
// For more information https://softwareengineering.stackexchange.com/questions/451038/when-should-i-be-worried-of-time-of-check-time-of-use-vulnerabilities-during-dat
|
|
||||||
if (!isOrganizationAdmin) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizationPost = await knex('OrganizationPost')
|
|
||||||
.insert({
|
|
||||||
organization_id: organization.organization_id,
|
|
||||||
content: organization.content,
|
|
||||||
original_author: organization.original_author
|
|
||||||
})
|
|
||||||
.returning('*');
|
|
||||||
|
|
||||||
return organizationPost[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the specified Person is an Organization Administrator
|
|
||||||
* of the Organization the Post belongs to.
|
|
||||||
* @param {*} postId
|
|
||||||
* @param {*} personId
|
|
||||||
* @returns true or false
|
|
||||||
*/
|
|
||||||
async function isPersonPostAdministrator(postId, personId) {
|
|
||||||
return await knex('OrganizationPost')
|
|
||||||
.join('OrganizationAdministrator', 'OrganizationPost.organization_id', 'OrganizationAdministrator.id_organization')
|
|
||||||
.where('OrganizationPost.id', postId)
|
|
||||||
.where('OrganizationAdministrator.id_person', personId)
|
|
||||||
.select('*')
|
|
||||||
.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the specified OrganizationPost if the requester is one
|
|
||||||
* of the Administrators of the Organization the Post belongs to
|
|
||||||
* @param {*} postId Id of the Post to delete
|
|
||||||
* @param {*} requester Id of the Person requesting the deletion
|
|
||||||
*/
|
|
||||||
async function deleteOrganizationPost(postId, requester) {
|
|
||||||
if (await isPersonPostAdministrator(postId, requester)) {
|
|
||||||
return await knex('OrganizationPost')
|
|
||||||
.where('id', postId)
|
|
||||||
.del() == 1;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createOrganizationPost,
|
|
||||||
insertOrganizationPost,
|
|
||||||
isPersonPostAdministrator,
|
|
||||||
deleteOrganizationPost
|
|
||||||
};
|
|
|
@ -25,17 +25,18 @@ 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, placeOfLiving, aboutMe, qualification, openToWork, enabled) {
|
||||||
const person = {
|
const person = {
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
password,
|
password,
|
||||||
display_name: displayName,
|
display_name: displayName,
|
||||||
date_of_birth: dateOfBirth,
|
date_of_birth: dateOfBirth,
|
||||||
available,
|
open_to_work: openToWork,
|
||||||
enabled,
|
|
||||||
place_of_living: placeOfLiving,
|
place_of_living: placeOfLiving,
|
||||||
about_me: aboutMe,
|
about_me: aboutMe,
|
||||||
qualification
|
qualification,
|
||||||
|
enabled,
|
||||||
|
visibility: "EVERYONE"
|
||||||
};
|
};
|
||||||
return person;
|
return person;
|
||||||
}
|
}
|
||||||
|
@ -80,7 +81,7 @@ async function insert(person, activationLink) {
|
||||||
const insertedPerson = await tr('Person')
|
const insertedPerson = await tr('Person')
|
||||||
.insert(person)
|
.insert(person)
|
||||||
.returning('*');
|
.returning('*');
|
||||||
if(activationLink != null){
|
if (activationLink != null) {
|
||||||
await tr('ActivationLink')
|
await tr('ActivationLink')
|
||||||
.insert({
|
.insert({
|
||||||
person_id: insertedPerson[0].id,
|
person_id: insertedPerson[0].id,
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
* ⚠ WARNING ⚠
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Posts are now scheduled to be developed at a later stage in the development
|
|
||||||
* process, with the possibility that it may not be developed at all.
|
|
||||||
* I am unsure whether it is a good thing or it'll only be used to
|
|
||||||
* flood timelines with low-effort content, like other competing platforms.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
const organizationPostModel = require('../models/organization_post_model');
|
|
||||||
const express = require('express');
|
|
||||||
const jwtUtils = require('../utils/jwt_utils');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST Request
|
|
||||||
*
|
|
||||||
* Creates a Post belonging to an organization
|
|
||||||
*
|
|
||||||
* Required field(s): organization_id, content
|
|
||||||
* @returns the inserted Post
|
|
||||||
*/
|
|
||||||
async function createOrganizationPost(req, res) {
|
|
||||||
// Ensure that the required fields are present before proceeding
|
|
||||||
if (!req.params.idOrganization || !req.body.content) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: 'Invalid request'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizationPost = organizationPostModel.createOrganizationPost(
|
|
||||||
req.params.idOrganization,
|
|
||||||
req.body.content,
|
|
||||||
req.jwt.person_id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const insertedOrganization = await organizationPostModel.insertOrganizationPost(organizationPost);
|
|
||||||
if(!!insertedOrganization){
|
|
||||||
return res.status(200).json(insertedOrganization);
|
|
||||||
}
|
|
||||||
return res.status(401).json({
|
|
||||||
error: 'Forbidden'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error in function ${createOrganizationPost.name}: ${error}`);
|
|
||||||
return res.status(500).json({
|
|
||||||
error: 'Internal server error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE Request
|
|
||||||
*
|
|
||||||
* Deletes a Post belonging to an Organization, only if
|
|
||||||
* the logged user is an administrator of that Organization.
|
|
||||||
*
|
|
||||||
* Required field(s): none.
|
|
||||||
*/
|
|
||||||
async function deleteOrganizationPost(req, res) {
|
|
||||||
try {
|
|
||||||
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'
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error in function ${deleteOrganizationPost.name}: ${error}`);
|
|
||||||
res.status(500).json({
|
|
||||||
error: 'Internal server error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const routes = express.Router();
|
|
||||||
routes.post('/:idOrganization/posts', jwtUtils.extractToken, createOrganizationPost);
|
|
||||||
routes.delete('/posts/:id', jwtUtils.extractToken, deleteOrganizationPost);
|
|
||||||
|
|
||||||
// Exporting a function
|
|
||||||
// means making a JavaScript function defined in one
|
|
||||||
// module available for use in another module.
|
|
||||||
module.exports = {
|
|
||||||
routes
|
|
||||||
};
|
|
|
@ -16,7 +16,7 @@ const jwtUtils = require('../utils/jwt_utils');
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const Person = require('../models/person_model');
|
const Person = require('../models/person_model');
|
||||||
const activationModel = require('../models/activation_model');
|
const Activation = require('../models/activation_model');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const mailUtils = require('../utils/mail_utils');
|
const mailUtils = require('../utils/mail_utils');
|
||||||
|
|
||||||
|
@ -66,13 +66,14 @@ async function registerPerson(req, res) {
|
||||||
const personToInsert = Person.createPerson(
|
const personToInsert = Person.createPerson(
|
||||||
req.body.email,
|
req.body.email,
|
||||||
await hashPasswordPromise,
|
await hashPasswordPromise,
|
||||||
req.body.display_name,
|
req.body.displayName,
|
||||||
req.body.date_of_birth,
|
req.body.dateOfBirth,
|
||||||
req.body.available,
|
req.body.placeOfLiving,
|
||||||
|
req.body.aboutMe,
|
||||||
|
req.body.qualification,
|
||||||
|
req.body.openToWork,
|
||||||
isEnabled,
|
isEnabled,
|
||||||
req.body.place_of_living,
|
);
|
||||||
req.body.about_me,
|
|
||||||
req.body.qualification);
|
|
||||||
const insertedPerson = await Person.insert(personToInsert, activationCode);
|
const insertedPerson = await Person.insert(personToInsert, activationCode);
|
||||||
delete insertedPerson.password;
|
delete insertedPerson.password;
|
||||||
|
|
||||||
|
@ -190,8 +191,8 @@ async function getMyself(req, res) {
|
||||||
* not present, they shall be ignored. An user can
|
* not present, they shall be ignored. An user can
|
||||||
* only update themselves
|
* only update themselves
|
||||||
*
|
*
|
||||||
* Required field(s): At least one. Both old_password and
|
* Required field(s): At least one. Both oldPassword and
|
||||||
* new_password if updating the password.
|
* newPassword if updating the password.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function updatePerson(req, res) {
|
async function updatePerson(req, res) {
|
||||||
|
@ -205,46 +206,50 @@ async function updatePerson(req, res) {
|
||||||
|
|
||||||
const updatePerson = {};
|
const updatePerson = {};
|
||||||
|
|
||||||
if (req.body.display_name !== undefined) {
|
if (req.body.displayName !== undefined) {
|
||||||
updatePerson.display_name = req.body.display_name;
|
updatePerson.display_name = req.body.displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.date_of_birth !== undefined) {
|
if (req.body.dateOfBirth !== undefined) {
|
||||||
updatePerson.date_of_birth = req.body.date_of_birth;
|
updatePerson.date_of_birth = req.body.dateOfBirth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.available !== undefined) {
|
if (req.body.openToWork !== undefined) {
|
||||||
updatePerson.available = req.body.available;
|
updatePerson.open_to_work = req.body.openToWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.place_of_living !== undefined) {
|
if (req.body.placeOfLiving !== undefined) {
|
||||||
updatePerson.place_of_living = req.body.place_of_living;
|
updatePerson.place_of_living = req.body.placeOfLiving;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.about_me !== undefined) {
|
if (req.body.aboutMe !== undefined) {
|
||||||
updatePerson.about_me = req.body.about_me;
|
updatePerson.about_me = req.body.aboutMe;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.qualification !== undefined) {
|
if (req.body.qualification !== undefined) {
|
||||||
updatePerson.qualification = req.body.qualification;
|
updatePerson.qualification = req.body.qualification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.body.visibility !== undefined) {
|
||||||
|
updatePerson.visibility = req.body.visibility;
|
||||||
|
}
|
||||||
|
|
||||||
// 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 !== undefined || req.body.new_password !== undefined) {
|
if (req.body.oldPassword !== undefined || req.body.newPassword !== undefined) {
|
||||||
if (req.body.old_password === undefined) {
|
if (req.body.oldPassword === undefined) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: 'The old password must be specified'
|
error: 'The old password must be specified'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (req.body.new_password === undefined) {
|
if (req.body.newPassword === undefined) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: 'The new password must be specified'
|
error: 'The new password must be specified'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const user = await Person.findById(req.jwt.person_id);
|
const user = await Person.findById(req.jwt.person_id);
|
||||||
const passwordMatches = await bcrypt.compare(req.body.old_password, user.password);
|
const passwordMatches = await bcrypt.compare(req.body.oldPassword, user.password);
|
||||||
if (passwordMatches) {
|
if (passwordMatches) {
|
||||||
updatePerson.password = await bcrypt.hash(req.body.new_password, 10);
|
updatePerson.password = await bcrypt.hash(req.body.newPassword, 10);
|
||||||
} else {
|
} else {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: 'Password verification failed'
|
error: 'Password verification failed'
|
||||||
|
@ -306,7 +311,7 @@ async function confirmActivation(req, res) {
|
||||||
errors: errors.array()
|
errors: errors.array()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const personId = await activationModel.getPersonIdByIdentifier(req.body.code);
|
const personId = await Activation.getPersonIdByIdentifier(req.body.code);
|
||||||
if (!personId) {
|
if (!personId) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: 'Activation Link either not valid or expired'
|
error: 'Activation Link either not valid or expired'
|
||||||
|
|
|
@ -23,8 +23,8 @@ const createOrganizationValidator = [
|
||||||
check('location').trim().escape().isLength({
|
check('location').trim().escape().isLength({
|
||||||
max: 256
|
max: 256
|
||||||
}),
|
}),
|
||||||
check('description').trim().escape(),
|
check('description').optional().trim().escape(),
|
||||||
check('is_hiring').isBoolean()
|
check('isHiring').optional().isBoolean()
|
||||||
];
|
];
|
||||||
|
|
||||||
const updateOrganizationValidator = [
|
const updateOrganizationValidator = [
|
||||||
|
@ -34,8 +34,8 @@ const updateOrganizationValidator = [
|
||||||
check('location').trim().escape().isLength({
|
check('location').trim().escape().isLength({
|
||||||
max: 256
|
max: 256
|
||||||
}),
|
}),
|
||||||
check('description').trim().escape(),
|
check('description').optional().trim().escape(),
|
||||||
check('is_hiring').optional().isBoolean()
|
check('isHiring').optional().isBoolean()
|
||||||
];
|
];
|
||||||
|
|
||||||
const deleteOrGetOrganizationValidator = [
|
const deleteOrGetOrganizationValidator = [
|
||||||
|
|
|
@ -17,7 +17,7 @@ const {
|
||||||
} = require("express-validator"); // This is the destructuring part. It specifies which properties of the imported object (express-validator) you want to extract.
|
} = require("express-validator"); // This is the destructuring part. It specifies which properties of the imported object (express-validator) you want to extract.
|
||||||
|
|
||||||
const registerValidator = [
|
const registerValidator = [
|
||||||
check('display_name').trim().notEmpty().escape().isLength({
|
check('displayName').trim().notEmpty().escape().isLength({
|
||||||
max: 128
|
max: 128
|
||||||
}),
|
}),
|
||||||
check('email').isEmail().normalizeEmail().escape().isLength({
|
check('email').isEmail().normalizeEmail().escape().isLength({
|
||||||
|
@ -26,12 +26,12 @@ const registerValidator = [
|
||||||
check('password').isLength({
|
check('password').isLength({
|
||||||
min: 5
|
min: 5
|
||||||
}).trim().escape().withMessage('Password must be at least 5 characters'),
|
}).trim().escape().withMessage('Password must be at least 5 characters'),
|
||||||
check('date_of_birth').optional().isDate().withMessage('Invalid date format. Date must be YYYY-MM-DD'),
|
check('dateOfBirth').optional().isDate().withMessage('Invalid date format. Date must be YYYY-MM-DD'),
|
||||||
check('available').optional().isBoolean(),
|
check('openToWork').optional().isBoolean(),
|
||||||
check('place_of_living').isLength({
|
check('placeOfLiving').isLength({
|
||||||
max: 128
|
max: 128
|
||||||
}).escape(),
|
}).escape(),
|
||||||
check('about_me').isLength({
|
check('aboutMe').isLength({
|
||||||
max: 4096
|
max: 4096
|
||||||
}).escape(),
|
}).escape(),
|
||||||
check('qualification').isLength({
|
check('qualification').isLength({
|
||||||
|
@ -45,22 +45,22 @@ const getTokenValidator = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const updatePersonValidator = [
|
const updatePersonValidator = [
|
||||||
check('display_name').trim().escape().isLength({
|
check('displayName').trim().escape().isLength({
|
||||||
max: 128
|
max: 128
|
||||||
}),
|
}),
|
||||||
check('date_of_birth').optional().isDate().withMessage('Invalid date format. Date must be YYYY-MM-DD'),
|
check('dateOfBirth').optional().isDate().withMessage('Invalid date format. Date must be YYYY-MM-DD'),
|
||||||
check('available').optional().isBoolean(),
|
check('openToWork').optional().isBoolean(),
|
||||||
check('place_of_living').isLength({
|
check('placeOfLiving').optional().isLength({
|
||||||
max: 128
|
max: 128
|
||||||
}).escape(),
|
}).escape(),
|
||||||
check('about_me').isLength({
|
check('aboutMe').optional().isLength({
|
||||||
max: 4096
|
max: 4096
|
||||||
}).escape(),
|
}).escape(),
|
||||||
check('qualification').isLength({
|
check('qualification').optional().isLength({
|
||||||
max: 64
|
max: 64
|
||||||
}).escape(),
|
}).escape(),
|
||||||
check('old_password').trim().escape(),
|
check('oldPassword').optional().trim().escape(),
|
||||||
check('new_password').isLength({
|
check('newPassword').optional().isLength({
|
||||||
min: 5
|
min: 5
|
||||||
}).trim().escape().withMessage('Password must be at least 5 characters'),
|
}).trim().escape().withMessage('Password must be at least 5 characters'),
|
||||||
];
|
];
|
||||||
|
|
|
@ -2,12 +2,4 @@
|
||||||
|
|
||||||
-- DROP DATABASE IF EXISTS "Blink";
|
-- DROP DATABASE IF EXISTS "Blink";
|
||||||
|
|
||||||
CREATE DATABASE "Blink"
|
CREATE DATABASE "Blink";
|
||||||
WITH
|
|
||||||
OWNER = pg_database_owner
|
|
||||||
ENCODING = 'UTF8'
|
|
||||||
LC_COLLATE = 'Italian_Italy.1252'
|
|
||||||
LC_CTYPE = 'Italian_Italy.1252'
|
|
||||||
TABLESPACE = pg_default
|
|
||||||
CONNECTION LIMIT = -1
|
|
||||||
IS_TEMPLATE = False;
|
|
|
@ -1,140 +1,117 @@
|
||||||
|
CREATE TYPE Visibility AS ENUM ('NOBODY', 'THIS_INSTANCE', 'EVERYONE');
|
||||||
|
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');
|
||||||
|
|
||||||
-- Table: Person
|
-- Table: Person
|
||||||
CREATE TABLE IF NOT EXISTS "Person" (
|
CREATE TABLE IF NOT EXISTS "Person" (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
email character varying(128) NOT NULL UNIQUE,
|
email CHARACTER VARYING(128) NOT NULL UNIQUE,
|
||||||
-- Primary e-mail
|
password CHARACTER VARYING(128) NOT NULL,
|
||||||
password character varying(128) NOT NULL,
|
display_name CHARACTER VARYING(128) NOT NULL,
|
||||||
display_name character varying(128) NOT NULL,
|
|
||||||
date_of_birth date,
|
date_of_birth date,
|
||||||
available boolean,
|
place_of_living CHARACTER VARYING(128),
|
||||||
-- Whether this person is available to be hired
|
about_me CHARACTER VARYING(4096),
|
||||||
enabled boolean NOT NULL DEFAULT false,
|
qualification CHARACTER VARYING(64),
|
||||||
-- Whether this profile is active
|
open_to_work BOOLEAN,
|
||||||
place_of_living character varying(128),
|
enabled BOOLEAN NOT NULL DEFAULT false,
|
||||||
about_me character varying(4096),
|
visibility Visibility NOT NULL
|
||||||
qualification character varying(64)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: ActivationLink
|
-- Table: ActivationLink
|
||||||
CREATE TABLE IF NOT EXISTS "ActivationLink" (
|
CREATE TABLE IF NOT EXISTS "ActivationLink" (
|
||||||
identifier character varying PRIMARY KEY,
|
identifier CHARACTER VARYING PRIMARY KEY,
|
||||||
person_id integer NOT NULL,
|
person_id INTEGER NOT NULL,
|
||||||
CONSTRAINT "PersonActivationLinkFK" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
|
CONSTRAINT "PersonActivationLinkFK" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: Organization
|
-- Table: Organization
|
||||||
CREATE TABLE IF NOT EXISTS "Organization" (
|
CREATE TABLE IF NOT EXISTS "Organization" (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name character varying(128) NOT NULL,
|
name CHARACTER VARYING(128) NOT NULL,
|
||||||
location character varying,
|
location CHARACTER VARYING,
|
||||||
description text
|
description TEXT,
|
||||||
);
|
is_hiring BOOLEAN
|
||||||
|
|
||||||
-- Table: OrganizationPost
|
|
||||||
CREATE TABLE IF NOT EXISTS "OrganizationPost" (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
organization_id integer NOT NULL,
|
|
||||||
content text NOT NULL,
|
|
||||||
created_at timestamp without time zone DEFAULT now(),
|
|
||||||
original_author integer NOT NULL,
|
|
||||||
CONSTRAINT "AuthorIdFK" FOREIGN KEY (original_author) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE NOT VALID,
|
|
||||||
CONSTRAINT "OrganizationIdFk" FOREIGN KEY (organization_id) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE NOT VALID
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: OrganizationAdministrator
|
-- Table: OrganizationAdministrator
|
||||||
CREATE TABLE IF NOT EXISTS "OrganizationAdministrator" (
|
CREATE TABLE IF NOT EXISTS "OrganizationAdministrator" (
|
||||||
id_person integer NOT NULL,
|
id_person INTEGER NOT NULL,
|
||||||
id_organization integer NOT NULL,
|
id_organization INTEGER NOT NULL,
|
||||||
CONSTRAINT "OrganizationAdministrator_pkey" PRIMARY KEY (id_organization, id_person),
|
CONSTRAINT "OrganizationAdministrator_pkey" PRIMARY KEY (id_organization, id_person),
|
||||||
CONSTRAINT "OrganizationAdministratorOrganizationId" FOREIGN KEY (id_organization) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE NOT VALID,
|
CONSTRAINT "OrganizationAdministratorOrganizationId" FOREIGN KEY (id_organization) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE NOT VALID,
|
||||||
CONSTRAINT "OrganizationAdministratorUserId" FOREIGN KEY (id_person) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
|
CONSTRAINT "OrganizationAdministratorUserId" FOREIGN KEY (id_person) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: Message
|
|
||||||
CREATE TABLE IF NOT EXISTS "Message" (
|
|
||||||
id serial NOT NULL,
|
|
||||||
person_id integer NOT NULL,
|
|
||||||
organization_id integer NOT NULL,
|
|
||||||
author_on_behalf_of_organization integer,
|
|
||||||
"timestamp" timestamp without time zone NOT NULL,
|
|
||||||
content character varying(4096) NOT NULL,
|
|
||||||
sender_type character varying(12) NOT NULL,
|
|
||||||
CONSTRAINT "Message_pkey" PRIMARY KEY (id),
|
|
||||||
CONSTRAINT "Message_author_on_behalf_of_company_fkey" FOREIGN KEY (
|
|
||||||
author_on_behalf_of_organization
|
|
||||||
) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE,
|
|
||||||
CONSTRAINT "Message_organization_id_fkey" FOREIGN KEY (organization_id) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE,
|
|
||||||
CONSTRAINT "Message_person_id_fkey" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE,
|
|
||||||
CONSTRAINT "Message_sender_type_check" CHECK (
|
|
||||||
sender_type :: text = 'ORGANIZATION' :: text
|
|
||||||
OR sender_type :: text = 'PERSON' :: text
|
|
||||||
),
|
|
||||||
CONSTRAINT "Message_sender_constraint" CHECK (
|
|
||||||
author_on_behalf_of_organization IS NULL
|
|
||||||
AND sender_type :: text = 'PERSON' :: text
|
|
||||||
OR author_on_behalf_of_organization IS NOT NULL
|
|
||||||
AND sender_type :: text = 'ORGANIZATION' :: text
|
|
||||||
)
|
|
||||||
);
|
|
||||||
COMMENT ON COLUMN "Message".sender_type IS 'sender_type can be either be PERSON or ORGANIZATION, depending who''s sending the message. It is PERSON if and only if author_on_behalf_of_organization is NULL. It is ORGANIZATION if and only if author_on_behalf_of_organization is NOT NULL. This column may seem redundant if we already know how to identify a sender, but it does give more clarity to the API implementers';
|
|
||||||
COMMENT ON CONSTRAINT "Message_sender_type_check" ON "Message" IS 'We want the sender to be either PERSON or ORGANIZATION';
|
|
||||||
COMMENT ON CONSTRAINT "Message_sender_constraint" ON "Message" IS 'If ''author_on_behalf_of_organization'' is NULL, then the sender is a person, instead, if ''author_on_behalf_of_organization'' is not NULL, the sender is a organization';
|
|
||||||
|
|
||||||
-- Table: Tag
|
-- Table: Tag
|
||||||
CREATE TABLE IF NOT EXISTS "Tag" (
|
CREATE TABLE IF NOT EXISTS "Tag" (
|
||||||
id SERIAL,
|
id SERIAL PRIMARY KEY,
|
||||||
tag character varying(256) NOT NULL,
|
tag CHARACTER VARYING(256) NOT NULL
|
||||||
CONSTRAINT "Tag_pkey" PRIMARY KEY (id)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: JobOffer
|
-- Table: JobOffer
|
||||||
CREATE TABLE IF NOT EXISTS "JobOffer" (
|
CREATE TABLE IF NOT EXISTS "JobOffer" (
|
||||||
id SERIAL,
|
id SERIAL PRIMARY KEY,
|
||||||
title character varying(2048) NOT NULL,
|
organization_id INTEGER,
|
||||||
description character varying(4096),
|
title CHARACTER VARYING(2048) NOT NULL,
|
||||||
requirements character varying(4096),
|
description CHARACTER VARYING(4096),
|
||||||
salary money NOT NULL,
|
salary int4range,
|
||||||
salary_frequency character varying(64) NOT NULL,
|
salary_currency SalaryCurrency,
|
||||||
salary_currency character varying(64) NOT NULL,
|
location CHARACTER VARYING(256),
|
||||||
location character varying(256),
|
remote RemotePosition NOT NULL,
|
||||||
organization_id integer,
|
contract_type ContractType,
|
||||||
CONSTRAINT "JobOffer_pkey" PRIMARY KEY (id),
|
|
||||||
CONSTRAINT "OrganizationFK" FOREIGN KEY (organization_id) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE NOT VALID
|
CONSTRAINT "OrganizationFK" FOREIGN KEY (organization_id) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE NOT VALID
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: JobOfferTag
|
-- Table: JobOfferTag
|
||||||
-- This table allows to create a N-to-N map between Tag and JobOffer
|
-- This table allows to create a N-to-N map between Tag and JobOffer
|
||||||
CREATE TABLE IF NOT EXISTS "JobOfferTag" (
|
CREATE TABLE IF NOT EXISTS "JobOfferTag" (
|
||||||
id serial,
|
id SERIAL PRIMARY KEY,
|
||||||
job_offer_id integer NOT NULL,
|
job_offer_id INTEGER NOT NULL,
|
||||||
tag_id integer NOT NULL,
|
tag_id INTEGER NOT NULL,
|
||||||
CONSTRAINT "JobOfferTag_pkey" PRIMARY KEY (id),
|
|
||||||
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,
|
||||||
CONSTRAINT "TagFk" FOREIGN KEY (tag_id) REFERENCES "Tag" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
|
CONSTRAINT "TagFk" FOREIGN KEY (tag_id) REFERENCES "Tag" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Table: Applicant
|
||||||
|
CREATE TABLE IF NOT EXISTS "Applicant" (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
person_id INTEGER NOT NULL,
|
||||||
|
job_offer_id INTEGER NOT NULL,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table: Skills
|
||||||
|
CREATE TABLE IF NOT EXISTS "Skill" (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
person_id INTEGER NOT NULL,
|
||||||
|
tag_id INTEGER NOT NULL,
|
||||||
|
CONSTRAINT "PersonFk" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
CONSTRAINT "TagFk" FOREIGN KEY (tag_id) REFERENCES "Tag" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
-- Table: RequestResetPassword
|
-- Table: RequestResetPassword
|
||||||
CREATE TABLE IF NOT EXISTS "RequestResetPassword" (
|
CREATE TABLE IF NOT EXISTS "RequestResetPassword" (
|
||||||
id serial,
|
id SERIAL,
|
||||||
email character varying(128) NOT NULL,
|
email CHARACTER VARYING(128) NOT NULL,
|
||||||
secret character varying NOT NULL,
|
secret CHARACTER VARYING NOT NULL,
|
||||||
time_of_request timestamp without time zone NOT NULL DEFAULT now(),
|
time_of_request timestamp without time zone NOT NULL DEFAULT now(),
|
||||||
CONSTRAINT "RequestResetPassword_pkey" PRIMARY KEY (id)
|
CONSTRAINT "RequestResetPassword_pkey" PRIMARY KEY (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Table: Experience
|
-- Table: Experience
|
||||||
CREATE TABLE IF NOT EXISTS "Experience" (
|
CREATE TABLE IF NOT EXISTS "Experience" (
|
||||||
id serial,
|
id SERIAL PRIMARY KEY,
|
||||||
title character varying(128) NOT NULL,
|
title CHARACTER VARYING(128) NOT NULL,
|
||||||
description text NOT NULL,
|
description TEXT NOT NULL,
|
||||||
organization character varying(128) NOT NULL,
|
organization CHARACTER VARYING(128) NOT NULL,
|
||||||
|
organization_id INTEGER,
|
||||||
date daterange NOT NULL,
|
date daterange NOT NULL,
|
||||||
organization_id integer,
|
person_id INTEGER NOT NULL,
|
||||||
person_id integer NOT NULL,
|
type ExperienceType NOT NULL,
|
||||||
type character varying(32) NOT NULL,
|
CONSTRAINT "OrganizationFk" FOREIGN KEY (organization_id) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE SET NULL,
|
||||||
CONSTRAINT "Experience_pkey" PRIMARY KEY (id),
|
|
||||||
CONSTRAINT "OrganizationFk" FOREIGN KEY (organization_id) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE
|
|
||||||
SET
|
|
||||||
NULL,
|
|
||||||
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
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue