Simplified ShlinkApiClient and moved runtime creation logic to external service

This commit is contained in:
Alejandro Celaya 2018-12-18 10:14:25 +01:00
parent 7bd4b39b5a
commit 4f54e3315f
14 changed files with 90 additions and 57 deletions

View file

@ -1,26 +1,18 @@
import axios from 'axios';
import qs from 'qs'; import qs from 'qs';
import { isEmpty, isNil, reject } from 'ramda'; import { isEmpty, isNil, reject } from 'ramda';
const API_VERSION = '1'; const API_VERSION = '1';
const STATUS_UNAUTHORIZED = 401; const STATUS_UNAUTHORIZED = 401;
const buildRestUrl = (url) => url ? `${url}/rest/v${API_VERSION}` : '';
export class ShlinkApiClient { export default class ShlinkApiClient {
constructor(axios) { constructor(axios, baseUrl, apiKey) {
this.axios = axios; this.axios = axios;
this._baseUrl = ''; this._baseUrl = buildRestUrl(baseUrl);
this._apiKey = ''; this._apiKey = apiKey || '';
this._token = ''; this._token = '';
} }
/**
* Sets the base URL to be used on any request
*/
setConfig = ({ url, apiKey }) => {
this._baseUrl = `${url}/rest/v${API_VERSION}`;
this._apiKey = apiKey;
};
listShortUrls = (options = {}) => listShortUrls = (options = {}) =>
this._performRequest('/short-codes', 'GET', options) this._performRequest('/short-codes', 'GET', options)
.then((resp) => resp.data.shortUrls) .then((resp) => resp.data.shortUrls)
@ -113,7 +105,3 @@ export class ShlinkApiClient {
return Promise.reject(e); return Promise.reject(e);
}; };
} }
const shlinkApiClient = new ShlinkApiClient(axios);
export default shlinkApiClient;

View file

@ -0,0 +1,18 @@
import * as axios from 'axios';
import ShlinkApiClient from './ShlinkApiClient';
const apiClients = {};
const buildShlinkApiClient = (axios) => ({ url, apiKey }) => {
const clientKey = `${url}_${apiKey}`;
if (!apiClients[clientKey]) {
apiClients[clientKey] = new ShlinkApiClient(axios, url, apiKey);
}
return apiClients[clientKey];
};
export default buildShlinkApiClient;
export const buildShlinkApiClientWithAxios = buildShlinkApiClient(axios);

View file

@ -25,7 +25,7 @@ import { ColorGenerator } from '../utils/ColorGenerator';
import { Storage } from '../utils/Storage'; import { Storage } from '../utils/Storage';
import ShortUrlsRow from '../short-urls/helpers/ShortUrlsRow'; import ShortUrlsRow from '../short-urls/helpers/ShortUrlsRow';
import ShortUrlsRowMenu from '../short-urls/helpers/ShortUrlsRowMenu'; import ShortUrlsRowMenu from '../short-urls/helpers/ShortUrlsRowMenu';
import { ShlinkApiClient } from '../api/ShlinkApiClient'; import ShlinkApiClient from '../api/ShlinkApiClient';
import DeleteServerModal from '../servers/DeleteServerModal'; import DeleteServerModal from '../servers/DeleteServerModal';
import DeleteServerButton from '../servers/DeleteServerButton'; import DeleteServerButton from '../servers/DeleteServerButton';
import AsideMenu from '../common/AsideMenu'; import AsideMenu from '../common/AsideMenu';
@ -40,16 +40,17 @@ import DeleteShortUrlModal from '../short-urls/helpers/DeleteShortUrlModal';
import { deleteShortUrl, resetDeleteShortUrl, shortUrlDeleted } from '../short-urls/reducers/shortUrlDeletion'; import { deleteShortUrl, resetDeleteShortUrl, shortUrlDeleted } from '../short-urls/reducers/shortUrlDeletion';
import EditTagsModal from '../short-urls/helpers/EditTagsModal'; import EditTagsModal from '../short-urls/helpers/EditTagsModal';
import { editShortUrlTags, resetShortUrlsTags, shortUrlTagsEdited } from '../short-urls/reducers/shortUrlTags'; import { editShortUrlTags, resetShortUrlsTags, shortUrlTagsEdited } from '../short-urls/reducers/shortUrlTags';
import buildShlinkApiClient from '../api/ShlinkApiClientBuilder';
const bottle = new Bottle(); const bottle = new Bottle();
const { container } = bottle; const { container } = bottle;
const mapActionService = (map, actionName) => { const mapActionService = (map, actionName) => ({
// Wrap actual action service in a function so that it is lazily created the first time it is called ...map,
map[actionName] = (...args) => container[actionName](...args);
return map; // Wrap actual action service in a function so that it is lazily created the first time it is called
}; [actionName]: (...args) => container[actionName](...args),
});
const connectDecorator = (propsFromState, actionServiceNames) => const connectDecorator = (propsFromState, actionServiceNames) =>
connect( connect(
pick(propsFromState), pick(propsFromState),
@ -144,8 +145,10 @@ bottle.decorator('EditTagsModal', connectDecorator(
[ 'editShortUrlTags', 'resetShortUrlsTags', 'shortUrlTagsEdited' ] [ 'editShortUrlTags', 'resetShortUrlsTags', 'shortUrlTagsEdited' ]
)); ));
bottle.serviceFactory('editShortUrlTags', editShortUrlTags, 'ShlinkApiClient'); bottle.serviceFactory('editShortUrlTags', editShortUrlTags, 'buildShlinkApiClient');
bottle.serviceFactory('resetShortUrlsTags', () => resetShortUrlsTags); bottle.serviceFactory('resetShortUrlsTags', () => resetShortUrlsTags);
bottle.serviceFactory('shortUrlTagsEdited', () => shortUrlTagsEdited); bottle.serviceFactory('shortUrlTagsEdited', () => shortUrlTagsEdited);
bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'axios');
export default container; export default container;

View file

@ -1,4 +1,3 @@
import shlinkApiClient from '../../api/ShlinkApiClient';
import serversService from '../../servers/services/ServersService'; import serversService from '../../servers/services/ServersService';
import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams'; import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams';
@ -22,17 +21,15 @@ export default function reducer(state = defaultState, action) {
export const resetSelectedServer = () => ({ type: RESET_SELECTED_SERVER }); export const resetSelectedServer = () => ({ type: RESET_SELECTED_SERVER });
export const _selectServer = (shlinkApiClient, serversService) => (serverId) => (dispatch) => { export const _selectServer = (serversService) => (serverId) => (dispatch) => {
dispatch(resetShortUrlParams()); dispatch(resetShortUrlParams());
const selectedServer = serversService.findServerById(serverId); const selectedServer = serversService.findServerById(serverId);
shlinkApiClient.setConfig(selectedServer);
dispatch({ dispatch({
type: SELECT_SERVER, type: SELECT_SERVER,
selectedServer, selectedServer,
}); });
}; };
export const selectServer = _selectServer(shlinkApiClient, serversService); export const selectServer = _selectServer(serversService);

View file

@ -1,6 +1,6 @@
import { curry } from 'ramda'; import { curry } from 'ramda';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements, newline-after-var */ /* eslint-disable padding-line-between-statements, newline-after-var */
export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START'; export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START';
@ -50,9 +50,12 @@ export default function reducer(state = defaultState, action) {
} }
} }
export const _createShortUrl = (shlinkApiClient, data) => async (dispatch) => { export const _createShortUrl = (buildShlinkApiClient, data) => async (dispatch, getState) => {
dispatch({ type: CREATE_SHORT_URL_START }); dispatch({ type: CREATE_SHORT_URL_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
const result = await shlinkApiClient.createShortUrl(data); const result = await shlinkApiClient.createShortUrl(data);
@ -62,6 +65,6 @@ export const _createShortUrl = (shlinkApiClient, data) => async (dispatch) => {
} }
}; };
export const createShortUrl = curry(_createShortUrl)(shlinkApiClient); export const createShortUrl = curry(_createShortUrl)(buildShlinkApiClient);
export const resetCreateShortUrl = () => ({ type: RESET_CREATE_SHORT_URL }); export const resetCreateShortUrl = () => ({ type: RESET_CREATE_SHORT_URL });

View file

@ -1,6 +1,6 @@
import { curry } from 'ramda'; import { curry } from 'ramda';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements, newline-after-var */ /* eslint-disable padding-line-between-statements, newline-after-var */
const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START'; const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START';
@ -56,9 +56,12 @@ export default function reducer(state = defaultState, action) {
} }
} }
export const _deleteShortUrl = (shlinkApiClient, shortCode) => async (dispatch) => { export const _deleteShortUrl = (buildShlinkApiClient, shortCode) => async (dispatch, getState) => {
dispatch({ type: DELETE_SHORT_URL_START }); dispatch({ type: DELETE_SHORT_URL_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
await shlinkApiClient.deleteShortUrl(shortCode); await shlinkApiClient.deleteShortUrl(shortCode);
dispatch({ type: DELETE_SHORT_URL, shortCode }); dispatch({ type: DELETE_SHORT_URL, shortCode });
@ -69,7 +72,7 @@ export const _deleteShortUrl = (shlinkApiClient, shortCode) => async (dispatch)
} }
}; };
export const deleteShortUrl = curry(_deleteShortUrl)(shlinkApiClient); export const deleteShortUrl = curry(_deleteShortUrl)(buildShlinkApiClient);
export const resetDeleteShortUrl = () => ({ type: RESET_DELETE_SHORT_URL }); export const resetDeleteShortUrl = () => ({ type: RESET_DELETE_SHORT_URL });

View file

@ -50,8 +50,10 @@ export default function reducer(state = defaultState, action) {
} }
} }
export const editShortUrlTags = (shlinkApiClient) => (shortCode, tags) => async (dispatch) => { export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, tags) => async (dispatch, getState) => {
dispatch({ type: EDIT_SHORT_URL_TAGS_START }); dispatch({ type: EDIT_SHORT_URL_TAGS_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
const normalizedTags = await shlinkApiClient.updateShortUrlTags(shortCode, tags); const normalizedTags = await shlinkApiClient.updateShortUrlTags(shortCode, tags);

View file

@ -1,6 +1,6 @@
import { assoc, assocPath, reject } from 'ramda'; import { assoc, assocPath, reject } from 'ramda';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
import { SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_TAGS_EDITED } from './shortUrlTags';
import { SHORT_URL_DELETED } from './shortUrlDeletion'; import { SHORT_URL_DELETED } from './shortUrlDeletion';
@ -55,9 +55,12 @@ export default function reducer(state = initialState, action) {
} }
} }
export const _listShortUrls = (shlinkApiClient, params = {}) => async (dispatch) => { export const _listShortUrls = (buildShlinkApiClient, params = {}) => async (dispatch, getState) => {
dispatch({ type: LIST_SHORT_URLS_START }); dispatch({ type: LIST_SHORT_URLS_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
const shortUrls = await shlinkApiClient.listShortUrls(params); const shortUrls = await shlinkApiClient.listShortUrls(params);
@ -67,4 +70,4 @@ export const _listShortUrls = (shlinkApiClient, params = {}) => async (dispatch)
} }
}; };
export const listShortUrls = (params = {}) => _listShortUrls(shlinkApiClient, params); export const listShortUrls = (params = {}) => _listShortUrls(buildShlinkApiClient, params);

View file

@ -1,6 +1,6 @@
import { curry } from 'ramda'; import { curry } from 'ramda';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements, newline-after-var */ /* eslint-disable padding-line-between-statements, newline-after-var */
export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START'; export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START';
@ -41,9 +41,12 @@ export default function reducer(state = defaultState, action) {
} }
} }
export const _deleteTag = (shlinkApiClient, tag) => async (dispatch) => { export const _deleteTag = (buildShlinkApiClient, tag) => async (dispatch, getState) => {
dispatch({ type: DELETE_TAG_START }); dispatch({ type: DELETE_TAG_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
await shlinkApiClient.deleteTags([ tag ]); await shlinkApiClient.deleteTags([ tag ]);
dispatch({ type: DELETE_TAG }); dispatch({ type: DELETE_TAG });
@ -54,6 +57,6 @@ export const _deleteTag = (shlinkApiClient, tag) => async (dispatch) => {
} }
}; };
export const deleteTag = curry(_deleteTag)(shlinkApiClient); export const deleteTag = curry(_deleteTag)(buildShlinkApiClient);
export const tagDeleted = (tag) => ({ type: TAG_DELETED, tag }); export const tagDeleted = (tag) => ({ type: TAG_DELETED, tag });

View file

@ -1,5 +1,5 @@
import { curry, pick } from 'ramda'; import { curry, pick } from 'ramda';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
import colorGenerator from '../../utils/ColorGenerator'; import colorGenerator from '../../utils/ColorGenerator';
/* eslint-disable padding-line-between-statements, newline-after-var */ /* eslint-disable padding-line-between-statements, newline-after-var */
@ -42,9 +42,15 @@ export default function reducer(state = defaultState, action) {
} }
} }
export const _editTag = (shlinkApiClient, colorGenerator, oldName, newName, color) => async (dispatch) => { export const _editTag = (buildShlinkApiClient, colorGenerator, oldName, newName, color) => async (
dispatch,
getState
) => {
dispatch({ type: EDIT_TAG_START }); dispatch({ type: EDIT_TAG_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
await shlinkApiClient.editTag(oldName, newName); await shlinkApiClient.editTag(oldName, newName);
colorGenerator.setColorForKey(newName, color); colorGenerator.setColorForKey(newName, color);
@ -56,7 +62,7 @@ export const _editTag = (shlinkApiClient, colorGenerator, oldName, newName, colo
} }
}; };
export const editTag = curry(_editTag)(shlinkApiClient, colorGenerator); export const editTag = curry(_editTag)(buildShlinkApiClient, colorGenerator);
export const tagEdited = (oldName, newName, color) => ({ export const tagEdited = (oldName, newName, color) => ({
type: TAG_EDITED, type: TAG_EDITED,

View file

@ -1,5 +1,5 @@
import { isEmpty, reject } from 'ramda'; import { isEmpty, reject } from 'ramda';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
import { TAG_DELETED } from './tagDelete'; import { TAG_DELETED } from './tagDelete';
import { TAG_EDITED } from './tagEdit'; import { TAG_EDITED } from './tagEdit';
@ -66,8 +66,8 @@ export default function reducer(state = defaultState, action) {
} }
} }
export const _listTags = (shlinkApiClient, force = false) => async (dispatch, getState) => { export const _listTags = (buildShlinkApiClient, force = false) => async (dispatch, getState) => {
const { tagsList } = getState(); const { tagsList, selectedServer } = getState();
if (!force && (tagsList.loading || !isEmpty(tagsList.tags))) { if (!force && (tagsList.loading || !isEmpty(tagsList.tags))) {
return; return;
@ -76,6 +76,7 @@ export const _listTags = (shlinkApiClient, force = false) => async (dispatch, ge
dispatch({ type: LIST_TAGS_START }); dispatch({ type: LIST_TAGS_START });
try { try {
const shlinkApiClient = buildShlinkApiClient(selectedServer);
const tags = await shlinkApiClient.listTags(); const tags = await shlinkApiClient.listTags();
dispatch({ tags, type: LIST_TAGS }); dispatch({ tags, type: LIST_TAGS });
@ -84,9 +85,9 @@ export const _listTags = (shlinkApiClient, force = false) => async (dispatch, ge
} }
}; };
export const listTags = () => _listTags(shlinkApiClient); export const listTags = () => _listTags(buildShlinkApiClient);
export const forceListTags = () => _listTags(shlinkApiClient, true); export const forceListTags = () => _listTags(buildShlinkApiClient, true);
export const filterTags = (searchTerm) => ({ export const filterTags = (searchTerm) => ({
type: FILTER_TAGS, type: FILTER_TAGS,

View file

@ -1,6 +1,6 @@
import { curry } from 'ramda'; import { curry } from 'ramda';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
import { shortUrlType } from '../../short-urls/reducers/shortUrlsList'; import { shortUrlType } from '../../short-urls/reducers/shortUrlsList';
/* eslint-disable padding-line-between-statements, newline-after-var */ /* eslint-disable padding-line-between-statements, newline-after-var */
@ -45,9 +45,12 @@ export default function reducer(state = initialState, action) {
} }
} }
export const _getShortUrlDetail = (shlinkApiClient, shortCode) => async (dispatch) => { export const _getShortUrlDetail = (buildShlinkApiClient, shortCode) => async (dispatch, getState) => {
dispatch({ type: GET_SHORT_URL_DETAIL_START }); dispatch({ type: GET_SHORT_URL_DETAIL_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
const shortUrl = await shlinkApiClient.getShortUrl(shortCode); const shortUrl = await shlinkApiClient.getShortUrl(shortCode);
@ -57,4 +60,4 @@ export const _getShortUrlDetail = (shlinkApiClient, shortCode) => async (dispatc
} }
}; };
export const getShortUrlDetail = curry(_getShortUrlDetail)(shlinkApiClient); export const getShortUrlDetail = curry(_getShortUrlDetail)(buildShlinkApiClient);

View file

@ -1,6 +1,6 @@
import { curry } from 'ramda'; import { curry } from 'ramda';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shlinkApiClient from '../../api/ShlinkApiClient'; import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements, newline-after-var */ /* eslint-disable padding-line-between-statements, newline-after-var */
export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START'; export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
@ -44,9 +44,12 @@ export default function reducer(state = initialState, action) {
} }
} }
export const _getShortUrlVisits = (shlinkApiClient, shortCode, dates) => async (dispatch) => { export const _getShortUrlVisits = (buildShlinkApiClient, shortCode, dates) => async (dispatch, getState) => {
dispatch({ type: GET_SHORT_URL_VISITS_START }); dispatch({ type: GET_SHORT_URL_VISITS_START });
const { selectedServer } = getState();
const shlinkApiClient = buildShlinkApiClient(selectedServer);
try { try {
const visits = await shlinkApiClient.getShortUrlVisits(shortCode, dates); const visits = await shlinkApiClient.getShortUrlVisits(shortCode, dates);
@ -56,4 +59,4 @@ export const _getShortUrlVisits = (shlinkApiClient, shortCode, dates) => async (
} }
}; };
export const getShortUrlVisits = curry(_getShortUrlVisits)(shlinkApiClient); export const getShortUrlVisits = curry(_getShortUrlVisits)(buildShlinkApiClient);

View file

@ -1,6 +1,6 @@
import sinon from 'sinon'; import sinon from 'sinon';
import { head, last } from 'ramda'; import { head, last } from 'ramda';
import { ShlinkApiClient } from '../../src/api/ShlinkApiClient'; import ShlinkApiClient from '../../src/api/ShlinkApiClient';
describe('ShlinkApiClient', () => { describe('ShlinkApiClient', () => {
const createAxiosMock = (extraData) => () => const createAxiosMock = (extraData) => () =>