diff options
author | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2024-12-15 13:05:50 -0800 |
---|---|---|
committer | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2024-12-15 13:53:43 -0800 |
commit | c0a96e82af651724314114df2d0019ecb83c2830 (patch) | |
tree | 9e65dea31c62786cb8970b713a078bab2868be1b /src/email.ts | |
parent | 6f45fe5a10174fd33932d17dc056898f06466067 (diff) | |
download | uptime-c0a96e82af651724314114df2d0019ecb83c2830.tar.gz uptime-c0a96e82af651724314114df2d0019ecb83c2830.zip |
small refactorings
Diffstat (limited to 'src/email.ts')
-rw-r--r-- | src/email.ts | 114 |
1 files changed, 77 insertions, 37 deletions
diff --git a/src/email.ts b/src/email.ts index 337572c..b822cd9 100644 --- a/src/email.ts +++ b/src/email.ts @@ -3,7 +3,7 @@ import * as TE from "fp-ts/lib/TaskEither"; import * as O from "fp-ts/lib/Option"; import { createTransport } from "nodemailer"; import { toError } from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; +import { flow, pipe } from "fp-ts/lib/function"; import { ImapFlow, type FetchMessageObject, @@ -26,7 +26,7 @@ interface ImapClientI { opts: Record<string, any>, ) => Promise<boolean>; logout: () => Promise<void>; - mailboxClose: () => Promise<void>; + mailboxClose: () => Promise<boolean>; } type Email = { @@ -63,14 +63,24 @@ const ToErrorWithLock = type EmailGenerator = ( from: EmailFromInstruction, to: EmailToInstruction, + logger: Logger, ) => IO.IO<Email>; -const generateEmail: EmailGenerator = - (from: EmailFromInstruction, to: EmailToInstruction) => () => ({ - from: from.email, - to: to.email, - subject: [new Date().toISOString(), crypto.randomUUID()].join(" | "), - text: crypto.randomUUID(), - }); +const generateEmail: EmailGenerator = ( + from: EmailFromInstruction, + to: EmailToInstruction, + logger: Logger, +) => + pipe( + IO.of(logger.info("Generating email...")), + IO.chain(() => + IO.of({ + from: from.email, + to: to.email, + subject: [new Date().toISOString(), crypto.randomUUID()].join(" | "), + text: crypto.randomUUID(), + }), + ), + ); /** * Get the transport layer for a mailbox to send a piece of mail. @@ -79,13 +89,12 @@ const generateEmail: EmailGenerator = */ type GetSendEmail = ( from: EmailFromInstruction, + logger: Logger, ) => (email: Email) => TE.TaskEither<Error, Email>; -const getSendTransport: GetSendEmail = ({ - username, - password, - server, - send_port, -}) => { +const getSendTransport: GetSendEmail = ( + { username, password, server, send_port }, + _logger, +) => { const transport = createTransport({ host: server, port: send_port, @@ -120,8 +129,12 @@ const getSendTransport: GetSendEmail = ({ */ type GetImapClient = ( to: EmailToInstruction, + logger: Logger, ) => TE.TaskEither<Error, ImapClientI>; -const getImap: GetImapClient = ({ username, password, server, read_port }) => { +const getImap: GetImapClient = ( + { username, password, server, read_port }, + logger, +) => { const imap = new ImapFlow({ logger: false, host: server, @@ -132,7 +145,14 @@ const getImap: GetImapClient = ({ username, password, server, read_port }) => { pass: password, }, }); - return TE.tryCatch(() => imap.connect().then(() => imap), toError); + return pipe( + TE.fromIO(logger.info("Connecting to IMAP server...")), + TE.flatMap(() => + TE.tryCatch(() => imap.connect().then(() => imap), toError), + ), + TE.tap(() => TE.fromIO(logger.info("Connected to IMAP server."))), + TE.tapError((error) => TE.fromIO(logger.error(error.message))), + ); }; /** @@ -141,16 +161,24 @@ const getImap: GetImapClient = ({ username, password, server, read_port }) => { */ const fetchMessages = ( imap: ImapClientI, + logger: Logger, ): TE.TaskEither<Error, FetchMessageObject[]> => - TE.tryCatch( - () => - imap.fetchAll("*", { - uid: true, - envelope: true, - headers: true, - bodyParts: ["text"], - }), - toError, + pipe( + TE.fromIO(logger.info("Fetching messages...")), + TE.chain(() => + TE.tryCatch( + () => + imap.fetchAll("*", { + uid: true, + envelope: true, + headers: true, + bodyParts: ["text"], + }), + toError, + ), + ), + TE.tap(() => TE.fromIO(logger.info("Fetched messages."))), + TE.tapError((error) => TE.fromIO(logger.error(error.message))), ); /** @@ -164,8 +192,8 @@ const matchesEmail: EmailMatcher = (email) => (message) => { const bodyMatches = message.bodyParts?.get("text")?.toString().trim() === email.text.trim(); const headers = message.headers?.toLocaleString(); - const fromMatches = headers.includes(`Return-Path: <${email.from}>`); - const toMatches = headers.includes(`Delivered-To: ${email.to}`); + const fromMatches = headers?.includes(`Return-Path: <${email.from}>`); + const toMatches = headers?.includes(`Delivered-To: ${email.to}`); return subjectMatches && bodyMatches && fromMatches && toMatches; }; @@ -175,6 +203,7 @@ const matchesEmail: EmailMatcher = (email) => (message) => { * @param email is the email to search for. * @param retries is the number of retries left. * @param pollIntervalMs is the time to wait between retries. + * @param logger is the logger instance. * @returns a Right(number) if the email was found, else a Left(error). */ type FindEmailUidInInbox = ( @@ -182,17 +211,17 @@ type FindEmailUidInInbox = ( equalsEmail: (message: FetchMessageObject) => boolean, retries: number, pollIntervalMs: number, - logger?: Logger, + logger: Logger, ) => TE.TaskEither<Error, number>; const findEmailUidInInbox: FindEmailUidInInbox = ( imap, equalsEmail, retries, pollIntervalMs, - logger = ConsoleLogger, + logger, ) => pipe( - fetchMessages(imap), + fetchMessages(imap, logger), TE.flatMap((messages) => { const message = messages.find(equalsEmail); if (message) { @@ -204,7 +233,7 @@ const findEmailUidInInbox: FindEmailUidInInbox = ( (e) => pipe( TE.fromIO( - logger.log(`failed to find email; ${retries} retries left.`), + logger.info(`Failed to find email; ${retries} retries left.`), ), TE.chain(() => retries === 0 @@ -212,14 +241,20 @@ const findEmailUidInInbox: FindEmailUidInInbox = ( : T.delay(pollIntervalMs)(TE.right(null)), ), TE.chain(() => - findEmailUidInInbox(imap, equalsEmail, retries - 1, pollIntervalMs), + findEmailUidInInbox( + imap, + equalsEmail, + retries - 1, + pollIntervalMs, + logger, + ), ), ), (s) => pipe( s, TE.of, - TE.tap(() => TE.fromIO(logger.log("Email succeeded"))), + TE.tap(() => TE.fromIO(logger.info("Email succeeded"))), ), ), ); @@ -235,6 +270,7 @@ export type EmailJobDependencies = { /** * Perform an email job. * @param job is the job to perform. + * @param logger is the logger instance. */ export const perform = ( { from, to, readRetry: { retries, interval } }: EmailJob, @@ -245,16 +281,19 @@ export const perform = ( findEmailUidInInboxImpl = findEmailUidInInbox, matchesEmailImpl = matchesEmail, }: Partial<EmailJobDependencies> = {}, + logger: Logger = ConsoleLogger, ): TE.TaskEither<Error, boolean> => pipe( // arrange. - TE.fromIO(generateEmailImpl(from, to)), + TE.fromIO(generateEmailImpl(from, to, logger)), TE.bindTo("email"), // act. TE.tap(({ email }) => - pipe(getSendImpl(from)(email), TE.mapLeft(ToErrorWithLock())), + pipe(getSendImpl(from, logger)(email), TE.mapLeft(ToErrorWithLock())), + ), + TE.bind("imap", () => + pipe(getImapImpl(to, logger), TE.mapLeft(ToErrorWithLock())), ), - TE.bind("imap", () => pipe(getImapImpl(to), TE.mapLeft(ToErrorWithLock()))), TE.bind("mailboxLock", ({ imap }) => TE.tryCatch( () => imap.getMailboxLock("INBOX"), @@ -269,6 +308,7 @@ export const perform = ( matchesEmailImpl(email), retries, interval, + logger, ), TE.mapLeft(ToErrorWithLock(mailboxLock, imap)), ), |