Compare commits

...

4 Commits

Author SHA1 Message Date
xfarrow 6023ee56d0 Update 1-create_tables.sql 2024-10-17 18:01:19 +02:00
xfarrow 10aa3460a3 update 2024-10-17 17:11:56 +02:00
xfarrow ca5e201dc4 update 2024-10-17 15:44:20 +02:00
xfarrow 5835bfbc16 person ok 2024-10-17 15:37:34 +02:00
9 changed files with 126 additions and 369 deletions

View File

@ -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);
/* /*

View File

@ -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
};

View File

@ -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,12 +81,12 @@ 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,
identifier: activationLink identifier: activationLink
}); });
} }
return insertedPerson[0]; return insertedPerson[0];
}); });

View File

@ -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
};

View File

@ -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'

View File

@ -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 = [

View File

@ -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'),
]; ];

View File

@ -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;

View File

@ -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 "PersonFk" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
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
); );