2022-11-02 22:40:14 +03:00
|
|
|
import { createSlice, PayloadAction, createAsyncThunk, SliceCaseReducers } from '@reduxjs/toolkit';
|
|
|
|
import { Dispatch } from 'redux';
|
|
|
|
import { AxiosError } from 'axios';
|
2022-10-12 11:35:16 +03:00
|
|
|
import { ShlinkDomainRedirects } from '../../api/types';
|
2020-12-22 11:55:39 +03:00
|
|
|
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
2022-11-02 22:40:14 +03:00
|
|
|
import { GetState, ShlinkState } from '../../container/types';
|
2021-08-21 18:53:06 +03:00
|
|
|
import { ApiErrorAction } from '../../api/types/actions';
|
2021-12-26 15:38:17 +03:00
|
|
|
import { Domain, DomainStatus } from '../data';
|
|
|
|
import { hasServerData } from '../../servers/data';
|
|
|
|
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
|
2021-08-21 18:53:06 +03:00
|
|
|
import { EDIT_DOMAIN_REDIRECTS, EditDomainRedirectsAction } from './domainRedirects';
|
2022-10-12 11:35:16 +03:00
|
|
|
import { ProblemDetailsError } from '../../api/types/errors';
|
2022-11-02 22:40:14 +03:00
|
|
|
import { parseApiError } from '../../api/utils';
|
|
|
|
import { buildReducer } from '../../utils/helpers/redux';
|
2020-11-25 23:05:27 +03:00
|
|
|
|
|
|
|
export const LIST_DOMAINS_START = 'shlink/domainsList/LIST_DOMAINS_START';
|
|
|
|
export const LIST_DOMAINS_ERROR = 'shlink/domainsList/LIST_DOMAINS_ERROR';
|
|
|
|
export const LIST_DOMAINS = 'shlink/domainsList/LIST_DOMAINS';
|
2021-08-21 18:53:06 +03:00
|
|
|
export const FILTER_DOMAINS = 'shlink/domainsList/FILTER_DOMAINS';
|
2021-12-26 15:38:17 +03:00
|
|
|
export const VALIDATE_DOMAIN = 'shlink/domainsList/VALIDATE_DOMAIN';
|
2020-11-25 23:05:27 +03:00
|
|
|
|
|
|
|
export interface DomainsList {
|
2021-12-26 15:38:17 +03:00
|
|
|
domains: Domain[];
|
|
|
|
filteredDomains: Domain[];
|
2021-12-09 15:44:29 +03:00
|
|
|
defaultRedirects?: ShlinkDomainRedirects;
|
2020-11-25 23:05:27 +03:00
|
|
|
loading: boolean;
|
|
|
|
error: boolean;
|
2021-08-21 18:53:06 +03:00
|
|
|
errorData?: ProblemDetailsError;
|
2020-11-25 23:05:27 +03:00
|
|
|
}
|
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
type ListDomainsAction = PayloadAction<{
|
2021-12-26 15:38:17 +03:00
|
|
|
domains: Domain[];
|
2021-12-09 15:44:29 +03:00
|
|
|
defaultRedirects?: ShlinkDomainRedirects;
|
2022-11-02 22:40:14 +03:00
|
|
|
}>;
|
2020-11-25 23:05:27 +03:00
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
type FilterDomainsAction = PayloadAction<string>;
|
2021-08-21 18:53:06 +03:00
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
type ValidateDomain = PayloadAction<{
|
2021-12-26 15:38:17 +03:00
|
|
|
domain: string;
|
|
|
|
status: DomainStatus;
|
2022-11-02 22:40:14 +03:00
|
|
|
}>;
|
2021-12-26 15:38:17 +03:00
|
|
|
|
2020-11-25 23:05:27 +03:00
|
|
|
const initialState: DomainsList = {
|
|
|
|
domains: [],
|
2021-08-21 18:53:06 +03:00
|
|
|
filteredDomains: [],
|
2020-11-25 23:05:27 +03:00
|
|
|
loading: false,
|
|
|
|
error: false,
|
|
|
|
};
|
|
|
|
|
2021-08-22 10:00:58 +03:00
|
|
|
export type DomainsCombinedAction = ListDomainsAction
|
2021-08-21 18:53:06 +03:00
|
|
|
& ApiErrorAction
|
|
|
|
& FilterDomainsAction
|
2021-12-26 15:38:17 +03:00
|
|
|
& EditDomainRedirectsAction
|
|
|
|
& ValidateDomain;
|
2021-08-21 18:53:06 +03:00
|
|
|
|
2021-08-22 10:00:58 +03:00
|
|
|
export const replaceRedirectsOnDomain = (domain: string, redirects: ShlinkDomainRedirects) =>
|
2022-03-26 14:17:42 +03:00
|
|
|
(d: Domain): Domain => (d.domain !== domain ? d : { ...d, redirects });
|
2021-12-26 15:38:17 +03:00
|
|
|
|
|
|
|
export const replaceStatusOnDomain = (domain: string, status: DomainStatus) =>
|
2022-03-26 14:17:42 +03:00
|
|
|
(d: Domain): Domain => (d.domain !== domain ? d : { ...d, status });
|
2021-08-21 18:53:06 +03:00
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
const oldReducer = buildReducer<DomainsList, DomainsCombinedAction>({
|
2020-11-25 23:05:27 +03:00
|
|
|
[LIST_DOMAINS_START]: () => ({ ...initialState, loading: true }),
|
2022-11-02 22:40:14 +03:00
|
|
|
[LIST_DOMAINS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
|
|
|
[LIST_DOMAINS]: (_, { payload }) => ({ ...initialState, searchTerm: payload, filteredDomains: payload.domains }),
|
|
|
|
[FILTER_DOMAINS]: (state, { payload }) => ({
|
2021-08-21 18:53:06 +03:00
|
|
|
...state,
|
2022-11-02 22:40:14 +03:00
|
|
|
filteredDomains: state.domains.filter(({ domain }) => domain.toLowerCase().match(payload.toLowerCase())),
|
2021-08-21 18:53:06 +03:00
|
|
|
}),
|
|
|
|
[EDIT_DOMAIN_REDIRECTS]: (state, { domain, redirects }) => ({
|
|
|
|
...state,
|
|
|
|
domains: state.domains.map(replaceRedirectsOnDomain(domain, redirects)),
|
|
|
|
filteredDomains: state.filteredDomains.map(replaceRedirectsOnDomain(domain, redirects)),
|
|
|
|
}),
|
2022-11-02 22:40:14 +03:00
|
|
|
[VALIDATE_DOMAIN]: (state, { payload }) => ({
|
2021-12-26 15:38:17 +03:00
|
|
|
...state,
|
2022-11-02 22:40:14 +03:00
|
|
|
domains: state.domains.map(replaceStatusOnDomain(payload.domain, payload.status)),
|
|
|
|
filteredDomains: state.filteredDomains.map(replaceStatusOnDomain(payload.domain, payload.status)),
|
2021-12-26 15:38:17 +03:00
|
|
|
}),
|
2020-11-25 23:05:27 +03:00
|
|
|
}, initialState);
|
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
export default oldReducer;
|
|
|
|
|
2020-11-25 23:05:27 +03:00
|
|
|
export const listDomains = (buildShlinkApiClient: ShlinkApiClientBuilder) => () => async (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState,
|
|
|
|
) => {
|
|
|
|
dispatch({ type: LIST_DOMAINS_START });
|
2022-03-26 14:17:42 +03:00
|
|
|
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
2020-11-25 23:05:27 +03:00
|
|
|
|
|
|
|
try {
|
2022-11-02 22:40:14 +03:00
|
|
|
const payload = await shlinkListDomains().then(({ data, defaultRedirects }) => ({
|
2021-12-26 15:38:17 +03:00
|
|
|
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
|
|
|
defaultRedirects,
|
|
|
|
}));
|
2020-11-25 23:05:27 +03:00
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
dispatch<ListDomainsAction>({ type: LIST_DOMAINS, payload });
|
2021-10-31 14:38:42 +03:00
|
|
|
} catch (e: any) {
|
2021-08-21 18:53:06 +03:00
|
|
|
dispatch<ApiErrorAction>({ type: LIST_DOMAINS_ERROR, errorData: parseApiError(e) });
|
2020-11-25 23:05:27 +03:00
|
|
|
}
|
|
|
|
};
|
2021-08-21 18:53:06 +03:00
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
export const filterDomains = (searchTerm: string): FilterDomainsAction => ({
|
|
|
|
type: FILTER_DOMAINS,
|
|
|
|
payload: searchTerm,
|
|
|
|
});
|
2021-12-26 15:38:17 +03:00
|
|
|
|
|
|
|
export const checkDomainHealth = (buildShlinkApiClient: ShlinkApiClientBuilder) => (domain: string) => async (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState,
|
|
|
|
) => {
|
|
|
|
const { selectedServer } = getState();
|
|
|
|
|
|
|
|
if (!hasServerData(selectedServer)) {
|
2022-11-02 22:40:14 +03:00
|
|
|
dispatch<ValidateDomain>({
|
|
|
|
type: VALIDATE_DOMAIN,
|
|
|
|
payload: { domain, status: 'invalid' },
|
|
|
|
});
|
2021-12-26 15:38:17 +03:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const { url, ...rest } = selectedServer;
|
|
|
|
const { health } = buildShlinkApiClient({
|
|
|
|
...rest,
|
|
|
|
url: replaceAuthorityFromUri(url, domain),
|
|
|
|
});
|
|
|
|
|
|
|
|
const { status } = await health();
|
|
|
|
|
2022-11-02 22:40:14 +03:00
|
|
|
dispatch<ValidateDomain>({
|
|
|
|
type: VALIDATE_DOMAIN,
|
|
|
|
payload: { domain, status: status === 'pass' ? 'valid' : 'invalid' },
|
|
|
|
});
|
2021-12-26 15:38:17 +03:00
|
|
|
} catch (e) {
|
2022-11-02 22:40:14 +03:00
|
|
|
dispatch<ValidateDomain>({
|
|
|
|
type: VALIDATE_DOMAIN,
|
|
|
|
payload: { domain, status: 'invalid' },
|
|
|
|
});
|
2021-12-26 15:38:17 +03:00
|
|
|
}
|
|
|
|
};
|
2022-11-02 22:40:14 +03:00
|
|
|
|
|
|
|
export const domainsReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
|
|
const listDomains = createAsyncThunk<{
|
|
|
|
domains: Domain[];
|
|
|
|
defaultRedirects?: ShlinkDomainRedirects;
|
|
|
|
}, void, { state: ShlinkState }>(
|
|
|
|
LIST_DOMAINS,
|
|
|
|
async (_, { getState }) => {
|
|
|
|
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
|
|
|
const { data, defaultRedirects } = await shlinkListDomains();
|
|
|
|
|
|
|
|
return {
|
|
|
|
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
|
|
|
defaultRedirects,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
|
|
const checkDomainHealth = createAsyncThunk<{ domain: string; status: DomainStatus }, string, { state: ShlinkState }>(
|
|
|
|
VALIDATE_DOMAIN,
|
|
|
|
async (domain: string, { getState }) => {
|
|
|
|
const { selectedServer } = getState();
|
|
|
|
|
|
|
|
if (!hasServerData(selectedServer)) {
|
|
|
|
return { domain, status: 'invalid' };
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const { url, ...rest } = selectedServer;
|
|
|
|
const { health } = buildShlinkApiClient({
|
|
|
|
...rest,
|
|
|
|
url: replaceAuthorityFromUri(url, domain),
|
|
|
|
});
|
|
|
|
|
|
|
|
const { status } = await health();
|
|
|
|
|
|
|
|
return { domain, status: status === 'pass' ? 'valid' : 'invalid' };
|
|
|
|
} catch (e) {
|
|
|
|
return { domain, status: 'invalid' };
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const { actions, reducer } = createSlice<DomainsList, SliceCaseReducers<DomainsList>>({
|
|
|
|
name: 'domainsList',
|
|
|
|
initialState,
|
|
|
|
reducers: {
|
|
|
|
filterDomains: (state, { payload }) => {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
state.filteredDomains = state.domains.filter(
|
|
|
|
({ domain }) => domain.toLowerCase().match(payload.toLowerCase()),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
extraReducers: (builder) => {
|
|
|
|
builder.addCase(listDomains.pending, () => ({ ...initialState, loading: true }));
|
|
|
|
builder.addCase(listDomains.rejected, (_, { error }) => (
|
|
|
|
{ ...initialState, error: true, errorData: parseApiError(error as AxiosError<ProblemDetailsError>) } // TODO Fix this casting
|
|
|
|
));
|
|
|
|
builder.addCase(listDomains.fulfilled, (_, { payload }) => (
|
|
|
|
{ ...initialState, ...payload, filteredDomains: payload.domains }
|
|
|
|
));
|
|
|
|
|
|
|
|
builder.addCase(checkDomainHealth.fulfilled, (state, { payload }) => {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
state.domains = state.domains.map(replaceStatusOnDomain(payload.domain, payload.status));
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
state.filteredDomains = state.filteredDomains.map(replaceStatusOnDomain(payload.domain, payload.status));
|
|
|
|
});
|
|
|
|
|
|
|
|
builder.addCase(EDIT_DOMAIN_REDIRECTS, (state, { domain, redirects }: any) => { // TODO Fix this "any"
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
state.domains = state.domains.map(replaceRedirectsOnDomain(domain, redirects));
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
state.filteredDomains = state.filteredDomains.map(replaceRedirectsOnDomain(domain, redirects));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
reducer,
|
|
|
|
listDomains,
|
|
|
|
checkDomainHealth,
|
|
|
|
...actions,
|
|
|
|
};
|
|
|
|
};
|