Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proponent Contact Profile #235

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions app/src/controllers/contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { contactService } from '../services';
import { addDashesToUuid, mixedQueryToArray } from '../utils/utils';

import type { NextFunction, Request, Response } from 'express';
import type { Contact, ContactSearchParameters } from '../types';

const controller = {
// Get current user's contact information
getCurrentUserContact: async (req: Request<never, never, never, never>, res: Response, next: NextFunction) => {
try {
const response = await contactService.searchContacts({
userId: [req.currentContext.userId as string]
});
res.status(200).json(response[0]);
} catch (e: unknown) {
next(e);
}
},
searchContacts: async (
req: Request<never, never, never, ContactSearchParameters>,
res: Response,
next: NextFunction
) => {
try {
const contactIds = mixedQueryToArray(req.query.contactId);
const userIds = mixedQueryToArray(req.query.userId);
const response = await contactService.searchContacts({
userId: userIds ? userIds.map((id) => addDashesToUuid(id)) : userIds,
contactId: contactIds ? contactIds.map((id) => addDashesToUuid(id)) : contactIds,
email: req.query.email,
firstName: req.query.firstName,
lastName: req.query.lastName,
contactApplicantRelationship: req.query.contactApplicantRelationship,
phoneNumber: req.query.phoneNumber
Dismissed Show dismissed Hide dismissed
});
res.status(200).json(response);
} catch (e: unknown) {
next(e);
}
},

updateContact: async (req: Request<never, never, Contact, never>, res: Response, next: NextFunction) => {
try {
const response = await contactService.upsertContacts([req.body], req.currentContext);
res.status(200).json(response);
} catch (e: unknown) {
next(e);
}
}
};

export default controller;
4 changes: 2 additions & 2 deletions app/src/controllers/enquiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const controller = {
const enquiry = await controller.generateEnquiryData(req, IntakeStatus.SUBMITTED);

// Create or update contacts
await contactService.upsertContacts(enquiry.activityId, req.body.contacts, req.currentContext);
await contactService.upsertContacts(req.body.contacts, req.currentContext, enquiry.activityId);

// Create new enquiry
const result = await enquiryService.createEnquiry({
Expand Down Expand Up @@ -139,7 +139,7 @@ const controller = {

updateEnquiry: async (req: Request<never, never, Enquiry>, res: Response, next: NextFunction) => {
try {
await contactService.upsertContacts(req.body.activityId, req.body.contacts, req.currentContext);
await contactService.upsertContacts(req.body.contacts, req.currentContext, req.body.activityId);

const result = await enquiryService.updateEnquiry({
...req.body,
Expand Down
1 change: 1 addition & 0 deletions app/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as accessRequestController } from './accessRequest';
export { default as atsController } from './ats';
export { default as contactController } from './contact';
export { default as documentController } from './document';
export { default as enquiryController } from './enquiry';
export { default as noteController } from './note';
Expand Down
6 changes: 3 additions & 3 deletions app/src/controllers/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ const controller = {

// Create contacts
if (req.body.contacts)
await contactService.upsertContacts(submission.activityId, req.body.contacts, req.currentContext);
await contactService.upsertContacts(req.body.contacts, req.currentContext, submission.activityId);

// Create new submission
const result = await submissionService.createSubmission({
Expand Down Expand Up @@ -369,7 +369,7 @@ const controller = {

// Create contacts
if (req.body.contacts)
await contactService.upsertContacts(submission.activityId, req.body.contacts, req.currentContext);
await contactService.upsertContacts(req.body.contacts, req.currentContext, submission.activityId);

// Create new submission
const result = await submissionService.createSubmission({
Expand Down Expand Up @@ -447,7 +447,7 @@ const controller = {

updateSubmission: async (req: Request<never, never, Submission>, res: Response, next: NextFunction) => {
try {
await contactService.upsertContacts(req.body.activityId, req.body.contacts, req.currentContext);
await contactService.upsertContacts(req.body.contacts, req.currentContext, req.body.activityId);

const response = await submissionService.updateSubmission({
...req.body,
Expand Down
226 changes: 226 additions & 0 deletions app/src/db/migrations/20250107000000_019-contact-management.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/* eslint-disable max-len */
import type { Knex } from 'knex';

import { Action, GroupName, Initiative, Resource } from '../../utils/enums/application';

const resources = [
{
name: Resource.CONTACT
}
];

const actions = [
{
name: Action.CREATE
},
{
name: Action.READ
},
{
name: Action.UPDATE
},
{
name: Action.DELETE
}
];

export async function up(knex: Knex): Promise<void> {
return Promise.resolve()

.then(() => {
return knex('yars.resource').insert(resources);
})

.then(() => {
/*
* Add policies
*/

const items = [];
for (const resource of resources) {
for (const action of actions) {
items.push({
resource_id: knex('yars.resource').where({ name: resource.name }).select('resource_id'),
action_id: knex('yars.action').where({ name: action.name }).select('action_id')
});
}
}

return knex('yars.policy').insert(items);
})

.then(async () => {
/*
* Add roles
*/

const items: Array<{ name: string; description: string }> = [];

const addRolesForResource = (resourceName: string) => {
items.push(
{
name: `${resourceName.toUpperCase()}_CREATOR`,
description: `Can create ${resourceName.toLowerCase()}s`
},
{
name: `${resourceName.toUpperCase()}_VIEWER`,
description: `Can view ${resourceName.toLowerCase()}s`
},
{
name: `${resourceName.toUpperCase()}_EDITOR`,
description: `Can edit ${resourceName.toLowerCase()}s`
}
);
};

for (const resource of resources) {
addRolesForResource(resource.name);
}

return knex('yars.role').insert(items);
})

.then(async () => {
/*
* Add role to policy mappings
*/

const policies = await knex
.select('p.policy_id', 'r.name as resource_name', 'a.name as action_name')
.from({ p: 'yars.policy' })
.innerJoin({ r: 'yars.resource' }, 'p.resource_id', '=', 'r.resource_id')
.innerJoin({ a: 'yars.action' }, 'p.action_id', '=', 'a.action_id');

const items: Array<{ role_id: number; policy_id: number }> = [];

const addRolePolicies = async (resourceName: string) => {
const creatorId = await knex('yars.role')
.where({ name: `${resourceName.toUpperCase()}_CREATOR` })
.select('role_id');
const viewerId = await knex('yars.role')
.where({ name: `${resourceName.toUpperCase()}_VIEWER` })
.select('role_id');
const editorId = await knex('yars.role')
.where({ name: `${resourceName.toUpperCase()}_EDITOR` })
.select('role_id');

const resourcePolicies = policies.filter((x) => x.resource_name === resourceName);
items.push(
{
role_id: creatorId[0].role_id,
policy_id: resourcePolicies.find((x) => x.action_name == Action.CREATE).policy_id
},
{
role_id: viewerId[0].role_id,
policy_id: resourcePolicies.find((x) => x.action_name == Action.READ).policy_id
},
{
role_id: editorId[0].role_id,
policy_id: resourcePolicies.find((x) => x.action_name == Action.UPDATE).policy_id
},

{
role_id: editorId[0].role_id,
policy_id: resourcePolicies.find((x) => x.action_name == Action.DELETE).policy_id
}
);
};

await addRolePolicies(Resource.CONTACT);

return knex('yars.role_policy').insert(items);
})

.then(async () => {
/*
* Add group to role mappings
*/

const housing_id = knex('initiative')
.where({
code: Initiative.HOUSING
})
.select('initiative_id');

const navigator_group_id = await knex('yars.group')
.where({ initiative_id: housing_id, name: GroupName.NAVIGATOR })
.select('group_id');

const navigator_read_group_id = await knex('yars.group')
.where({ initiative_id: housing_id, name: GroupName.NAVIGATOR_READ_ONLY })
.select('group_id');

const superviser_group_id = await knex('yars.group')
.where({ initiative_id: housing_id, name: GroupName.SUPERVISOR })
.select('group_id');

const admin_group_id = await knex('yars.group')
.where({ initiative_id: housing_id, name: GroupName.ADMIN })
.select('group_id');

const proponent_group_id = await knex('yars.group')
.where({ initiative_id: housing_id, name: GroupName.PROPONENT })
.select('group_id');

const items: Array<{ group_id: number; role_id: number }> = [];

const addResourceRoles = async (group_id: number, resourceName: Resource, actionNames: Array<Action>) => {
if (actionNames.includes(Action.CREATE)) {
items.push({
group_id: group_id,
role_id: (
await knex('yars.role')
.where({ name: `${resourceName}_CREATOR` })
.select('role_id')
)[0].role_id
});
}

if (actionNames.includes(Action.READ)) {
items.push({
group_id: group_id,
role_id: (
await knex('yars.role')
.where({ name: `${resourceName}_VIEWER` })
.select('role_id')
)[0].role_id
});
}

if (actionNames.includes(Action.UPDATE) || actionNames.includes(Action.DELETE)) {
items.push({
group_id: group_id,
role_id: (
await knex('yars.role')
.where({ name: `${resourceName}_EDITOR` })
.select('role_id')
)[0].role_id
});
}
};

// Note: Only UPDATE or DELETE is required to be given EDITOR role, don't include both
// prettier-ignore
{
// Add all navigator role mappings
await addResourceRoles(navigator_group_id[0].group_id, Resource.CONTACT, [Action.READ, Action.UPDATE]);

// Add all navigator read only role mappings
await addResourceRoles(navigator_read_group_id[0].group_id, Resource.CONTACT, [Action.READ, Action.UPDATE]);

// Add all supervisor role mappings
await addResourceRoles(superviser_group_id[0].group_id, Resource.CONTACT, [Action.READ, Action.UPDATE]);

// Add all admin role mappings
await addResourceRoles(admin_group_id[0].group_id, Resource.CONTACT, [Action.READ, Action.UPDATE]);

// Add all proponent role mappings
await addResourceRoles(proponent_group_id[0].group_id, Resource.CONTACT, [Action.READ, Action.UPDATE]);
}
return knex('yars.group_role').insert(items);
});
}

export async function down(): Promise<void> {
return Promise.resolve();
}
45 changes: 45 additions & 0 deletions app/src/routes/v1/contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import express from 'express';

import { contactController } from '../../controllers';
import { hasAuthorization } from '../../middleware/authorization';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';
import { requireSomeGroup } from '../../middleware/requireSomeGroup';
import { Action, Resource } from '../../utils/enums/application';
import { contactValidator } from '../../validators';

import type { NextFunction, Request, Response } from 'express';
import type { Contact, ContactSearchParameters } from '../../types';

const router = express.Router();
router.use(requireSomeAuth);
router.use(requireSomeGroup);

// Get current user's contact information endpoint
router.get(
'/',
hasAuthorization(Resource.CONTACT, Action.READ),
(req: Request<never, never, never, never>, res: Response, next: NextFunction): void => {
contactController.getCurrentUserContact(req, res, next);
}
);

// Search users endpoint
router.get(
'/search',
hasAuthorization(Resource.CONTACT, Action.READ),
contactValidator.searchContacts,
(req: Request<never, never, never, ContactSearchParameters>, res: Response, next: NextFunction): void => {
contactController.searchContacts(req, res, next);
}
);

router.put(
'/:contactId',
hasAuthorization(Resource.CONTACT, Action.UPDATE),
contactValidator.updateContact,
(req: Request<never, never, Contact, never>, res: Response, next: NextFunction): void => {
contactController.updateContact(req, res, next);
}
);

export default router;
Loading
Loading