Migrated ShlinkApiClientBuilder to TS

This commit is contained in:
Alejandro Celaya 2020-08-27 22:09:16 +02:00
parent eb3775859a
commit 54290d4c9a
16 changed files with 70 additions and 51 deletions

View file

@ -1,8 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { ShlinkApiClientBuilder, ShlinkMercureInfo } from '../../utils/services/types'; import { ShlinkMercureInfo } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START'; export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START';

View file

@ -4,7 +4,7 @@ export interface ServerData {
apiKey: string; apiKey: string;
} }
export interface ServerWithId { export interface ServerWithId extends ServerData {
id: string; id: string;
} }
@ -24,3 +24,6 @@ export interface NotFoundServer {
export type RegularServer = ReachableServer | NonReachableServer; export type RegularServer = ReachableServer | NonReachableServer;
export type SelectedServer = RegularServer | NotFoundServer | null; export type SelectedServer = RegularServer | NotFoundServer | null;
export const hasServerData = (server: ServerData | NotFoundServer | null): server is ServerData =>
!!(server as ServerData)?.url && !!(server as ServerData)?.apiKey;

View file

@ -4,8 +4,9 @@ import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListPara
import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version';
import { SelectedServer } from '../data'; import { SelectedServer } from '../data';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder, ShlinkHealth } from '../../utils/services/types'; import { ShlinkHealth } from '../../utils/services/types';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER'; export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER';

View file

@ -1,9 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { ShlinkApiClientBuilder } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShortUrl, ShortUrlData } from '../data'; import { ShortUrl, ShortUrlData } from '../data';
import { buildReducer, buildActionCreator } from '../../utils/helpers/redux'; import { buildReducer, buildActionCreator } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START'; export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START';

View file

@ -1,7 +1,8 @@
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ProblemDetailsError, ShlinkApiClientBuilder } from '../../utils/services/types'; import { ProblemDetailsError} from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START'; export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START';

View file

@ -1,9 +1,9 @@
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { OptionalString } from '../../utils/utils'; import { OptionalString } from '../../utils/utils';
import { ShortUrlIdentifier } from '../data'; import { ShortUrlIdentifier } from '../data';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const EDIT_SHORT_URL_START = 'shlink/shortUrlEdition/EDIT_SHORT_URL_START'; export const EDIT_SHORT_URL_START = 'shlink/shortUrlEdition/EDIT_SHORT_URL_START';

View file

@ -1,10 +1,10 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Dispatch, Action } from 'redux'; import { Dispatch, Action } from 'redux';
import { ShortUrlIdentifier, ShortUrlMeta } from '../data'; import { ShortUrlIdentifier, ShortUrlMeta } from '../data';
import { ShlinkApiClientBuilder } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { OptionalString } from '../../utils/utils'; import { OptionalString } from '../../utils/utils';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START'; export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START';

View file

@ -1,10 +1,10 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { OptionalString } from '../../utils/utils'; import { OptionalString } from '../../utils/utils';
import { ShortUrlIdentifier } from '../data'; import { ShortUrlIdentifier } from '../data';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START';

View file

@ -6,12 +6,12 @@ import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCrea
import { ShortUrl, ShortUrlIdentifier } from '../data'; import { ShortUrl, ShortUrlIdentifier } from '../data';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder } from '../../utils/services/types';
import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags';
import { SHORT_URL_DELETED } from './shortUrlDeletion'; import { SHORT_URL_DELETED } from './shortUrlDeletion';
import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta';
import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition';
import { ShortUrlsListParams } from './shortUrlsListParams'; import { ShortUrlsListParams } from './shortUrlsListParams';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';

View file

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START'; export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START';

View file

@ -2,8 +2,8 @@ import { pick } from 'ramda';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder } from '../../utils/services/types';
import ColorGenerator from '../../utils/services/ColorGenerator'; import ColorGenerator from '../../utils/services/ColorGenerator';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START'; export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START';

View file

@ -3,10 +3,11 @@ import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder, ShlinkTags } from '../../utils/services/types'; import { ShlinkTags } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { DeleteTagAction, TAG_DELETED } from './tagDelete'; import { DeleteTagAction, TAG_DELETED } from './tagDelete';
import { EditTagAction, TAG_EDITED } from './tagEdit'; import { EditTagAction, TAG_EDITED } from './tagEdit';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START';

View file

@ -1,24 +0,0 @@
import ShlinkApiClient from './ShlinkApiClient';
const apiClients = {};
const getSelectedServerFromState = (getState) => {
const { selectedServer } = getState();
return selectedServer;
};
const buildShlinkApiClient = (axios) => (getStateOrSelectedServer) => {
const { url, apiKey } = typeof getStateOrSelectedServer === 'function'
? getSelectedServerFromState(getStateOrSelectedServer)
: getStateOrSelectedServer;
const clientKey = `${url}_${apiKey}`;
if (!apiClients[clientKey]) {
apiClients[clientKey] = new ShlinkApiClient(axios, url, apiKey);
}
return apiClients[clientKey];
};
export default buildShlinkApiClient;

View file

@ -0,0 +1,36 @@
import { AxiosInstance } from 'axios';
import { prop } from 'ramda';
import { hasServerData, SelectedServer, ServerWithId } from '../../servers/data';
import { GetState } from '../../container/types';
import ShlinkApiClient from './ShlinkApiClient';
const apiClients: Record<string, ShlinkApiClient> = {};
const isGetState = (getStateOrSelectedServer: GetState | ServerWithId): getStateOrSelectedServer is GetState =>
typeof getStateOrSelectedServer === 'function';
const getSelectedServerFromState = (getState: GetState): SelectedServer => prop('selectedServer', getState());
export type ShlinkApiClientBuilder = (getStateOrSelectedServer: GetState | ServerWithId) => ShlinkApiClient;
const buildShlinkApiClient = (axios: AxiosInstance): ShlinkApiClientBuilder => (
getStateOrSelectedServer: GetState | ServerWithId,
) => {
const server = isGetState(getStateOrSelectedServer)
? getSelectedServerFromState(getStateOrSelectedServer)
: getStateOrSelectedServer;
if (!hasServerData(server)) {
throw new Error('There\'s no selected server or it is not found');
}
const { url, apiKey } = server;
const clientKey = `${url}_${apiKey}`;
if (!apiClients[clientKey]) {
apiClients[clientKey] = new ShlinkApiClient(axios, url, apiKey);
}
return apiClients[clientKey];
};
export default buildShlinkApiClient;

View file

@ -1,10 +1,3 @@
import { ServerWithId } from '../../servers/data';
import { GetState } from '../../container/types';
import ShlinkApiClient from './ShlinkApiClient';
// FIXME Move to ShlinkApiClientBuilder
export type ShlinkApiClientBuilder = (getStateOrSelectedServer: ServerWithId | GetState) => ShlinkApiClient;
export interface ShlinkMercureInfo { export interface ShlinkMercureInfo {
token: string; token: string;
mercureHubUrl: string; mercureHubUrl: string;

View file

@ -1,18 +1,25 @@
import { Mock } from 'ts-mockery';
import { AxiosInstance } from 'axios';
import buildShlinkApiClient from '../../../src/utils/services/ShlinkApiClientBuilder'; import buildShlinkApiClient from '../../../src/utils/services/ShlinkApiClientBuilder';
import { ReachableServer, SelectedServer } from '../../../src/servers/data';
import { ShlinkState } from '../../../src/container/types';
describe('ShlinkApiClientBuilder', () => { describe('ShlinkApiClientBuilder', () => {
const createBuilder = () => { const axiosMock = Mock.all<AxiosInstance>();
const builder = buildShlinkApiClient({}); const server = (data: Partial<ReachableServer>) => Mock.of<ReachableServer>(data);
return (selectedServer) => builder(() => ({ selectedServer })); const createBuilder = () => {
const builder = buildShlinkApiClient(axiosMock);
return (selectedServer: SelectedServer) => builder(() => Mock.of<ShlinkState>({ selectedServer }));
}; };
it('creates new instances when provided params are different', async () => { it('creates new instances when provided params are different', async () => {
const builder = createBuilder(); const builder = createBuilder();
const [ firstApiClient, secondApiClient, thirdApiClient ] = await Promise.all([ const [ firstApiClient, secondApiClient, thirdApiClient ] = await Promise.all([
builder({ url: 'foo', apiKey: 'bar' }), builder(server({ url: 'foo', apiKey: 'bar' })),
builder({ url: 'bar', apiKey: 'bar' }), builder(server({ url: 'bar', apiKey: 'bar' })),
builder({ url: 'bar', apiKey: 'foo' }), builder(server({ url: 'bar', apiKey: 'foo' })),
]); ]);
expect(firstApiClient).not.toBe(secondApiClient); expect(firstApiClient).not.toBe(secondApiClient);
@ -22,7 +29,7 @@ describe('ShlinkApiClientBuilder', () => {
it('returns existing instances when provided params are the same', async () => { it('returns existing instances when provided params are the same', async () => {
const builder = createBuilder(); const builder = createBuilder();
const selectedServer = { url: 'foo', apiKey: 'bar' }; const selectedServer = server({ url: 'foo', apiKey: 'bar' });
const [ firstApiClient, secondApiClient, thirdApiClient ] = await Promise.all([ const [ firstApiClient, secondApiClient, thirdApiClient ] = await Promise.all([
builder(selectedServer), builder(selectedServer),
builder(selectedServer), builder(selectedServer),
@ -37,7 +44,7 @@ describe('ShlinkApiClientBuilder', () => {
it('does not fetch from state when provided param is already selected server', () => { it('does not fetch from state when provided param is already selected server', () => {
const url = 'url'; const url = 'url';
const apiKey = 'apiKey'; const apiKey = 'apiKey';
const apiClient = buildShlinkApiClient({})({ url, apiKey }); const apiClient = buildShlinkApiClient(axiosMock)(server({ url, apiKey }));
expect(apiClient._baseUrl).toEqual(url); expect(apiClient._baseUrl).toEqual(url);
expect(apiClient._apiKey).toEqual(apiKey); expect(apiClient._apiKey).toEqual(apiKey);