diff options
Diffstat (limited to 'deno/mail-relay/aws/app.ts')
-rw-r--r-- | deno/mail-relay/aws/app.ts | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/deno/mail-relay/aws/app.ts b/deno/mail-relay/aws/app.ts new file mode 100644 index 0000000..1fda64e --- /dev/null +++ b/deno/mail-relay/aws/app.ts @@ -0,0 +1,136 @@ +import { parseArgs } from "@std/cli"; +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; + +import log from "../log.ts"; +import config from "../config.ts"; +import { AppBase } from "../app.ts"; +import { AwsContext } from "./context.ts"; +import { + AwsMailDeliverer, + AwsMailMessageIdRewriteHook, + AwsMailMessageIdSaveHook, +} from "./deliver.ts"; +import { AwsMailRetriever } from "./retriever.ts"; + +export class AwsRelayApp extends AppBase { + readonly #aws = new AwsContext(); + readonly #retriever; + protected readonly outboundDeliverer = new AwsMailDeliverer(this.#aws); + + constructor() { + super(); + this.#retriever = new AwsMailRetriever(this.#aws, this.inboundDeliverer); + + this.outboundDeliverer.preHooks.push( + new AwsMailMessageIdRewriteHook(this.db), + ); + this.outboundDeliverer.postHooks.push( + new AwsMailMessageIdSaveHook(this.db), + ); + + this.hono.post( + `/${config.get("awsInboundPath")}`, + async (ctx, next) => { + const auth = ctx.req.header("Authorization"); + if (auth !== config.get("awsInboundKey")) { + return ctx.json({ "msg": "Bad auth!" }, 403); + } + await next(); + }, + zValidator( + "json", + z.object({ + key: z.string(), + recipients: z.optional(z.array(z.string())), + }), + ), + async (ctx) => { + const { key, recipients } = ctx.req.valid("json"); + await this.#retriever.deliverS3Mail(key, recipients); + return ctx.json({ "msg": "Done!" }); + }, + ); + } + + realServe() { + this.createCron({ + name: "live-mail-recycler", + interval: 6 * 3600 * 1000, + callback: () => { + return this.#retriever.recycleLiveMails(); + }, + startNow: true, + }); + + return this.serve(); + } + + readonly cli = { + "init": (_: unknown) => { + log.info("Just init!"); + return Promise.resolve(); + }, + "list-lives": async (_: unknown) => { + const liveMails = await this.#retriever.listLiveMails(); + log.info(`Total ${liveMails.length}:`); + log.info(liveMails.join("\n")); + }, + "recycle-lives": async (_: unknown) => { + await this.#retriever.recycleLiveMails(); + }, + "serve": async (_: unknown) => { + await this.serve().http.finished; + }, + "real-serve": async (_: unknown) => { + await this.realServe().http.finished; + }, + } as const; +} + +const nonServerCli = { + "sendmail": async (_: unknown) => { + const decoder = new TextDecoder(); + let text = ""; + for await (const chunk of Deno.stdin.readable) { + text += decoder.decode(chunk); + } + + const res = await fetch( + `http://localhost:${config.HTTP_PORT}/send/raw`, + { + method: "post", + body: text, + }, + ); + log.infoOrError(!res.ok, res); + log.infoOrError(!res.ok, "Body\n" + await res.text()); + if (!res.ok) Deno.exit(-1); + }, +} as const; + +if (import.meta.main) { + const args = parseArgs(Deno.args); + + if (args._.length === 0) { + throw new Error("You must specify a command."); + } + + const command = args._[0]; + + if (command in nonServerCli) { + log.info(`Run non-server command ${command}.`); + await nonServerCli[command as keyof typeof nonServerCli](args); + Deno.exit(0); + } + + const app = new AwsRelayApp(); + await app.setup(); + if (command in app.cli) { + log.info(`Run command ${command}.`); + await app.cli[command as keyof AwsRelayApp["cli"]](args); + Deno.exit(0); + } else { + throw new Error(command + " is not a valid command."); + } +} |