diff --git a/backend/apis/nodejs/src/models/organization_post_model.js b/backend/apis/nodejs/src/models/organization_post_model.js deleted file mode 100644 index 91ed93b..0000000 --- a/backend/apis/nodejs/src/models/organization_post_model.js +++ /dev/null @@ -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 -}; \ No newline at end of file diff --git a/backend/apis/nodejs/src/models/person_model.js b/backend/apis/nodejs/src/models/person_model.js index f5d55f7..9800fae 100644 --- a/backend/apis/nodejs/src/models/person_model.js +++ b/backend/apis/nodejs/src/models/person_model.js @@ -25,17 +25,18 @@ const bcrypt = require('bcrypt'); * @param {*} placeOfLiving * @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 = { email: email.toLowerCase(), password, display_name: displayName, date_of_birth: dateOfBirth, - available, - enabled, + open_to_work: openToWork, place_of_living: placeOfLiving, about_me: aboutMe, - qualification + qualification, + enabled, + visibility: "EVERYONE" }; return person; } @@ -80,12 +81,12 @@ async function insert(person, activationLink) { const insertedPerson = await tr('Person') .insert(person) .returning('*'); - if(activationLink != null){ + if (activationLink != null) { await tr('ActivationLink') - .insert({ - person_id: insertedPerson[0].id, - identifier: activationLink - }); + .insert({ + person_id: insertedPerson[0].id, + identifier: activationLink + }); } return insertedPerson[0]; }); diff --git a/backend/apis/nodejs/src/routes/organization_post_routes.js b/backend/apis/nodejs/src/routes/organization_post_routes.js deleted file mode 100644 index a4d0e25..0000000 --- a/backend/apis/nodejs/src/routes/organization_post_routes.js +++ /dev/null @@ -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 -}; \ No newline at end of file diff --git a/backend/apis/nodejs/src/routes/person_routes.js b/backend/apis/nodejs/src/routes/person_routes.js index ebca732..e6a7c02 100644 --- a/backend/apis/nodejs/src/routes/person_routes.js +++ b/backend/apis/nodejs/src/routes/person_routes.js @@ -16,7 +16,7 @@ const jwtUtils = require('../utils/jwt_utils'); const bcrypt = require('bcrypt'); const crypto = require('crypto'); const Person = require('../models/person_model'); -const activationModel = require('../models/activation_model'); +const Activation = require('../models/activation_model'); const express = require('express'); const mailUtils = require('../utils/mail_utils'); @@ -66,13 +66,14 @@ async function registerPerson(req, res) { const personToInsert = Person.createPerson( req.body.email, await hashPasswordPromise, - req.body.display_name, - req.body.date_of_birth, - req.body.available, + req.body.displayName, + req.body.dateOfBirth, + req.body.placeOfLiving, + req.body.aboutMe, + req.body.qualification, + req.body.openToWork, isEnabled, - req.body.place_of_living, - req.body.about_me, - req.body.qualification); + ); const insertedPerson = await Person.insert(personToInsert, activationCode); delete insertedPerson.password; @@ -190,8 +191,8 @@ async function getMyself(req, res) { * not present, they shall be ignored. An user can * only update themselves * - * Required field(s): At least one. Both old_password and - * new_password if updating the password. + * Required field(s): At least one. Both oldPassword and + * newPassword if updating the password. * */ async function updatePerson(req, res) { @@ -205,46 +206,50 @@ async function updatePerson(req, res) { const updatePerson = {}; - if (req.body.display_name !== undefined) { - updatePerson.display_name = req.body.display_name; + if (req.body.displayName !== undefined) { + updatePerson.display_name = req.body.displayName; } - if (req.body.date_of_birth !== undefined) { - updatePerson.date_of_birth = req.body.date_of_birth; + if (req.body.dateOfBirth !== undefined) { + updatePerson.date_of_birth = req.body.dateOfBirth; } - if (req.body.available !== undefined) { - updatePerson.available = req.body.available; + if (req.body.openToWork !== undefined) { + updatePerson.open_to_work = req.body.openToWork; } - if (req.body.place_of_living !== undefined) { - updatePerson.place_of_living = req.body.place_of_living; + if (req.body.placeOfLiving !== undefined) { + updatePerson.place_of_living = req.body.placeOfLiving; } - if (req.body.about_me !== undefined) { - updatePerson.about_me = req.body.about_me; + if (req.body.aboutMe !== undefined) { + updatePerson.about_me = req.body.aboutMe; } if (req.body.qualification !== undefined) { 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 (req.body.old_password !== undefined || req.body.new_password !== undefined) { - if (req.body.old_password === undefined) { + if (req.body.oldPassword !== undefined || req.body.newPassword !== undefined) { + if (req.body.oldPassword === undefined) { return res.status(401).json({ error: 'The old password must be specified' }); } - if (req.body.new_password === undefined) { + if (req.body.newPassword === undefined) { return res.status(401).json({ error: 'The new password must be specified' }); } 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) { - updatePerson.password = await bcrypt.hash(req.body.new_password, 10); + updatePerson.password = await bcrypt.hash(req.body.newPassword, 10); } else { return res.status(401).json({ error: 'Password verification failed' @@ -306,7 +311,7 @@ async function confirmActivation(req, res) { errors: errors.array() }); } - const personId = await activationModel.getPersonIdByIdentifier(req.body.code); + const personId = await Activation.getPersonIdByIdentifier(req.body.code); if (!personId) { return res.status(401).json({ error: 'Activation Link either not valid or expired' diff --git a/backend/apis/nodejs/src/utils/validators/person_validator.js b/backend/apis/nodejs/src/utils/validators/person_validator.js index 41aae1b..24f2251 100644 --- a/backend/apis/nodejs/src/utils/validators/person_validator.js +++ b/backend/apis/nodejs/src/utils/validators/person_validator.js @@ -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. const registerValidator = [ - check('display_name').trim().notEmpty().escape().isLength({ + check('displayName').trim().notEmpty().escape().isLength({ max: 128 }), check('email').isEmail().normalizeEmail().escape().isLength({ @@ -26,12 +26,12 @@ const registerValidator = [ check('password').isLength({ min: 5 }).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('available').optional().isBoolean(), - check('place_of_living').isLength({ + check('dateOfBirth').optional().isDate().withMessage('Invalid date format. Date must be YYYY-MM-DD'), + check('openToWork').optional().isBoolean(), + check('placeOfLiving').isLength({ max: 128 }).escape(), - check('about_me').isLength({ + check('aboutMe').isLength({ max: 4096 }).escape(), check('qualification').isLength({ @@ -45,15 +45,15 @@ const getTokenValidator = [ ]; const updatePersonValidator = [ - check('display_name').trim().escape().isLength({ + check('displayName').trim().escape().isLength({ max: 128 }), - check('date_of_birth').optional().isDate().withMessage('Invalid date format. Date must be YYYY-MM-DD'), - check('available').optional().isBoolean(), - check('place_of_living').isLength({ + check('dateOfBirth').optional().isDate().withMessage('Invalid date format. Date must be YYYY-MM-DD'), + check('openToWork').optional().isBoolean(), + check('placeOfLiving').isLength({ max: 128 }).escape(), - check('about_me').isLength({ + check('aboutMe').isLength({ max: 4096 }).escape(), check('qualification').isLength({ diff --git a/backend/sql/0-create_databse.sql b/backend/sql/0-create_databse.sql index 013346c..8d59674 100644 --- a/backend/sql/0-create_databse.sql +++ b/backend/sql/0-create_databse.sql @@ -2,12 +2,4 @@ -- DROP DATABASE IF EXISTS "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; \ No newline at end of file +CREATE DATABASE "Blink"; \ No newline at end of file diff --git a/backend/sql/1-create_tables.sql b/backend/sql/1-create_tables.sql index 2d93efd..85ef5c4 100644 --- a/backend/sql/1-create_tables.sql +++ b/backend/sql/1-create_tables.sql @@ -1,140 +1,115 @@ +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'); + -- Table: Person CREATE TABLE IF NOT EXISTS "Person" ( id SERIAL PRIMARY KEY, - email character varying(128) NOT NULL UNIQUE, - -- Primary e-mail - password character varying(128) NOT NULL, - display_name character varying(128) NOT NULL, + email CHARACTER VARYING(128) NOT NULL UNIQUE, + password CHARACTER VARYING(128) NOT NULL, + display_name CHARACTER VARYING(128) NOT NULL, date_of_birth date, - available boolean, - -- Whether this person is available to be hired - enabled boolean NOT NULL DEFAULT false, - -- Whether this profile is active - place_of_living character varying(128), - about_me character varying(4096), - qualification character varying(64) + place_of_living CHARACTER VARYING(128), + about_me CHARACTER VARYING(4096), + qualification CHARACTER VARYING(64), + open_to_work BOOLEAN, + enabled BOOLEAN NOT NULL DEFAULT false, + visibility Visibility NOT NULL ); -- Table: ActivationLink CREATE TABLE IF NOT EXISTS "ActivationLink" ( - identifier character varying PRIMARY KEY, - person_id integer NOT NULL, + identifier CHARACTER VARYING PRIMARY KEY, + person_id INTEGER NOT NULL, CONSTRAINT "PersonActivationLinkFK" FOREIGN KEY (person_id) REFERENCES "Person" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE ); -- Table: Organization CREATE TABLE IF NOT EXISTS "Organization" ( id SERIAL PRIMARY KEY, - name character varying(128) NOT NULL, - location character varying, - description text -); - --- 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 + name CHARACTER VARYING(128) NOT NULL, + location CHARACTER VARYING, + description TEXT ); -- Table: OrganizationAdministrator CREATE TABLE IF NOT EXISTS "OrganizationAdministrator" ( - id_person integer NOT NULL, - id_organization integer NOT NULL, + id_person INTEGER NOT NULL, + id_organization INTEGER NOT NULL, 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 "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 CREATE TABLE IF NOT EXISTS "Tag" ( - id SERIAL, - tag character varying(256) NOT NULL, - CONSTRAINT "Tag_pkey" PRIMARY KEY (id) + id SERIAL PRIMARY KEY, + tag CHARACTER VARYING(256) NOT NULL ); -- Table: JobOffer CREATE TABLE IF NOT EXISTS "JobOffer" ( - id SERIAL, - title character varying(2048) NOT NULL, - description character varying(4096), - requirements character varying(4096), - salary money NOT NULL, - salary_frequency character varying(64) NOT NULL, - salary_currency character varying(64) NOT NULL, - location character varying(256), - organization_id integer, - CONSTRAINT "JobOffer_pkey" PRIMARY KEY (id), + id SERIAL PRIMARY KEY, + organization_id INTEGER, + title CHARACTER VARYING(2048) NOT NULL, + description CHARACTER VARYING(4096), + salary int4range, + salary_currency SalaryCurrency, + location CHARACTER VARYING(256), + remote RemotePosition NOT NULL, + contract_type ContractType, CONSTRAINT "OrganizationFK" FOREIGN KEY (organization_id) REFERENCES "Organization" (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE NOT VALID ); -- Table: JobOfferTag -- This table allows to create a N-to-N map between Tag and JobOffer CREATE TABLE IF NOT EXISTS "JobOfferTag" ( - id serial, - job_offer_id integer NOT NULL, - tag_id integer NOT NULL, - CONSTRAINT "JobOfferTag_pkey" PRIMARY KEY (id), + id SERIAL PRIMARY KEY, + job_offer_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, 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 ); +-- 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 CREATE TABLE IF NOT EXISTS "RequestResetPassword" ( - id serial, - email character varying(128) NOT NULL, - secret character varying NOT NULL, + id SERIAL, + email CHARACTER VARYING(128) NOT NULL, + secret CHARACTER VARYING NOT NULL, time_of_request timestamp without time zone NOT NULL DEFAULT now(), CONSTRAINT "RequestResetPassword_pkey" PRIMARY KEY (id) ); -- Table: Experience CREATE TABLE IF NOT EXISTS "Experience" ( - id serial, - title character varying(128) NOT NULL, - description text NOT NULL, - organization character varying(128) NOT NULL, + id SERIAL PRIMARY KEY, + title CHARACTER VARYING(128) NOT NULL, + description TEXT NOT NULL, + organization CHARACTER VARYING(128) NOT NULL, + organization_id INTEGER, date daterange NOT NULL, - organization_id integer, - person_id integer NOT NULL, - type character varying(32) NOT 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 + person_id INTEGER NOT NULL, + type ExperienceType NOT NULL, + 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 );