aboutsummaryrefslogtreecommitdiff
path: root/deno/mail-relay/aws/app.ts
diff options
context:
space:
mode:
Diffstat (limited to 'deno/mail-relay/aws/app.ts')
-rw-r--r--deno/mail-relay/aws/app.ts136
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.");
+ }
+}