aboutsummaryrefslogtreecommitdiff
path: root/services/docker/mail-server/relay/aws/app.ts
blob: 9135d21fb2a752df4de176fc262a2dd0c96e2764 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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 } 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.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.array(z.string()).optional(),
        }),
      ),
      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();
  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.");
  }
}