1import * as Lambda from "@/AWS/Lambda";
2import * as RedshiftServerless from "@/AWS/RedshiftServerless";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
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, "snapshot-handler.ts");
11
12export class SnapshotFunction extends Lambda.Function<Lambda.Function>()(
13 "SnapshotFunction",
14) {}
15
16/**
17 * Exercises the snapshot/recovery-point bindings against a namespace only —
18 * no workgroup, so the fixture carries no RPU billing floor.
19 */
20export default SnapshotFunction.make(
21 {
22 main,
23 functionUrl: true,
24 timeout: Duration.seconds(60),
25 },
26 Effect.gen(function* () {
27 const namespace = yield* RedshiftServerless.Namespace("SnapshotNamespace", {
28 namespaceName: "alchemy-test-rssnap-ns",
29 dbName: "dev",
30 adminUsername: "alchemyadmin",
31 manageAdminPassword: true,
32 });
33
34 const NamespaceName = yield* namespace.namespaceName;
35 const createSnapshot = yield* RedshiftServerless.CreateSnapshot(namespace);
36 const getSnapshot = yield* RedshiftServerless.GetSnapshot();
37 const listSnapshots = yield* RedshiftServerless.ListSnapshots();
38 const updateSnapshot = yield* RedshiftServerless.UpdateSnapshot();
39 const deleteSnapshot = yield* RedshiftServerless.DeleteSnapshot();
40 const listRecoveryPoints = yield* RedshiftServerless.ListRecoveryPoints();
41 const listTableRestoreStatus =
42 yield* RedshiftServerless.ListTableRestoreStatus();
43
44 return {
45 fetch: Effect.gen(function* () {
46 const request = yield* HttpServerRequest;
47 const url = new URL(request.originalUrl);
48 const pathname = url.pathname;
49 const name = url.searchParams.get("name") ?? "";
50
51 if (request.method === "POST" && pathname === "/snapshot") {
52 const { snapshot } = yield* createSnapshot({
53 snapshotName: name,
54 retentionPeriod: 1,
55 });
56 return yield* HttpServerResponse.json({
57 snapshotName: snapshot?.snapshotName,
58 status: snapshot?.status,
59 });
60 }
61
62 if (request.method === "GET" && pathname === "/snapshot") {
63 const { snapshot } = yield* getSnapshot({ snapshotName: name });
64 return yield* HttpServerResponse.json({
65 snapshotName: snapshot?.snapshotName,
66 namespaceName: snapshot?.namespaceName,
67 retentionPeriod: snapshot?.snapshotRetentionPeriod,
68 status: snapshot?.status,
69 });
70 }
71
72 if (request.method === "GET" && pathname === "/snapshots") {
73 const namespaceName = yield* NamespaceName;
74 const { snapshots } = yield* listSnapshots({ namespaceName });
75 return yield* HttpServerResponse.json({
76 names: (snapshots ?? []).map((s) => s.snapshotName),
77 });
78 }
79
80 if (request.method === "POST" && pathname === "/snapshot/retention") {
81 const { snapshot } = yield* updateSnapshot({
82 snapshotName: name,
83 retentionPeriod: 2,
84 });
85 return yield* HttpServerResponse.json({
86 retentionPeriod: snapshot?.snapshotRetentionPeriod,
87 });
88 }
89
90 if (request.method === "DELETE" && pathname === "/snapshot") {
91 yield* deleteSnapshot({ snapshotName: name });
92 return yield* HttpServerResponse.json({ deleted: true });
93 }
94
95 if (request.method === "GET" && pathname === "/recovery-points") {
96 const namespaceName = yield* NamespaceName;
97 const { recoveryPoints } = yield* listRecoveryPoints({
98 namespaceName,
99 });
100 return yield* HttpServerResponse.json({
101 count: (recoveryPoints ?? []).length,
102 });
103 }
104
105 if (request.method === "GET" && pathname === "/table-restores") {
106 const namespaceName = yield* NamespaceName;
107 const { tableRestoreStatuses } = yield* listTableRestoreStatus({
108 namespaceName,
109 });
110 return yield* HttpServerResponse.json({
111 count: (tableRestoreStatuses ?? []).length,
112 });
113 }
114
115 return yield* HttpServerResponse.json(
116 { error: "Not found", method: request.method, pathname },
117 { status: 404 },
118 );
119 }).pipe(Effect.orDie),
120 };
121 }).pipe(
122 Effect.provide(
123 Layer.mergeAll(
124 RedshiftServerless.CreateSnapshotHttp,
125 RedshiftServerless.GetSnapshotHttp,
126 RedshiftServerless.ListSnapshotsHttp,
127 RedshiftServerless.UpdateSnapshotHttp,
128 RedshiftServerless.DeleteSnapshotHttp,
129 RedshiftServerless.ListRecoveryPointsHttp,
130 RedshiftServerless.ListTableRestoreStatusHttp,
131 ),
132 ),
133 ),
134);