| 1 | import type { ReadBucketClient } from "@/Prisma/ReadBucket.ts"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 4 | |
| 5 | /** |
| 6 | * Shared read-side routes so every method of {@link ReadBucketClient} |
| 7 | * (`get`, `head`, `list`, `presignGet`) is driven over `fetch`. Returns |
| 8 | * `undefined` when the path is not a read route so the caller can fall |
| 9 | * through (used by the read-write fixtures). |
| 10 | */ |
| 11 | export const readRoutes = (store: ReadBucketClient, url: URL) => |
| 12 | Effect.gen(function* () { |
| 13 | if (url.pathname === "/get") { |
| 14 | const key = url.searchParams.get("key") ?? ""; |
| 15 | const object = yield* store.get(key).pipe(Effect.orDie); |
| 16 | const value = object ? yield* object.text().pipe(Effect.orDie) : null; |
| 17 | return yield* HttpServerResponse.json({ |
| 18 | value, |
| 19 | contentType: object?.contentType ?? null, |
| 20 | metadata: object?.metadata ?? null, |
| 21 | }); |
| 22 | } |
| 23 | if (url.pathname === "/head") { |
| 24 | const key = url.searchParams.get("key") ?? ""; |
| 25 | const object = yield* store.head(key).pipe(Effect.orDie); |
| 26 | return yield* HttpServerResponse.json({ |
| 27 | exists: object !== null, |
| 28 | size: object?.size ?? null, |
| 29 | }); |
| 30 | } |
| 31 | if (url.pathname === "/list") { |
| 32 | const limit = url.searchParams.get("limit"); |
| 33 | const result = yield* store |
| 34 | .list({ |
| 35 | prefix: url.searchParams.get("prefix") ?? undefined, |
| 36 | delimiter: url.searchParams.get("delimiter") ?? undefined, |
| 37 | cursor: url.searchParams.get("cursor") ?? undefined, |
| 38 | limit: limit ? Number(limit) : undefined, |
| 39 | }) |
| 40 | .pipe(Effect.orDie); |
| 41 | return yield* HttpServerResponse.json({ |
| 42 | keys: result.objects.map((o) => o.key), |
| 43 | delimitedPrefixes: result.delimitedPrefixes, |
| 44 | truncated: result.truncated, |
| 45 | cursor: result.truncated ? result.cursor : null, |
| 46 | }); |
| 47 | } |
| 48 | // The app mints the URL from its bound credentials and hands it back; the |
| 49 | // test then fetches that URL directly, carrying no credentials of its own. |
| 50 | if (url.pathname === "/presign-get") { |
| 51 | const key = url.searchParams.get("key") ?? ""; |
| 52 | const expiresIn = url.searchParams.get("expiresIn"); |
| 53 | const contentType = url.searchParams.get("contentType"); |
| 54 | const presigned = yield* store |
| 55 | .presignGet(key, { |
| 56 | expiresIn: expiresIn ? Number(expiresIn) : undefined, |
| 57 | contentType: contentType ?? undefined, |
| 58 | }) |
| 59 | .pipe(Effect.orDie); |
| 60 | return yield* HttpServerResponse.json({ url: presigned }); |
| 61 | } |
| 62 | return undefined; |
| 63 | }); |