Skip to content

Commit

Permalink
Apply feedbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
foyarash committed Nov 29, 2023
1 parent fe9e839 commit 6677a08
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 67 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Run tests
run: |
yarn turbo test:e2e
BASE_URL=http://localhost:3000/legacy/admin yarn test:e2e
BASE_URL=http://localhost:3000/pagerouter/admin yarn test:e2e
- uses: actions/upload-artifact@v3
if: always()
with:
Expand Down
176 changes: 115 additions & 61 deletions apps/docs/pages/docs/api-docs.mdx
@@ -1,86 +1,140 @@
import { Tabs } from "nextra/components";

# API

## `nextAdminRouter` function
## Functions

`nextAdminRouter` is a function that returns a promise of a _Node Router_ that you can use in your getServerSideProps function to start using Next Admin. Its usage is only related to Page router.
<Tabs items={['App router', 'Page router']}>
<Tabs.Tab>
The following is used only for App router.

Usage example:
## `getPropsFromParams` function

```ts
// pages/api/admin/[[...nextadmin]].ts
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/router"
);
const adminRouter = await nextAdminRouter(prisma, schema);
return adminRouter.run(req, res) as Promise<
GetServerSidePropsResult<{ [key: string]: any }>
>;
};
```
`getPropsFromParams` is a function that returns the props for the [`NextAdmin`](#nextadmin--component) component. It accepts one argument which is an object with the following properties:

It takes 3 parameters:
- `params`: the array of route params retrieved from the [optional catch-all segment](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#optional-catch-all-segments)
- `searchParams`: the query params [retrieved from the page](https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional)
- `options`: the [options](#next-admin-options) object
- `schema`: the json schema generated by the `prisma generate` command
- `prisma`: your Prisma client instance
- `action`: the [server action](https://nextjs.org/docs/app/api-reference/functions/server-actions) used to submit the form. It should be your own action, that wraps the `submitForm` action imported from `@premieroctet/next-admin/dist/actions`.

- Your Prisma client instance, _required_
- Your Prisma schema, _required_
</Tabs.Tab>
<Tabs.Tab>
The following is used only for Page router

and an _optional_ object of type [`NextAdminOptions`](#next-admin-options) to customize your admin with the following properties:
## `nextAdminRouter` function

```ts
import { NextAdminOptions } from "@premieroctet/next-admin";
`nextAdminRouter` is a function that returns a promise of a _Node Router_ that you can use in your getServerSideProps function to start using Next Admin. Its usage is only related to Page router.

const options: NextAdminOptions = {
model: {
User: {
toString: (user) => `${user.email} / ${user.name}`,
},
},
};
Usage example:

const adminRouter = await nextAdminRouter(prisma, schema, options);
```
```ts
// pages/api/admin/[[...nextadmin]].ts
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/router"
);
const adminRouter = await nextAdminRouter(prisma, schema);
return adminRouter.run(req, res) as Promise<
GetServerSidePropsResult<{ [key: string]: any }>
>;
};
```

## `getPropsFromParams` function
It takes 3 parameters:

`getPropsFromParams` is a function that returns the props for the [`NextAdmin`](#nextadmin--component) component. It accepts one argument which is an object with the following properties:
- Your Prisma client instance, _required_
- Your Prisma schema, _required_

- `params`: the array of route params retrieved from the [optional catch-all segment](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#optional-catch-all-segments)
- `searchParams`: the query params [retrieved from the page](https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional)
- `options`: the [options](#next-admin-options) object
- `schema`: the json schema generated by the `prisma generate` command
- `prisma`: your Prisma client instance
- `action`: the [server action](https://nextjs.org/docs/app/api-reference/functions/server-actions) used to submit the form. It should be your own action, that wraps the `submitForm` action imported from `@premieroctet/next-admin/dist/actions`.
and an _optional_ object of type [`NextAdminOptions`](#next-admin-options) to customize your admin with the following properties:

## Authentication
```ts
import { NextAdminOptions } from "@premieroctet/next-admin";

The library does not provide an authentication system. If you want to add your own, you can do so by adding a role check to the `getServerSideProps` function:
const options: NextAdminOptions = {
model: {
User: {
toString: (user) => `${user.email} / ${user.name}`,
},
},
};

> The following example uses [next-auth](https://next-auth.js.org/) to handle authentication
const adminRouter = await nextAdminRouter(prisma, schema, options);
```

```ts
// pages/api/admin/[[...nextadmin]].ts
</Tabs.Tab>
</Tabs>

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getServerSession(req, res, authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check
## Authentication

if (!isAdmin) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
<Tabs items={['App router', 'Page router']}>
<Tabs.Tab>
The library does not provide an authentication system. If you want to add your own, you can do so by adding a role check in the page:

> The following example uses [next-auth](https://next-auth.js.org/) to handle authentication
```ts
// app/admin/[[...nextadmin]]/page.tsx

export default async function AdminPage({
params,
searchParams,
}: {
params: { [key: string]: string[] };
searchParams: { [key: string]: string | string[] | undefined } | undefined;
}) {
const session = await getServerSession(authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check

if (!isAdmin) {
redirect('/', { permanent: false })
}

const props = await getPropsFromParams({
params: params.nextadmin,
searchParams,
options,
prisma,
schema,
action: submitFormAction,
});

return <NextAdmin {...props} dashboard={Dashboard} />;
}
```

</Tabs.Tab>
<Tabs.Tab>
The library does not provide an authentication system. If you want to add your own, you can do so by adding a role check to the `getServerSideProps` function:

> The following example uses [next-auth](https://next-auth.js.org/) to handle authentication
```ts
// pages/api/admin/[[...nextadmin]].ts

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getServerSession(req, res, authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check

if (!isAdmin) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}

const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/nextAdminRouter"
);
return nextAdminRouter(client).run(req, res);
};
```
const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/nextAdminRouter"
);
return nextAdminRouter(client).run(req, res);
};
```

For App router, this check will happen directly in your page.
</Tabs.Tab>
</Tabs>

## `<NextAdmin />` component

Expand Down
Expand Up @@ -8,7 +8,7 @@ import schema from "../../prisma/json-schema/json-schema.json";

const pageOptions = {
...options,
basePath: "/legacy/admin",
basePath: "/pagerouter/admin",
};

export default function Admin(props: AdminComponentProps) {
Expand Down
4 changes: 0 additions & 4 deletions packages/next-admin/src/components/Form.tsx
Expand Up @@ -170,10 +170,6 @@ const Form = ({
);
};

useEffect(() => {
setValidation(validationProp);
}, [validationProp]);

const extraErrors: ErrorSchema | undefined = validation?.reduce(
(acc, curr) => {
// @ts-expect-error
Expand Down
5 changes: 5 additions & 0 deletions packages/next-admin/src/utils/actions.ts
@@ -1,5 +1,10 @@
import { ActionParams } from "../types";

/**
* Following https://nextjs.org/docs/app/api-reference/functions/server-actions#binding-arguments
* We need the params and schema options to be there when the action is called.
* Other params (prisma, options) will be added by the app's action implementation.
*/
export const createBoundServerAction = (
{ params, schema }: ActionParams,
action: (params: ActionParams, formData: FormData) => Promise<any>
Expand Down
66 changes: 66 additions & 0 deletions packages/next-admin/src/utils/server.test.ts
@@ -0,0 +1,66 @@
import {
getResourceFromParams,
getResourceFromUrl,
getResourceIdFromParam,
getResourceIdFromUrl,
} from "./server";

describe("Server utils", () => {
describe("getResourceFromUrl", () => {
it("should return a resource with /api/User", () => {
expect(getResourceFromUrl("/api/User", ["User"])).toEqual("User");
});

it("should return a resource with /api/User/1", () => {
expect(getResourceFromUrl("/api/User/1", ["User"])).toEqual("User");
});

it("should not return a resource with /api/Post", () => {
expect(getResourceFromUrl("/api/Post", ["User"])).toEqual(undefined);
});
});

describe("getResourceFromParams", () => {
it("should return a resource with /api/User", () => {
expect(getResourceFromParams(["User"], ["User"])).toEqual("User");
});

it("should return a resource with /api/User/1", () => {
expect(getResourceFromParams(["User", "1"], ["User"])).toEqual("User");
});

it("should not return a resource with /api/Post", () => {
expect(getResourceFromParams(["Post"], ["User"])).toEqual(undefined);
});
});

describe("getResourceIdFromUrl", () => {
it("should get the id from /api/User/1", () => {
expect(getResourceIdFromUrl("/api/User/1", "User")).toEqual(1);
});

it("should not return an id from /api/User/new", () => {
expect(getResourceIdFromUrl("/api/User/new", "User")).toEqual(undefined);
});

it("should not return an id from /api/Dummy/--__", () => {
expect(getResourceIdFromUrl("/api/Dummy/--__", "User")).toEqual(
undefined
);
});
});

describe("getResourceIdFromParam", () => {
it("should get the id from /api/User/1", () => {
expect(getResourceIdFromParam("1", "User")).toEqual(1);
});

it("should not return an id from /api/User/new", () => {
expect(getResourceIdFromParam("new", "User")).toEqual(undefined);
});

it("should not return an id from /api/Dummy/--__", () => {
expect(getResourceIdFromParam("--__", "User")).toEqual(NaN);
});
});
});

0 comments on commit 6677a08

Please sign in to comment.