summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Hunt <elizabeth.hunt@simponic.xyz>2024-12-15 01:57:28 -0800
committerElizabeth Hunt <elizabeth.hunt@simponic.xyz>2024-12-15 02:05:18 -0800
commit4f1e974623f7e38693d3e202cd387c51f652b9d8 (patch)
tree947892a7f7608cb31424c8c37adea7c26a857bee
parent53187bede7e871ceca8fe4fabd18822002eb9316 (diff)
downloaduptime-4f1e974623f7e38693d3e202cd387c51f652b9d8.tar.gz
uptime-4f1e974623f7e38693d3e202cd387c51f652b9d8.zip
logout on end
-rw-r--r--src/api.ts9
-rw-r--r--src/duration.ts24
-rw-r--r--src/email.ts24
-rw-r--r--tst/email.spec.ts4
4 files changed, 50 insertions, 11 deletions
diff --git a/src/api.ts b/src/api.ts
index cfb446a..722dc70 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -1,5 +1,5 @@
+import { transformDurations } from "./duration";
import { perform } from "./email";
-import type { EmailJob } from "./job";
import { ConsoleLogger } from "./logger";
export const main = (port: number) => {
@@ -10,7 +10,12 @@ export const main = (port: number) => {
const url = new URL(req.url);
if (req.method === "POST" && url.pathname === "/api/email") {
- const job: EmailJob = await req.json();
+ const prevalidatedJob = transformDurations(await req.json());
+ if (prevalidatedJob._tag === "Left") {
+ return new Response(prevalidatedJob.left, { status: 400 });
+ }
+ const job = prevalidatedJob.right;
+
const jobInsensitive = structuredClone(job);
jobInsensitive.from.username = "****REDACTED****";
jobInsensitive.from.password = "****REDACTED****";
diff --git a/src/duration.ts b/src/duration.ts
index 3d1a44c..e8dc7d1 100644
--- a/src/duration.ts
+++ b/src/duration.ts
@@ -12,6 +12,7 @@ export enum DurationUnit {
MINUTE,
HOUR,
}
+
const durationUnitMap: Record<string, DurationUnit> = {
ms: DurationUnit.MILLISECOND,
milliseconds: DurationUnit.MILLISECOND,
@@ -153,3 +154,26 @@ export const parse = (duration: string): E.Either<string, Duration> => {
E.map(build),
);
};
+
+export const transformDurations = (obj: any): E.Either<string, any> => {
+ const transform = (o: any): E.Either<string, any> => {
+ const entries = Object.entries(o);
+
+ for (let [key, value] of entries) {
+ if (key === "duration" && typeof value === "string") {
+ return parse(value);
+ } else if (typeof value === "object" && value !== null) {
+ const result = transform(value);
+ if (E.isLeft(result)) {
+ return result;
+ } else {
+ o[key] = result.right;
+ }
+ }
+ }
+
+ return E.right(o);
+ };
+
+ return transform(obj);
+}
diff --git a/src/email.ts b/src/email.ts
index 6ad05a0..906b86d 100644
--- a/src/email.ts
+++ b/src/email.ts
@@ -14,7 +14,7 @@ interface ImapClientI {
connect: () => Promise<void>;
getMailboxLock: (mailbox: string) => Promise<MailboxLockObject>;
messageDelete: (uids: number[], opts: Record<string, any>) => Promise<boolean>;
- close: () => void;
+ logout: () => Promise<void>;
}
type Email = {
@@ -26,13 +26,16 @@ type Email = {
class ErrorWithLock extends Error {
lock: O.Option<MailboxLockObject>;
- constructor(message: string, lock?: MailboxLockObject) {
+ imap: O.Option<ImapClientI>;
+ constructor(message: string, lock?: MailboxLockObject, imap?: ImapClientI) {
super(message);
this.lock = O.fromNullable(lock);
+ this.imap = O.fromNullable(imap);
}
}
-const ToErrorWithLock = (lock?: MailboxLockObject) => (error: unknown) =>
- new ErrorWithLock(error instanceof Error ? error.message : "Unknown error", lock);
+
+const ToErrorWithLock = (lock?: MailboxLockObject, imap?: ImapClientI) => (error: unknown) =>
+ new ErrorWithLock(error instanceof Error ? error.message : "Unknown error", lock, imap);
/**
* Generate a unique email.
@@ -203,19 +206,19 @@ export const perform = (
// act.
TE.tap(({ email }) => pipe(getSendImpl(from)(email), TE.mapLeft(ToErrorWithLock()))),
TE.bind("imap", () => pipe(getImapImpl(to), TE.mapLeft(ToErrorWithLock()))),
- TE.bind("mailboxLock", ({ imap }) => TE.tryCatch(() => imap.getMailboxLock("INBOX"), ToErrorWithLock())),
+ TE.bind("mailboxLock", ({ imap }) => TE.tryCatch(() => imap.getMailboxLock("INBOX"), ToErrorWithLock(undefined, imap))),
// "assert".
TE.bind("uid", ({ imap, email, mailboxLock }) =>
pipe(
findEmailUidInInboxImpl(imap, matchesEmailImpl(email), retries, interval),
- TE.mapLeft(ToErrorWithLock(mailboxLock))
+ TE.mapLeft(ToErrorWithLock(mailboxLock, imap))
)
),
// cleanup.
TE.bind("deleted", ({ imap, uid, mailboxLock }) =>
TE.tryCatch(
() => imap.messageDelete([uid], { uid: true }),
- ToErrorWithLock(mailboxLock),
+ ToErrorWithLock(mailboxLock, imap),
),
),
TE.fold(
@@ -223,10 +226,15 @@ export const perform = (
if (O.isSome(e.lock)) {
e.lock.value.release();
}
+ if (O.isSome(e.imap)) {
+ const imap = e.imap.value;
+ return pipe(TE.tryCatch(() => imap.logout(), toError), TE.flatMap(() => TE.left(e)));
+ }
return TE.left(e);
},
- ({ mailboxLock, deleted }) => {
+ ({ mailboxLock, deleted, imap }) => {
mailboxLock.release();
+ imap.logout();
return TE.right(deleted);
}
)
diff --git a/tst/email.spec.ts b/tst/email.spec.ts
index 7315816..e966030 100644
--- a/tst/email.spec.ts
+++ b/tst/email.spec.ts
@@ -31,7 +31,7 @@ const getMocks = () => {
connect: mock(() => Promise.resolve()),
getMailboxLock: mock(() => Promise.resolve(lock)),
messageDelete: mock(() => Promise.resolve(true)),
- close: mock(() => constVoid()),
+ logout: mock(() => Promise.resolve()),
};
const mockDependencies: Partial<EmailJobDependencies> = {
@@ -116,6 +116,7 @@ test("releases lock on left", async () => {
TE.mapLeft(() => {
expect(imap.getMailboxLock).toHaveBeenCalledTimes(1);
expect(lock.release).toHaveBeenCalledTimes(1);
+ expect(imap.logout).toHaveBeenCalledTimes(1);
}),
)();
});
@@ -131,6 +132,7 @@ test("releases lock on right", async () => {
TE.map(() => {
expect(imap.getMailboxLock).toHaveBeenCalledTimes(1);
expect(lock.release).toHaveBeenCalledTimes(1);
+ expect(imap.logout).toHaveBeenCalledTimes(1);
}),
TE.mapLeft(() => expect(false).toBeTruthy()),
)();