mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
api architecture
This commit is contained in:
@ -7,7 +7,10 @@ const nextConfig = {
|
||||
distDir: "build",
|
||||
};
|
||||
|
||||
const withTM = require("next-transpile-modules")(["@documenso/prisma"]);
|
||||
const withTM = require("next-transpile-modules")([
|
||||
"@documenso/prisma",
|
||||
"@documenso/lib",
|
||||
]);
|
||||
const plugins = [];
|
||||
plugins.push(withTM);
|
||||
|
||||
|
||||
@ -1,49 +1 @@
|
||||
import PrismaClient from "@documenso/prisma";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function userHandler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { method, body } = req;
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Check Session
|
||||
|
||||
switch (method) {
|
||||
case "POST":
|
||||
if (!body.userId) {
|
||||
res.status(400).end("Owner ID cannot be empty.");
|
||||
}
|
||||
|
||||
try {
|
||||
const newDocument: any = await prisma.document
|
||||
.create({
|
||||
data: { userId: body.userId, document: body.document },
|
||||
})
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
res.status(200).send(newDocument);
|
||||
});
|
||||
} catch (error) {
|
||||
await prisma.$disconnect();
|
||||
res.status(500).end("An error has occured.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "GET":
|
||||
// GET all docs for user in session
|
||||
let documents = await prisma.document.findMany({
|
||||
where: {
|
||||
userId: body.userId,
|
||||
},
|
||||
});
|
||||
res.status(200).send(documents);
|
||||
break;
|
||||
|
||||
default:
|
||||
res.setHeader("Allow", ["GET", "POST"]);
|
||||
res.status(405).end(`Method ${method} Not Allowed`);
|
||||
}
|
||||
}
|
||||
export {};
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
type Data = {
|
||||
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
||||
import prisma from "@documenso/prisma";
|
||||
|
||||
type responseData = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
res.status(200).json({ status: "Api up and running :)" });
|
||||
// Return a healthy 200 status code for uptime monitoring and render.com zero-downtime-deploy
|
||||
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// A generic database access to make sure the service is healthy.
|
||||
const users = await prisma.user.findFirst();
|
||||
res.status(200).json({ message: "Api up and running :)" });
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
|
||||
});
|
||||
|
||||
@ -1,42 +1,31 @@
|
||||
// POST to create
|
||||
import PrismaClient from "@documenso/prisma";
|
||||
import User from "@documenso/prisma";
|
||||
import {
|
||||
defaultHandler,
|
||||
defaultResponder,
|
||||
HttpError,
|
||||
} from "@documenso/lib/server";
|
||||
import prisma from "@documenso/prisma";
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { json } from "stream/consumers";
|
||||
|
||||
export default async function userHandler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<User>
|
||||
) {
|
||||
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { method, body } = req;
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
switch (method) {
|
||||
case "POST":
|
||||
if (!body.email) {
|
||||
res.status(400).end("Email cannot be empty.");
|
||||
}
|
||||
|
||||
try {
|
||||
let newUser: any;
|
||||
newUser = await prisma.user
|
||||
.create({
|
||||
data: { email: body.email },
|
||||
})
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
res.status(200).send(newUser);
|
||||
});
|
||||
} catch (error) {
|
||||
await prisma.$disconnect();
|
||||
res.status(500).end("An error has occured. Error: " + error);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
res.setHeader("Allow", ["POST"]);
|
||||
res.status(405).end(`Method ${method} Not Allowed`);
|
||||
if (!body.email) {
|
||||
return res.status(400).json({ message: "Email cannot be empty." });
|
||||
}
|
||||
|
||||
let newUser: any;
|
||||
newUser = await prisma.user
|
||||
.create({
|
||||
data: { email: body.email },
|
||||
})
|
||||
.then(async () => {
|
||||
return res.status(201).send(newUser);
|
||||
});
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
POST: Promise.resolve({ default: defaultResponder(postHandler) }),
|
||||
});
|
||||
|
||||
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";
|
||||
@ -1,3 +1,9 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
export default PrismaClient;
|
||||
declare global {
|
||||
var prismaClientSingleton: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
export const prisma = globalThis.prismaClientSingleton || new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
|
||||
Reference in New Issue
Block a user