1import * as AWS from "alchemy/AWS";
2import { Stack } from "alchemy/Stack";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import { HttpServerRequest } from "effect/http/HttpServerRequest";
6import * as HttpServerResponse from "effect/http/HttpServerResponse";
7import {
8 JobNotifications,
9 JobNotificationsSNS,
10 NotifyJobError,
11} from "./JobNotifications.ts";
12import {
13 GetJobError,
14 JobStorage,
15 JobStorageDynamoDB,
16 PutJobError,
17} from "./JobStorage.ts";
18
19export default class JobFunction extends AWS.Lambda.Function<JobFunction>()(
20 "JobFunction",
21 Stack.useSync((stack) => ({
22 main: import.meta.url,
23 memorySize: stack.stage === "prod" ? 1024 : 512,
24 functionUrl: true,
25 })),
26 Effect.gen(function* () {
27 const jobStorage = yield* JobStorage;
28 const notifications = yield* JobNotifications;
29
30 return {
31 fetch: Effect.gen(function* () {
32 const request = yield* HttpServerRequest;
33 const url = new URL(request.originalUrl);
34
35 if (request.method === "GET" && url.pathname === "/") {
36 const jobId = url.searchParams.get("jobId");
37 if (!jobId) {
38 return HttpServerResponse.text("Job ID is required", {
39 status: 400,
40 });
41 }
42
43 const job = yield* jobStorage.getJob(jobId).pipe(
44 Effect.match({
45 onFailure: (error) => error,
46 onSuccess: (job) => job,
47 }),
48 );
49
50 if (job instanceof GetJobError) {
51 return HttpServerResponse.text(job.message, { status: 500 });
52 }
53
54 if (!job) {
55 return HttpServerResponse.text("Job not found", { status: 404 });
56 }
57
58 return yield* HttpServerResponse.json(job);
59 }
60
61 if (request.method === "POST" && url.pathname === "/") {
62 const content = yield* request.text;
63 if (!content) {
64 return HttpServerResponse.text("Job content is required", {
65 status: 400,
66 });
67 }
68
69 const job = yield* jobStorage
70 .putJob({
71 id: crypto.randomUUID(),
72 content,
73 })
74 .pipe(
75 Effect.match({
76 onFailure: (error) => error,
77 onSuccess: (job) => job,
78 }),
79 );
80
81 if (job instanceof PutJobError) {
82 return HttpServerResponse.text(job.message, { status: 500 });
83 }
84
85 const notificationResult = yield* notifications
86 .notifyJobCreated(job)
87 .pipe(
88 Effect.match({
89 onFailure: (error) => error,
90 onSuccess: () => undefined,
91 }),
92 );
93
94 if (notificationResult instanceof NotifyJobError) {
95 return HttpServerResponse.text(notificationResult.message, {
96 status: 500,
97 });
98 }
99
100 return yield* HttpServerResponse.json(
101 { jobId: job.id },
102 { status: 201 },
103 );
104 }
105
106 return HttpServerResponse.text("Not found", { status: 404 });
107 }),
108 };
109 }).pipe(
110 Effect.provide(
111 Layer.mergeAll(
112 // Services go here
113 JobStorageDynamoDB,
114 JobNotificationsSNS,
115 // JobStorageS3,
116 ),
117 ),
118 ),
119) {}