From 7e94e3c1a977b83c351a3d623d47ad1d15ee6891 Mon Sep 17 00:00:00 2001 From: Gwynn DP Date: Sun, 12 Dec 2021 11:13:27 -0800 Subject: [PATCH] feat: link stakeholders --- server/handlers/stakeholderHandler.js | 99 ++++++++-- server/models/Stakeholder.js | 198 +++++++++++++------ server/repositories/StakeholderRepository.js | 194 +++++++++++++----- server/routes.js | 23 ++- 4 files changed, 382 insertions(+), 132 deletions(-) diff --git a/server/handlers/stakeholderHandler.js b/server/handlers/stakeholderHandler.js index 50f9aa4..ec9e61b 100644 --- a/server/handlers/stakeholderHandler.js +++ b/server/handlers/stakeholderHandler.js @@ -2,10 +2,12 @@ const Joi = require('joi'); const log = require('loglevel'); const { - createStakeholder, getStakeholders, getAllStakeholders, + getUnlinkedStakeholders, + updateLinkStakeholder, updateStakeholder, + createStakeholder, } = require('../models/Stakeholder'); // const { dispatch } = require('../models/DomainEvent'); @@ -53,7 +55,6 @@ const stakeholderGetAll = async (req, res) => { const stakeholderGet = async function (req, res) { const filter = req.query.filter ? JSON.parse(req.query.filter) : {}; - // console.log('filter', filter); const query = { ...req.query, filter }; await stakeholderGetQuerySchema.validateAsync(query, { abortEarly: false, @@ -71,29 +72,90 @@ const stakeholderGet = async function (req, res) { res.end(); }; -const stakeholderPost = async function (req, res) { +const stakeholderGetUnlinked = async function (req, res) { + const { stakeholder_id } = req.params; + const session = new Session(false); + const stakeholderRepo = new StakeholderRepository(session); + const executeGetStakeholder = getUnlinkedStakeholders( + stakeholderRepo, + Number(stakeholder_id), + ); + const result = await executeGetStakeholder(); + console.log('result', result.stakeholders.length); + res.send(result); + res.end(); +}; + +const stakeholderUpdateLink = async function (req, res, next) { + const { stakeholder_id } = req.params; + const session = new Session(); + const stakeholderRepo = new StakeholderRepository(session); + const executeUpdateLink = updateLinkStakeholder( + stakeholderRepo, + Number(stakeholder_id), + ); + + // only fields that are required to have a value + const updateStakeholderSchema = Joi.object({ + id: Joi.number().required(), + type: Joi.string().required(), + linked: Joi.boolean().required(), + }); + + console.log('req.body', req.body); + + try { + // const value = await updateStakeholderSchema + // .unknown(true) + // .validateAsync(req.body, { + // abortEarly: false, + // }); + + const result = await executeUpdateLink(req.body); + + res.send(result); + res.end(); + } catch (e) { + // if (session.isTransactionInProgress()) { + // await session.rollbackTransaction(); + // } + next(e); + } +}; + +const stakeholderPost = async function (req, res, next) { + const { stakeholder_id } = req.params; const session = new Session(); const stakeholderRepo = new StakeholderRepository(session); // const eventRepository = new EventRepository(session); const executeCreateStakeholder = createStakeholder( stakeholderRepo, - id, + stakeholder_id, // eventRepository, ); // const eventDispatch = dispatch(eventRepository, publishMessage); + // only fields that are required to have a value + const stakeholderPostSchema = Joi.object({ + type: Joi.string(), + email: Joi.string(), + phone: Joi.string(), + }).unknown(); + try { - console.log('STAKEHOLDER ROUTER post', req.body); - const stakeholderObj = stakeholderFromRequest({ ...req.body }); - await session.beginTransaction(); - const { newStakeholder /*raisedEvents*/ } = await executeCreateStakeholder( - stakeholderObj, - ); + const value = await stakeholderPostSchema.validateAsync(req.body, { + abortEarly: false, + }); - console.log('STAKEHOLDER ROUTER newStakeholder', newStakeholder); + // await session.beginTransaction(); + const { newStakeholder /*raisedEvents*/ } = await executeCreateStakeholder({ + ...value, + }); + + console.log('STAKEHOLDER ROUTER newStakeholder', value, newStakeholder); - await session.commitTransaction(); + // await session.commitTransaction(); // raisedEvents.forEach((domainEvent) => // eventDispatch('stakeholder-created', domainEvent), // ); @@ -102,11 +164,10 @@ const stakeholderPost = async function (req, res) { }); } catch (e) { log.error(e); - if (session.isTransactionInProgress()) { - await session.rollbackTransaction(); - } - let result = e; - res.status(422).json({ ...result }); + // if (session.isTransactionInProgress()) { + // await session.rollbackTransaction(); + // } + res.status(422).json({ ...e }); } }; @@ -119,7 +180,7 @@ const stakeholderPatch = async function (req, res, next) { stakeholder_id, ); - // remove fields that aren't required to have a value + // only fields that are required to have a value const updateStakeholderSchema = Joi.object({ id: Joi.number().required(), stakeholder_uuid: Joi.string().required(), @@ -152,6 +213,8 @@ const stakeholderPatch = async function (req, res, next) { module.exports = { stakeholderGet, stakeholderGetAll, + stakeholderGetUnlinked, + stakeholderUpdateLink, stakeholderPost, stakeholderPatch, }; diff --git a/server/models/Stakeholder.js b/server/models/Stakeholder.js index a95b1d1..e3f4e22 100644 --- a/server/models/Stakeholder.js +++ b/server/models/Stakeholder.js @@ -3,51 +3,48 @@ const { v4: uuidv4 } = require('uuid'); const { camelToSnakeCase } = require('../utils/utils'); -const StakeholderRequestObject = ({ - id, - stakeholder_uuid, +const StakeholderPostObject = ({ + // id, + // stakeholder_uuid, type, org_name, first_name, last_name, email, phone, - pwd_reset_required, + // pwd_reset_required, website, - wallet, - password, - salt, - active_contract_id, - offering_pay_to_plant, - tree_validation_contract_id, + // wallet, + // password, + // salt, + // active_contract_id, + // offering_pay_to_plant, + // tree_validation_contract_id, logo_url, map, - filter, }) => { return Object.freeze({ - id: id || uuidv4, - stakeholder_uuid, + stakeholder_uuid: uuidv4(), // give it a uuid, type, org_name, first_name, last_name, email, phone, - pwd_reset_required, + // pwd_reset_required, website, - wallet, - password, - salt, - active_contract_id, - offering_pay_to_plant, - tree_validation_contract_id, + // wallet, + // password, + // salt, + // active_contract_id, + // offering_pay_to_plant, + // tree_validation_contract_id, logo_url, map, - filter, }); }; -const Stakeholder = ({ +const StakeholderTree = ({ id, stakeholder_uuid, type, @@ -56,14 +53,14 @@ const Stakeholder = ({ last_name, email, phone, - pwd_reset_required, + // pwd_reset_required, website, - wallet, - password, - salt, - active_contract_id, - offering_pay_to_plant, - tree_validation_contract_id, + // wallet, + // password, + // salt, + // active_contract_id, + // offering_pay_to_plant, + // tree_validation_contract_id, logo_url, map, children = [], @@ -78,14 +75,14 @@ const Stakeholder = ({ last_name, email, phone, - pwd_reset_required, + // pwd_reset_required, website, - wallet, - password, - salt, - active_contract_id, - offering_pay_to_plant, - tree_validation_contract_id, + // wallet, + // password, + // salt, + // active_contract_id, + // offering_pay_to_plant, + // tree_validation_contract_id, logo_url, map, children, @@ -93,6 +90,34 @@ const Stakeholder = ({ }); }; +const Stakeholder = ({ + id, + stakeholder_uuid, + type, + org_name, + first_name, + last_name, + email, + phone, + website, + logo_url, + map, +}) => { + return Object.freeze({ + id, + stakeholder_uuid, + type, + org_name, + first_name, + last_name, + email, + phone, + website, + logo_url, + map, + }); +}; + const FilterCriteria = ({ id = null, stakeholder_uuid = null, @@ -167,7 +192,7 @@ const getAllStakeholders = async ({ filter: { where, order }, ...idFilters } = undefined, url) => { let filter = {}; filter = FilterCriteria({ ...idFilters, ...where }); - + console.log('getAllStakeholders --> WHERE, FILTER ------> ', where, filter); // use default limit and offset values until there is more info on whether used & how updated let options = { limit: 100, offset: 0 }; options = { @@ -198,7 +223,7 @@ const getAllStakeholders = stakeholders: stakeholders && stakeholders.map((row) => { - return Stakeholder({ ...row }); + return StakeholderTree({ ...row }); }), totalCount: count, links: { @@ -213,7 +238,7 @@ const getStakeholders = async ({ filter: { where, order }, ...idFilters } = undefined, url) => { let filter = {}; filter = FilterCriteria({ ...idFilters, ...where }); - console.log('------> WHERE, FILTER ------> ', where, filter); + console.log('getStakeholders --> WHERE, FILTER ------> ', where, filter); // use default limit and offset values until there is more info on whether used & how updated let options = { limit: 100, offset: 0 }; options = { @@ -254,7 +279,7 @@ const getStakeholders = stakeholders: stakeholders && stakeholders.map((row) => { - return Stakeholder({ ...row }); + return StakeholderTree({ ...row }); }), totalCount: count, links: { @@ -264,20 +289,65 @@ const getStakeholders = }; }; -const updateStakeholder = +const getUnlinkedStakeholders = + (stakeholderRepo, acctStakeholder_id) => async () => { + const { stakeholders, count } = + await stakeholderRepo.getUnlinkedStakeholders(acctStakeholder_id); + + return { + stakeholders: + stakeholders && + stakeholders.map((row) => { + return Stakeholder({ ...row }); + }), + totalCount: count, + }; + }; + +const updateLinkStakeholder = (stakeholderRepo, acctStakeholder_id = null) => async (object) => { - // const relatedStakeholders = await stakeholderRepo.getRelatedIds( - // acctStakeholder_id, - // ); + // const object = Stakeholder({ ...requestBody }); + + const acctStakeholder = await stakeholderRepo.getStakeholderById( + acctStakeholder_id, + ); + + const foundStakeholder = await stakeholderRepo.getStakeholderById( + object.id, + ); + + // confirm stakeholder is related (it is allowed to edit) OR just that it exists (if no id provided) before updating + if (foundStakeholder.stakeholder.email) { + const stakeholderRelation = await stakeholderRepo.updateLinkStakeholder( + acctStakeholder.stakeholder.stakeholder_uuid, + object, + ); + + console.log('updated link -------> ', stakeholderRelation); + + return stakeholderRelation; + } + + return { error: { message: "Whoops! That stakeholder doesn't exist" } }; + }; + +const updateStakeholder = + (stakeholderRepo, acctStakeholder_id = null) => + async (requestBody) => { + const object = StakeholderTree({ ...requestBody }); + + const relatedStakeholders = await stakeholderRepo.getRelatedIds( + acctStakeholder_id, + ); const foundStakeholder = await stakeholderRepo.getStakeholderById( object.id, ); - // confirm stakeholder is related (if id provided is allowed to edit) OR just that it exists (if no id provided) before updating + // confirm stakeholder is related (it is allowed to edit) OR just that it exists (if no id provided) before updating if ( - // (acctStakeholder_id && relatedStakeholders.includes(object.id)) || + (acctStakeholder_id && relatedStakeholders.includes(object.id)) || foundStakeholder.stakeholder.email ) { // remove children and parents @@ -287,38 +357,46 @@ const updateStakeholder = updateObj, ); - console.log('updated stakeholder -------> ', stakeholder); + // console.log('updated stakeholder -------> ', stakeholder); - return Stakeholder({ ...stakeholder, children, parents }); + return StakeholderTree({ ...stakeholder, children, parents }); } return { error: { message: "Whoops! That stakeholder doesn't exist" } }; }; const createStakeholder = - async (stakeholderRepo, acctStakeholder_id) => async (requestBody) => { - const { relation = null, ...obj } = requestBody; - const stakeholderObj = StakeholderRequestObject({ ...obj }); - - console.log('STAKEHOLDER MODEL requestBody', requestBody, stakeholderObj); + (stakeholderRepo, acctStakeholder_id = null) => + async (requestBody) => { + // const { relation = null, ...obj } = requestBody; + const stakeholderObj = StakeholderPostObject({ ...requestBody }); - const stakeholder = await stakeholderRepo.createStakeholder(stakeholderObj); + console.log('stakeholderObj ---->', stakeholderObj); - const linked = await stakeholderRepo.linkStakeholder( + const stakeholder = await stakeholderRepo.createStakeholder( acctStakeholder_id, - relation, - stakeholder.id, + stakeholderObj, ); - console.log('linked', linked); + console.log('created ---->', stakeholder); + + // const linked = await stakeholderRepo.linkStakeholder( + // acctStakeholder_id, + // relation, + // stakeholder.id, + // ); + + // console.log('linked', linked); - return { stakeholder: Stakeholder({ ...stakeholder }) }; + return { stakeholder: StakeholderTree({ ...stakeholder }) }; }; module.exports = { getStakeholders, getAllStakeholders, - Stakeholder, + getUnlinkedStakeholders, + updateLinkStakeholder, + StakeholderTree, FilterCriteria, createStakeholder, updateStakeholder, diff --git a/server/repositories/StakeholderRepository.js b/server/repositories/StakeholderRepository.js index eaba78e..5221522 100644 --- a/server/repositories/StakeholderRepository.js +++ b/server/repositories/StakeholderRepository.js @@ -167,10 +167,23 @@ class StakeholderRepository extends BaseRepository { } async getFilter(filter, options) { + console.log('GET FILTER', filter, options); + const { org_name, first_name, last_name, email, phone, ...otherFilters } = + filter; + const results = await this._session .getDB()(this._tableName) .select('*') .where({ ...filter }) + // .where((builder) => + // org_name && first_name && last_name + // ? builder + // .where({ ...otherFilters }) + // .orWhere('org_name', 'like', org_name) + // .orWhere('first_name', 'like', first_name) + // .orWhere('last_name', 'like', last_name) + // : builder.where({ ...otherFilters }), + // ) .orderBy('org_name', 'asc') .limit(options.limit) .offset(options.offset); @@ -191,6 +204,15 @@ class StakeholderRepository extends BaseRepository { .getDB()(this._tableName) .count('*') .where({ ...filter }); + // .where((builder) => + // org_name && first_name && last_name + // ? builder + // .where({ ...otherFilters }) + // .orWhere('org_name', 'like', org_name) + // .orWhere('first_name', 'like', first_name) + // .orWhere('last_name', 'like', last_name) + // : builder.where({ ...otherFilters }), + // ); return { stakeholders, count: +count[0].count }; } @@ -198,12 +220,14 @@ class StakeholderRepository extends BaseRepository { async getRelatedIds(id) { let stakeholder_uuid = null; let stakeholder_id = null; - if (Number.isInteger(id)) { + if (Number.isInteger(+id)) { stakeholder_id = id; - } else { + } else if (id !== 'null') { stakeholder_uuid = id; } + console.log('getRelatedIds', id, stakeholder_id, stakeholder_uuid); + const relatedIds = await this._session .getDB()('stakeholder as s') .select('sr.child_id', 'sr.parent_id') @@ -231,29 +255,41 @@ class StakeholderRepository extends BaseRepository { filter; const relatedIds = await this.getRelatedIds(id); - console.log( - 'filter cols -------->', - org_name, - first_name, - last_name, - email, - phone, - ); + // const searchFields = Object.entries({ + // org_name, + // first_name, + // last_name, + // email, + // phone, + // }); + + // console.log('filter cols -------->', searchFields); console.log('other filters -------->', otherFilters); + // const searchString = searchFields + // .reduce((acc, [key, value]) => { + // if (value) { + // acc.push(`"${key}" like "${value}"`); + // } + // return acc; + // }, []) + // .join(' and '); + + // console.log('searchString', searchString); + const stakeholders = await this._session .getDB()(this._tableName) .select('*') .whereIn('stakeholder_uuid', relatedIds) - .andWhere({ ...otherFilters }) - .andWhere( - (builder) => - builder.where('org_name', 'like', org_name ? org_name.regexp : null), - // .orWhere('first_name', 'like', first_name.regexp) - // .orWhere('last_name', 'like', last_name.regexp) - // .orWhere('email', 'like', email.regexp) - // .orWhere('phone', 'like', phone.regexp), - ) + .andWhere({ ...filter }) + // .andWhere({ ...otherFilters }) + // .andWhere((builder) => + // builder + // .orWhere('org_name', 'like', org_name) + // .orWhere('first_name', 'like', first_name) + // .orWhere('last_name', 'like', last_name), + // ) + // .andWhere(this._session.getDB().raw(searchString)) .orderBy('org_name', 'asc') .limit(options.limit) .offset(options.offset); @@ -264,14 +300,14 @@ class StakeholderRepository extends BaseRepository { .getDB()(this._tableName) .count('*') .whereIn('stakeholder_uuid', relatedIds) - .andWhere({ ...otherFilters }) - .andWhere( - (builder) => builder.where('org_name', 'like', org_name.regexp), - // .orWhere('first_name', 'like', first_name.regexp) - // .orWhere('last_name', 'like', last_name.regexp) - // .orWhere('email', 'like', email.regexp) - // .orWhere('phone', 'like', phone.regexp), - ); + .andWhere({ ...filter }); + // .andWhere({ ...otherFilters }) + // .andWhere((builder) => + // builder + // .orWhere('org_name', 'like', org_name) + // .orWhere('first_name', 'like', first_name) + // .orWhere('last_name', 'like', last_name), + // ); return { stakeholders, count: +count[0].count }; } @@ -288,13 +324,6 @@ class StakeholderRepository extends BaseRepository { }, ]); - const linked = this.linkStakeholder(id, object.id); - expect(linked).match([ - { - id: expect.uuid(), - }, - ]); - return created[0]; } @@ -313,25 +342,92 @@ class StakeholderRepository extends BaseRepository { return updated[0]; } - async linkStakeholder(id, relation, linkId) { - const relationObj = {}; - relation.parent_id = relation === 'parent' ? linkId : id; - relation.child_id = relation === 'child' ? linkId : id; + async getUnlinkedStakeholders(id) { + const relatedIds = await this.getRelatedIds(id); + const ids = relatedIds || []; - console.log('relationObj', relationObj); + const stakeholders = await this._session + .getDB()(this._tableName) + .select('*') + // .whereNotIn('id', ids) + .whereNotIn('stakeholder_uuid', ids) + .orderBy('org_name', 'asc'); - const linked = this._session - .getDB()('stakeholder_relations') - .insert(relationObj) - .returning('*'); + // console.log('unlinked stakeholders', stakeholders.length); - expect(linked).match([ - { - id: expect.uuid(), - }, - ]); + const count = await this._session + .getDB()(this._tableName) + .count('*') + // .whereNotIn('id', ids) + .whereNotIn('stakeholder_uuid', ids); + + return { stakeholders, count: +count[0].count }; + } + + async updateLinkStakeholder(stakeholder_id, { type, linked, data }) { + console.log('updateLinkStakeholder', stakeholder_id, type, linked, data); + + let linkedStakeholders; + + if (linked) { + // to link + const insertObj = {}; + + if (type === 'parents' || type === 'children') { + // eslint-disable-next-line no-param-reassign + insertObj.parent_id = + type === 'parents' ? data.stakeholder_uuid : stakeholder_id; + // eslint-disable-next-line no-param-reassign + insertObj.child_id = + type === 'children' ? data.stakeholder_uuid : stakeholder_id; + } + // // eslint-disable-next-line no-param-reassign + // insertObj.grower_id = type === 'growers' ? id : null; + // // eslint-disable-next-line no-param-reassign + // insertObj.user_id = type === 'users' ? id : null; + + console.log('insertObj', insertObj); + + linkedStakeholders = await this._session + .getDB()('stakeholder_relations') + .insert(insertObj) + .returning('*'); + + console.log('linked', linkedStakeholders); + + // expect(linked).match([ + // { + // id: expect.uuid(), + // }, + // ]); + } else { + // to unlink + const removeObj = {}; + + if (type === 'parents' || type === 'children') { + // eslint-disable-next-line no-param-reassign + removeObj.parent_id = type === 'parents' ? id : stakeholder_id; + // eslint-disable-next-line no-param-reassign + removeObj.child_id = type === 'children' ? id : stakeholder_id; + } + + console.log('removeObj', removeObj); + + linkedStakeholders = await this._session + .getDB()('stakeholder_relations') + .delete(removeObj) + .returning('*'); + + console.log('linked', linkedStakeholders); + + // expect(linked).match([ + // { + // id: expect.uuid(), + // }, + // ]); + } - return linked[0]; + return linkedStakeholders[0]; } } diff --git a/server/routes.js b/server/routes.js index 9d9865f..6e599e0 100644 --- a/server/routes.js +++ b/server/routes.js @@ -6,11 +6,29 @@ const router = require('express').Router(); const { stakeholderGet, stakeholderGetAll, + stakeholderGetUnlinked, + stakeholderUpdateLink, stakeholderPatch, stakeholderPost, } = require('./handlers/stakeholderHandler'); const { handlerWrapper } = require('./utils/utils'); +router + .route('/links/:stakeholder_id') + .get(handlerWrapper(stakeholderGetUnlinked)) + .patch(handlerWrapper(stakeholderUpdateLink)); + +router + .route('/links') + .get(handlerWrapper(stakeholderGetUnlinked)) + .patch(handlerWrapper(stakeholderUpdateLink)); + +router + .route('/:stakeholder_id') + .get(handlerWrapper(stakeholderGet)) + .patch(handlerWrapper(stakeholderPatch)) + .post(handlerWrapper(stakeholderPost)); // for account sign-ons + router .route('/') .get(handlerWrapper(stakeholderGetAll)) @@ -18,9 +36,4 @@ router .post(handlerWrapper(stakeholderPost)); // .delete(handlerWrapper(stakeholderDelete)); -router - .route('/:stakeholder_id') - .get(handlerWrapper(stakeholderGet)) - .patch(handlerWrapper(stakeholderPatch)); // for account sign-ons - module.exports = router;