1import * as Lambda from "@/AWS/Lambda";
2import * as Redshift from "@/AWS/Redshift";
3import * as Duration from "effect/Duration";
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";
8import path from "pathe";
9
10const main = path.resolve(import.meta.dirname, "connect-handler.ts");
11
12export class RedshiftConnectFunction extends Lambda.Function<Lambda.Function>()(
13 "RedshiftConnectFunction",
14) {}
15
16const summarize = (info: Redshift.ClusterConnectionInfo) => ({
17 host: info.host,
18 port: info.port,
19 database: info.database,
20 username: info.username,
21 hasPassword: info.password !== undefined,
22 ssl: info.ssl,
23 urlScheme: Redacted.value(info.url).split("://")[0],
24 expiresInFuture:
25 info.expiration !== undefined && info.expiration.getTime() > Date.now(),
26});
27
28export default RedshiftConnectFunction.make(
29 {
30 main,
31 functionUrl: true,
32 timeout: Duration.seconds(60),
33 },
34 Effect.gen(function* () {
35 const cluster = yield* Redshift.Cluster("ConnectCluster", {
36 clusterIdentifier: "alchemy-test-redshift-connect",
37 manageMasterPassword: true,
38 publiclyAccessible: false,
39 });
40
41 // Default strategy: the database user is mapped 1:1 to the Lambda's IAM
42 // identity via redshift:GetClusterCredentialsWithIAM.
43 const connectIam = yield* Redshift.Connect(cluster);
44 // Named-user strategy: redshift:GetClusterCredentials with AutoCreate.
45 const connectDbUser = yield* Redshift.Connect(cluster, {
46 dbUser: "alchemy_etl",
47 autoCreate: true,
48 });
49
50 return {
51 fetch: Effect.gen(function* () {
52 const request = yield* HttpServerRequest;
53 const url = new URL(request.originalUrl);
54 const pathname = url.pathname;
55
56 if (request.method === "GET" && pathname === "/info") {
57 const info = yield* connectIam;
58 return yield* HttpServerResponse.json(summarize(info));
59 }
60 if (request.method === "GET" && pathname === "/info-dbuser") {
61 const info = yield* connectDbUser;
62 return yield* HttpServerResponse.json(summarize(info));
63 }
64
65 return yield* HttpServerResponse.json(
66 { error: "Not found", method: request.method, pathname },
67 { status: 404 },
68 );
69 }).pipe(Effect.orDie),
70 };
71 }).pipe(Effect.provide(Redshift.ConnectHttp)),
72);