Skip to content

Commit

Permalink
Create CHES email api logging functionality
Browse files Browse the repository at this point in the history
Create email_log table
Create typescript EmailLog types
Create service function for logging
Add log function to email service functions
  • Loading branch information
wilwong89 committed Dec 13, 2024
1 parent 3e55efc commit ce3c721
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 0 deletions.
47 changes: 47 additions & 0 deletions app/src/db/migrations/20241206000000_016-ches-logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable max-len */
import stamps from '../stamps';

import type { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
return Promise.resolve().then(() =>
// Create public schema tables
knex.schema
.createTable('email_log', (table) => {
table.uuid('email_id').primary();
table.integer('http_status').defaultTo(null);
table.uuid('msg_id').defaultTo(null);
table.text('to').defaultTo(null);
table.uuid('tx_id').defaultTo(null);
stamps(knex, table);
})

// Create before update triggers
.then(() =>
knex.schema.raw(`CREATE TRIGGER before_update_email_log_trigger
BEFORE UPDATE ON email_log
FOR EACH ROW EXECUTE PROCEDURE public.set_updated_at();`)
)

// Create audit triggers
.then(() =>
knex.schema.raw(`CREATE TRIGGER audit_email_log_trigger
AFTER UPDATE OR DELETE ON email_log
FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`)
)
);
}

export async function down(knex: Knex): Promise<void> {
return (
Promise.resolve()
// Drop audit triggers
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_email_log_trigger ON email_log'))

// Drop public schema table triggers
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_email_log_trigger ON email_log'))

// Drop public schema tables
.then(() => knex.schema.dropTableIfExists('email_log'))
);
}
39 changes: 39 additions & 0 deletions app/src/db/models/email_log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Prisma } from '@prisma/client';

import type { EmailLog } from '../../types';

// Define types
const _emailLog = Prisma.validator<Prisma.email_logDefaultArgs>()({});
const _emailLogWithGraph = Prisma.validator<Prisma.email_logDefaultArgs>()({});

type PrismaRelationEmailLog = Prisma.email_logGetPayload<typeof _emailLog>;
type PrismaGraphEmailLog = Prisma.email_logGetPayload<typeof _emailLogWithGraph>;

export default {
toPrismaModel(input: EmailLog): PrismaRelationEmailLog {
return {
email_id: input.emailId as string,
http_status: input.httpStatus,
msg_id: input.msgId ?? null,
to: input.to ?? null,
tx_id: input.txId ?? null,
created_at: input.createdAt ? new Date(input.createdAt) : null,
created_by: input.createdBy as string,
updated_at: input.updatedAt ? new Date(input.updatedAt) : null,
updated_by: input.updatedBy as string
};
},

fromPrismaModel(input: PrismaGraphEmailLog): EmailLog {
return {
emailId: input.email_id,
httpStatus: Number(input.http_status),
msgId: input.msg_id || '',
to: input.to || '',
createdAt: input.created_at?.toISOString() ?? null,
createdBy: input.created_by,
updatedAt: input.updated_at?.toISOString() ?? null,
updatedBy: input.updated_by
};
}
};
1 change: 1 addition & 0 deletions app/src/db/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as access_request } from './access_request';
export { default as contact } from './contact';
export { default as document } from './document';
export { default as draft } from './draft';
export { default as email_log } from './email_log';
export { default as enquiry } from './enquiry';
export { default as identity_provider } from './identity_provider';
export { default as note } from './note';
Expand Down
14 changes: 14 additions & 0 deletions app/src/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,20 @@ model draft_code {
@@schema("public")
}

model email_log {
email_id String @id @db.Uuid
msg_id String? @db.Uuid
to String?
tx_id String? @db.Uuid
http_status Int?
created_by String? @default("00000000-0000-0000-0000-000000000000")
created_at DateTime? @default(now()) @db.Timestamptz(6)
updated_by String?
updated_at DateTime? @db.Timestamptz(6)
@@schema("public")
}

view group_role_policy_vw {
row_number BigInt @unique
group_id Int?
Expand Down
40 changes: 40 additions & 0 deletions app/src/services/email.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import axios from 'axios';
import config from 'config';
import prisma from '../db/dataConnection';
import { v4 as uuidv4 } from 'uuid';

import type { AxiosInstance } from 'axios';
import type { Email } from '../types';

type Message = {
msgId: string;
to: Array<string>;
};

type Email_data = {
messages: Array<Message>;
txId: string;
};
/**
* @function getToken
* Gets Auth token using CHES client credentials
Expand Down Expand Up @@ -55,6 +66,9 @@ const service = {
* @returns Axios response status and data
*/
email: async (emailData: Email) => {
// Generate list of unique emails to be sent
const uniqueEmails = Array.from(new Set([...emailData.to, ...(emailData?.cc || []), ...(emailData?.bcc || [])]));

try {
const { data, status } = await chesAxios().post('/email', emailData, {
headers: {
Expand All @@ -63,14 +77,18 @@ const service = {
maxContentLength: Infinity,
maxBodyLength: Infinity
});

service.logEmail(data, uniqueEmails, status);
return { data, status };
} catch (e: unknown) {
if (axios.isAxiosError(e)) {
service.logEmail(null, uniqueEmails, e.response ? e.response.status : 500);
return {
data: e.response?.data.errors[0].message,
status: e.response ? e.response.status : 500
};
} else {
service.logEmail(null, uniqueEmails, 500);
return {
data: 'Email error',
status: 500
Expand All @@ -79,6 +97,28 @@ const service = {
}
},

/**
* @function logEmail
* Logs CHES email api calls
* @param {Email_data | null} data Object containing CHES response, or null on error
* @param {Array<string>} recipients Array of email strings
* @param {status} status Http status of CHES response
* @returns null
*/
logEmail: async (data: Email_data | null, recipients: Array<string>, status: number) => {
await prisma.$transaction(async (trx) => {
await trx.email_log.createMany({
data: recipients.map((x) => ({
email_id: uuidv4(),
msg_id: data?.messages?.[0].msgId ?? null,
to: x,
tx_id: data?.txId ?? null,
http_status: status
}))
});
});
},

/**
* @function health
* Checks CHES service health
Expand Down
9 changes: 9 additions & 0 deletions app/src/types/EmailLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IStamps } from '../interfaces/IStamps';

export type EmailLog = {
emailId?: string; // Primary Key
httpStatus: number;
msgId?: string;
to?: string;
txId?: string;
} & Partial<IStamps>;
1 change: 1 addition & 0 deletions app/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type { CurrentContext } from './CurrentContext';
export type { Document } from './Document';
export type { Draft } from './Draft';
export type { Email } from './Email';
export type { EmailLog } from './EmailLog';
export type { Enquiry } from './Enquiry';
export type { EnquiryIntake } from './EnquiryIntake';
export type { EnquirySearchParameters } from './EnquirySearchParameters';
Expand Down

0 comments on commit ce3c721

Please sign in to comment.