Skip to content

Commit

Permalink
refactor: email service (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
niall-shaw authored Jan 18, 2024
1 parent e47e4ec commit e308e2c
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 84 deletions.
6 changes: 4 additions & 2 deletions packages/backend/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
createInvitationRepository,
createSubmissionRepository,
} from './submission/mongoRepository'
import { createEmailService } from './email/service'
import { createSubmissionService } from './submission/service'
import { createSchemaRepository } from './schema/mongoRepository'
import { createSchemaService } from './schema/service'
Expand All @@ -22,6 +23,7 @@ interface IO {

export async function createAppContext(io: IO) {
const { database, didResolver, emailClient } = io
const emailService = await createEmailService(emailClient)
const invitationRepository = await createInvitationRepository(database)
const submissionRepository = await createSubmissionRepository(database)
const schemaRepository = await createSchemaRepository(database)
Expand All @@ -42,14 +44,14 @@ export async function createAppContext(io: IO) {
)

const authController = await createAuthController()

const submissionController = await createSubmissionController(
submissionService,
validationService,
emailClient,
emailService,
)

return {
emailService,
invitationRepository,
submissionRepository,
submissionService,
Expand Down
16 changes: 1 addition & 15 deletions packages/backend/src/email/client-stub.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { compileEmailTemplate, getSubmitUrls } from './helpers'
import { Invitation } from '@ssi-trust-registry/common'
import { compileEmailTemplate } from './helpers'

type SentMessage = {
to: string
Expand All @@ -14,7 +13,6 @@ export interface EmailClientStub {
templatePath: string,
templateParams: Record<string, unknown>,
) => Promise<void>
sendInvitationEmail: (invitation: Invitation) => Promise<void>
sentMessages: SentMessage[]
}

Expand All @@ -30,18 +28,6 @@ export function createEmailClientStub(): EmailClientStub {
const html = await compileEmailTemplate(templatePath, templateParams)
sentMessages.push({ to, subject, html })
},
async sendInvitationEmail(invitation: Invitation) {
const { submitApiUrl, submitUiUrl } = getSubmitUrls(invitation)
await this.sendMailFromTemplate(
invitation.emailAddress,
'Invitation',
'./src/email/templates/invitation.html',
{
submitApiUrl,
submitUiUrl,
},
)
},
sentMessages,
}
}
26 changes: 1 addition & 25 deletions packages/backend/src/email/client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import partial from 'lodash.partial'
import nodemailer, { SentMessageInfo, Transporter } from 'nodemailer'
import { compileEmailTemplate, getSubmitUrls } from './helpers'
import { Invitation } from '@ssi-trust-registry/common'
import { createLogger } from '../logger'

const logger = createLogger(__filename)
import { compileEmailTemplate } from './helpers'

export interface EmailClient {
sendMailFromTemplate: (
Expand All @@ -13,7 +9,6 @@ export interface EmailClient {
templatePath: string,
templateParams: Record<string, unknown>,
) => Promise<SentMessageInfo>
sendInvitationEmail: (invitation: Invitation) => Promise<void>
}

interface SmtpConfig {
Expand All @@ -29,7 +24,6 @@ export function createEmailClient(config: SmtpConfig): EmailClient {
const transporter = nodemailer.createTransport(config)
return {
sendMailFromTemplate: partial(sendMailFromTemplate, transporter),
sendInvitationEmail: partial(sendInvitationEmail, transporter),
}
}

Expand All @@ -47,21 +41,3 @@ async function sendMailFromTemplate(
html,
})
}

async function sendInvitationEmail(
transporter: Transporter,
invitation: Invitation,
) {
const { submitApiUrl, submitUiUrl } = getSubmitUrls(invitation)
logger.info(`Sending invitation via email to: `, invitation.emailAddress)
await sendMailFromTemplate(
transporter,
invitation.emailAddress,
'Invitation',
'./src/email/templates/invitation.html',
{
submitApiUrl,
submitUiUrl,
},
)
}
4 changes: 4 additions & 0 deletions packages/backend/src/email/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export function getSubmitUrls(invitation: Invitation) {
submitUiUrl,
}
}

export function getEntityUrl(entityId: string) {
return `${config.server.frontendUrl}/entities/${entityId}`
}
70 changes: 70 additions & 0 deletions packages/backend/src/email/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Invitation } from '@ssi-trust-registry/common'
import partial from 'lodash.partial'
import { EmailClient } from './client'
import { getEntityUrl, getSubmitUrls } from './helpers'
import { createLogger } from '../logger'

const logger = createLogger(__filename)

export interface EmailService {
sendInvitationEmail: (invitation: Invitation) => Promise<void>
sendApprovalEmail: (invitation: Invitation, entityId: string) => Promise<void>
sendRejectionEmail: (invitation: Invitation) => Promise<void>
}

export async function createEmailService(
emailClient: EmailClient,
): Promise<EmailService> {
return {
sendInvitationEmail: partial(sendInvitationEmail, emailClient),
sendApprovalEmail: partial(sendApprovalEmail, emailClient),
sendRejectionEmail: partial(sendRejectionEmail, emailClient),
}
}

async function sendInvitationEmail(
emailClient: EmailClient,
invitation: Invitation,
) {
const { submitApiUrl, submitUiUrl } = getSubmitUrls(invitation)
logger.info(`Sending submission approved email to: `, invitation.emailAddress)
await emailClient.sendMailFromTemplate(
invitation.emailAddress,
'SSI Trust Registry - Invitation',
'./src/email/templates/invitation.html',
{
submitApiUrl,
submitUiUrl,
},
)
}

async function sendApprovalEmail(
emailClient: EmailClient,
invitation: Invitation,
entityId: string,
) {
const entityUrl = getEntityUrl(entityId)
logger.info(`Sending submission approved email to: `, invitation.emailAddress)
await emailClient.sendMailFromTemplate(
invitation.emailAddress,
'SSI Trust Registry - Submission Approved',
'./src/email/templates/approved.html',
{
entityUrl,
},
)
}

async function sendRejectionEmail(
emailClient: EmailClient,
invitation: Invitation,
) {
logger.info(`Sending submission rejected email to: `, invitation.emailAddress)
await emailClient.sendMailFromTemplate(
invitation.emailAddress,
'SSI Trust Registry - Submission Rejected',
'./src/email/templates/rejected.html',
{},
)
}
4 changes: 2 additions & 2 deletions packages/backend/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ describe('api', () => {
expect(emailClient.sentMessages).toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: 'Congratulations! Your submission has been approved!',
subject: 'SSI Trust Registry - Submission Approved',
}),
]),
)
Expand All @@ -332,7 +332,7 @@ describe('api', () => {
expect(emailClient.sentMessages).toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: 'Sorry. Your submission has been rejected.',
subject: 'SSI Trust Registry - Submission Rejected',
}),
]),
)
Expand Down
64 changes: 24 additions & 40 deletions packages/backend/src/submission/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import { RequestWithToken } from '../auth/middleware'
import partial from 'lodash.partial'
import { ValidationService } from '../entity/validationService'
import { SubmissionService } from './service'
import { EmailClient } from '../email/client'
import { config } from '../config'
import { getSubmitUrls } from '../email/helpers'
import { EmailService } from '../email/service'

const logger = createLogger(__filename)

Expand All @@ -33,10 +32,14 @@ export interface SubmissionController {
export async function createSubmissionController(
submissionService: SubmissionService,
validationService: ValidationService,
emailClient: EmailClient,
emailService: EmailService,
): Promise<SubmissionController> {
return {
createInvitation: partial(createInvitation, submissionService, emailClient),
createInvitation: partial(
createInvitation,
submissionService,
emailService,
),
getAllInvitations: partial(getAllInvitations, submissionService),
getInvitationById: partial(getInvitationById, submissionService),
createSubmission: partial(
Expand All @@ -54,35 +57,39 @@ export async function createSubmissionController(
updateSubmissionState,
submissionService,
validationService,
emailClient,
emailService,
),
resendInvitation: partial(
resendInvitation,
submissionService,
emailService,
),
resendInvitation: partial(resendInvitation, submissionService, emailClient),
}
}

async function createInvitation(
service: SubmissionService,
emailClient: EmailClient,
emailService: EmailService,
req: Request,
res: Response,
) {
const invitationDto = InvitationDto.parse(req.body)
logger.info(`Creating new invitation for: `, invitationDto.emailAddress)
const invitation = await service.createInvitation(invitationDto)
const { submitUiUrl } = getSubmitUrls(invitation)
await emailClient.sendInvitationEmail(invitation)
await emailService.sendInvitationEmail(invitation)
res.status(201).json({ ...invitation, url: submitUiUrl })
}

async function resendInvitation(
service: SubmissionService,
emailClient: EmailClient,
emailService: EmailService,
req: Request,
res: Response,
) {
const invitation = await service.getInvitationById(req.params.id)
const { submitUiUrl } = getSubmitUrls(invitation)
await emailClient.sendInvitationEmail(invitation)
await emailService.sendInvitationEmail(invitation)
res.status(200).json({ ...invitation, url: submitUiUrl })
}

Expand Down Expand Up @@ -161,7 +168,7 @@ async function getSubmissionsByInvitationId(
async function updateSubmissionState(
submissionService: SubmissionService,
validationService: ValidationService,
emailClient: EmailClient,
emailService: EmailService,
req: RequestWithToken,
res: Response,
) {
Expand All @@ -179,36 +186,13 @@ async function updateSubmissionState(
await validationService.validateDids(submission.dids)
await validationService.validateSchemas(submission.credentials)

let result
if (state === 'approved') {
const result = await submissionService.approveSubmission(submission)
const entityUrl = `${config.server.frontendUrl}/entities/${result.entity.id}`
logger.info(
`Sending submission approved email to: `,
invitation.emailAddress,
)
await emailClient.sendMailFromTemplate(
invitation.emailAddress,
'Congratulations! Your submission has been approved!',
'./src/email/templates/approved.html',
{
entityUrl,
},
)
res.status(200).json(result)
result = await submissionService.approveSubmission(submission)
await emailService.sendApprovalEmail(invitation, result.entity.id)
} else {
const result = await submissionService.rejectSubmission(submission)
logger.info(
`Sending submission rejected email to: `,
invitation.emailAddress,
)
await emailClient.sendMailFromTemplate(
invitation.emailAddress,
'Sorry. Your submission has been rejected.',
'./src/email/templates/rejected.html',
{
invitationUrl: `${config.server.frontendUrl}/submit/${invitation.id}`,
},
)
res.status(200).json(result)
result = await submissionService.rejectSubmission(submission)
await emailService.sendRejectionEmail(invitation)
}
res.status(200).json(result)
}
Empty file.

0 comments on commit e308e2c

Please sign in to comment.