chore(content-releases): refactor rtk with type contracts (#18867)

This commit is contained in:
markkaylor 2023-11-23 11:15:29 +01:00 committed by GitHub
parent 1efe5dcdd1
commit 9f5e68ce57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 126 additions and 125 deletions

View File

@ -13,8 +13,8 @@ import { useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import * as yup from 'yup';
import { useCreateReleaseMutation } from '../modules/releaseSlice';
import { isErrorAxiosError } from '../utils/errors';
import { isAxiosError } from '../services/axios';
import { useCreateReleaseMutation } from '../services/release';
const RELEASE_SCHEMA = yup.object({
name: yup.string().required(),
@ -56,7 +56,7 @@ export const AddReleaseDialog = ({ handleClose }: AddReleaseDialogProps) => {
});
push(`/plugins/content-releases/${response.data.data.id}`);
} else if (isErrorAxiosError(response.error)) {
} else if (isAxiosError(response.error)) {
// When the response returns an object with 'error', handle axios error
toggleNotification({
type: 'warning',

View File

@ -2,8 +2,8 @@ import { prefixPluginTranslations } from '@strapi/helper-plugin';
import { PaperPlane } from '@strapi/icons';
import { PERMISSIONS } from './constants';
import { releaseApi } from './modules/releaseSlice';
import { pluginId } from './pluginId';
import { releaseApi } from './services/release';
import type { Plugin } from '@strapi/types';

View File

@ -0,0 +1,87 @@
import { getFetchClient } from '@strapi/helper-plugin';
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
/* -------------------------------------------------------------------------------------------------
* Axios data
* -----------------------------------------------------------------------------------------------*/
export interface QueryArguments<TSend> {
url: string;
method: 'PUT' | 'GET' | 'POST' | 'DELETE';
data?: TSend;
config?: AxiosRequestConfig<TSend>;
}
const axiosBaseQuery = async <TData = any, TSend = any>({
url,
method,
data,
config,
}: QueryArguments<TSend>) => {
try {
const { get, post, del, put } = getFetchClient();
if (method === 'POST') {
const result = await post<TData, AxiosResponse<TData>, TSend>(url, data, config);
return { data: result.data };
}
if (method === 'DELETE') {
const result = await del<TData, AxiosResponse<TData>, TSend>(url, config);
return { data: result.data };
}
if (method === 'PUT') {
const result = await put<TData, AxiosResponse<TData>, TSend>(url, data, config);
return { data: result.data };
}
/**
* Default is GET.
*/
const result = await get<TData, AxiosResponse<TData>, TSend>(url, config);
return { data: result.data };
} catch (error) {
const err = error as AxiosError;
/**
* Handle error of type AxiosError
*
* This format mimics what we want from an AxiosError which is what the
* rest of the app works with, except this format is "serializable" since
* it goes into the redux store.
*
* NOTE passing the whole response will highlight this "serializability" issue.
*/
return {
error: {
status: err.response?.status,
code: err.code,
response: {
data: err.response?.data,
},
},
};
}
};
/* -------------------------------------------------------------------------------------------------
* Axios error
* -----------------------------------------------------------------------------------------------*/
/**
* This asserts the errors from redux-toolkit-query are
* axios errors so we can pass them to our utility functions
* to correctly render error messages.
*/
const isAxiosError = (err: unknown): err is AxiosError<{ error: any }> => {
return (
typeof err === 'object' &&
err !== null &&
'response' in err &&
typeof err.response === 'object' &&
err.response !== null &&
'data' in err.response
);
};
export { isAxiosError, axiosBaseQuery };

View File

@ -1,13 +1,10 @@
import { createApi } from '@reduxjs/toolkit/query/react';
import { pluginId } from '../pluginId';
import { axiosBaseQuery } from '../utils/data';
import type {
CreateRelease,
GetReleases,
ReleaseDataResponse,
} from '../../../shared/contracts/releases';
import { axiosBaseQuery } from './axios';
import type { CreateRelease, GetReleases } from '../../../shared/contracts/releases';
const releaseApi = createApi({
reducerPath: pluginId,
@ -24,7 +21,7 @@ const releaseApi = createApi({
},
providesTags: ['Releases'],
}),
createRelease: build.mutation<{ data: ReleaseDataResponse }, CreateRelease.Request['body']>({
createRelease: build.mutation<CreateRelease.Response, CreateRelease.Request['body']>({
query(data) {
return {
url: '/content-releases',

View File

@ -1,61 +0,0 @@
import { getFetchClient } from '@strapi/helper-plugin';
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
export interface QueryArguments<TSend> {
url: string;
method: 'PUT' | 'GET' | 'POST' | 'DELETE';
data?: TSend;
config?: AxiosRequestConfig<TSend>;
}
const axiosBaseQuery = async <TData = any, TSend = any>({
url,
method,
data,
config,
}: QueryArguments<TSend>) => {
try {
const { get, post, del, put } = getFetchClient();
if (method === 'POST') {
const res = await post<TData, AxiosResponse<TData>, TSend>(url, data, config);
return { data: res.data };
}
if (method === 'DELETE') {
const res = await del<TData, AxiosResponse<TData>, TSend>(url, config);
return { data: res.data };
}
if (method === 'PUT') {
const res = await put<TData, AxiosResponse<TData>, TSend>(url, data, config);
return { data: res.data };
}
/**
* Default is GET.
*/
const res = await get<TData, AxiosResponse<TData>, TSend>(url, config);
return { data: res.data };
} catch (error) {
const err = error as AxiosError;
/**
* This format mimics what we want from an AxiosError which is what the
* rest of the app works with, except this format is "serializable" since
* it goes into the redux store.
*
* NOTE passing the whole response will highlight this "serializability" issue.
*/
return {
error: {
status: err.response?.status,
code: err.code,
response: {
data: err.response?.data,
},
},
};
}
};
export { axiosBaseQuery };

View File

@ -1,19 +0,0 @@
import { AxiosError } from 'axios';
/**
* This asserts the errors from redux-toolkit-query are
* axios errors so we can pass them to our utility functions
* to correctly render error messages.
*/
const isErrorAxiosError = (err: unknown): err is AxiosError<{ error: any }> => {
return (
typeof err === 'object' &&
err !== null &&
'response' in err &&
typeof err.response === 'object' &&
err.response !== null &&
'data' in err.response
);
};
export { isErrorAxiosError };

View File

@ -19,7 +19,7 @@ import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { MemoryRouter, MemoryRouterProps } from 'react-router-dom';
import { releaseApi } from '../src/modules/releaseSlice';
import { releaseApi } from '../src/services/release';
import { server } from './server';
import { initialState } from './store';

View File

@ -2,7 +2,12 @@ import type Koa from 'koa';
import { errors } from '@strapi/utils';
import { RELEASE_MODEL_UID } from '../constants';
import { validateRelease } from './validation/release';
import type { CreateRelease, UpdateRelease, GetRelease, Release } from '../../../shared/contracts/releases';
import type {
CreateRelease,
UpdateRelease,
GetRelease,
Release,
} from '../../../shared/contracts/releases';
import type { UserInfo } from '../../../shared/types';
import { getService } from '../utils';
@ -34,7 +39,7 @@ const releaseController = {
};
});
ctx.body = { data, pagination };
ctx.body = { data, meta: { pagination } };
},
async findOne(ctx: Koa.Context) {
@ -100,7 +105,7 @@ const releaseController = {
ctx.body = {
data: await permissionsManager.sanitizeOutput(release),
};
}
},
};
export default releaseController;

View File

@ -34,11 +34,7 @@ export declare namespace CreateReleaseAction {
}
export interface Response {
data:
| ReleaseAction
| {
data: null;
error: errors.ApplicationError | errors.ValidationError | errors.NotFoundError;
};
data: ReleaseAction;
error?: errors.ApplicationError | errors.ValidationError | errors.NotFoundError;
}
}

View File

@ -31,15 +31,13 @@ export declare namespace GetReleases {
query?: Partial<Pick<Pagination, 'page' | 'pageSize'>>;
}
export type Response =
| {
data: ReleaseDataResponse[];
pagination: Pagination;
}
| {
data: null;
error: errors.ApplicationError;
};
export interface Response {
data: ReleaseDataResponse[];
meta: {
pagination: Pagination;
};
error?: errors.ApplicationError;
}
}
/**
@ -55,14 +53,10 @@ export declare namespace GetRelease {
};
}
export type Response =
| {
data: ReleaseDataResponse;
}
| {
data: null;
error: errors.ApplicationError | errors.NotFoundError;
};
export interface Response {
data: ReleaseDataResponse;
error?: errors.ApplicationError | errors.NotFoundError;
}
}
/**
@ -78,9 +72,10 @@ export declare namespace CreateRelease {
};
}
export type Response =
| { data: ReleaseDataResponse }
| { data: null; error: errors.ApplicationError | errors.ValidationError };
export interface Response {
data: ReleaseDataResponse;
error?: errors.ApplicationError | errors.ValidationError;
}
}
/**
@ -99,7 +94,8 @@ export declare namespace UpdateRelease {
};
}
export type Response =
| { data: ReleaseDataResponse }
| { data: null; error: errors.ApplicationError | errors.ValidationError };
}
export interface Response {
data: ReleaseDataResponse;
error?: errors.ApplicationError | errors.ValidationError;
}
}