Files
diachron/backend/diachron/app.ts

138 lines
3.3 KiB
TypeScript

// FIXME: rename this to make-app.ts and adjust imports accordingly
import{contentTypes} from './content-types'
import{httpCodes}from'./http-codes'
import express, {
type Express,
type NextFunction,
type Request as ExpressRequest,
type Response as ExpressResponse,
} from "express";
import { formatError, formatErrorHtml } from "./errors";
import {isRedirect, InternalHandler, AuthenticationRequired,
AuthorizationDenied, Call,type Method, type ProcessedRoute,methodParser, type Result, type Route,massageMethod } from "./types";
import { cli } from "./cli";
import{processRoutes}from'./routing'
process.on('uncaughtException', (err) => {
console.error(formatError(err));
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error(formatError(reason));
});
type MakeAppArgs={routes:Route[],
processTitle?: string,
}
export interface DiachronApp extends Express {
start: () => void
}
const makeApp = ({routes, processTitle}: MakeAppArgs) => {
if (process.title) {
process.title = `diachron:${cli.listen.port}`;
}
const processedRoutes = processRoutes(routes)
async function handler(
req: ExpressRequest,
_res: ExpressResponse,
): Promise<Result> {
const method = await methodParser.parseAsync(req.method);
const byMethod = processedRoutes[method];
console.log(
"DEBUG: req.path =",
JSON.stringify(req.path),
"method =",
method,
);
for (const [_idx, pr] of byMethod.entries()) {
const match = pr.matcher(req.path);
console.log("DEBUG: trying pattern, match result =", match);
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;
}
// I don't like going around tsc but this is simple enough that it's probably OK.
const app = express() as DiachronApp
app.start = function() {
this.listen(cli.listen.port, cli.listen.host, () => {
console.log(`Listening on ${cli.listen.host}:${cli.listen.port}`);
});
};
app.use(express.json())
app.use(express.urlencoded({ extended: true }));
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);
// Set any cookies from the result
if (result0.cookies) {
for (const cookie of result0.cookies) {
res.cookie(cookie.name, cookie.value, cookie.options ?? {});
}
}
if (isRedirect(result0)) {
res.redirect(code, result0.redirect);
} else {
res.status(code).send(result);
}
});
app.use(
(
err: Error,
_req: ExpressRequest,
res: ExpressResponse,
_next: NextFunction,
) => {
console.error(formatError(err));
res.status(500).type("html").send(formatErrorHtml(err));
},
);
return app;
}
export{makeApp};
function _isPromise<T>(value: T | Promise<T>): value is Promise<T> {
return typeof (value as any)?.then === "function";
}