1import {
2 AuthAccessWriteScope,
3 EnvironmentId,
4 HubError,
5 HubManagementRequest,
6} from "@t3tools/contracts";
7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";
9import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
10import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
11import * as HubProxy from "./HubProxy.ts";
12import * as Schema from "effect/Schema";
13import * as Hub from "./Hub.ts";
14
15const isHubError = Schema.is(HubError);
16
17const errors = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
18 effect.pipe(
19 Effect.catch((error) => {
20 if (isHubError(error))
21 return Effect.succeed(
22 HttpServerResponse.jsonUnsafe(
23 { _tag: error._tag, reason: error.reason, message: error.message },
24 {
25 status:
26 error.reason === "not-found"
27 ? 404
28 : error.reason === "credential"
29 ? 403
30 : error.reason === "address"
31 ? 400
32 : 502,
33 },
34 ),
35 );
36 if (EnvironmentAuth.isServerAuthCredentialError(error))
37 return Effect.succeed(
38 HttpServerResponse.jsonUnsafe({ code: "unauthorized" }, { status: 401 }),
39 );
40 return Effect.succeed(
41 HttpServerResponse.jsonUnsafe({ code: "internal_error" }, { status: 500 }),
42 );
43 }),
44 );
45
46const authenticate = Effect.gen(function* () {
47 const auth = yield* EnvironmentAuth.EnvironmentAuth;
48 const request = yield* HttpServerRequest.HttpServerRequest;
49 return yield* auth.authenticateWebSocketUpgrade(request);
50});
51
52const directory = Effect.gen(function* () {
53 const session = yield* authenticate;
54 return HttpServerResponse.jsonUnsafe(
55 yield* (yield* Hub.Hub).directory(session, { refresh: false }),
56 {
57 headers: { "cache-control": "no-store" },
58 },
59 );
60});
61
62const manage = Effect.gen(function* () {
63 const session = yield* authenticate;
64 if (!session.scopes.includes(AuthAccessWriteScope))
65 return HttpServerResponse.empty({ status: 403 });
66 const input = yield* HttpServerRequest.schemaBodyJson(HubManagementRequest);
67 const hub = yield* Hub.Hub;
68 yield* hub.manage(input);
69 return HttpServerResponse.jsonUnsafe(yield* hub.directory(session, { refresh: false }), {
70 headers: { "cache-control": "no-store" },
71 });
72});
73
74const proxy = Effect.gen(function* () {
75 const params = yield* HttpRouter.params;
76 const request = yield* HttpServerRequest.HttpServerRequest;
77 const proxy = yield* HubProxy.HubProxy;
78 return yield* proxy.forward(
79 request,
80 EnvironmentId.make(params.environmentId ?? ""),
81 `/${params["*"] ?? ""}`,
82 );
83});
84
85export const layer = Layer.mergeAll(
86 HttpRouter.add("GET", "/api/hub/environments", directory.pipe(errors)),
87 HttpRouter.add("POST", "/api/hub/manage", manage.pipe(errors)),
88 HttpRouter.add("*", "/environments/:environmentId/*", proxy.pipe(errors)),
89);