Skip to content

Commit

Permalink
Implement Mailer class
Browse files Browse the repository at this point in the history
  • Loading branch information
Brijesh committed Aug 9, 2023
1 parent 758bf79 commit f5018c4
Show file tree
Hide file tree
Showing 14 changed files with 442 additions and 20 deletions.
80 changes: 80 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dependencies": {
"@babel/runtime": "^7.1.5",
"@bcgov/nodejs-common-utils": "0.0.16",
"axios": "^1.4.0",
"body-parser": "^1.18.2",
"chalk": "^2.3.2",
"compression": "^1.7.2",
Expand Down
2 changes: 2 additions & 0 deletions src/libs/db2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import Version from './model/version';
import config from '../../config';
import UserClientLink from './model/userclientlink';
import PlanFile from './model/PlanFile';
import EmailTemplate from './model/emailtemplate';

export const connection = knex({
client: 'pg',
Expand Down Expand Up @@ -138,5 +139,6 @@ export default class DataManager {
this.Version = Version;
this.UserClientLink = UserClientLink;
this.PlanFile = PlanFile;
this.EmailTemplate = EmailTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
exports.up = async function(knex) {
await knex.raw(`
create table email_template (
id serial2 NOT NULL,
name varchar NOT NULL,
from_email varchar NOT NULL,
subject varchar NOT NULL,
body varchar NULL,
CONSTRAINT email_template_pk PRIMARY KEY (id)
);
INSERT INTO email_template
(name, from_email, subject, body)
VALUES('Plan Status Change', '[email protected]', 'Plan status changed - {agreementId}', 'Plan status changed from {fromStatus} to {toStatus} for the agreement {agreementId}');
`);

};

exports.down = async function(knex) {
await knex.raw(`
drop table email_template;
`);
};
114 changes: 114 additions & 0 deletions src/libs/db2/model/emailtemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use strict";

import Model from "./model";

export default class EmailTemplate extends Model {
constructor(data, db = undefined) {
super(data, db);
}

static mapRow(row) {
return {
id: row.id,
name: row.name,
fromEmail: row.from_email,
subject: row.subject,
body: row.body,
};
}

static get fields() {
// primary key *must* be first!
return [
"id",
"name",
"from_email",
"subject",
"body",
].map(field => `${this.table}.${field}`);
}

static get table() {
return "email_template";
}

static async update(db, where, values) {
const obj = {};
Object.keys(values).forEach(key => {
obj[Model.toSnakeCase(key)] = values[key];
});

try {
const count = await db
.table(EmailTemplate.table)
.where(where)
.update(obj);

if (count > 0) {
const [{ id }] = await db
.table(EmailTemplate.table)
.where(where)
.returning("id");

const res = await db.raw(
`
SELECT email_template.* FROM email_template
WHERE email_template.id = ?;
`,
[id]
);
return res.rows.map(EmailTemplate.mapRow)[0];
}

return [];
} catch (err) {
throw err;
}
}

static async create(db, values) {
const obj = {};
Object.keys(values).forEach(key => {
obj[Model.toSnakeCase(key)] = values[key];
});

try {
const results = await db
.table(EmailTemplate.table)
.returning("id")
.insert(obj);

return await EmailTemplate.findOne(db, { id: results.pop() });
} catch (err) {
throw err;
}
}

static async findWithExclusion(db, where, order = null, exclude) {
try {
const q = db
.table(EmailTemplate.table)
.select("id")
.where(where);

if (exclude) {
q.andWhereNot(...exclude);
}

const results = await q;
const emailTemplateIds = results.map(obj => obj.id);

const res = await db.raw(
`
SELECT DISTINCT ON (email_template.id) id, email_template.* FROM email_template
WHERE email_template.id = ANY (?) ORDER BY email_template.id, ?;
`,
[emailTemplateIds, order],
);

return res.rows.map(EmailTemplate.mapRow);
} catch (err) {
throw err;
}
}
}
4 changes: 2 additions & 2 deletions src/libs/db2/model/plan.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ export default class Plan extends Model {
}

const results = await db
.select('agreement_id')
.select('*')
.from(Plan.table)
.where({ id: planId });

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

const [result] = results;
return result.agreement_id;
return result;
}

static async createSnapshot(db, planId, userId) {
Expand Down
10 changes: 10 additions & 0 deletions src/libs/db2/model/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ export default class User extends Model {

return clientLinks.map(clientLink => clientLink.clientId);
}

static async fromClientId(db, clientId) {
const [result] = await db
.table('user_account')
.join('user_client_link', { 'user_client_link.user_id': 'user_account.id' })
.where({
'user_client_link.client_id': clientId,
});
return result || [];
}
}

//
Expand Down
60 changes: 60 additions & 0 deletions src/libs/mailer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { errorWithCode, logger } from '@bcgov/nodejs-common-utils';
import axios from "axios";

export class Mailer {

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) {
this.authenticationURL = authenticaitonURL;
this.emailServiceURL = emailServiceURL;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.enabled = enabled;
}

async getBearerToken() {
const tokenEndpoint = `${this.authenticationURL}/auth/realms/comsvcauth/protocol/openid-connect/token`
const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')
try {
const response = await axios.post(tokenEndpoint, 'grant_type=client_credentials', {
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
}
});
console.debug("Bearer token retrieved")
return response.data.access_token
}
catch (error) {
logger.error(`Failed to retrieve bearer token: ${JSON.stringify(error)}`)
throw errorWithCode('Failed to retrieve bearer token', 500)
}
}

async sendEmail(to, from, subject, body, bodyType) {
if (!eval(this.enabled))
return;
const emailEndpoint = `${this.emailServiceURL}/api/v1/email`
try {
const token = await this.getBearerToken()
const emailPayload = { to, from, subject, body, bodyType, }
logger.debug("email payload: " + JSON.stringify(emailPayload))
await axios.post(emailEndpoint, JSON.stringify(emailPayload), {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
}).then(response => {
if (response.status > 199 && response.status < 300)
logger.info('Email sent successfully')
else
logger.error(`Could not send Email: ${response.statusText}`)
}).catch(error => {
logger.error(`Error sending email: ${JSON.stringify(error)}`)
throw errorWithCode('Error sending email', 500)
});
} catch (error) {
logger.error(`Failed sending email: ${JSON.stringify(error)}`)
throw errorWithCode('Failed sending email', 500)
}
}
}
7 changes: 7 additions & 0 deletions src/libs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,10 @@ export const objPathToSnakeCase = path =>
),
'.',
);

export const substituteFields = (str, fields) => {
for (const key of Object.keys(fields)) {
str = str.replace(new RegExp(key, 'g'), fields[key]);
}
return str
}
Loading

0 comments on commit f5018c4

Please sign in to comment.