@premieroctet/next-admin
Version:
Next-Admin provides a customizable and turnkey admin dashboard for applications built with Next.js and powered by the Prisma ORM. It aims to simplify the development process by providing a turnkey admin system that can be easily integrated into your proje
246 lines (245 loc) • 8.99 kB
JavaScript
import { createEdgeRouter } from "next-connect";
import { HookError } from "./exceptions/HookError.mjs";
import { handleOptionsSearch } from "./handlers/options.mjs";
import { deleteResource, submitResource } from "./handlers/resources.mjs";
import { Permission } from "./types.mjs";
import { hasPermission } from "./utils/permissions.mjs";
import { getRawData } from "./utils/prisma.mjs";
import { formatId, getFormValuesFromFormData, getModelIdProperty, getResourceFromParams, getResources } from "./utils/server.mjs";
import { getSchema, initGlobals } from "./utils/globals.mjs";
const createHandler = ({ apiBasePath, options, prisma, paramKey = "nextadmin", onRequest })=>{
const router = createEdgeRouter();
router.use(async (req, res, next)=>{
await initGlobals();
return next();
});
if (onRequest) router.use(async (req, ctxPromise, next)=>{
const ctx = await ctxPromise;
const response = await onRequest(req, ctx);
if (response) return response;
return next();
});
router.get(`${apiBasePath}/:model/raw`, async (req, ctx)=>{
const resources = getResources(options);
const params = await ctx.params;
const resource = getResourceFromParams(params[paramKey], resources);
if (!resource) return Response.json({
error: "Resource not found"
}, {
status: 404
});
const searchParams = new URL(req.url).searchParams;
const ids = searchParams.get("ids")?.split(",").map((id)=>formatId(resource, id));
const depth = searchParams.get("depth");
if (!ids) return Response.json({
error: "No ids provided"
}, {
status: 400
});
if (depth && isNaN(Number(depth))) return Response.json({
error: "Depth should be a number"
}, {
status: 400
});
const data = await getRawData({
prisma,
resource,
resourceIds: ids,
maxDepth: depth ? Number(depth) : void 0
});
return Response.json(data);
}).post(`${apiBasePath}/:model/actions/:id`, async (req, ctx)=>{
const resources = getResources(options);
const params = await ctx.params;
const id = params[paramKey].at(-1);
const resource = getResourceFromParams([
params[paramKey][0]
], resources);
if (!resource) return Response.json({
type: "error",
message: "Resource not found"
}, {
status: 404
});
const modelAction = options?.model?.[resource]?.actions?.find((action)=>action.id === id);
if (!modelAction) return Response.json({
type: "error",
message: "Action not found"
}, {
status: 404
});
if ("type" in modelAction && "dialog" === modelAction.type) return Response.json({
type: "error",
message: "Action not found"
}, {
status: 404
});
const body = await req.json();
try {
const result = await modelAction.action(body);
return Response.json(result ?? null);
} catch (e) {
return Response.json({
type: "error",
message: e.message
}, {
status: 500
});
}
}).post(`${apiBasePath}/options`, async (req, _ctx)=>{
const body = await req.json();
const data = await handleOptionsSearch(body, prisma, options);
return Response.json(data);
}).post(`${apiBasePath}/:model/order`, async (req, ctx)=>{
const resources = getResources(options);
const body = await req.json();
const params = await ctx.params;
const resource = getResourceFromParams(params[paramKey], resources);
const optimisticData = body;
if (!resource) return Response.json({
error: "Resource not found"
}, {
status: 404
});
if (!resource) return Response.json({
error: "Resource not found"
}, {
status: 404
});
const resourceIdField = getModelIdProperty(resource);
const orderField = options?.model?.[resource]?.list?.orderField;
if (!orderField) return Response.json({
error: "Order field not found"
}, {
status: 404
});
await prisma.$transaction(async (tx)=>{
for (const item of optimisticData)await tx[resource].update({
where: {
[resourceIdField]: item[resourceIdField].value
},
data: {
[orderField]: item[orderField].value
}
});
});
return Response.json({
ok: true
});
}).post(`${apiBasePath}/:model/:id?`, async (req, ctx)=>{
const resources = getResources(options);
const params = await ctx.params;
const resource = getResourceFromParams(params[paramKey], resources);
if (!resource) return Response.json({
error: "Resource not found"
}, {
status: 404
});
const body = await getFormValuesFromFormData(await req.formData(), options?.model?.[resource]?.edit?.fields);
const id = 2 === params[paramKey].length ? formatId(resource, params[paramKey].at(-1)) : void 0;
const editOptions = options?.model?.[resource]?.edit;
const mode = id ? "edit" : "create";
try {
const transformedBody = await editOptions?.hooks?.beforeDb?.(body, mode, req);
let response = await submitResource({
prisma,
resource,
body: transformedBody ?? body,
id,
options,
schema: getSchema()
});
if (response.error) return Response.json({
error: response.error,
validation: response.validation
}, {
status: 400
});
response = await editOptions?.hooks?.afterDb?.(response, mode, req) ?? response;
return Response.json(response, {
status: id ? 200 : 201
});
} catch (e) {
if (e instanceof HookError) return Response.json(e.data, {
status: e.status
});
return Response.json({
error: e.message
}, {
status: 500
});
}
}).delete(`${apiBasePath}/:model/:id`, async (req, ctx)=>{
const resources = getResources(options);
const params = await ctx.params;
const resource = getResourceFromParams(params[paramKey], resources);
if (!resource) return Response.json({
error: "Resource not found"
}, {
status: 404
});
if (!hasPermission(options?.model?.[resource], Permission.DELETE)) return Response.json({
error: "You don't have permission to delete this resource"
}, {
status: 403
});
try {
const deleted = await deleteResource({
body: [
params[paramKey][1]
],
prisma,
resource,
modelOptions: options?.model?.[resource]
});
if (!deleted) throw new Error("Deletion failed");
return Response.json({
ok: true
});
} catch (e) {
return Response.json({
error: e.message
}, {
status: 500
});
}
}).delete(`${apiBasePath}/:model`, async (req, ctx)=>{
const resources = getResources(options);
const params = await ctx.params;
const resource = getResourceFromParams(params[paramKey], resources);
if (!resource) return Response.json({
error: "Resource not found"
}, {
status: 404
});
if (!hasPermission(options?.model?.[resource], Permission.DELETE)) return Response.json({
error: "You don't have permission to delete this resource"
}, {
status: 403
});
try {
const body = await req.json();
await deleteResource({
body,
prisma,
resource,
modelOptions: options?.model?.[resource]
});
return Response.json({
ok: true
});
} catch (e) {
return Response.json({
error: e.message
}, {
status: 500
});
}
});
const executeRouteHandler = (req, context)=>router.run(req, context);
return {
run: executeRouteHandler,
router
};
};
export { createHandler };