feat: rpc client

This commit is contained in:
Max Koon
2025-11-29 00:57:32 -05:00
parent 3ebb7ee796
commit 74f4da1d3d
9 changed files with 214 additions and 105 deletions

View File

@@ -11,11 +11,14 @@ import { WebhookReceiverRoute } from "./webhook";
import { ZeroMutateRoute, ZeroQueryRoute } from "./zero/handler";
import { RpcRoute } from "./rpc/handler";
import { BASE_URL } from "@money/shared";
import { CurrentSession, SessionMiddleware } from "./middleware/session";
const RootRoute = HttpLayerRouter.add(
"GET",
"/",
Effect.gen(function* () {
const d = yield* CurrentSession;
return HttpServerResponse.text("OK");
}),
);
@@ -28,11 +31,11 @@ const AllRoutes = Layer.mergeAll(
RpcRoute,
WebhookReceiverRoute,
).pipe(
Layer.provide(SessionMiddleware.layer),
Layer.provide(
HttpLayerRouter.cors({
allowedOrigins: ["https://money.koon.us", `${BASE_URL}:8081`],
allowedMethods: ["POST", "GET", "OPTIONS"],
// allowedHeaders: ["Content-Type", "Authorization", ""],
credentials: true,
}),
),

View File

@@ -1,13 +1,76 @@
import { RpcSerialization, RpcServer } from "@effect/rpc";
import { Effect, Layer, Schema } from "effect";
import { LinkRpcs, Link } from "@money/shared/rpc";
import { Console, Effect, Layer, Schema } from "effect";
import { LinkRpcs, Link, AuthMiddleware } from "@money/shared/rpc";
import { CurrentSession } from "../middleware/session";
import { Authorization } from "../auth/context";
import { HttpServerRequest } from "@effect/platform";
import { AuthSchema } from "@money/shared/auth";
const parseAuthorization = (input: string) =>
Effect.gen(function* () {
const m = /^Bearer\s+(.+)$/.exec(input);
if (!m) {
return yield* Effect.fail(new Error("Invalid token"));
}
return m[1];
});
export const AuthLive = Layer.scoped(
AuthMiddleware,
Effect.gen(function* () {
const auth = yield* Authorization;
return AuthMiddleware.of(({ headers, payload, rpc }) =>
Effect.gen(function* () {
const newHeaders = { ...headers };
const token = yield* Schema.decodeUnknown(
Schema.Struct({
authorization: Schema.optional(Schema.String),
}),
)(headers).pipe(
// Effect.tap(Console.debug),
Effect.flatMap(({ authorization }) =>
authorization != undefined
? parseAuthorization(authorization)
: Effect.succeed(undefined),
),
);
if (token) {
newHeaders["cookie"] = token;
}
const session = yield* auth
.use((auth) => auth.api.getSession({ headers: newHeaders }))
.pipe(
Effect.flatMap((s) =>
s == null ? Effect.succeed(null) : Schema.decode(AuthSchema)(s),
),
Effect.tap((s) => Console.debug("Auth result", s)),
);
return { auth: session };
}).pipe(Effect.orDie),
);
}),
);
const LinkHandlers = LinkRpcs.toLayer({
CreateLink: () => Effect.succeed(new Link({ href: "hi" })),
CreateLink: () =>
Effect.gen(function* () {
const session = yield* CurrentSession;
return new Link({ href: session.auth?.user.name || "anon" });
}),
});
export const RpcRoute = RpcServer.layerHttpRouter({
group: LinkRpcs,
path: "/rpc",
protocol: "http",
}).pipe(Layer.provide(LinkHandlers), Layer.provide(RpcSerialization.layerJson));
}).pipe(
Layer.provide(LinkHandlers),
Layer.provide(RpcSerialization.layerJson),
Layer.provide(AuthLive),
);