1import * as Fly from "@/Fly";
2import * as Effect from "effect/Effect";
3import * as Stream from "effect/Stream";
4import { HttpServerRequest } from "effect/http/HttpServerRequest";
5import * as HttpServerResponse from "effect/http/HttpServerResponse";
6
7export const BUCKET_PORT = 3000;
8export const OBJECT_KEY = "alchemy-marker.txt";
9export const OBJECT_BODY = "hello-from-tigris";
10
11export const BucketSite = Fly.App("BucketSite", {
12 enableSubdomains: true,
13});
14
15export const Data = Fly.Bucket("Data");
16
17export const BucketIp = Fly.IpAssignment("Shared", {
18 app: BucketSite,
19 type: "shared_v4",
20});
21
22/**
23 * HTTP Service that puts and gets an object on a Tigris bucket via
24 * the S3 API.
25 */
26export default class BucketApi extends Fly.Service<BucketApi>()(
27 "BucketApi",
28 Effect.gen(function* () {
29 const bucket = yield* Data;
30 return {
31 app: BucketSite,
32 main: import.meta.url,
33 region: "iad",
34 port: BUCKET_PORT,
35 guest: { cpuKind: "shared" as const, cpus: 1, memoryMb: 256 },
36 env: {
37 BUCKET_NAME: bucket.name,
38 AWS_ACCESS_KEY_ID: bucket.accessKeyId,
39 AWS_SECRET_ACCESS_KEY: bucket.secretAccessKey,
40 AWS_ENDPOINT_URL_S3: bucket.endpoint,
41 AWS_ENDPOINT_URL: bucket.endpoint,
42 AWS_REGION: bucket.region,
43 },
44 };
45 }),
46 Effect.gen(function* () {
47 const putObject = yield* Fly.PutObject(Data);
48 const getObject = yield* Fly.GetObject(Data);
49
50 return {
51 fetch: Effect.gen(function* () {
52 const request = yield* HttpServerRequest;
53 const path = new URL(request.url, "http://service").pathname;
54
55 if (path === "/health") {
56 return yield* HttpServerResponse.json({ ok: true });
57 }
58
59 if (path === "/put") {
60 yield* putObject({
61 Key: OBJECT_KEY,
62 Body: OBJECT_BODY,
63 ContentType: "text/plain",
64 }).pipe(Effect.orDie);
65 return yield* HttpServerResponse.json({ ok: true, key: OBJECT_KEY });
66 }
67
68 if (path === "/get") {
69 const obj = yield* getObject({ Key: OBJECT_KEY }).pipe(Effect.orDie);
70 const text =
71 obj.Body === undefined
72 ? ""
73 : yield* Stream.mkString(Stream.decodeText(obj.Body)).pipe(
74 Effect.orDie,
75 );
76 return yield* HttpServerResponse.json({
77 ok: text === OBJECT_BODY,
78 text,
79 });
80 }
81
82 return yield* HttpServerResponse.json({ ok: false }, { status: 404 });
83 }),
84 };
85 }).pipe(Effect.provide([Fly.PutObjectHttp, Fly.GetObjectHttp])),
86) {}