1import * as machines from "@distilled.cloud/fly-io/machines";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as FetchHttpClient from "effect/http/FetchHttpClient";
5import { CredentialsFromAmbientOrEnv } from "./Credentials.ts";
6import {
7 type SecretAuth,
8 makeHttpAppBinding,
9 unwrapSecretValue,
10} from "./SecretHttp.ts";
11import {
12 WriteCertificates,
13 type WriteCertificatesClient,
14} from "./WriteCertificates.ts";
15
16/**
17 * HTTP implementation of {@link WriteCertificates}. Provide it on the
18 * {@link Service} or Action Effect.
19 *
20 * ### Provide the layer
21 * **Example:** On a Service
22 * ```typescript
23 * Effect.gen(function* () {
24 * const certs = yield* Fly.WriteCertificates(Site);
25 * // ...
26 * }).pipe(Effect.provide(Fly.WriteCertificatesHttp))
27 * ```
28 *
29 * @layer
30 * @product Certificate
31 * @provides Fly.WriteCertificates
32 */
33export const WriteCertificatesHttp = Layer.effect(
34 WriteCertificates,
35 Effect.suspend(() =>
36 makeHttpAppBinding<WriteCertificatesClient>({
37 makeClient: certificatesWriteClient,
38 }),
39 ),
40).pipe(
41 Layer.provide(FetchHttpClient.layer),
42 Layer.provide(CredentialsFromAmbientOrEnv),
43);
44
45/** Build the client over an injectable auth and App name. */
46export const certificatesWriteClient = (
47 auth: SecretAuth,
48 appName: Effect.Effect<string>,
49): WriteCertificatesClient => {
50 const authorize = auth.authorize;
51 return {
52 request: Effect.fn("Fly.Certificates.request")(function* (hostname) {
53 return yield* authorize(
54 machines
55 .createAppAcmeCertificate({ app_name: yield* appName, hostname })
56 .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined))),
57 );
58 }),
59 upload: Effect.fn("Fly.Certificates.upload")(function* (request) {
60 const app_name = yield* appName;
61 const body = {
62 app_name,
63 hostname: request.hostname,
64 fullchain: request.fullchain,
65 private_key: unwrapSecretValue(request.privateKey),
66 };
67 return yield* authorize(
68 machines.createAppCustomCertificate(body).pipe(
69 // An existing custom certificate conflicts: replace it.
70 Effect.catchTag("Conflict", () =>
71 machines
72 .deleteAppCustomCertificate({
73 app_name,
74 hostname: request.hostname,
75 })
76 .pipe(
77 Effect.catchTag("NotFound", () => Effect.void),
78 Effect.andThen(machines.createAppCustomCertificate(body)),
79 ),
80 ),
81 ),
82 );
83 }),
84 check: Effect.fn("Fly.Certificates.check")(function* (hostname) {
85 return yield* authorize(
86 machines.checkAppCertificate({ app_name: yield* appName, hostname }),
87 );
88 }),
89 get: Effect.fn("Fly.Certificates.get")(function* (hostname) {
90 return yield* authorize(
91 machines
92 .getAppCertificate({ app_name: yield* appName, hostname })
93 .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined))),
94 );
95 }),
96 remove: Effect.fn("Fly.Certificates.remove")(function* (hostname) {
97 return yield* authorize(
98 machines
99 .deleteAppCertificate({ app_name: yield* appName, hostname })
100 .pipe(
101 Effect.catchTag("NotFound", () => Effect.void),
102 Effect.asVoid,
103 ),
104 );
105 }),
106 };
107};