Skip to content

Commit f5018c4

Browse files
author
Brijesh
committed
Implement Mailer class
1 parent 758bf79 commit f5018c4

14 files changed

Lines changed: 442 additions & 20 deletions

package-lock.json

Lines changed: 80 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"dependencies": {
1010
"@babel/runtime": "^7.1.5",
1111
"@bcgov/nodejs-common-utils": "0.0.16",
12+
"axios": "^1.4.0",
1213
"body-parser": "^1.18.2",
1314
"chalk": "^2.3.2",
1415
"compression": "^1.7.2",

src/libs/db2/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import Version from './model/version';
6363
import config from '../../config';
6464
import UserClientLink from './model/userclientlink';
6565
import PlanFile from './model/PlanFile';
66+
import EmailTemplate from './model/emailtemplate';
6667

6768
export const connection = knex({
6869
client: 'pg',
@@ -138,5 +139,6 @@ export default class DataManager {
138139
this.Version = Version;
139140
this.UserClientLink = UserClientLink;
140141
this.PlanFile = PlanFile;
142+
this.EmailTemplate = EmailTemplate;
141143
}
142144
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
exports.up = async function(knex) {
2+
await knex.raw(`
3+
create table email_template (
4+
id serial2 NOT NULL,
5+
name varchar NOT NULL,
6+
from_email varchar NOT NULL,
7+
subject varchar NOT NULL,
8+
body varchar NULL,
9+
CONSTRAINT email_template_pk PRIMARY KEY (id)
10+
);
11+
INSERT INTO email_template
12+
(name, from_email, subject, body)
13+
VALUES('Plan Status Change', 'myrange@bc.gov.ca', 'Plan status changed - {agreementId}', 'Plan status changed from {fromStatus} to {toStatus} for the agreement {agreementId}');
14+
`);
15+
16+
};
17+
18+
exports.down = async function(knex) {
19+
await knex.raw(`
20+
drop table email_template;
21+
`);
22+
};
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"use strict";
2+
3+
import Model from "./model";
4+
5+
export default class EmailTemplate extends Model {
6+
constructor(data, db = undefined) {
7+
super(data, db);
8+
}
9+
10+
static mapRow(row) {
11+
return {
12+
id: row.id,
13+
name: row.name,
14+
fromEmail: row.from_email,
15+
subject: row.subject,
16+
body: row.body,
17+
};
18+
}
19+
20+
static get fields() {
21+
// primary key *must* be first!
22+
return [
23+
"id",
24+
"name",
25+
"from_email",
26+
"subject",
27+
"body",
28+
].map(field => `${this.table}.${field}`);
29+
}
30+
31+
static get table() {
32+
return "email_template";
33+
}
34+
35+
static async update(db, where, values) {
36+
const obj = {};
37+
Object.keys(values).forEach(key => {
38+
obj[Model.toSnakeCase(key)] = values[key];
39+
});
40+
41+
try {
42+
const count = await db
43+
.table(EmailTemplate.table)
44+
.where(where)
45+
.update(obj);
46+
47+
if (count > 0) {
48+
const [{ id }] = await db
49+
.table(EmailTemplate.table)
50+
.where(where)
51+
.returning("id");
52+
53+
const res = await db.raw(
54+
`
55+
SELECT email_template.* FROM email_template
56+
WHERE email_template.id = ?;
57+
`,
58+
[id]
59+
);
60+
return res.rows.map(EmailTemplate.mapRow)[0];
61+
}
62+
63+
return [];
64+
} catch (err) {
65+
throw err;
66+
}
67+
}
68+
69+
static async create(db, values) {
70+
const obj = {};
71+
Object.keys(values).forEach(key => {
72+
obj[Model.toSnakeCase(key)] = values[key];
73+
});
74+
75+
try {
76+
const results = await db
77+
.table(EmailTemplate.table)
78+
.returning("id")
79+
.insert(obj);
80+
81+
return await EmailTemplate.findOne(db, { id: results.pop() });
82+
} catch (err) {
83+
throw err;
84+
}
85+
}
86+
87+
static async findWithExclusion(db, where, order = null, exclude) {
88+
try {
89+
const q = db
90+
.table(EmailTemplate.table)
91+
.select("id")
92+
.where(where);
93+
94+
if (exclude) {
95+
q.andWhereNot(...exclude);
96+
}
97+
98+
const results = await q;
99+
const emailTemplateIds = results.map(obj => obj.id);
100+
101+
const res = await db.raw(
102+
`
103+
SELECT DISTINCT ON (email_template.id) id, email_template.* FROM email_template
104+
WHERE email_template.id = ANY (?) ORDER BY email_template.id, ?;
105+
`,
106+
[emailTemplateIds, order],
107+
);
108+
109+
return res.rows.map(EmailTemplate.mapRow);
110+
} catch (err) {
111+
throw err;
112+
}
113+
}
114+
}

src/libs/db2/model/plan.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,14 @@ export default class Plan extends Model {
148148
}
149149

150150
const results = await db
151-
.select('agreement_id')
151+
.select('*')
152152
.from(Plan.table)
153153
.where({ id: planId });
154154

155155
if (results.length === 0) return null;
156156

157157
const [result] = results;
158-
return result.agreement_id;
158+
return result;
159159
}
160160

161161
static async createSnapshot(db, planId, userId) {

src/libs/db2/model/user.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ export default class User extends Model {
168168

169169
return clientLinks.map(clientLink => clientLink.clientId);
170170
}
171+
172+
static async fromClientId(db, clientId) {
173+
const [result] = await db
174+
.table('user_account')
175+
.join('user_client_link', { 'user_client_link.user_id': 'user_account.id' })
176+
.where({
177+
'user_client_link.client_id': clientId,
178+
});
179+
return result || [];
180+
}
171181
}
172182

173183
//

src/libs/mailer.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { errorWithCode, logger } from '@bcgov/nodejs-common-utils';
2+
import axios from "axios";
3+
4+
export class Mailer {
5+
6+
constructor(authenticaitonURL = process.env.CHES_AUTHENTICATION_URL, emailServiceURL = process.env.CHES_EMAIL_SERVICE_URL, clientId = process.env.CHES_CLIENT_ID, clientSecret = process.env.CHES_CLIENT_SECRET, enabled = process.env.CHES_ENABLED) {
7+
this.authenticationURL = authenticaitonURL;
8+
this.emailServiceURL = emailServiceURL;
9+
this.clientId = clientId;
10+
this.clientSecret = clientSecret;
11+
this.enabled = enabled;
12+
}
13+
14+
async getBearerToken() {
15+
const tokenEndpoint = `${this.authenticationURL}/auth/realms/comsvcauth/protocol/openid-connect/token`
16+
const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')
17+
try {
18+
const response = await axios.post(tokenEndpoint, 'grant_type=client_credentials', {
19+
headers: {
20+
'Authorization': `Basic ${credentials}`,
21+
'Content-Type': 'application/x-www-form-urlencoded',
22+
}
23+
});
24+
console.debug("Bearer token retrieved")
25+
return response.data.access_token
26+
}
27+
catch (error) {
28+
logger.error(`Failed to retrieve bearer token: ${JSON.stringify(error)}`)
29+
throw errorWithCode('Failed to retrieve bearer token', 500)
30+
}
31+
}
32+
33+
async sendEmail(to, from, subject, body, bodyType) {
34+
if (!eval(this.enabled))
35+
return;
36+
const emailEndpoint = `${this.emailServiceURL}/api/v1/email`
37+
try {
38+
const token = await this.getBearerToken()
39+
const emailPayload = { to, from, subject, body, bodyType, }
40+
logger.debug("email payload: " + JSON.stringify(emailPayload))
41+
await axios.post(emailEndpoint, JSON.stringify(emailPayload), {
42+
headers: {
43+
'Content-Type': 'application/json',
44+
Authorization: `Bearer ${token}`,
45+
},
46+
}).then(response => {
47+
if (response.status > 199 && response.status < 300)
48+
logger.info('Email sent successfully')
49+
else
50+
logger.error(`Could not send Email: ${response.statusText}`)
51+
}).catch(error => {
52+
logger.error(`Error sending email: ${JSON.stringify(error)}`)
53+
throw errorWithCode('Error sending email', 500)
54+
});
55+
} catch (error) {
56+
logger.error(`Failed sending email: ${JSON.stringify(error)}`)
57+
throw errorWithCode('Failed sending email', 500)
58+
}
59+
}
60+
}

src/libs/utils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,10 @@ export const objPathToSnakeCase = path =>
112112
),
113113
'.',
114114
);
115+
116+
export const substituteFields = (str, fields) => {
117+
for (const key of Object.keys(fields)) {
118+
str = str.replace(new RegExp(key, 'g'), fields[key]);
119+
}
120+
return str
121+
}

0 commit comments

Comments
 (0)