1import * as Fly from "@/Fly";
2import type { Input } from "@/Input";
3import * as Config from "effect/Config";
4import * as Effect from "effect/Effect";
5import * as Redacted from "effect/Redacted";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8
9export const Site = Fly.App("Site");
10export const TRIGGER_SECRET = "RUNTIME_WRITER_TRIGGER";
11export const Token = Fly.Secret("Token", {
12 app: Site,
13 name: "ACCEPTANCE_RUNTIME_SECRET",
14 value: Redacted.make("fixture-token-two"),
15});
16
17export class Writer extends Fly.Service<Writer>()("Writer") {}
18
19export const writerLayer = (triggerDigest?: Input<string | undefined>) =>
20 Writer.make(
21 {
22 app: Site,
23 main: import.meta.url,
24 region: "iad",
25 port: 3000,
26 guest: { cpuKind: "shared", cpus: 1, memoryMb: 256 },
27 env: { TRIGGER_READY: triggerDigest ?? "" },
28 services: [
29 {
30 protocol: "tcp",
31 internalPort: 3000,
32 autostop: "off",
33 ports: [{ port: 443, handlers: ["tls", "http"] }],
34 checks: [
35 {
36 type: "http",
37 port: 3000,
38 path: "/health",
39 interval: "2s",
40 timeout: "1s",
41 },
42 ],
43 },
44 ],
45 },
46 Effect.gen(function* () {
47 const token = yield* Token;
48 const name = yield* token.name;
49 const write = yield* Fly.WriteSecret(Token);
50 return {
51 fetch: Effect.gen(function* () {
52 const request = yield* HttpServerRequest;
53 if (request.url === "/health" && request.method === "GET") {
54 return HttpServerResponse.empty({ status: 200 });
55 }
56 if (
57 request.url !== "/writer" ||
58 (request.method !== "GET" && request.method !== "POST")
59 ) {
60 return HttpServerResponse.empty({ status: 404 });
61 }
62 return yield* Effect.gen(function* () {
63 const trigger = yield* Config.Redacted(TRIGGER_SECRET);
64 if (
65 request.headers.authorization !==
66 `Bearer ${Redacted.value(trigger)}`
67 ) {
68 return HttpServerResponse.empty({ status: 401 });
69 }
70 const machineId = yield* Effect.sync(
71 () => process.env.FLY_MACHINE_ID,
72 );
73 if (!machineId) {
74 return HttpServerResponse.empty({ status: 503 });
75 }
76 if (request.method === "GET") {
77 return yield* HttpServerResponse.json({
78 version: 0,
79 machineId,
80 marker: "ready",
81 });
82 }
83 const updated = yield* write.update(
84 yield* name,
85 Redacted.make("fixture-token-three"),
86 );
87 const version = updated.version ?? updated.Version;
88 if (
89 version === undefined ||
90 !Number.isSafeInteger(version) ||
91 version <= 0
92 ) {
93 return HttpServerResponse.empty({ status: 502 });
94 }
95 return yield* HttpServerResponse.json({
96 version,
97 machineId,
98 marker: "three",
99 });
100 }).pipe(
101 // SDK failures may contain request details; never return or log them.
102 Effect.catch(() =>
103 Effect.succeed(HttpServerResponse.empty({ status: 502 })),
104 ),
105 );
106 }),
107 };
108 }).pipe(Effect.provide(Fly.WriteSecretHttp)),
109 );
110
111export default writerLayer();