Repository navigation
Expand file tree
/
Copy pathnest.ts
More file actions
86 lines (77 loc) · 2.46 KB
/
Copy pathnest.ts
File metadata and controls
86 lines (77 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import "reflect-metadata";
import {
Controller,
Headers,
HttpCode,
Inject,
Module,
Post,
Req,
Res,
type DynamicModule,
type RawBodyRequest,
} from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import type { Request, Response } from "express";
import { handleWebhook, type Ledger } from "./webhook.js";
import type { RunningApp } from "./running.js";
/**
* NestJS 12 on its default Express platform.
*
* Nest has the raw body built in: create the app with `rawBody: true` and the
* request gains `rawBody`, a `Buffer` of what was sent, *alongside* the parsed
* `body` - so the rest of the application is unaffected. Without the flag,
* `rawBody` is simply undefined, which is why the handler checks for it.
*/
/**
* Injected by token rather than by type. Nest's type-based injection reads
* `emitDecoratorMetadata` output, which esbuild - and so Vitest, tsx and most
* modern tooling - does not produce. An explicit token works under every
* compiler, so a recipe that is going to be copied should use one.
*/
export const LEDGER = Symbol("Ledger");
@Controller("webhooks")
class WebhookController {
constructor(@Inject(LEDGER) private readonly ledger: Ledger) {}
@Post()
@HttpCode(200)
receive(
@Req() request: RawBodyRequest<Request>,
@Headers("x-signature") signature: string | undefined,
@Res() response: Response,
): void {
const raw = request.rawBody ?? Buffer.alloc(0);
const result = handleWebhook(this.ledger, raw, signature);
// @Res() hands the response to us, so the status set here is final; the
// @HttpCode above only documents the success case.
response.status(result.status).json(result.body);
}
}
@Module({})
class WebhookModule {
static forLedger(ledger: Ledger): DynamicModule {
return {
module: WebhookModule,
controllers: [WebhookController],
providers: [{ provide: LEDGER, useValue: ledger }],
};
}
}
/**
* Listens on an ephemeral loopback port.
*
* `app.listen(0, host)` then `getUrl()`, which reports the bound address.
* `logger: false` keeps Nest's startup banner out of the test output; drop it
* while debugging a recipe that will not start.
*/
export async function startNest(ledger: Ledger): Promise<RunningApp> {
const app = await NestFactory.create(WebhookModule.forLedger(ledger), {
rawBody: true,
logger: false,
});
await app.listen(0, "127.0.0.1");
return {
baseUrl: await app.getUrl(),
close: () => app.close(),
};
}