From db671d26c43e47b8285d319a0fd758df60d729c7 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 7 Jan 2024 01:21:06 -0700 Subject: initial update with nodemailer --- src/lib/utils.ts | 14 ---------- src/lib/utils/index.ts | 3 +++ src/lib/utils/mailer.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++ src/lib/utils/retry.ts | 45 ++++++++++++++++++++++++++++++++ src/lib/utils/setImageUrl.ts | 12 +++++++++ 5 files changed, 121 insertions(+), 14 deletions(-) delete mode 100644 src/lib/utils.ts create mode 100644 src/lib/utils/index.ts create mode 100644 src/lib/utils/mailer.ts create mode 100644 src/lib/utils/retry.ts create mode 100644 src/lib/utils/setImageUrl.ts (limited to 'src/lib') diff --git a/src/lib/utils.ts b/src/lib/utils.ts deleted file mode 100644 index 5586b47..0000000 --- a/src/lib/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { supabase } from './supabase'; - -const setImageUrl = (imageSpec) => { - const { publicURL, error } = supabase - .storage - .from('mistymountains') - .getPublicUrl(imageSpec.image); - if (!error) { - return { ...imageSpec, image: publicURL }; - } - return imageSpec; -} - -export default setImageUrl; \ No newline at end of file diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts new file mode 100644 index 0000000..61d383d --- /dev/null +++ b/src/lib/utils/index.ts @@ -0,0 +1,3 @@ +export * from './setImageUrl'; +export * from './mailer'; +export * from './retry'; diff --git a/src/lib/utils/mailer.ts b/src/lib/utils/mailer.ts new file mode 100644 index 0000000..e5edd74 --- /dev/null +++ b/src/lib/utils/mailer.ts @@ -0,0 +1,61 @@ +import * as nodemailer from 'nodemailer'; +import { continueRetryUntilValidation } from './retry'; + +export interface Mailer { + sendMail(to: string, subject: string, message: string): Promise; +} + +export class MistyMountainsMailer implements Mailer { + private from: string; + private domain: string; + private username: string; + private password: string; + private port: number; + + private transporter: nodemailer.Transporter; + + constructor(username: string, password: string, from: string, domain: string, port: number) { + this.from = from; + this.username = username; + this.password = password; + this.domain = domain; + this.port = port; + + this.transporter = nodemailer.createTransport({ + host: this.domain, + port: this.port, + auth: { + user: this.username, + pass: this.password + }, + requireTLS: true, + tls: { + rejectUnauthorized: true + } + }); + } + + public async sendMail(to: string, subject: string, message: string) { + const mail = { + from: this.from, + subject, + html: message, + to + }; + + return !!(await continueRetryUntilValidation(async () => { + const { messageId } = await this.transporter.sendMail(mail); + return messageId; + })); + } +} + +export const EnvMistyMountainsMailerFactory = () => { + return new MistyMountainsMailer( + process.env.SMTP_USERNAME, + process.env.SMTP_PASSWORD, + process.env.FROM_EMAIL, + process.env.SMTP_SERVER, + Number(process.env.SMTP_PORT) + ); +}; diff --git a/src/lib/utils/retry.ts b/src/lib/utils/retry.ts new file mode 100644 index 0000000..34bc73b --- /dev/null +++ b/src/lib/utils/retry.ts @@ -0,0 +1,45 @@ +export type RetryStrategyF = (retries: number) => number; + +export const MAX_DEFAULT_RETRY_AMOUNT = 5; +export const WAIT_MS = 1_000; +export const RETRY_EXPONENT = 2; +export const RETRY_EXPONENTIAL_FACTOR = 1.1; +export const RETRY_JITTER_MAX = 3_000; + +const waitFor = (ms: number) => new Promise((res) => setTimeout(res, ms)); + +const exponentialStrategyWithJitter: RetryStrategyF = (retries: number) => + WAIT_MS * Math.pow(RETRY_EXPONENT, RETRY_EXPONENTIAL_FACTOR * retries) + + RETRY_JITTER_MAX * Math.random(); + +export const continueRetryUntilValidation = async ( + promiseFn: () => Promise | T | Promise | void, + validationFn: (x: T) => Promise | boolean = (x: T) => + Promise.resolve(!!x), + maxRetries = MAX_DEFAULT_RETRY_AMOUNT, + waitTimeStrategy: RetryStrategyF = exponentialStrategyWithJitter, + retries = 0, + lastError: undefined | unknown = undefined +): Promise => { + if (retries >= maxRetries) { + if (lastError) throw lastError; + throw new Error("Exceeded maximum retry amount"); + } + try { + if (retries) await waitFor(waitTimeStrategy(retries)); + + const res = await promiseFn(); + if (res && (await validationFn(res))) return res; + + throw new Error("Validation predicate unsuccessful"); + } catch (e: unknown) { + return continueRetryUntilValidation( + promiseFn, + validationFn, + maxRetries, + waitTimeStrategy, + retries + 1, + e + ); + } +}; \ No newline at end of file diff --git a/src/lib/utils/setImageUrl.ts b/src/lib/utils/setImageUrl.ts new file mode 100644 index 0000000..15566a8 --- /dev/null +++ b/src/lib/utils/setImageUrl.ts @@ -0,0 +1,12 @@ +import { supabase } from '../supabase'; + +export const setImageUrl = (imageSpec) => { + const { publicURL, error } = supabase + .storage + .from('mistymountains') + .getPublicUrl(imageSpec.image); + if (!error) { + return { ...imageSpec, image: publicURL }; + } + return imageSpec; +} -- cgit v1.2.3-70-g09d2