Skip to content

Commit dc28127

Browse files
committed
API Platform Admin documentation
1 parent 46e3ca8 commit dc28127

File tree

6 files changed

+519
-4
lines changed

6 files changed

+519
-4
lines changed

admin/authentication-support.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Authentication Support
2+
3+
Authentication can easily be handled when using the API Platform's admin library.
4+
In the following section, we will assume [the API is secured using JWT](https://api-platform.com/docs/core/jwt), but the
5+
process is similar for other authentication mechanisms.
6+
7+
The first step is to create a client to handle the authentication process:
8+
9+
```javascript
10+
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR } from 'admin-on-rest';
11+
12+
const entrypoint = 'https://demo.api-platform.com'; // Change this by your own entrypoint
13+
14+
export default (type, params) => {
15+
switch (type) {
16+
case AUTH_LOGIN:
17+
const { username, password } = params;
18+
const request = new Request(`${entrypoint}/login_check`, {
19+
method: 'POST',
20+
body: JSON.stringify({ email: username, password }),
21+
headers: new Headers({ 'Content-Type': 'application/json' }),
22+
});
23+
24+
return fetch(request)
25+
.then(response => {
26+
if (response.status < 200 || response.status >= 300) throw new Error(response.statusText);
27+
28+
return response.json();
29+
})
30+
.then(({ token }) => {
31+
localStorage.setItem('token', token); // The JWT token is stored in the browser's local storage
32+
});
33+
34+
case AUTH_LOGOUT:
35+
localStorage.removeItem('token');
36+
break;
37+
38+
case AUTH_ERROR:
39+
if (401 === params.status || 403 === params.status) {
40+
localStorage.removeItem('token');
41+
42+
return Promise.reject();
43+
}
44+
break;
45+
46+
default:
47+
return Promise.resolve();
48+
}
49+
}
50+
```
51+
52+
Then, configure the `Admin` component to use the authentication client we just created:
53+
54+
```javascript
55+
import React, { Component } from 'react';
56+
import { HydraAdmin, hydraClient, fetchHydra } from 'api-platform-admin';
57+
import authClient from './authClient';
58+
59+
const entrypoint = 'https://demo.api-platform.com';
60+
61+
const fetchWithAuth = (url, options = {}) => {
62+
if (!options.headers) options.headers = new Headers({ Accept: 'application/ld+json' });
63+
64+
options.headers.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
65+
return fetchHydra(url, options);
66+
};
67+
68+
class Admin extends Component {
69+
render() {
70+
return <HydraAdmin entrypoint={entrypoint} restClient={hydraClient(entrypoint, fetchWithAuth)} authClient={authClient}/>
71+
}
72+
}
73+
74+
export default Admin;
75+
```
76+
77+
Refer to [the chapter dedicated to authentication in the Admin On Rest documentation](https://marmelab.com/admin-on-rest/Authentication.html)
78+
for more information.
79+
80+
Previous chapter: [Getting Started](getting-started.md)
81+
82+
Next chapter: [Handling Relations to Collections](handling-relations-to-collections.md)

admin/getting-started.md

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# Getting Started
2+
3+
## Installation
4+
5+
Install the skeleton and the library:
6+
7+
Start by installing [the Yarn package manager](https://yarnpkg.com/) ([NPM](https://www.npmjs.com/) is also supported) and
8+
the [Create React App](https://github.com/facebookincubator/create-react-app) tool.
9+
10+
Then, create a new React application for your admin:
11+
12+
$ create-react-app my-admin
13+
14+
**Warning:** Admin On Rest and Material UI [aren't compatible with React 15.6 yet](https://github.com/marmelab/admin-on-rest/issues/802). During the meantime, you need to downgrade React to v15.5. Apply the following patch to `packages.json` then run `yarn upgrade` to downgrade:
15+
16+
```patch
17+
- "react": "^15.6.1",
18+
+ "react": "~15.5.4",
19+
-    "react-dom": "^15.6.1",
20+
+    "react-dom": "~15.5.4",
21+
```
22+
23+
Now, add install `api-platform-admin` library in your newly created project:
24+
25+
$ yarn add api-platform-admin
26+
27+
## Creating the Admin
28+
29+
Edit the `src/App.js` file like the following:
30+
31+
```javascript
32+
import React, { Component } from 'react';
33+
import { HydraAdmin } from 'api-platform-admin';
34+
35+
class App extends Component {
36+
render() {
37+
return <HydraAdmin entrypoint="https://demo.api-platform.com"/> // Replace with your own API entrypoint
38+
}
39+
}
40+
41+
export default App;
42+
```
43+
44+
Your new administration interface is ready! Type `yarn start` to try it!
45+
46+
Note: if you don't want to hardcode the API URL, you can [use an environment variable](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-custom-environment-variables).
47+
48+
## Customizing the Admin
49+
50+
The API Platform's admin parses the Hydra documentation exposed by the API and transforms it to an object data structure. This data structure can be customized to add, remove or customize resources and properties. To do so, we can leverage the `AdminBuilder` component provided by the library. It's a lower level component than the `HydraAdmin` one we used in the previous example. It allows to access to the object storing the structure of admin's screens.
51+
52+
### Using Custom Components
53+
54+
In the following example, we change components used for the `description` property of the `books` resource to ones accepting HTML (respectively `RichTextField` that renders HTML markup and `RichTextInput`, a WYSWYG editor).
55+
(To use the `RichTextInput`, the `aor-rich-text-input` package is must be installed: `yarn add aor-rich-text-input`).
56+
57+
```javascript
58+
import React from 'react';
59+
import { RichTextField } from 'admin-on-rest';
60+
import RichTextInput from 'aor-rich-text-input';
61+
import HydraAdmin from 'api-platform-admin/lib/hydra/HydraAdmin';
62+
import parseHydraDocumentation from 'api-doc-parser/lib/hydra/parseHydraDocumentation';
63+
64+
const entrypoint = 'https://demo.api-platform.com';
65+
66+
const apiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoint)
67+
.then(api => {
68+
api.resources.map(resource => {
69+
const books = api.resources.find(r => 'books' === r.name);
70+
books.fields.find(f => 'description' === f.name).fieldComponent = <RichTextField source="description" key="description"/>;
71+
books.fields.find(f => 'description' === f.name).inputComponent = <RichTextInput source="description" key="description"/>;
72+
73+
return resource;
74+
});
75+
76+
return api;
77+
})
78+
;
79+
80+
export default (props) => (
81+
<HydraAdmin apiDocumentationParser={apiDocumentationParser} entrypoint={entrypoint}/>
82+
);
83+
```
84+
85+
The `fieldComponent` property of the `Field` class allows to set the component used to render a property in list and show screens.
86+
The `inputComponent` property allows to set the component to use to render the input used in create and edit screens.
87+
88+
Any [field](https://marmelab.com/admin-on-rest/Fields.html) or [input](https://marmelab.com/admin-on-rest/Inputs.html) provided by the Admin On Rest library can be used.
89+
90+
To go further, take a look to the "[Including admin-on-rest on another React app](https://marmelab.com/admin-on-rest/CustomApp.html)" documentation page of Admin On Rest to learn how to use directly redux, react-router, and redux-saga along with components provided by this library.
91+
92+
### Managing Files and Images
93+
94+
In the following example, we will:
95+
* find every [ImageObject](http://schema.org/ImageObject) resources. For each [contentUrl](http://schema.org/contentUrl) fields, we will use [ImageField](https://marmelab.com/admin-on-rest/Fields.html#imagefield) as `field` and [ImageInput](https://marmelab.com/admin-on-rest/Inputs.html#imageinput) as `input`.
96+
* [ImageInput](https://marmelab.com/admin-on-rest/Inputs.html#imageinput) will return a [File](https://developer.mozilla.org/en/docs/Web/API/File) instance. In this example, we will send a multi-part form data to a special action (`https://demo.api-platform.com/images/upload`). The action will return the ID of the uploaded image. We will "replace" the [File](https://developer.mozilla.org/en/docs/Web/API/File) instance by the ID in `normalizeData`.
97+
* As `contentUrl` fields will return a string, we have to convert Hydra data to AOR data. This action will be done by `denormalizeData`.
98+
99+
```javascript
100+
import { FunctionField, ImageField, ImageInput } from 'admin-on-rest/lib/mui';
101+
import React from 'react';
102+
import HydraAdmin from 'api-platform-admin/lib/hydra/HydraAdmin';
103+
import parseHydraDocumentation from 'api-doc-parser/lib/hydra/parseHydraDocumentation';
104+
105+
const entrypoint = 'https://demo.api-platform.com';
106+
107+
const apiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoint)
108+
.then(api => {
109+
api.resources.map(resource => {
110+
if ('http://schema.org/ImageObject' === resource.id) {
111+
resource.fields.map(field => {
112+
if ('http://schema.org/contentUrl' === field.id) {
113+
field.denormalizeData = value => ({
114+
src: value
115+
});
116+
117+
field.fieldComponent = (
118+
<FunctionField
119+
key={field.name}
120+
render={
121+
record => (
122+
<ImageField key={field.name} record={record} source={`${field.name}.src`}/>
123+
)
124+
}
125+
source={field.name}
126+
/>
127+
);
128+
129+
field.inputComponent = (
130+
<ImageInput accept="image/*" key={field.name} multiple={false} source={field.name}>
131+
<ImageField source="src"/>
132+
</ImageInput>
133+
);
134+
135+
field.normalizeData = value => {
136+
if (value[0] && value[0].rawFile instanceof File) {
137+
const body = new FormData();
138+
body.append('file', value[0].rawFile);
139+
140+
return fetch(`${entrypoint}/images/upload`, { body, method: 'POST' })
141+
.then(response => response.json());
142+
}
143+
144+
return value.src;
145+
};
146+
}
147+
148+
return field;
149+
});
150+
}
151+
152+
return resource;
153+
});
154+
155+
return api;
156+
})
157+
;
158+
159+
export default (props) => (
160+
<HydraAdmin apiDocumentationParser={apiDocumentationParser} entrypoint={entrypoint}/>
161+
);
162+
```
163+
164+
__Note__: In this example, we choose to send the file via a multi-part form data, but you are totally free to use another solution (like `base64`). But keep in mind that multi-part form data is the most efficient solution.
165+
166+
### Using a Custom Validation Function or Inject Custom Props
167+
168+
You can use `fieldProps` and `inputProps` to respectively inject custom properties to fields and inputs generated by API
169+
Platform Admin. This is particularly useful to add custom validation rules:
170+
171+
```javascript
172+
import React, { Component } from 'react';
173+
import { AdminBuilder, hydraClient } from 'api-platform-admin';
174+
import parseHydraDocumentation from 'api-doc-parser/lib/hydra/parseHydraDocumentation';
175+
176+
const entrypoint = 'https://demo.api-platform.com';
177+
178+
class App extends Component {
179+
state = {api: null};
180+
181+
componentDidMount() {
182+
parseHydraDocumentation(entrypoint).then(api => {
183+
const books = api.resources.find(r => 'books' === r.name);
184+
185+
books.writableFields.find(f => 'description' === f.name).inputProps = {
186+
validate: value => value.length >= 30 ? undefined : 'Minimum length: 30';
187+
};
188+
189+
this.setState({api: api});
190+
})
191+
}
192+
193+
render() {
194+
if (null === this.state.api) return <div>Loading...</div>;
195+
196+
return <AdminBuilder api={this.state.api} restClient={hydraClient(entrypoint)}/>
197+
}
198+
}
199+
200+
export default App;
201+
```
202+
203+
Previous chapter: [Introduction](index.md)
204+
205+
Next chapter: [Authentication Support](authentication-support.md)

0 commit comments

Comments
 (0)