1import * as Fly from "@/Fly";
2import type { Input } from "@/Input";
3import * as Redis from "@/Redis";
4import * as Config from "effect/Config";
5import * as Effect from "effect/Effect";
6import * as Redacted from "effect/Redacted";
7import { HttpServerRequest } from "effect/http/HttpServerRequest";
8import * as HttpServerResponse from "effect/http/HttpServerResponse";
9import {
10 Cache,
11 LedgerSite,
12 scripts,
13 services,
14} from "./bluegreen-worker-shared.ts";
15
16export class Ledger extends Fly.Service<Ledger>()("Ledger") {}
17export const ledgerLayer = (secretDigest?: Input<string | undefined>) =>
18 Ledger.make(
19 {
20 app: LedgerSite,
21 main: import.meta.url,
22 region: "iad",
23 port: 3000,
24 guest: { cpuKind: "shared", cpus: 1, memoryMb: 256 },
25 env: { SECRET_READY: secretDigest ?? "" },
26 services,
27 },
28 Effect.gen(function* () {
29 yield* Fly.ReadWriteRedis(Cache);
30 const resource = yield* Cache;
31 const boundUrl = yield* resource.url;
32 const cache = Redis.makeReadWrite(
33 Effect.gen(function* () {
34 const url = yield* boundUrl;
35 if (!url) return yield* Effect.die(new Error("Redis URL missing"));
36 return Redacted.value(url);
37 }),
38 );
39 return {
40 fetch: Effect.gen(function* () {
41 const request = yield* HttpServerRequest;
42 if (request.url === "/health") {
43 yield* cache.ping().pipe(Effect.orDie);
44 return HttpServerResponse.text("ready");
45 }
46 const token = yield* Config.Redacted("LEDGER_TOKEN").pipe(
47 Effect.orDie,
48 );
49 if (
50 request.headers.authorization !== `Bearer ${Redacted.value(token)}`
51 ) {
52 return HttpServerResponse.empty({ status: 401 });
53 }
54 const body = (yield* request.json.pipe(Effect.orDie)) as {
55 operation: keyof typeof scripts;
56 args?: string[];
57 };
58 if (!Object.hasOwn(scripts, body.operation))
59 return HttpServerResponse.empty({ status: 400 });
60 const result = yield* cache
61 .send("EVAL", [scripts[body.operation], 0, ...(body.args ?? [])])
62 .pipe(Effect.orDie);
63 return yield* HttpServerResponse.json({ result });
64 }),
65 };
66 }).pipe(Effect.provide(Fly.ReadWriteRedisHttp)),
67 );
68
69export default ledgerLayer();