import type { IncomingMessage, ServerResponse } from "node:http";
import type { Session, Store } from "./store";
export class HttpError extends Error {
constructor(
readonly status: number,
readonly code: string,
message: string,
readonly details?: unknown,
) {
super(message);
}
}
export type Ctx = {
req: IncomingMessage;
res: ServerResponse;
url: URL;
params: string[];
// Request bodies are checked field by field in each handler.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: any;
session: Session | null;
store: Store;
};
export type Route = {
method: string;
pattern: RegExp;
/** public: anyone; session: signed in with an email code; step: also passed the second factor; patients: also accepted the terms (as requireClinician({ realPatients })). */
auth: "public" | "session" | "step" | "patients";
handler: (ctx: Ctx) => unknown;
/** The backend answers 201 for creates and 202 for an access request. */
status: number;
};
export const route = (method: string, path: string, auth: Route["auth"], handler: Route["handler"], status = 200): Route => ({
method,
pattern: new RegExp(`^${path.replace(/:[a-zA-Z]+/g, "([^/]+)")}$`),
auth,
handler,
status,
});
export const text = (value: unknown, max = 500) => (typeof value === "string" ? value.trim().slice(0, max) : "");
export const need = <T>(value: T | undefined | null, what: string): T => {
if (value === undefined || value === null) throw new HttpError(404, "NOT_FOUND", `${what} was not found.`);
return value;
};
export const invalid = (message: string, details?: unknown) => new HttpError(400, "VALIDATION_ERROR", message, details);
export const clinicianName = (store: Store) => store.clinician.fullName;