149 lines
4.1 KiB
TypeScript
149 lines
4.1 KiB
TypeScript
import express, {
|
|
type Request as ExpressRequest,
|
|
type Response as ExpressResponse,
|
|
} from "express";
|
|
import { match } from "path-to-regexp";
|
|
import { Session } from "./auth";
|
|
import { cli } from "./cli";
|
|
import { contentTypes } from "./content-types";
|
|
import { httpCodes } from "./http-codes";
|
|
import { routes } from "./routes";
|
|
import { services } from "./services";
|
|
// import { URLPattern } from 'node:url';
|
|
import {
|
|
AuthenticationRequired,
|
|
AuthorizationDenied,
|
|
type Call,
|
|
type InternalHandler,
|
|
type Method,
|
|
massageMethod,
|
|
methodParser,
|
|
type ProcessedRoute,
|
|
type Result,
|
|
type Route,
|
|
} from "./types";
|
|
|
|
const app = express();
|
|
|
|
// Parse JSON request bodies
|
|
app.use(express.json());
|
|
|
|
services.logging.log({ source: "logging", text: ["1"] });
|
|
const processedRoutes: { [K in Method]: ProcessedRoute[] } = {
|
|
GET: [],
|
|
POST: [],
|
|
PUT: [],
|
|
PATCH: [],
|
|
DELETE: [],
|
|
};
|
|
|
|
function _isPromise<T>(value: T | Promise<T>): value is Promise<T> {
|
|
return typeof (value as any)?.then === "function";
|
|
}
|
|
|
|
routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => {
|
|
// const pattern /*: URLPattern */ = new URLPattern({ pathname: route.path });
|
|
const matcher = match<Record<string, string>>(route.path);
|
|
const methodList = route.methods;
|
|
|
|
const handler: InternalHandler = async (
|
|
request: ExpressRequest,
|
|
): Promise<Result> => {
|
|
const method = massageMethod(request.method);
|
|
|
|
console.log("method", method);
|
|
|
|
if (!methodList.includes(method)) {
|
|
// XXX: Worth asserting this?
|
|
}
|
|
|
|
console.log("request.originalUrl", request.originalUrl);
|
|
|
|
// Authenticate the request
|
|
const auth = await services.auth.validateRequest(request);
|
|
|
|
const req: Call = {
|
|
pattern: route.path,
|
|
path: request.originalUrl,
|
|
method,
|
|
parameters: { one: 1, two: 2 },
|
|
request,
|
|
user: auth.user,
|
|
session: new Session(auth.session, auth.user),
|
|
};
|
|
|
|
try {
|
|
const retval = await route.handler(req);
|
|
return retval;
|
|
} catch (error) {
|
|
// Handle authentication errors
|
|
if (error instanceof AuthenticationRequired) {
|
|
return {
|
|
code: httpCodes.clientErrors.Unauthorized,
|
|
contentType: contentTypes.application.json,
|
|
result: JSON.stringify({
|
|
error: "Authentication required",
|
|
}),
|
|
};
|
|
}
|
|
if (error instanceof AuthorizationDenied) {
|
|
return {
|
|
code: httpCodes.clientErrors.Forbidden,
|
|
contentType: contentTypes.application.json,
|
|
result: JSON.stringify({ error: "Access denied" }),
|
|
};
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
for (const [_idx, method] of methodList.entries()) {
|
|
const pr: ProcessedRoute = { matcher, method, handler };
|
|
|
|
processedRoutes[method].push(pr);
|
|
}
|
|
});
|
|
|
|
async function handler(
|
|
req: ExpressRequest,
|
|
_res: ExpressResponse,
|
|
): Promise<Result> {
|
|
const method = await methodParser.parseAsync(req.method);
|
|
|
|
const byMethod = processedRoutes[method];
|
|
for (const [_idx, pr] of byMethod.entries()) {
|
|
const match = pr.matcher(req.url);
|
|
if (match) {
|
|
console.log("match", match);
|
|
const resp = await pr.handler(req);
|
|
|
|
return resp;
|
|
}
|
|
}
|
|
|
|
const retval: Result = {
|
|
code: httpCodes.clientErrors.NotFound,
|
|
contentType: contentTypes.text.plain,
|
|
result: "not found",
|
|
};
|
|
|
|
return retval;
|
|
}
|
|
|
|
app.use(async (req: ExpressRequest, res: ExpressResponse) => {
|
|
const result0 = await handler(req, res);
|
|
|
|
const code = result0.code.code;
|
|
const result = result0.result;
|
|
|
|
console.log(result);
|
|
|
|
res.status(code).send(result);
|
|
});
|
|
|
|
process.title = `diachron:${cli.listen.port}`;
|
|
|
|
app.listen(cli.listen.port, cli.listen.host, () => {
|
|
console.log(`Listening on ${cli.listen.host}:${cli.listen.port}`);
|
|
});
|