1import * as GCP from "alchemy/GCP";
2import * as Effect from "effect/Effect";
3import { HttpServerRequest } from "effect/http/HttpServerRequest";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5import { Shop, type Order } from "./resources.ts";
6
7/**
8 * The public front door: it writes orders and does nothing else.
9 *
10 * Everything that should happen *because* an order exists — auditing,
11 * fulfilment, notifications — hangs off Firestore's change stream in
12 * {@link ../Auditor.ts}, so adding a reaction never touches this service.
13 *
14 * - `POST /orders` — store `{ item, quantity }` at `orders/{id}`.
15 */
16export default class Api extends GCP.Function<Api>()(
17 "Api",
18 {
19 main: import.meta.url,
20 invokerIamDisabled: true,
21 },
22 Effect.gen(function* () {
23 const shop = yield* Shop;
24 // roles/datastore.user on the project, conditioned on this database.
25 const db = yield* GCP.Firestore.WriteDatabase(shop);
26
27 return {
28 fetch: Effect.gen(function* () {
29 const request = yield* HttpServerRequest;
30 const url = new URL(request.originalUrl);
31
32 if (request.method === "GET" && url.pathname === "/") {
33 return HttpServerResponse.text("ok");
34 }
35
36 if (request.method === "POST" && url.pathname === "/orders") {
37 const body = (yield* request.json) as {
38 item?: string;
39 quantity?: number;
40 };
41 if (!body.item) {
42 return yield* HttpServerResponse.json(
43 { error: "item is required" },
44 { status: 400 },
45 );
46 }
47
48 const id = crypto.randomUUID();
49 const order: Order = {
50 item: body.item,
51 quantity: body.quantity ?? 1,
52 createdAt: new Date(),
53 };
54 // `create` rather than `set`: a create is what emits the
55 // `document.v1.created` event the auditor listens for.
56 yield* db.create(`orders/${id}`, { ...order }).pipe(Effect.orDie);
57
58 return yield* HttpServerResponse.json({ id }, { status: 201 });
59 }
60
61 return yield* HttpServerResponse.json(
62 { error: "not found" },
63 { status: 404 },
64 );
65 }),
66 };
67 }).pipe(Effect.provide(GCP.Firestore.WriteDatabaseHttp)),
68) {}