summaryrefslogtreecommitdiff
path: root/src/lib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/utils')
-rw-r--r--src/lib/utils/index.ts3
-rw-r--r--src/lib/utils/mailer.ts61
-rw-r--r--src/lib/utils/retry.ts45
-rw-r--r--src/lib/utils/setImageUrl.ts12
4 files changed, 121 insertions, 0 deletions
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<boolean>;
+}
+
+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<string>(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 <T>(
+ promiseFn: () => Promise<T> | T | Promise<void> | void,
+ validationFn: (x: T) => Promise<boolean> | boolean = (x: T) =>
+ Promise.resolve(!!x),
+ maxRetries = MAX_DEFAULT_RETRY_AMOUNT,
+ waitTimeStrategy: RetryStrategyF = exponentialStrategyWithJitter,
+ retries = 0,
+ lastError: undefined | unknown = undefined
+): Promise<T> => {
+ 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;
+}