1import * as Drizzle from "alchemy/Drizzle/Postgres";
2import * as Railway from "alchemy/Railway";
3import { desc, sql } from "drizzle-orm";
4import * as Effect from "effect/Effect";
5import { HttpServerRequest } from "effect/http/HttpServerRequest";
6import * as HttpServerResponse from "effect/http/HttpServerResponse";
7import { ENSURE_NOTES_SQL, Notes } from "./schema.ts";
8import { Db, Site } from "./shared.ts";
9
10const cors = {
11 "access-control-allow-origin": "*",
12 "access-control-allow-methods": "GET,POST,OPTIONS",
13 "access-control-allow-headers": "content-type",
14};
15
16const json = (value: unknown, status = 200) =>
17 HttpServerResponse.json(value, { status, headers: cors });
18
19/**
20 * HTTP Service. Binds {@link Db} via {@link Railway.ConnectPostgres}
21 * and serves `/notes`.
22 */
23export default class Api extends Railway.Service<Api>()(
24 "Api",
25 {
26 project: Site,
27 main: import.meta.url,
28 port: 3000,
29 build: { install: ["pg"] },
30 healthcheck: "/health",
31 },
32 Effect.gen(function* () {
33 const conn = yield* Railway.ConnectPostgres(Db);
34 const db = yield* Drizzle.Postgres(conn.connectionString);
35
36 return {
37 fetch: Effect.gen(function* () {
38 const request = yield* HttpServerRequest;
39 const path = new URL(request.url, "http://service").pathname;
40 if (request.method === "OPTIONS") {
41 return yield* json({}, 204);
42 }
43 yield* db.execute(sql.raw(ENSURE_NOTES_SQL));
44 if (path === "/health") {
45 return yield* json({ ok: true });
46 }
47 if (path === "/notes" && request.method === "GET") {
48 const notes = yield* db
49 .select()
50 .from(Notes)
51 .orderBy(desc(Notes.createdAt));
52 return yield* json({ notes });
53 }
54 if (path === "/notes" && request.method === "POST") {
55 const raw = yield* request.text.pipe(Effect.orDie);
56 const payload = JSON.parse(raw) as { body?: unknown };
57 const body =
58 typeof payload.body === "string" ? payload.body.trim() : "";
59 if (body.length === 0) {
60 return yield* json({ error: "body required" }, 400);
61 }
62 const [note] = yield* db.insert(Notes).values({ body }).returning();
63 return yield* json({ note }, 201);
64 }
65 return yield* json({ error: "not found" }, 404);
66 }).pipe(
67 Effect.catch((cause: unknown) =>
68 json({ ok: false, error: String(cause) }, 500),
69 ),
70 Effect.orDie,
71 ),
72 };
73 }).pipe(Effect.provide(Railway.ConnectPostgresHttp)),
74) {}