Skip to content

Commit 78dedcf

Browse files
authored
Merge pull request #419 from bcgov/chore/move-emails-backend
feat: move all email generation to back end
2 parents c0685ff + 422b867 commit 78dedcf

52 files changed

Lines changed: 1159 additions & 957 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/environments/values.dev.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ config:
44
configMap:
55
FRONTEND_APIPATH: api/v1
66
FRONTEND_CHES_ROADMAP_BCC: NRM.PermittingAndData@gov.bc.ca
7-
FRONTEND_CHES_SUBMISSION_CC: NRM.PermittingAndData@gov.bc.ca
87
FRONTEND_COMS_APIPATH: https://coms-dev.api.gov.bc.ca/api/v1
98
FRONTEND_COMS_BUCKETID: 5aa446ca-23c5-4f3b-9300-d8623bc4d101
109
FRONTEND_GEOCODER_APIPATH: https://geocoder.api.gov.bc.ca
@@ -18,6 +17,7 @@ config:
1817
SERVER_BODYLIMIT: 30mb
1918
SERVER_CHEFS_APIPATH: https://submit.digital.gov.bc.ca/app/api/v1
2019
SERVER_CHES_APIPATH: https://ches-dev.api.gov.bc.ca/api/v1
20+
SERVER_CHES_SUBMISSION_CC: NRM.PermittingAndData@gov.bc.ca
2121
SERVER_CHES_TOKENURL: https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token
2222
SERVER_DB_PORT: "5432"
2323
SERVER_ENV: dev

.github/environments/values.prod.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ config:
44
configMap:
55
FRONTEND_APIPATH: api/v1
66
FRONTEND_CHES_ROADMAP_BCC: Navigator.Service@gov.bc.ca
7-
FRONTEND_CHES_SUBMISSION_CC: Navigator.Service@gov.bc.ca
87
FRONTEND_COMS_APIPATH: https://coms.api.gov.bc.ca/api/v1
98
FRONTEND_COMS_BUCKETID: 0089d041-5aab-485e-842d-8875475d0ed6
109
FRONTEND_GEOCODER_APIPATH: https://geocoder.api.gov.bc.ca
@@ -18,6 +17,7 @@ config:
1817
SERVER_BODYLIMIT: 30mb
1918
SERVER_CHEFS_APIPATH: https://submit.digital.gov.bc.ca/app/api/v1
2019
SERVER_CHES_APIPATH: https://ches.api.gov.bc.ca/api/v1
20+
SERVER_CHES_SUBMISSION_CC: Navigator.Service@gov.bc.ca
2121
SERVER_CHES_TOKENURL: https://loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token
2222
SERVER_DB_PORT: "5432"
2323
SERVER_ENV: prod

.github/environments/values.test.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ config:
44
configMap:
55
FRONTEND_APIPATH: api/v1
66
FRONTEND_CHES_ROADMAP_BCC: NRM.PermittingAndData@gov.bc.ca
7-
FRONTEND_CHES_SUBMISSION_CC: NRM.PermittingAndData@gov.bc.ca
87
FRONTEND_COMS_APIPATH: https://coms-test.api.gov.bc.ca/api/v1
98
FRONTEND_COMS_BUCKETID: a9eabd1d-5f77-4c60-bf6b-83ffa0e21c59
109
FRONTEND_GEOCODER_APIPATH: https://geocoder.api.gov.bc.ca
@@ -18,6 +17,7 @@ config:
1817
SERVER_BODYLIMIT: 30mb
1918
SERVER_CHEFS_APIPATH: https://submit.digital.gov.bc.ca/app/api/v1
2019
SERVER_CHES_APIPATH: https://ches-test.api.gov.bc.ca/api/v1
20+
SERVER_CHES_SUBMISSION_CC: NRM.PermittingAndData@gov.bc.ca
2121
SERVER_CHES_TOKENURL: https://test.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token
2222
SERVER_DB_PORT: "5432"
2323
SERVER_ENV: test
@@ -30,7 +30,7 @@ config:
3030
SERVER_OIDC_PUBLICKEY: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiFdv9GA83uHuy8Eu9yiZHGGF9j6J8t7FkbcpaN81GDjwbjsIJ0OJO9dKRAx6BAtTC4ubJTBJMPvQER5ikOhIeBi4o25fg61jpgsU6oRZHkCXc9gX6mrjMjbsPaf3/bjjYxP5jicBDJQeD1oRa24+tiGggoQ7k6gDEN+cRYqqNpzC/GQbkUPk8YsgroncEgu8ChMh/3ERsLV2zorchMANUq76max16mHrhtWIQxrb/STpSt4JuSlUzzBV/dcXjJe5gywZHe0jAutFhNqjHzHdgyaC4RAd3eYQo+Kl/JOgy2AZrnx+CiPmvOJKe9tAW4k4H087ng8aVE40v4HW/FEbnwIDAQAB
3131
SERVER_OPENMAPS_APIPATH: https://openmaps.gov.bc.ca
3232
SERVER_PCNS_APPURL: https://pcns-test-master.apps.silver.devops.gov.bc.ca
33-
SERVER_PCNS_NAVEMAIL: Navigator.Service@gov.bc.ca
33+
SERVER_PCNS_NAVEMAIL: NRM.PermittingAndData@gov.bc.ca
3434
SERVER_PEACH_APIPATH: "https://nr-peach-test-main-api.azurewebsites.net/api/v1"
3535
SERVER_PEACH_TOKENURL: https://test.loginproxy.gov.bc.ca/auth/realms/permittingexchange/protocol/openid-connect/token
3636
SERVER_PORT: "8080"

app/config/custom-environment-variables.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
"ches": {
55
"roadmap": {
66
"bcc": "FRONTEND_CHES_ROADMAP_BCC"
7-
},
8-
"submission": {
9-
"cc": "FRONTEND_CHES_SUBMISSION_CC"
107
}
118
},
129
"coms": {
@@ -41,6 +38,9 @@
4138
"apiPath": "SERVER_CHES_APIPATH",
4239
"clientId": "SERVER_CHES_CLIENTID",
4340
"clientSecret": "SERVER_CHES_CLIENTSECRET",
41+
"submission": {
42+
"cc": "SERVER_CHES_SUBMISSION_CC"
43+
},
4444
"tokenUrl": "SERVER_CHES_TOKENURL"
4545
},
4646
"chefs": {

app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.9.0",
44
"license": "Apache-2.0",
55
"private": true,
6-
"description": "Permit Connect Services",
6+
"description": "Permit Connect Navigator Service",
77
"author": "NRM Permitting and Data Solutions <NRM.PermittingAndData@gov.bc.ca>",
88
"main": "server.ts",
99
"scripts": {

app/peachSync.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ async function syncPeachToPcns() {
3030
if (updatedPermits.length === 0) return;
3131

3232
try {
33-
await sendPermitUpdateNotifications(updatedPermits);
33+
for (const permit of updatedPermits) {
34+
await sendPermitUpdateNotifications(permit, true);
35+
}
3436
} catch (error) {
3537
log.warn('PEACH sync completed but sending notifications failed', error);
3638
}

app/src/controllers/activityContact.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ const getTeamMemberEmailTemplateData = async (
4343
const templateParams = {
4444
dearName: `${contact.firstName} ${contact.lastName}`,
4545
adminName: adminContact?.length ? `${adminContact[0].firstName} ${adminContact[0].lastName}` : undefined,
46-
projectName
46+
projectName,
47+
initiative: project.activity?.initiative?.code.toLowerCase(),
48+
projectId: project.projectId
4749
};
4850

4951
const navEmail: string = config.get('server.pcns.navEmail');

app/src/controllers/electrificationProject.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import config from 'config';
12
import { v4 as uuidv4 } from 'uuid';
23

34
import { transactionWrapper } from '../db/utils/transactionWrapper.ts';
@@ -24,7 +25,8 @@ import {
2425
} from '../services/electrificationProject.ts';
2526
import { Initiative } from '../utils/enums/application.ts';
2627
import { ActivityContactRole, ApplicationStatus, DraftCode, SubmissionType } from '../utils/enums/projectCommon.ts';
27-
import { isTruthy } from '../utils/utils.ts';
28+
import { confirmationTemplateElectrificationSubmission } from '../utils/templates';
29+
import { isTruthy, toTitleCase } from '../utils/utils.ts';
2830

2931
import type { Request, Response } from 'express';
3032
import type { PrismaTransactionClient } from '../db/dataConnection.ts';
@@ -61,11 +63,11 @@ const generateElectrificationProjectData = async (
6163
const contacts = await searchContacts(tx, { userId: [currentContext.userId!] });
6264
if (contacts[0]) await createActivityContact(tx, activityId, contacts[0].contactId, ActivityContactRole.PRIMARY);
6365
}
64-
66+
const UUID = uuidv4();
6567
// Put new electrification project together
6668
const electrificationProjectData: ElectrificationProject = {
6769
...data.project,
68-
electrificationProjectId: uuidv4(),
70+
electrificationProjectId: UUID,
6971
activityId: activityId,
7072
submittedAt: new Date(),
7173
submissionType: SubmissionType.GUIDANCE,
@@ -239,10 +241,36 @@ export const submitElectrificationProjectDraftController = async (
239241
return { ...data, contact: contactResponse[0] };
240242
}
241243
);
242-
244+
await emailProjectConfirmation(result);
243245
res.status(201).json({ ...result, contact: result.contact });
244246
};
245247

248+
async function emailProjectConfirmation(projectWithContact: ElectrificationProject & { contact: Contact }) {
249+
const configCC = config.get<string>('server.ches.submission.cc');
250+
const subject = 'Confirmation of Project Submission';
251+
252+
const body = confirmationTemplateElectrificationSubmission({
253+
contactName:
254+
projectWithContact.contact?.firstName && projectWithContact.contact?.lastName
255+
? `${projectWithContact.contact?.firstName} ${projectWithContact.contact?.lastName}`
256+
: '',
257+
initiative: toTitleCase(Initiative.ELECTRIFICATION),
258+
activityId: projectWithContact.activityId,
259+
projectId: projectWithContact.electrificationProjectId
260+
});
261+
262+
const emailData = {
263+
from: configCC,
264+
to: [projectWithContact.contact.email!],
265+
cc: [configCC],
266+
subject: subject,
267+
bodyType: 'html',
268+
body: body
269+
};
270+
271+
await email(emailData);
272+
}
273+
246274
export const upsertElectrificationProjectDraftController = async (req: Request<never, never, Draft>, res: Response) => {
247275
const update = req.body.draftId && req.body.activityId;
248276

app/src/controllers/enquiry.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import config from 'config';
12
import { v4 as uuidv4 } from 'uuid';
23

34
import { transactionWrapper } from '../db/utils/transactionWrapper.ts';
@@ -10,6 +11,7 @@ import {
1011
import { createActivity, deleteActivity } from '../services/activity.ts';
1112
import { createActivityContact, listActivityContacts } from '../services/activityContact.ts';
1213
import { searchContacts, upsertContacts } from '../services/contact.ts';
14+
import { email } from '../services/email.ts';
1315
import {
1416
createEnquiry,
1517
deleteEnquiry,
@@ -19,12 +21,14 @@ import {
1921
searchEnquiries,
2022
updateEnquiry
2123
} from '../services/enquiry.ts';
24+
import { getProjectByActivityId } from '../services/project.ts';
2225
import {
2326
ActivityContactRole,
2427
ApplicationStatus,
2528
EnquirySubmittedMethod,
2629
SubmissionType
2730
} from '../utils/enums/projectCommon.ts';
31+
import { confirmationTemplateEnquiry } from '../utils/templates';
2832
import { getCurrentUsername, isTruthy } from '../utils/utils.ts';
2933

3034
import type { Request, Response } from 'express';
@@ -110,9 +114,69 @@ export const createEnquiryController = async (req: Request<never, never, Enquiry
110114
return { ...data, contact: contactResponse[0] };
111115
});
112116

117+
await emailEnquiryConfirmation(result, req.currentContext.initiative!, req.body.basic?.relatedActivityId);
113118
res.status(201).json(result);
114119
};
115120

121+
async function emailEnquiryConfirmation(
122+
enquiryWithContact: Enquiry & { contact: Contact },
123+
initiative: string,
124+
relatedActivityId?: string
125+
) {
126+
await transactionWrapper<void>(async (tx: PrismaTransactionClient) => {
127+
const configCC = config.get<string>('server.ches.submission.cc');
128+
129+
let permitDescription = '';
130+
let enquiryDescription: string = enquiryWithContact.enquiryDescription || '';
131+
let firstTwoSentences: string;
132+
133+
// If has permit description convert \n to <br>
134+
if (enquiryDescription.includes('Tracking ID:')) {
135+
const descriptionSplit = enquiryDescription.split('\n\n');
136+
permitDescription = descriptionSplit[0]?.replace(/\n/g, '<br>') + '<br><br>';
137+
enquiryDescription = descriptionSplit.slice(1, descriptionSplit.length).join(' ');
138+
}
139+
140+
// Get the first two sentences of the enquiry description using proper sentence segmentation
141+
// If there are more than two sentences in enquiryDescription, add '..' to the end
142+
const segmenter = new Intl.Segmenter('en', { granularity: 'sentence' });
143+
const segments = Array.from(segmenter.segment(enquiryDescription));
144+
const sentences = segments.map((s) => s.segment.trim()).filter((sentence: string) => sentence.length > 0);
145+
146+
firstTwoSentences = sentences.slice(0, 2).join(' ');
147+
if (sentences.length > 2) {
148+
firstTwoSentences = firstTwoSentences.concat('..');
149+
}
150+
151+
if (permitDescription) firstTwoSentences = permitDescription + firstTwoSentences;
152+
153+
const projectId = relatedActivityId ? (await getProjectByActivityId(tx, relatedActivityId))?.projectId : undefined;
154+
155+
const body = confirmationTemplateEnquiry({
156+
contactName:
157+
enquiryWithContact.contact?.firstName && enquiryWithContact.contact?.lastName
158+
? `${enquiryWithContact.contact?.firstName} ${enquiryWithContact.contact?.lastName}`
159+
: '',
160+
activityId: enquiryWithContact.activityId,
161+
enquiryDescription: firstTwoSentences.trim(),
162+
enquiryId: enquiryWithContact.enquiryId,
163+
projectId: projectId,
164+
initiative: initiative.toLowerCase()
165+
});
166+
167+
const emailData = {
168+
from: configCC,
169+
to: [enquiryWithContact.contact.email!],
170+
cc: [configCC],
171+
subject: 'Confirmation of Enquiry Submission',
172+
bodyType: 'html',
173+
body: body
174+
};
175+
176+
await email(emailData);
177+
});
178+
}
179+
116180
export const deleteEnquiryController = async (req: Request<{ enquiryId: string }>, res: Response) => {
117181
await transactionWrapper<void>(async (tx: PrismaTransactionClient) => {
118182
const enquiry = await getEnquiry(tx, req.params.enquiryId);

app/src/controllers/housingProject.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import config from 'config';
12
import { v4 as uuidv4 } from 'uuid';
23

34
import { transactionWrapper } from '../db/utils/transactionWrapper.ts';
@@ -29,7 +30,8 @@ import { BasicResponse, Initiative } from '../utils/enums/application.ts';
2930
import { NumResidentialUnits } from '../utils/enums/housing.ts';
3031
import { PermitNeeded, PermitStage, PermitState } from '../utils/enums/permit.ts';
3132
import { ActivityContactRole, ApplicationStatus, DraftCode, SubmissionType } from '../utils/enums/projectCommon.ts';
32-
import { getCurrentUsername, isTruthy, omit } from '../utils/utils.ts';
33+
import { confirmationTemplateHousingSubmission } from '../utils/templates';
34+
import { getCurrentUsername, isTruthy, omit, toTitleCase } from '../utils/utils.ts';
3335

3436
import type { Request, Response } from 'express';
3537
import type { PrismaTransactionClient } from '../db/dataConnection.ts';
@@ -420,10 +422,35 @@ export const submitHousingProjectDraftController = async (
420422
return { ...data, contact: contactResponse[0] };
421423
}
422424
);
423-
425+
await emailProjectConfirmation(result);
424426
res.status(201).json({ ...result, contact: result.contact });
425427
};
426428

429+
async function emailProjectConfirmation(projectWithContact: HousingProject & { contact: Contact }) {
430+
const configCC = config.get<string>('server.ches.submission.cc');
431+
432+
const body = confirmationTemplateHousingSubmission({
433+
contactName:
434+
projectWithContact.contact?.firstName && projectWithContact.contact?.lastName
435+
? `${projectWithContact.contact?.firstName} ${projectWithContact.contact?.lastName}`
436+
: '',
437+
initiative: toTitleCase(Initiative.HOUSING),
438+
activityId: projectWithContact.activityId,
439+
projectId: projectWithContact.housingProjectId
440+
});
441+
442+
const emailData = {
443+
from: configCC,
444+
to: [projectWithContact.contact.email!],
445+
cc: [configCC],
446+
subject: 'Confirmation of Project Submission',
447+
bodyType: 'html',
448+
body: body
449+
};
450+
451+
await email(emailData);
452+
}
453+
427454
export const upsertHousingProjectDraftController = async (req: Request<never, never, Draft>, res: Response) => {
428455
const update = req.body.draftId && req.body.activityId;
429456

0 commit comments

Comments
 (0)