diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e717f5e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/README.md b/README.md index 4100d85..2edb97f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # feathers rest Client for admin-on-rest -For using [feathers](https://www.feathersjs.com) with [admin-on-rest](https://github.com/marmelab/admin-on-rest), convert AOR's REST dialect into one compatible with feathers. +For using [feathers](https://www.feathersjs.com) with [admin-on-rest](https://github.com/marmelab/admin-on-rest). ## Installation @@ -8,19 +8,80 @@ For using [feathers](https://www.feathersjs.com) with [admin-on-rest](https://gi npm install aor-feathers-client --save ``` -## Usage +## Usage with stable + +```js +// in src/feathersClient.js +import feathers from 'feathers-client'; + +const host = 'http://localhost:3030'; + +export default feathers() + .configure(feathers.hooks()) + .configure(feathers.rest(host).fetch(window.fetch.bind(window))) + .configure(feathers.authentication({ storage: window.localStorage })); +``` + +```js +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'admin-on-rest'; +import { authClient, restClient } from 'aor-feathers-client'; +import feathersClient from './feathersClient'; +import { PostList } from './posts'; + +const App = () => ( + + + +); + +export default App; +``` + +## Usage with Auk + +```js +// in src/feathersClient.js +import feathers from 'feathers-client'; +import hooks from 'feathers-hooks'; +import rest from 'feathers-rest/client'; +import authentication from 'feathers-authentication-client'; + +const host = 'http://localhost:3030'; + +export default feathers() + .configure(hooks()) + .configure(rest(host).fetch(window.fetch.bind(window))) + .configure(authentication({ + jwtStrategy: 'jwt', + storage: window.localStorage, + })); +``` ```js // in src/App.js import React from 'react'; import { Admin, Resource } from 'admin-on-rest'; -import feathersClient from 'aor-feathers-client'; +import { authClient, restClient } from 'aor-feathers-client'; +import feathersClient from './feathersClient'; import { PostList } from './posts'; +const authClientOptions = { + storageKey: 'feathers-jwt', + authenticate: { strategy: 'local' }, +}; + const App = () => ( - - - + + + ); export default App; diff --git a/package.json b/package.json index e036023..9c65868 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aor-feathers-client", - "version": "0.1.0", + "version": "0.2.0", "description": "A feathers client for admin-on-rest", "main": "lib/index.js", "scripts": { @@ -33,7 +33,7 @@ }, "homepage": "https://github.com/josx/aor-feathers-client#readme", "dependencies": { - "admin-on-rest": "^0.7.0" + "admin-on-rest": "^0.9.0" }, "devDependencies": { "babel-cli": "^6.18.0", diff --git a/src/authClient.js b/src/authClient.js new file mode 100644 index 0000000..99702f3 --- /dev/null +++ b/src/authClient.js @@ -0,0 +1,31 @@ +import { + AUTH_LOGIN, + AUTH_LOGOUT, + AUTH_CHECK, +} from 'admin-on-rest'; + +export default (client, options = {}) => (type, params) => { + const { + storageKey, + authenticate, + } = Object.assign({}, { + storageKey: 'token', + authenticate: { type: 'local' }, + }, options); + + switch (type) { + case AUTH_LOGIN: + const { username, password } = params; + return client.authenticate({ + ...authenticate, + email: username, + password, + }); + case AUTH_LOGOUT: + return client.logout(); + case AUTH_CHECK: + return localStorage.getItem(storageKey) ? Promise.resolve() : Promise.reject(); + default: + throw new Error(`Unsupported FeathersJS authClient action type ${type}`); + } +}; diff --git a/src/index.js b/src/index.js index fa8951a..8b30986 100644 --- a/src/index.js +++ b/src/index.js @@ -1,109 +1,2 @@ -import { queryParameters, fetchJson } from 'admin-on-rest/lib/util/fetch'; - -import { - GET_LIST, - GET_ONE, - CREATE, - UPDATE, - DELETE, - fetchUtils, -} from 'admin-on-rest/lib/rest/types'; - -/** - * Maps admin-on-rest queries to a feathers REST API - * - * @example - * GET_LIST => GET http://my.api.url/posts?$sort[title]=-1&$limit=10&$skip=10&title=value - * GET_ONE => GET http://my.api.url/posts/123 - * UPDATE => PUT http://my.api.url/posts/123 - * CREATE => POST http://my.api.url/posts/123 - * DELETE => DELETE http://my.api.url/posts/123 - */ -export default (apiUrl, httpClient = fetchJson) => { - /** - * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE' - * @param {String} resource Name of the resource to fetch, e.g. 'posts' - * @param {Object} params The REST request params, depending on the type - * @returns {Object} { url, options } The HTTP request parameters - */ - const convertRESTRequestToHTTP = (type, resource, params) => { - let url = ''; - const options = {}; - let query = {}; - switch (type) { - case GET_LIST: { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; - - let sortKey = '$sort[' + field + ']'; - let sortVal = (order === 'DESC') ? -1 : 1; - if(perPage && page) { - query['$limit'] = perPage; - query['$skip'] = perPage*(page-1); - } - if (order) { - query[sortKey] = JSON.stringify(sortVal); - } - - Object.assign(query, params.filter); - - url = `${apiUrl}/${resource}?${queryParameters(query)}`; - break; - } - case GET_ONE: - url = `${apiUrl}/${resource}/${params.id}`; - break; - case UPDATE: - url = `${apiUrl}/${resource}/${params.id}`; - options.method = 'PUT'; - options.body = JSON.stringify(params.data); - break; - case CREATE: - url = `${apiUrl}/${resource}`; - options.method = 'POST'; - options.body = JSON.stringify(params.data); - break; - case DELETE: - url = `${apiUrl}/${resource}/${params.id}`; - options.method = 'DELETE'; - break; - default: - throw new Error(`Unsupported fetch action type ${type}`); - } - return { url, options }; - }; - - /** - * @param {Object} response HTTP response from fetch() - * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE' - * @param {String} resource Name of the resource to fetch, e.g. 'posts' - * @param {Object} params The REST request params, depending on the type - * @returns {Object} REST response - */ - const convertHTTPResponseToREST = (response, type, resource, params) => { - const { headers, json } = response; - switch (type) { - case GET_LIST: - return { - data: json.data, - total: json.total - }; - case CREATE: - return { ...params.data, id: json.id }; - default: - return json; - } - }; - - /** - * @param {string} type Request type, e.g GET_LIST - * @param {string} resource Resource name, e.g. "posts" - * @param {Object} payload Request parameters. Depends on the request type - * @returns {Promise} the Promise for a REST response - */ - return (type, resource, params) => { - const { url, options } = convertRESTRequestToHTTP(type, resource, params); - return httpClient(url, options) - .then(response => convertHTTPResponseToREST(response, type, resource, params)); - }; -}; +export authClient from './authClient'; +export restClient from './restClient'; diff --git a/src/restClient.js b/src/restClient.js new file mode 100644 index 0000000..0530a3d --- /dev/null +++ b/src/restClient.js @@ -0,0 +1,62 @@ +import { + GET_MANY, + GET_MANY_REFERENCE, + GET_LIST, + GET_ONE, + CREATE, + UPDATE, + DELETE, +} from 'admin-on-rest'; + +export default client => { + const mapRequest = (type, resource, params) => { + const service = client.service(resource); + let query = {}; + + switch (type) { + case GET_MANY: + case GET_MANY_REFERENCE: + case GET_LIST: + const {page, perPage} = params.pagination || {}; + const {field, order} = params.sort || {}; + + let sortKey = '$sort[' + field + ']'; + let sortVal = (order === 'DESC') ? -1 : 1; + if (perPage && page) { + query['$limit'] = perPage; + query['$skip'] = perPage * (page - 1); + } + if (order) { + query[sortKey] = JSON.stringify(sortVal); + } + Object.assign(query, params.filter); + return service.find({ query }); + case GET_ONE: + return service.get(params.id); + case UPDATE: + return service.update(params.id, params.data); + case CREATE: + return service.create(params.data); + case DELETE: + return service.remove(params.id); + default: + throw new Error(`Unsupported FeathersJS restClient action type ${type}`); + } + }; + + const mapResponse = (response, type, resource, params) => { + switch (type) { + case GET_ONE: + case UPDATE: + return { data: response }; + case CREATE: + return { data: {...params.data, id: response.id} }; + default: + return response; + } + }; + + return (type, resource, params) => + mapRequest(type, resource, params) + .then(response => mapResponse(response, type, resource, params)); +}