Port to node.js
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user