mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
api architecture
This commit is contained in:
24
packages/lib/server/defaultHandler.ts
Normal file
24
packages/lib/server/defaultHandler.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
type Handlers = {
|
||||
[method in "GET" | "POST" | "PATCH" | "PUT" | "DELETE"]?: Promise<{ default: NextApiHandler }>;
|
||||
};
|
||||
|
||||
/** Allows us to split big API handlers by method */
|
||||
export const defaultHandler = (handlers: Handlers) => async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const handler = (await handlers[req.method as keyof typeof handlers])?.default;
|
||||
// auto catch unsupported methods.
|
||||
if (!handler) {
|
||||
return res
|
||||
.status(405)
|
||||
.json({ message: `Method Not Allowed (Allow: ${Object.keys(handlers).join(",")})` });
|
||||
}
|
||||
|
||||
try {
|
||||
await handler(req, res);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(500).json({ message: "Something went wrong" });
|
||||
}
|
||||
};
|
||||
20
packages/lib/server/defaultResponder.ts
Normal file
20
packages/lib/server/defaultResponder.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { getServerErrorFromUnknown } from "@documenso/lib/server";
|
||||
|
||||
type Handle<T> = (req: NextApiRequest, res: NextApiResponse) => Promise<T>;
|
||||
|
||||
/** Allows us to get type inference from API handler responses */
|
||||
export function defaultResponder<T>(f: Handle<T>) {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
try {
|
||||
const result = await f(req, res);
|
||||
if (result) res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = getServerErrorFromUnknown(err);
|
||||
res.statusCode = error.statusCode;
|
||||
res.json({ message: error.message });
|
||||
}
|
||||
};
|
||||
}
|
||||
43
packages/lib/server/getServerErrorFromUnknown.ts
Normal file
43
packages/lib/server/getServerErrorFromUnknown.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
PrismaClientKnownRequestError,
|
||||
NotFoundError,
|
||||
} from "@prisma/client/runtime";
|
||||
|
||||
import { HttpError } from "@documenso/lib/server";
|
||||
|
||||
export function getServerErrorFromUnknown(cause: unknown): HttpError {
|
||||
// Error was manually thrown and does not need to be parsed.
|
||||
if (cause instanceof HttpError) {
|
||||
return cause;
|
||||
}
|
||||
|
||||
if (cause instanceof SyntaxError) {
|
||||
return new HttpError({
|
||||
statusCode: 500,
|
||||
message: "Unexpected error, please reach out for our customer support.",
|
||||
});
|
||||
}
|
||||
|
||||
if (cause instanceof PrismaClientKnownRequestError) {
|
||||
return new HttpError({ statusCode: 400, message: cause.message, cause });
|
||||
}
|
||||
|
||||
if (cause instanceof NotFoundError) {
|
||||
return new HttpError({ statusCode: 404, message: cause.message, cause });
|
||||
}
|
||||
|
||||
if (cause instanceof Error) {
|
||||
return new HttpError({ statusCode: 500, message: cause.message, cause });
|
||||
}
|
||||
|
||||
if (typeof cause === "string") {
|
||||
// @ts-expect-error https://github.com/tc39/proposal-error-cause
|
||||
return new Error(cause, { cause });
|
||||
}
|
||||
|
||||
// Catch-All if none of the above triggered and something (even more) unexpected happend
|
||||
return new HttpError({
|
||||
statusCode: 500,
|
||||
message: `Unhandled error of type '${typeof cause}'. Please reach out for our customer support.`,
|
||||
});
|
||||
}
|
||||
33
packages/lib/server/http-error.ts
Normal file
33
packages/lib/server/http-error.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export class HttpError<TCode extends number = number> extends Error {
|
||||
public readonly cause?: Error;
|
||||
public readonly statusCode: TCode;
|
||||
public readonly message: string;
|
||||
public readonly url: string | undefined;
|
||||
public readonly method: string | undefined;
|
||||
|
||||
constructor(opts: { url?: string; method?: string; message?: string; statusCode: TCode; cause?: Error }) {
|
||||
super(opts.message ?? `HTTP Error ${opts.statusCode} `);
|
||||
|
||||
Object.setPrototypeOf(this, HttpError.prototype);
|
||||
this.name = HttpError.prototype.constructor.name;
|
||||
|
||||
this.cause = opts.cause;
|
||||
this.statusCode = opts.statusCode;
|
||||
this.url = opts.url;
|
||||
this.method = opts.method;
|
||||
this.message = opts.message ?? `HTTP Error ${opts.statusCode}`;
|
||||
|
||||
if (opts.cause instanceof Error && opts.cause.stack) {
|
||||
this.stack = opts.cause.stack;
|
||||
}
|
||||
}
|
||||
|
||||
public static fromRequest(request: Request, response: Response) {
|
||||
return new HttpError({
|
||||
message: response.statusText,
|
||||
url: response.url,
|
||||
method: request.method,
|
||||
statusCode: response.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
4
packages/lib/server/index.ts
Normal file
4
packages/lib/server/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { defaultHandler } from "./defaultHandler";
|
||||
export { defaultResponder } from "./defaultResponder";
|
||||
export { HttpError } from "./http-error";
|
||||
export { getServerErrorFromUnknown } from "./getServerErrorFromUnknown";
|
||||
Reference in New Issue
Block a user