diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js index 8830b2b6..4ec237ca 100644 --- a/js/actions/piece_list_actions.js +++ b/js/actions/piece_list_actions.js @@ -14,6 +14,7 @@ class PieceListActions { PieceListFetcher .fetch(page, pageSize, search, orderBy, orderAsc) .then((res) => { + console.log(res); this.actions.updatePieceList({ page, pageSize, @@ -23,9 +24,6 @@ class PieceListActions { 'pieceList': res.pieces, 'pieceListCount': res.count }); - }) - .catch((err) => { - console.log(err); }); } diff --git a/js/app.js b/js/app.js index b757b0ee..678741b7 100644 --- a/js/app.js +++ b/js/app.js @@ -2,12 +2,33 @@ import React from 'react'; import Router from 'react-router'; +import promise from 'es6-promise'; + +promise.polyfill(); import AscribeApp from './components/ascribe_app'; +import AppConstants from './constants/application_constants'; +import ApiUrls from './constants/api_urls'; import routes from './routes'; import alt from './alt'; +import fetch from './utils/fetch'; +import AlertDismissable from './components/ascribe_forms/alert'; +fetch.defaults({ + urlMap: ApiUrls, + http: { + headers: { + 'Authorization': 'Basic ' + AppConstants.debugCredentialBase64, + 'Accept': 'application/json' + } + }, + fatalErrorHandler: (err) => { + console.log(err); + //alert('Something went wrong, please reload the page'); + } +}); + Router.run(routes, Router.HashLocation, (AscribeApp) => { React.render( , diff --git a/js/components/ascribe_forms/alert.js b/js/components/ascribe_forms/alert.js index 72ced310..482afdfe 100644 --- a/js/components/ascribe_forms/alert.js +++ b/js/components/ascribe_forms/alert.js @@ -28,4 +28,4 @@ let AlertDismissable = React.createClass({ } }); -export default AlertDismissable; \ No newline at end of file +export default AlertDismissable; diff --git a/js/components/edition.js b/js/components/edition.js index ffeb9b25..0ac97d33 100644 --- a/js/components/edition.js +++ b/js/components/edition.js @@ -1,5 +1,4 @@ import React from 'react'; - import ImageViewer from './ascribe_media/image_viewer'; import TransferModalButton from './ascribe_modal/modal_transfer'; import ShareModalButton from './ascribe_modal/modal_share'; diff --git a/js/components/fatal_error.js b/js/components/fatal_error.js new file mode 100644 index 00000000..e69de29b diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index f0c7f9b6..3a6077b9 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -2,7 +2,9 @@ import AppConstants from './application_constants'; let apiUrls = { 'ownership_shares_mail' : AppConstants.baseUrl + 'ownership/shares/mail/', - 'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/' + 'ownership_transfers' : AppConstants.baseUrl + 'ownership/transfers/', + 'pieces_list': AppConstants.baseUrl + 'pieces/', + 'edition': AppConstants.baseUrl + 'editions/${bitcoin_id}/' }; -export default apiUrls; \ No newline at end of file +export default apiUrls; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 17a6d7df..b235d1d4 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -5,4 +5,4 @@ let constants = { 'aclList': ['edit', 'consign', 'transfer', 'loan', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection'] }; -export default constants; \ No newline at end of file +export default constants; diff --git a/js/fetchers/edition_fetcher.js b/js/fetchers/edition_fetcher.js index aa6538c4..7ce5bdab 100644 --- a/js/fetchers/edition_fetcher.js +++ b/js/fetchers/edition_fetcher.js @@ -1,4 +1,4 @@ -import fetch from 'isomorphic-fetch'; +import fetch from '../utils/fetch'; import AppConstants from '../constants/application_constants'; import FetchApiUtils from '../utils/fetch_api_utils'; @@ -11,13 +11,7 @@ let EditionFetcher = { * */ fetchOne(editionId) { - return fetch(AppConstants.baseUrl + 'editions/' + editionId + '/', { - headers: { - 'Authorization': 'Basic ' + AppConstants.debugCredentialBase64 - } - }).then( - (res) => res.json() - ); + return fetch.get('edition', {'bitcoin_id': editionId}); } }; diff --git a/js/fetchers/piece_list_fetcher.js b/js/fetchers/piece_list_fetcher.js index 320cf544..9490c6fb 100644 --- a/js/fetchers/piece_list_fetcher.js +++ b/js/fetchers/piece_list_fetcher.js @@ -1,7 +1,6 @@ -import fetch from 'isomorphic-fetch'; - import AppConstants from '../constants/application_constants'; import FetchApiUtils from '../utils/fetch_api_utils'; +import fetch from '../utils/fetch'; let PieceListFetcher = { @@ -10,21 +9,8 @@ let PieceListFetcher = { * Can be called with all supplied queryparams the API. */ fetch(page, pageSize, search, orderBy, orderAsc) { - let ordering = FetchApiUtils.generateOrderingQueryParams(orderBy, orderAsc); - - let params = FetchApiUtils.argsToQueryParams({ - page, - pageSize, - search, - ordering - }); - - return fetch(AppConstants.baseUrl + 'pieces/' + params, { - headers: { - 'Authorization': 'Basic ' + AppConstants.debugCredentialBase64 - } - }).then((res) => res.json()); + return fetch.get('pieces_list', { page, pageSize, search, ordering }); } }; diff --git a/js/utils/fetch.js b/js/utils/fetch.js new file mode 100644 index 00000000..1922d1bd --- /dev/null +++ b/js/utils/fetch.js @@ -0,0 +1,117 @@ +import { default as _fetch } from 'isomorphic-fetch'; +import FetchApiUtils from '../utils/fetch_api_utils'; + + +class UrlMapError extends Error {}; +class ServerError extends Error {}; +class APIError extends Error {}; + + +class Fetch { + _merge(defaults, options) { + let merged = {}; + for (let key in defaults) { + merged[key] = defaults[key]; + } + for (let key in options) { + merged[key] = options[key]; + } + return merged; + } + + unpackResponse(response) { + if (response.status >= 500) { + throw new ServerError(); + } + return response.text(); + } + + handleFatalError(err) { + this.fatalErrorHandler(err); + throw new ServerError(err); + } + + handleAPIError(json) { + if (!json['success']) { + let error = new APIError(); + error.json = json; + throw error; + } + return json; + } + + getUrl(url) { + let name = url; + if (!url.match(/^http/)) { + url = this.urlMap[url]; + if (!url) { + throw new UrlMapError(`Cannot find a mapping for "${name}"`); + } + } + + return url; + } + + extractParamsFromUrl(url) { + let re = /\${(\w+)}/g; + let match = null; + let results = []; + while (match = re.exec(url)) { + results.push(match[1]); + } + return results; + } + + prepareUrl(url, params) { + let newUrl = this.getUrl(url); + let re = /\${(\w+)}/g; + + newUrl = newUrl.replace(re, (match, key) => { + let val = params[key] + if (!val) { + throw new Error(`Cannot find param ${key}`); + } + delete params[key]; + return val; + }); + + if (params && Object.keys(params).length > 0) { + newUrl += FetchApiUtils.argsToQueryParams(params); + } + + return newUrl; + } + + request(verb, url, options) { + options = options || {}; + let merged = this._merge(this.httpOptions, options); + merged['method'] = verb; + + let promise = _fetch(url, merged); + return promise.then(this.unpackResponse) + .then(JSON.parse) + .catch(this.handleFatalError.bind(this)) + .then(this.handleAPIError); + } + + get(url, params, options) { + let newUrl = this.prepareUrl(url, params); + return this.request('get', newUrl, options); + } + + post(url, params, options) { + options = options || {}; + return this.request('post', url, options); + } + + defaults(options) { + this.httpOptions = options.http || {}; + this.urlMap = options.urlMap || {}; + this.fatalErrorHandler = options.fatalErrorHandler || (() => {}); + } +} + + +let fetch = new Fetch(); + +export default fetch; diff --git a/package.json b/package.json index 38710b47..ba626111 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "js/app.js", "author": "Ascribe", "license": "Copyright", + "private": true, "devDependencies": { "babel-jest": "^4.0.0", "babelify": "^6.1.2", @@ -32,9 +33,9 @@ "isomorphic-fetch": "^2.0.2", "object-assign": "^2.0.0", "react": "^0.13.2", + "react-bootstrap": "~0.22.6", "react-router": "^0.13.3", - "uglifyjs": "^2.4.10", - "react-bootstrap": "~0.22.6" + "uglifyjs": "^2.4.10" }, "jest": { "scriptPreprocessor": "node_modules/babel-jest",