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

191 lines (190 loc) 7.17 kB
import { createRouter } 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, getFormDataValues, getJsonBody, getResourceFromParams, getResources } from "./utils/server.mjs"; import { getSchema, initGlobals } from "./utils/globals.mjs"; const createHandler = ({ apiBasePath, options, prisma, paramKey = "nextadmin", onRequest })=>{ const router = createRouter(); router.use(async (req, res, next)=>{ await initGlobals(); return next(); }); if (onRequest) router.use(onRequest); router.get(`${apiBasePath}/:model/raw`, async (req, res)=>{ const resources = getResources(options); const resource = getResourceFromParams([ req.query[paramKey][0] ], resources); if (!resource) return res.status(404).json({ error: "Resource not found" }); let ids = req.query.ids; ids = Array.isArray(ids) ? ids.map((id)=>formatId(resource, id)) : ids?.split(",").map((id)=>formatId(resource, id)); const depth = req.query.depth; if (depth && isNaN(Number(depth))) return res.status(400).json({ error: "Depth should be a number" }); const data = await getRawData({ prisma, resource, resourceIds: ids, maxDepth: depth ? Number(depth) : void 0 }); return res.json(data); }).post(`${apiBasePath}/:model/actions/:id`, async (req, res)=>{ const resources = getResources(options); const id = req.query[paramKey].at(-1); const resource = getResourceFromParams([ req.query[paramKey][0] ], resources); if (!resource) return res.status(404).json({ type: "error", message: "Resource not found" }); const modelAction = options?.model?.[resource]?.actions?.find((action)=>action.id === id); if (!modelAction) return res.status(404).json({ type: "error", message: "Action not found" }); if ("type" in modelAction && "dialog" === modelAction.type) return res.status(404).json({ type: "error", message: "Action not found" }); let body; try { body = await getJsonBody(req); } catch { return res.status(400).json({ type: "error", message: "Invalid JSON body" }); } try { const result = await modelAction.action(body); return res.json(result ?? null); } catch (e) { return res.status(500).json({ type: "error", message: e.message }); } }).post(`${apiBasePath}/options`, async (req, res)=>{ let body; try { body = await getJsonBody(req); } catch { return res.status(400).json({ error: "Invalid JSON body" }); } const data = await handleOptionsSearch(body, prisma, options); return res.json(data); }).post(`${apiBasePath}/:model/:id?`, async (req, res)=>{ const resources = getResources(options); const resource = getResourceFromParams([ req.query[paramKey][0] ], resources); if (!resource) return res.status(404).json({ error: "Resource not found" }); const body = await getFormDataValues(req); const id = 2 === req.query[paramKey].length ? formatId(resource, req.query[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 res.status(400).json({ error: response.error, validation: response.validation }); response = await editOptions?.hooks?.afterDb?.(response, mode, req) ?? response; return res.status(id ? 200 : 201).json(response); } catch (e) { if (e instanceof HookError) return res.status(e.status).json(e.data); return res.status(500).json({ error: e.message }); } }).delete(`${apiBasePath}/:model/:id`, async (req, res)=>{ const resources = getResources(options); const resource = getResourceFromParams([ req.query[paramKey][0] ], resources); if (!resource) return res.status(404).json({ error: "Resource not found" }); if (!hasPermission(options?.model?.[resource], Permission.DELETE)) return res.status(403).json({ error: "You don't have permission to delete this resource" }); try { const deleted = await deleteResource({ body: [ req.query[paramKey][1] ], prisma, resource, modelOptions: options?.model?.[resource] }); if (!deleted) throw new Error("Deletion failed"); return res.json({ ok: true }); } catch (e) { return res.status(500).json({ error: e.message }); } }).delete(`${apiBasePath}/:model`, async (req, res)=>{ const resources = getResources(options); const resource = getResourceFromParams([ req.query[paramKey][0] ], resources); if (!resource) return res.status(404).json({ error: "Resource not found" }); if (!hasPermission(options?.model?.[resource], Permission.DELETE)) return res.status(403).json({ error: "You don't have permission to delete this resource" }); let body; try { body = await getJsonBody(req); } catch { return res.status(400).json({ error: "Invalid JSON body" }); } try { await deleteResource({ body, prisma, resource, modelOptions: options?.model?.[resource] }); return res.json({ ok: true }); } catch (e) { return res.status(500).json({ error: e.message }); } }); const executeRouteHandler = (req, res)=>router.run(req, res); return { run: executeRouteHandler, router }; }; export { createHandler };