feat: api handler

This commit is contained in:
Sonny
2023-06-03 20:09:26 +02:00
parent a89fa471e0
commit cfcc99fa6b
4 changed files with 101 additions and 30 deletions

View File

@@ -1,29 +0,0 @@
import PATHS from "constants/paths";
export { default } from "next-auth/middleware";
// WAIT: for fix - Next.js@13.4.4 seems to be broken
// cf: https://github.com/nextauthjs/next-auth/issues/7650
// (this file must renamed "middleware.ts")
export const config = {
matcher: [
PATHS.HOME,
PATHS.LINK.CREATE,
PATHS.LINK.EDIT,
PATHS.LINK.REMOVE,
PATHS.CATEGORY.CREATE,
PATHS.CATEGORY.EDIT,
PATHS.CATEGORY.REMOVE,
PATHS.API.CATEGORY.CREATE,
PATHS.API.CATEGORY.EDIT,
PATHS.API.CATEGORY.REMOVE,
PATHS.API.LINK.CREATE,
PATHS.API.LINK.EDIT,
PATHS.API.LINK.REMOVE,
],
};

93
src/lib/api/handler.ts Normal file
View File

@@ -0,0 +1,93 @@
import { User } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import { NextApiRequest, NextApiResponse } from "next";
import { Session } from "next-auth";
import getUserOrThrow from "lib/user/getUserOrThrow";
import { getSessionOrThrow } from "utils/session";
type ApiHandlerMethod = ({
req,
res,
session,
user,
}: {
req: NextApiRequest;
res: NextApiResponse;
session: Session;
user: User;
}) => Promise<void>;
// This API Handler strongly inspired by
// Source: https://jasonwatmore.com/next-js-13-middleware-for-authentication-and-error-handling-on-api-routes
export function apiHandler(handler: {
get?: ApiHandlerMethod;
post?: ApiHandlerMethod;
put?: ApiHandlerMethod;
patch?: ApiHandlerMethod;
delete?: ApiHandlerMethod;
}) {
return async (req: NextApiRequest, res: NextApiResponse) => {
const method = req.method.toLowerCase();
if (!handler[method])
return res
.status(405)
.json({ error: `Method ${req.method} Not Allowed` });
try {
const session = await getSessionOrThrow(req, res);
const user = await getUserOrThrow(session);
await handler[method]({ req, res, session, user });
} catch (err) {
errorHandler(err, res);
}
};
}
function errorHandler(error: any, response: NextApiResponse) {
if (typeof error === "string") {
const is404 = error.toLowerCase().endsWith("not found");
const statusCode = is404 ? 404 : 400;
return response.status(statusCode).json({ message: error });
}
// does not fit with current error throwed
// TODO: fix errors returned
// by getSessionOrThrow or getUserOrThrow
if (error.name === "UnauthorizedError") {
// authentication error
return response.status(401).json({ message: "You must be connected" });
}
const errorMessage =
error.constructor.name === "PrismaClientKnownRequestError"
? handlePrismaError(error) // Handle Prisma specific errors
: error.message;
console.error(error);
return response.status(500).json({ message: errorMessage });
}
function handlePrismaError({
meta,
code,
message,
}: PrismaClientKnownRequestError) {
switch (code) {
case "P2002":
return `Duplicate field value: ${meta.target}`;
case "P2003":
return `Foreign key constraint failed on the field: ${meta.field_name}`;
case "P2014":
return `Invalid ID: ${meta.target}`;
case "P2003":
return `Invalid input data: ${meta.target}`;
// Details should not leak to client, be carreful with this
default:
return `Something went wrong: ${message}`;
}
}

View File

@@ -2,6 +2,9 @@ import { Session } from "next-auth";
import prisma from "utils/prisma";
export default async function getUserOrThrow(session: Session) {
if (!session || session === null) {
throw new Error("You must be connected");
}
return await prisma.user.findFirstOrThrow({
where: {
email: session?.user?.email,

View File

@@ -3,11 +3,15 @@ import { getServerSession } from "next-auth/next";
import { authOptions } from "pages/api/auth/[...nextauth]";
export async function getSession(req: NextApiRequest, res: NextApiResponse) {
return await getServerSession(req, res, authOptions);
}
export async function getSessionOrThrow(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getServerSession(req, res, authOptions);
const session = await getSession(req, res);
if (!session) {
throw new Error("You must be connected");