Skip to content

Commit 1956005

Browse files
committed
admin on rest
1 parent cc4c89e commit 1956005

File tree

1 file changed

+261
-0
lines changed

1 file changed

+261
-0
lines changed

client-generator/admin-on-rest.md

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# Admin On REST generator
2+
3+
## Summary
4+
This generator is alternative to api-platform/admin [api-platform/admin](https://github.com/api-platform/admin)
5+
6+
**api-platform/admin** allows to generate all resources on the fly and is immune to API changes.
7+
8+
**Generator** allows more configuration to the resource components by generated config file (config file per resource). In case of API changes resource components have to be regenerated to expose changes
9+
10+
Example configuration of the resource component at the bottom of this document.
11+
12+
## Usage
13+
Create a React application using [Facebook's Create React App](https://github.com/facebookincubator/create-react-app):
14+
15+
$ create-react-app my-app
16+
$ cd my-app
17+
18+
React and React DOM will be directly provided as dependencies of Admin On REST. As having different versions of React
19+
causes issues, remove `react` and `react-dom` from the `dependencies` section of the generated `package.json` file:
20+
21+
```patch
22+
- "react": "^16.0.0",
23+
- "react-dom": "^16.0.0",
24+
```
25+
Install admin-on-rest
26+
27+
$ yarn add admin-on-rest
28+
29+
Install the generator globally:
30+
31+
$ yarn global add @api-platform/client-generator
32+
33+
In the app directory, generate the files for the resource you want:
34+
35+
$ generate-api-platform-client https://demo.api-platform.com src/ -g admin-on-rest --resource foo
36+
# Replace the URL by the entrypoint of your Hydra-enabled API
37+
# Omit the resource flag to generate files for all resource types exposed by the API
38+
39+
Create *apiPlatformRestClient.js*
40+
41+
```javascript
42+
import {
43+
GET_LIST,
44+
GET_ONE,
45+
GET_MANY,
46+
GET_MANY_REFERENCE,
47+
CREATE,
48+
UPDATE,
49+
DELETE,
50+
fetchUtils
51+
} from 'admin-on-rest';
52+
import {stringify} from 'query-string';
53+
54+
const {fetchJson} = fetchUtils;
55+
56+
export default (API_URL, authTokenName = null, authTokenValue = null, httpClient = fetchJson) => {
57+
/**
58+
* @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
59+
* @param {String} resource Name of the resource to fetch, e.g. 'posts'
60+
* @param {Object} params The REST request params, depending on the type
61+
* @returns {Object} { url, options } The HTTP request parameters
62+
*/
63+
const convertRESTRequestToHTTP = (type, resource, params) => {
64+
let url = '';
65+
const options = {};
66+
options.headers = new Headers({'Accept': 'application/ld+json'});
67+
if( authTokenName && authTokenValue) {
68+
let authHeader = {};
69+
authHeader[authTokenName] = authTokenValue;
70+
options.headers = new Headers(authHeader);
71+
}
72+
switch (type) {
73+
case GET_LIST: {
74+
const {page, perPage} = params.pagination;
75+
url = `${API_URL}/${resource}?page=${page}&itemsPerPage=${perPage}`;
76+
break;
77+
}
78+
case GET_ONE:
79+
url = `${API_URL}/${resource}/${params.id}`;
80+
break;
81+
case GET_MANY: {
82+
const query = {
83+
filter: JSON.stringify({id: params.ids}),
84+
};
85+
url = `${API_URL}/${resource}?${stringify(query)}`;
86+
break;
87+
}
88+
case GET_MANY_REFERENCE: {
89+
const {page, perPage} = params.pagination;
90+
const {field, order} = params.sort;
91+
const query = {
92+
sort: JSON.stringify([field, order]),
93+
range: JSON.stringify([(page - 1) * perPage, (page * perPage) - 1]),
94+
filter: JSON.stringify({...params.filter, [params.target]: params.id}),
95+
};
96+
url = `${API_URL}/${resource}?${stringify(query)}`;
97+
break;
98+
}
99+
case UPDATE:
100+
url = `${API_URL}/${resource}/${params.id}`;
101+
options.method = 'PUT';
102+
options.body = JSON.stringify(params.data);
103+
break;
104+
case CREATE:
105+
url = `${API_URL}/${resource}`;
106+
options.method = 'POST';
107+
options.body = JSON.stringify(params.data);
108+
break;
109+
case DELETE:
110+
url = `${API_URL}/${resource}/${params.id}`;
111+
options.method = 'DELETE';
112+
break;
113+
default:
114+
throw new Error(`Unsupported fetch action type ${type}`);
115+
}
116+
return { url, options };
117+
};
118+
119+
/**
120+
* @param {Object} response HTTP response from fetch()
121+
* @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
122+
* @param {String} resource Name of the resource to fetch, e.g. 'posts'
123+
* @param {Object} params The REST request params, depending on the type
124+
* @returns {Object} REST response
125+
*/
126+
const convertHTTPResponseToREST = (response, type, resource, params) => {
127+
const { json } = response;
128+
switch (type) {
129+
case GET_LIST:
130+
case GET_MANY_REFERENCE:
131+
return {
132+
data: json["hydra:member"].map(x => x),
133+
total: json["hydra:totalItems"]
134+
};
135+
case CREATE:
136+
return { data: { ...params.data, id: json.id } };
137+
case DELETE:
138+
return { data: { ...params.data } };
139+
default:
140+
return { data: json };
141+
}
142+
};
143+
144+
/**
145+
* @param {string} type Request type, e.g GET_LIST
146+
* @param {string} resource Resource name, e.g. "posts"
147+
* @param {Object} payload Request parameters. Depends on the request type
148+
* @returns {Promise} the Promise for a REST response
149+
*/
150+
return (type, resource, params) => {
151+
// json-server doesn't handle WHERE IN requests, so we fallback to calling GET_ONE n times instead
152+
if (type === GET_MANY) {
153+
return Promise.all(params.ids.map(id => httpClient(`${API_URL}/${resource}/${id}`)))
154+
.then(responses => ({ data: responses.map(response => response.json) }));
155+
}
156+
const { url, options } = convertRESTRequestToHTTP(type, resource, params);
157+
return httpClient(url, options)
158+
.then(response => convertHTTPResponseToREST(response, type, resource, params));
159+
};
160+
};
161+
```
162+
163+
Replace App.js content with
164+
165+
```javascript
166+
import React, { Component } from 'react';
167+
import { Admin } from "admin-on-rest";
168+
import restClient from './apiPlatformRestClient';
169+
import { API_HOST, API_PATH } from "./config/_entrypoint";
170+
import * as resources from "./resource-import";
171+
172+
const API_URL = (API_HOST + API_PATH).replace(/\/$/, "");
173+
174+
class App extends Component {
175+
render() {
176+
return (
177+
<Admin restClient={restClient(API_URL)}>
178+
{Object.keys(resources).map( (key) => resources[key] )}
179+
</Admin>
180+
);
181+
}
182+
}
183+
184+
export default App;
185+
186+
187+
````
188+
The code is ready to be executed!
189+
190+
Resource configuration example config/book.js:
191+
192+
Each resource component has fields and buttons config options.
193+
False setting hides field or button.
194+
```javascript
195+
export const configList = {
196+
'@id': true,
197+
id: true,
198+
isbn: true,
199+
description: true,
200+
author: true,
201+
title: true,
202+
publicationDate: true,
203+
buttons: {
204+
show: true,
205+
edit: true,
206+
create: true,
207+
refresh: true,
208+
delete: true,
209+
}
210+
}
211+
212+
export const configEdit = {
213+
'@id': true,
214+
id: true,
215+
isbn: true,
216+
description: true,
217+
author: true,
218+
title: true,
219+
publicationDate: true,
220+
buttons: {
221+
show: true,
222+
list: true,
223+
delete: true,
224+
refresh: true,
225+
}
226+
}
227+
228+
export const configCreate = {
229+
'@id': true,
230+
id: true,
231+
isbn: true,
232+
description: true,
233+
author: true,
234+
title: true,
235+
publicationDate: true,
236+
buttons: {
237+
list: true,
238+
}
239+
}
240+
241+
export const configShow = {
242+
'@id': true,
243+
id: true,
244+
isbn: true,
245+
description: true,
246+
author: true,
247+
title: true,
248+
publicationDate: true,
249+
buttons: {
250+
edit: true,
251+
list: true,
252+
delete: true,
253+
refresh: true,
254+
}
255+
}
256+
257+
```
258+
259+
Previous chapter: [Vue.js generator](vuejs.md)
260+
261+
Next chapter: [Troubleshooting](troubleshooting.md)

0 commit comments

Comments
 (0)