UNPKG

@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
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 };