Port to node.js

This commit is contained in:
2026-07-30 00:45:46 +02:00
parent 988025bfac
commit 1249781204
22 changed files with 1461 additions and 1768 deletions

View File

@@ -1,14 +1,16 @@
import { serve } from "@hono/node-server";
import { getConnInfo } from "@hono/node-server/conninfo";
import { serveStatic } from "@hono/node-server/serve-static";
import * as Body from "common/Body";
import { fetch } from "common/Fetch";
import { Cause, Effect, Layer, Match, Option, pipe, Record, Redacted, Stream } from "effect";
import { Effect, Layer, Match, pipe } from "effect";
import { Hono } from "hono";
import * as path from "node:path";
import { config } from "./config";
import * as Authentication from "./services/Authentication";
import * as Database from "./services/Database";
import { handle } from "./the_api";
import { config } from "./config.ts";
import * as Authentication from "./services/Authentication.ts";
import * as Database from "./services/Database.ts";
import { handle } from "./the_api.ts";
const FRONTEND_ROOT = "packages/frontend/build";
const FRONTEND_ASSETS_ROOT = path.join(FRONTEND_ROOT, "assets");
const CORS_HEADERS: [string, string][] = Match.value(config.NODE_ENV).pipe(
Match.when("development", (): [string, string][] => [
@@ -21,106 +23,86 @@ const CORS_HEADERS: [string, string][] = Match.value(config.NODE_ENV).pipe(
Match.exhaustive,
);
const assetRoutes = await pipe(
Stream.fromAsyncIterable(
new Bun.Glob("**/*").scan(FRONTEND_ASSETS_ROOT),
(error) => new Cause.UnknownException(error),
),
Stream.map((filepath): [string, Response] => [
`/assets/${filepath}`,
new Response(Bun.file(path.join(FRONTEND_ASSETS_ROOT, filepath))),
]),
Stream.runCollect,
Effect.map(Record.fromEntries),
Effect.runPromise,
);
const homepage = new Response(Bun.file(path.join(FRONTEND_ROOT, "index.html")));
const databaseLayer = Database.FromPath(config.DB_PATH);
Bun.serve({
routes: {
...assetRoutes,
"/login": {
GET: (req) => Effect.gen(function* () {
const searchParams = new URL(req.url).searchParams;
const app = new Hono()
.get("/login", (ctx) => Effect.gen(function* () {
const code = ctx.req.query("code");
const state = ctx.req.query("state");
// Callback URL with query response type
if (searchParams.has("code") || searchParams.has("state")) {
const code = searchParams.get("code");
const state = searchParams.get("state");
// Callback URL with query response type
if (code !== undefined || state !== undefined) {
yield* Authentication.getAndProcessIdToken(code ?? null, state ?? null);
return Response.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
}
yield* Authentication.getAndProcessIdToken(code, state);
// Initial login request; redirect to identity provider
const res = yield* pipe(
Authentication.Authentication,
Effect.flatMap(({ sessionId }) => Authentication.makeAuthorizationUrl(sessionId)),
Effect.map((url) => Response.redirect(url)),
);
return Response.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
}
return res;
}).pipe(
Effect.provide(Layer.provideMerge(Authentication.Live(ctx), databaseLayer)),
Effect.runPromise,
))
.post("/login", (ctx) => Effect.gen(function* () {
// Callback URL with form_post response type
// Initial login request; redirect to identity provider
const res = yield* pipe(
Authentication.Authentication,
Effect.flatMap(({ sessionId }) => Authentication.makeAuthorizationUrl(sessionId)),
Effect.map((url) => Response.redirect(url)),
);
const data = yield* Body.formData(ctx.req);
return res;
}).pipe(
Effect.provide(Layer.provideMerge(Authentication.Live(req), databaseLayer)),
Effect.runPromise,
),
POST: (req) => Effect.gen(function* () {
// Callback URL with form_post response type
const code = data.get("code") as string | null;
const state = data.get("state") as string | null;
const data = yield* Body.formData(req);
yield* Authentication.getAndProcessIdToken(code, state);
const code = data.get("code") as string | null;
const state = data.get("state") as string | null;
return Response.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
}).pipe(
Effect.provide(Layer.provideMerge(Authentication.Live(ctx), databaseLayer)),
Effect.runPromise,
))
.all("/api/:key", async (ctx) => {
const req = ctx.req;
yield* Authentication.getAndProcessIdToken(code, state);
const timestamp = new Date().toISOString();
const connInfo = getConnInfo(ctx);
console.log(`${timestamp} ${req.method} ${req.url} ${connInfo.remote.address!}`);
return Response.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
}).pipe(
Effect.provide(Layer.provideMerge(Authentication.Live(req), databaseLayer)),
Effect.runPromise,
),
},
"/api/:key": async (req, server) => {
if (req.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: CORS_HEADERS,
});
}
const timestamp = new Date().toISOString();
console.log(`${timestamp} ${req.method} ${req.url} ${server.requestIP(req)?.address}`);
if (req.method !== "POST") {
return new Response(null, {
status: 405,
headers: CORS_HEADERS,
});
}
if (req.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: CORS_HEADERS,
});
}
const authenticationLayer = Authentication.Live(ctx);
const layers = Layer.provideMerge(authenticationLayer, databaseLayer);
if (req.method !== "POST") {
return new Response(null, {
status: 405,
headers: CORS_HEADERS,
});
}
const response = await pipe(
handle(req),
Effect.provide(layers),
Effect.runPromise,
);
const authenticationLayer = Authentication.Live(req);
const layers = Layer.provideMerge(authenticationLayer, databaseLayer);
for (const [name, value] of CORS_HEADERS) {
response.headers.set(name, value);
}
const response = await pipe(
handle(req.params.key, req),
Effect.provide(layers),
Effect.runPromise,
);
return response;
})
.use("/assets/*", serveStatic({ root: FRONTEND_ROOT }))
.use("*", serveStatic({ path: path.join(FRONTEND_ROOT, "index.html") }));
for (const [name, value] of CORS_HEADERS) {
response.headers.set(name, value);
}
return response;
},
"/*": homepage,
},
websocket: {
message: () => { },
},
serve({
fetch: app.fetch,
port: config.PORT,
});