Simple react-admin dataprovider for prisma, supporting audit logs and roles
npm install ra-data-simple-prismaCreate a fullstack react-admin app adding just one file on the server!
Most of the examples will use Next.js but you can use any node-based server-side framework.
```
npm i ra-data-simple-prisma
yarn add ra-data-simple-prisma
pnpm i ra-data-simple-prisma
`js
import { Admin, Resource } from "react-admin";
import { dataProvider } from "ra-data-simple-prisma";
const ReactAdmin = () => {
return (
);
};
export default ReactAdmin;
`
Simplest implementation ever:
`js
// -- Example for Next Pages router --
// /api/[resource].ts <= catch all resource requests
import { defaultHandler } from "ra-data-simple-prisma";
import { prismaClient } from "../prisma/client"; // <= Your prisma client instance
export default async function handler(req, res) {
const result = await defaultHandler(req.body, prismaClient);
res.json(result);
}
// -- Example for Next App router --
// /app/api/[resource]/route.ts <= catch all resource requests
import { defaultHandler } from "ra-data-simple-prisma";
import { prismaClient } from "../prisma/client"; // <= Your prisma client instance
import { NextResponse } from "next/server";
const handler = async (req: Request) => {
const body = await req.json();
const result = await defaultHandler(body, prismaClient);
return NextResponse.json(result);
};
export { handler as GET, handler as POST };
`
To be used with an underscore after the source name
- contains: prisma native operator (Default for string)
- endsWith: prisma native operator
- enum: to be used with enums, where exact match is required
- eq: equals
- exact: equals
- gt: prisma native operator
- gte: prisma native operator
- has: prisma native operator
- lt: prisma native operator
- lte: prisma native operator
- not: prisma native operator
- search: prisma native operator
- startsWith: prisma native operator
- pgjson: if using postgres drill down the json field
Example
`ts` {...props}
filters={[
source={"status_enum"}
/>,
source={"created_at_gte"}
/>,
source={"body_search"}
/>,
source={"user.settings.language_enum"} // <= drill down in relationships
/>,
source={"metadata_pgjson.key.subkey"}
/>,
]}
>
- AND
- OR
- NOT
Enabling complex filtering capabilities in React Admin applications.
Previously, ra-data-simple-prisma did not support Prisma's logical operators, limiting users to simple field-based filtering. Complex queries requiring logical combinations of conditions were not possible. We can use it on navigation for example
`ts
const OR = [
{ amount: { gte: 1000 }, status: "ACTIVE" },
{ amount: { lt: 500 }, status: "REJECTED" },
];
navigate(/resource_name?filter=${JSON.stringify({ OR })});`
`js`
export default function handler(req) {
const session = await getServerSession(...);
await defaultHandler(req.body, prismaClient, {
audit: {
model: prismaClient.audit_log,
authProvider: authProvider(session)
},
});
...
}
audit:
- model: The prisma model of the audit log table eg. prisma.auditLog{id: "_id", date: "created_at"}
- authProvider: Insert your AuthProvider from React-Admin
- columns?: Map fields to your database columns {create: true, update: true, delete: false}
- enabledForAction?: Enabled for which action eg.
- enabledResources?: List of resources which are to be audited. Defaults to all.
All dataProvider methods can be overridden for a given resource, or all.
`js
// /api/post.ts <= override default handler for specific resource
export default function handler(req) {
switch (req.body.method) {
case "create":
await createHandler
connect: {
tags: "id",
// or
tagIds: {
tag: "id",
},
// or
mediaIds: {
postToMediaRels: {
media: "id",
}
},
},
audit: ...
debug: ...
});
return NextResponse.json(...);
case "delete":
await deleteHandler
softDeleteField: "deletedAt",
audit: ...
debug: ...
});
break;
case "deleteMany":
await deleteManyHandler
softDeleteField: "deletedAt",
audit: ...
debug: ...
});
break;
case "getList":
await getListHandler
req.body,
prismaClient,
{
select: ...
where: ...
noNullsOnSort: ...
filterMode: ...
debug: ...
include: { tags: true },
transformRow: (post: ServerPost, postIndex: number, posts: ServerPost[]): AugmentedPost => {
return {
...post
tagIds: post.tags.map((tag) => tag.id);
}
},
}
);
// OR, if using InfiniteList compoenent
await getInfiniteListHandler
req.body,
prismaClient,
{
select: ...
where: ...
noNullsOnSort: ...
filterMode: ...
debug: ...
include: { tags: true },
transformRow: (post: ServerPost, postIndex: number, posts: ServerPost[]): AugmentedPost => {
return {
...post
tagIds: post.tags.map((tag) => tag.id);
}
},
}
);
break;
case "getMany":
await getManyHandler
req.body,
prismaClient,
);
break;
case "getManyReference":
await getManyReferenceHandler
req.body,
prismaClient,
);
break;
case "getOne":
await getOneHandler
req.body,
prismaClient,
{
select: ...
include: ...
transform: (post: any) => {
post._computedProp = ...
},
transform: async (
post: QueryPost
): Promise
return {
...post,
_extraPropAfterTransform: await Promise.resolve(true),
};
},
}
)
break;
case "update":
await updateHandler
req.body,
prismaClient,
{
skipFields: {
computedField: true
},
set: {
tags: "id",
},
allowNestedUpdate: {
user_settings: true,
fixed_settings: false,
},
allowNestedUpsert: {
other_settings: true
},
allowJsonUpdate: {
raw_data_field: true;
}
}
);
break;
case "updateMany":
await updateManyHandler
req.body,
prismaClient,
{
skipFields: {
computedField: true
},
set: {
tags: "id",
},
}
);
break;
default: // <= fall back on default handler
await defaultHandler(req.body, prismaClient, {
audit: ...
create: ...
delete: ...
getList: ...
getMany: ...
getManyReference: ...
getOne: ...
update: ...
});
break;
}
}
`
Stuff you can use to write your own custom logic
- extractOrderBy
- extractSkipTake
- extractWhere
In your Api handler, call the function canAccess to infer if the user (session) can perform that particular action.
Example in admin demo
It will need the permission object which looks like this
``
export const permissionsConfig: PermissionsConfig = {
OWNER: [{ action: "", resource: "" }], //admin can do anything
COLLABORATOR: [
//collaborator can do anything except edit, delete, create admin users
{ action: "", resource: "" },
{
type: "deny",
action: ["edit", "delete", "create"],
resource: "adminUser",
},
],
READER: [{ action: ["list", "show", "export"], resource: "*" }],
};
Use the example app to test the changes.
In root folder run
```
pnpm publish
MIT