import axios from 'axios' import {reduce, replace} from 'lodash' let config = require('../../public/config.json') export default class AbstractService { ///////////////////////////// // Initial variable definitions /////////////////////////// http = null loading = false paths = { create: '', get: '', getAll: '', update: '', delete: '', } ///////////// // Service init /////////// /** * The abstract constructor. * @param paths An object with all paths. Default values are specified above. */ constructor(paths) { this.http = axios.create({ baseURL: config.VIKUNJA_API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }) // Set the default auth header if we have a token if ( localStorage.getItem('token') !== '' && localStorage.getItem('token') !== null && localStorage.getItem('token') !== undefined ) { this.http.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token') } this.paths = { create: paths.create !== undefined ? paths.create : '', get: paths.get !== undefined ? paths.get : '', getAll: paths.getAll !== undefined ? paths.getAll : '', update: paths.update !== undefined ? paths.update : '', delete: paths.delete !== undefined ? paths.delete : '', } } ///////////////////// // Global error handler /////////////////// /** * Handles the error and rejects the promise. * @param error * @returns {Promise} */ errorHandler(error) { return Promise.reject(error) } ///////////////// // Helper functions /////////////// /** * Returns an object with all route parameters and their values. * @param route * @returns object */ getRouteReplacements(route) { let parameters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {} let replace$$1 = {} let pattern = this.getRouteParameterPattern() pattern = new RegExp(pattern instanceof RegExp ? pattern.source : pattern, 'g') for (let parameter; (parameter = pattern.exec(route)) !== null;) { replace$$1[parameter[0]] = parameters[parameter[1]]; } return replace$$1; } /** * Holds the replacement pattern for url paths, can be overwritten by implementations. * @return {RegExp} */ getRouteParameterPattern() { return /{([^}]+)}/ } /** * Returns a fully-ready-ready-to-make-a-request-to route with replaced parameters. * @param path * @param pathparams * @return string */ getReplacedRoute(path, pathparams) { let replacements = this.getRouteReplacements(path, pathparams) return reduce(replacements, function (result, value, parameter) { return replace(result, parameter, value) }, path) } /** * setLoading is a method which sets the loading variable to true, after a timeout of 100ms. * It has the timeout to prevent the loading indicator from showing for only a blink of an eye in the * case the api returns a response in < 100ms. * But because the timeout is created using setTimeout, it will still trigger even if the request is * already finished, so we return a method to call in that case. * @returns {Function} */ setLoading() { const timeout = setTimeout(() => { this.loading = true }, 100) return () => { clearTimeout(timeout) this.loading = false } } ////////////////// // Default factories // It is possible to specify a factory for each type of request. // This makes it possible to have different models returned from different routes. // Specific factories for each request are completly optional, if these are not specified, the defautl factory is used. //////////////// /** * The modelFactory returns an model from an object. * This one here is the default one, usually the service definitions for a model will override this. * @param data * @returns {*} */ modelFactory(data) { return data } /** * This is the model factory for get requests. * @param data * @return {*} */ modelGetFactory(data) { return this.modelFactory(data) } /** * This is the model factory for get all requests. * @param data * @return {*} */ modelGetAllFactory(data) { return this.modelFactory(data) } /** * This is the model factory for create requests. * @param data * @return {*} */ modelCreateFactory(data) { return this.modelFactory(data) } /** * This is the model factory for update requests. * @param data * @return {*} */ modelUpdateFactory(data) { return this.modelFactory(data) } ////////////// // Preprocessors //////////// /** * Default preprocessor for get requests * @param model * @return {*} */ beforeGet(model) { return model } /** * Default preprocessor for create requests * @param model * @return {*} */ beforeCreate(model) { return model } /** * Default preprocessor for update requests * @param model * @return {*} */ /* beforeUpdate(model) { return model }*/ /** * Default preprocessor for delete requests * @param model * @return {*} */ beforeDelete(model) { return model } /////////////// // Global actions ///////////// /** * Performs a get request to the url specified before. * @param model The model to use. The request path is built using the values from the model. * @param params Optional query parameters * @returns {Q.Promise} */ get(model, params = {}) { if (this.paths.get === '') { return Promise.reject({message: 'This model is not able to get data.'}) } const cancel = this.setLoading() model = this.beforeGet(model) return this.http.get(this.getReplacedRoute(this.paths.get, model), {params: params}) .catch(error => { return this.errorHandler(error) }) .then(response => { return Promise.resolve(this.modelGetFactory(response.data)) }) .finally(() => { cancel() }) } /** * Performs a get request to the url specified before. * The difference between this and get() is this one is used to get a bunch of data (an array), not just a single object. * @param model The model to use. The request path is built using the values from the model. * @param params Optional query parameters * @returns {Q.Promise} */ getAll(model = {}, params = {}) { if (this.paths.getAll === '') { return Promise.reject({message: 'This model is not able to get data.'}) } const cancel = this.setLoading() model = this.beforeGet(model) return this.http.get(this.getReplacedRoute(this.paths.getAll, model), {params: params}) .catch(error => { return this.errorHandler(error) }) .then(response => { if (Array.isArray(response.data)) { return Promise.resolve(response.data.map(entry => { return this.modelGetAllFactory(entry) })) } return Promise.resolve(this.modelGetAllFactory(response.data)) }) .finally(() => { cancel() }) } /** * Performs a put request to the url specified before * @param model * @returns {Promise} */ create(model) { if (this.paths.create === '') { return Promise.reject({message: 'This model is not able to create data.'}) } const cancel = this.setLoading() model = this.beforeCreate(model) return this.http.put(this.getReplacedRoute(this.paths.create, model), model) .catch(error => { return this.errorHandler(error) }) .then(response => { return Promise.resolve(this.modelCreateFactory(response.data)) }) .finally(() => { cancel() }) } /** * Performs a post request to the update url * @param model * @returns {Q.Promise} */ update(model) { if (this.paths.update === '') { return Promise.reject({message: 'This model is not able to update data.'}) } const cancel = this.setLoading() if(typeof this.beforeUpdate === 'function') { model = this.beforeUpdate(model) } return this.http.post(this.getReplacedRoute(this.paths.update, model), model) .catch(error => { return this.errorHandler(error) }) .then(response => { return Promise.resolve(this.modelUpdateFactory(response.data)) }) .finally(() => { cancel() }) } /** * Performs a delete request to the update url * @param model * @returns {Q.Promise} */ delete(model) { if (this.paths.delete === '') { return Promise.reject({message: 'This model is not able to delete data.'}) } const cancel = this.setLoading() model = this.beforeDelete(model) return this.http.delete(this.getReplacedRoute(this.paths.delete, model), model) .catch(error => { return this.errorHandler(error) }) .then(response => { return Promise.resolve(response.data) }) .finally(() => { cancel() }) } }