mirror of
https://github.com/strapi/strapi.git
synced 2025-12-28 07:33:17 +00:00
fix(admin): reset all redux toolkit cache on logout (#20318)
* fix(admin): reset all redux toolkit cache on logout * chore: refactor API to use one createApi call from redux/toolkit * chore: fix e2e suite * chore: spelling error Co-authored-by: Bassel Kanso <basselkanso82@gmail.com> * chore: remove rogue import --------- Co-authored-by: Bassel Kanso <basselkanso82@gmail.com>
This commit is contained in:
parent
5ff7024fc8
commit
e98c3e2020
@ -7,6 +7,7 @@ import { createContext } from '../components/Context';
|
||||
import { useTypedDispatch } from '../core/store/hooks';
|
||||
import { useStrapiApp } from '../features/StrapiApp';
|
||||
import { setLocale } from '../reducer';
|
||||
import { adminApi } from '../services/api';
|
||||
import {
|
||||
useGetMeQuery,
|
||||
useGetMyPermissionsQuery,
|
||||
@ -189,9 +190,10 @@ const AuthProvider = ({ children, _defaultPermissions = [] }: AuthProviderProps)
|
||||
|
||||
const logout = React.useCallback(async () => {
|
||||
await logoutMutation();
|
||||
dispatch(adminApi.util.resetApiState());
|
||||
clearStorage();
|
||||
navigate('/auth/login');
|
||||
}, [clearStorage, logoutMutation, navigate]);
|
||||
}, [clearStorage, dispatch, logoutMutation, navigate]);
|
||||
|
||||
const refetchPermissions = React.useCallback(async () => {
|
||||
if (!isUninitialized) {
|
||||
|
||||
@ -14,7 +14,7 @@ export function useContentTypes(): {
|
||||
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
|
||||
const { toggleNotification } = useNotification();
|
||||
|
||||
const { data = [], isLoading, error } = useGetContentTypesQuery();
|
||||
const { data, isLoading, error } = useGetContentTypesQuery();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (error) {
|
||||
@ -25,25 +25,9 @@ export function useContentTypes(): {
|
||||
}
|
||||
}, [error, formatAPIError, toggleNotification]);
|
||||
|
||||
// the return value needs to be memoized, because intantiating
|
||||
// an empty array as default value would lead to an unstable return
|
||||
// value, which later on triggers infinite loops if used in the
|
||||
// dependency arrays of other hooks
|
||||
const collectionTypes = React.useMemo(() => {
|
||||
return data.filter(
|
||||
(contentType) => contentType.kind === 'collectionType' && contentType.isDisplayed
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
const singleTypes = React.useMemo(() => {
|
||||
return data.filter(
|
||||
(contentType) => contentType.kind !== 'collectionType' && contentType.isDisplayed
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
collectionTypes,
|
||||
singleTypes,
|
||||
collectionTypes: data?.collectionType ?? [],
|
||||
singleTypes: data?.singleType ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
@ -52,7 +52,6 @@ export { useAdminUsers } from './services/users';
|
||||
export type { StrapiApp, MenuItem, InjectionZoneComponent } from './StrapiApp';
|
||||
export type { Store } from './core/store/configure';
|
||||
export type { Plugin, PluginConfig } from './core/apis/Plugin';
|
||||
export type { FetchOptions, FetchResponse, FetchConfig } from './utils/getFetchClient';
|
||||
export type {
|
||||
SanitizedAdminUser,
|
||||
AdminUser,
|
||||
@ -66,7 +65,9 @@ export type { RBACContext, RBACMiddleware } from './core/apis/rbac';
|
||||
* Utils
|
||||
*/
|
||||
export { translatedErrors } from './utils/translatedErrors';
|
||||
export { getFetchClient, isFetchError, FetchError } from './utils/getFetchClient';
|
||||
export * from './utils/getFetchClient';
|
||||
export * from './utils/baseQuery';
|
||||
export * from './services/api';
|
||||
|
||||
/**
|
||||
* Components
|
||||
|
||||
@ -9,7 +9,7 @@ import { Layouts } from '../../../../components/Layouts/Layout';
|
||||
import { BackButton } from '../../../../features/BackButton';
|
||||
import { useNotification } from '../../../../features/Notifications';
|
||||
import { useAPIErrorHandler } from '../../../../hooks/useAPIErrorHandler';
|
||||
import { useRegenerateTokenMutation } from '../../../../services/api';
|
||||
import { useRegenerateTokenMutation } from '../../../../services/transferTokens';
|
||||
|
||||
import type { Data } from '@strapi/types';
|
||||
|
||||
|
||||
@ -19,93 +19,97 @@ interface ConfigurationLogo {
|
||||
default: string;
|
||||
}
|
||||
|
||||
const admin = adminApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
init: builder.query<Init.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/init',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: Init.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
information: builder.query<Information.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/information',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: Information.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
telemetryProperties: builder.query<TelemetryProperties.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/telemetry-properties',
|
||||
method: 'GET',
|
||||
config: {
|
||||
validateStatus: (status) => status < 500,
|
||||
const admin = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['ProjectSettings', 'LicenseLimits'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
init: builder.query<Init.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/init',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: Init.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
transformResponse(res: TelemetryProperties.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
projectSettings: builder.query<
|
||||
{ authLogo?: ConfigurationLogo['custom']; menuLogo?: ConfigurationLogo['custom'] },
|
||||
void
|
||||
>({
|
||||
query: () => ({
|
||||
url: '/admin/project-settings',
|
||||
method: 'GET',
|
||||
information: builder.query<Information.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/information',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: Information.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
providesTags: ['ProjectSettings'],
|
||||
transformResponse(data: GetProjectSettings.Response) {
|
||||
return {
|
||||
authLogo: data.authLogo
|
||||
? {
|
||||
name: data.authLogo.name,
|
||||
url: prefixFileUrlWithBackendUrl(data.authLogo.url),
|
||||
}
|
||||
: undefined,
|
||||
menuLogo: data.menuLogo
|
||||
? {
|
||||
name: data.menuLogo.name,
|
||||
url: prefixFileUrlWithBackendUrl(data.menuLogo.url),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
}),
|
||||
updateProjectSettings: builder.mutation<UpdateProjectSettings.Response, FormData>({
|
||||
query: (data) => ({
|
||||
url: '/admin/project-settings',
|
||||
method: 'POST',
|
||||
data,
|
||||
config: {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
telemetryProperties: builder.query<TelemetryProperties.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/telemetry-properties',
|
||||
method: 'GET',
|
||||
config: {
|
||||
validateStatus: (status) => status < 500,
|
||||
},
|
||||
}),
|
||||
transformResponse(res: TelemetryProperties.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
invalidatesTags: ['ProjectSettings'],
|
||||
}),
|
||||
getPlugins: builder.query<Plugins.Response, void>({
|
||||
query: () => ({
|
||||
url: '/admin/plugins',
|
||||
method: 'GET',
|
||||
projectSettings: builder.query<
|
||||
{ authLogo?: ConfigurationLogo['custom']; menuLogo?: ConfigurationLogo['custom'] },
|
||||
void
|
||||
>({
|
||||
query: () => ({
|
||||
url: '/admin/project-settings',
|
||||
method: 'GET',
|
||||
}),
|
||||
providesTags: ['ProjectSettings'],
|
||||
transformResponse(data: GetProjectSettings.Response) {
|
||||
return {
|
||||
authLogo: data.authLogo
|
||||
? {
|
||||
name: data.authLogo.name,
|
||||
url: prefixFileUrlWithBackendUrl(data.authLogo.url),
|
||||
}
|
||||
: undefined,
|
||||
menuLogo: data.menuLogo
|
||||
? {
|
||||
name: data.menuLogo.name,
|
||||
url: prefixFileUrlWithBackendUrl(data.menuLogo.url),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
}),
|
||||
updateProjectSettings: builder.mutation<UpdateProjectSettings.Response, FormData>({
|
||||
query: (data) => ({
|
||||
url: '/admin/project-settings',
|
||||
method: 'POST',
|
||||
data,
|
||||
config: {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
},
|
||||
}),
|
||||
invalidatesTags: ['ProjectSettings'],
|
||||
}),
|
||||
getPlugins: builder.query<Plugins.Response, void>({
|
||||
query: () => ({
|
||||
url: '/admin/plugins',
|
||||
method: 'GET',
|
||||
}),
|
||||
}),
|
||||
getLicenseLimits: builder.query<GetLicenseLimitInformation.Response, void>({
|
||||
query: () => ({
|
||||
url: '/admin/license-limit-information',
|
||||
method: 'GET',
|
||||
}),
|
||||
providesTags: ['LicenseLimits'],
|
||||
}),
|
||||
}),
|
||||
getLicenseLimits: builder.query<GetLicenseLimitInformation.Response, void>({
|
||||
query: () => ({
|
||||
url: '/admin/license-limit-information',
|
||||
method: 'GET',
|
||||
}),
|
||||
providesTags: ['LicenseLimits'],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
const {
|
||||
useInitQuery,
|
||||
|
||||
@ -1,37 +1,21 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import { TokenRegenerate } from '../../../shared/contracts/transfer';
|
||||
import { fetchBaseQuery, type UnknownApiError } from '../utils/baseQuery';
|
||||
import { fetchBaseQuery } from '../utils/baseQuery';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @description This is the redux toolkit api for the admin panel, users
|
||||
* should use a combination of `enhanceEndpoints` to add their TagTypes
|
||||
* to utilise in their `injectEndpoints` construction for automatic cache
|
||||
* re-validation. We specifically do not store any tagTypes by default leaving
|
||||
* the API surface as small as possible. None of the data-fetching looks for the
|
||||
* StrapiApp are stored here either.
|
||||
*/
|
||||
const adminApi = createApi({
|
||||
reducerPath: 'adminApi',
|
||||
baseQuery: fetchBaseQuery(),
|
||||
tagTypes: [
|
||||
'ApiToken',
|
||||
'LicenseLimits',
|
||||
'Me',
|
||||
'ProjectSettings',
|
||||
'ProvidersOptions',
|
||||
'Role',
|
||||
'RolePermissions',
|
||||
'TransferToken',
|
||||
'User',
|
||||
'Webhook',
|
||||
],
|
||||
endpoints: (builder) => ({
|
||||
/**
|
||||
* This is here because it's shared between the transfer-token routes & the api-tokens.
|
||||
*/
|
||||
regenerateToken: builder.mutation<TokenRegenerate.Response['data'], string>({
|
||||
query: (url) => ({
|
||||
method: 'POST',
|
||||
url: `${url}/regenerate`,
|
||||
}),
|
||||
transformResponse: (response: TokenRegenerate.Response) => response.data,
|
||||
}),
|
||||
}),
|
||||
tagTypes: [],
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
|
||||
const { useRegenerateTokenMutation } = adminApi;
|
||||
|
||||
export { adminApi, type UnknownApiError, useRegenerateTokenMutation };
|
||||
export { adminApi };
|
||||
|
||||
@ -2,59 +2,62 @@ import * as ApiToken from '../../../shared/contracts/api-token';
|
||||
|
||||
import { adminApi } from './api';
|
||||
|
||||
const transferTokenService = adminApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getAPITokens: builder.query<ApiToken.List.Response['data'], void>({
|
||||
query: () => '/admin/api-tokens',
|
||||
transformResponse: (response: ApiToken.List.Response) => response.data,
|
||||
providesTags: (res, _err) => [
|
||||
...(res?.map(({ id }) => ({ type: 'ApiToken' as const, id })) ?? []),
|
||||
{ type: 'ApiToken' as const, id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
getAPIToken: builder.query<ApiToken.Get.Response['data'], ApiToken.Get.Params['id']>({
|
||||
query: (id) => `/admin/api-tokens/${id}`,
|
||||
transformResponse: (response: ApiToken.Get.Response) => response.data,
|
||||
providesTags: (res, _err, id) => [{ type: 'ApiToken' as const, id }],
|
||||
}),
|
||||
createAPIToken: builder.mutation<
|
||||
ApiToken.Create.Response['data'],
|
||||
ApiToken.Create.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/api-tokens',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
const apiTokensService = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['ApiToken'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getAPITokens: builder.query<ApiToken.List.Response['data'], void>({
|
||||
query: () => '/admin/api-tokens',
|
||||
transformResponse: (response: ApiToken.List.Response) => response.data,
|
||||
providesTags: (res, _err) => [
|
||||
...(res?.map(({ id }) => ({ type: 'ApiToken' as const, id })) ?? []),
|
||||
{ type: 'ApiToken' as const, id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
transformResponse: (response: ApiToken.Create.Response) => response.data,
|
||||
invalidatesTags: [{ type: 'ApiToken' as const, id: 'LIST' }],
|
||||
}),
|
||||
deleteAPIToken: builder.mutation<
|
||||
ApiToken.Revoke.Response['data'],
|
||||
ApiToken.Revoke.Params['id']
|
||||
>({
|
||||
query: (id) => ({
|
||||
url: `/admin/api-tokens/${id}`,
|
||||
method: 'DELETE',
|
||||
getAPIToken: builder.query<ApiToken.Get.Response['data'], ApiToken.Get.Params['id']>({
|
||||
query: (id) => `/admin/api-tokens/${id}`,
|
||||
transformResponse: (response: ApiToken.Get.Response) => response.data,
|
||||
providesTags: (res, _err, id) => [{ type: 'ApiToken' as const, id }],
|
||||
}),
|
||||
transformResponse: (response: ApiToken.Revoke.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, id) => [{ type: 'ApiToken' as const, id }],
|
||||
}),
|
||||
updateAPIToken: builder.mutation<
|
||||
ApiToken.Update.Response['data'],
|
||||
ApiToken.Update.Params & ApiToken.Update.Request['body']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/api-tokens/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
createAPIToken: builder.mutation<
|
||||
ApiToken.Create.Response['data'],
|
||||
ApiToken.Create.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/api-tokens',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: ApiToken.Create.Response) => response.data,
|
||||
invalidatesTags: [{ type: 'ApiToken' as const, id: 'LIST' }],
|
||||
}),
|
||||
deleteAPIToken: builder.mutation<
|
||||
ApiToken.Revoke.Response['data'],
|
||||
ApiToken.Revoke.Params['id']
|
||||
>({
|
||||
query: (id) => ({
|
||||
url: `/admin/api-tokens/${id}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
transformResponse: (response: ApiToken.Revoke.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, id) => [{ type: 'ApiToken' as const, id }],
|
||||
}),
|
||||
updateAPIToken: builder.mutation<
|
||||
ApiToken.Update.Response['data'],
|
||||
ApiToken.Update.Params & ApiToken.Update.Request['body']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/api-tokens/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: ApiToken.Update.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'ApiToken' as const, id }],
|
||||
}),
|
||||
transformResponse: (response: ApiToken.Update.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'ApiToken' as const, id }],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
});
|
||||
|
||||
const {
|
||||
useGetAPITokensQuery,
|
||||
@ -62,7 +65,7 @@ const {
|
||||
useCreateAPITokenMutation,
|
||||
useDeleteAPITokenMutation,
|
||||
useUpdateAPITokenMutation,
|
||||
} = transferTokenService;
|
||||
} = apiTokensService;
|
||||
|
||||
export {
|
||||
useGetAPITokensQuery,
|
||||
|
||||
@ -14,177 +14,187 @@ import { type GetOwnPermissions, type GetMe, type UpdateMe } from '../../../shar
|
||||
|
||||
import { adminApi } from './api';
|
||||
|
||||
const authService = adminApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
/**
|
||||
* ME
|
||||
*/
|
||||
getMe: builder.query<GetMe.Response['data'], void>({
|
||||
query: () => ({
|
||||
method: 'GET',
|
||||
url: '/admin/users/me',
|
||||
const authService = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['User', 'Me', 'ProvidersOptions'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
/**
|
||||
* ME
|
||||
*/
|
||||
getMe: builder.query<GetMe.Response['data'], void>({
|
||||
query: () => ({
|
||||
method: 'GET',
|
||||
url: '/admin/users/me',
|
||||
}),
|
||||
transformResponse(res: GetMe.Response) {
|
||||
return res.data;
|
||||
},
|
||||
providesTags: (res) => (res ? ['Me', { type: 'User', id: res.id }] : ['Me']),
|
||||
}),
|
||||
transformResponse(res: GetMe.Response) {
|
||||
return res.data;
|
||||
},
|
||||
providesTags: (res) => (res ? ['Me', { type: 'User', id: res.id }] : ['Me']),
|
||||
}),
|
||||
getMyPermissions: builder.query<GetOwnPermissions.Response['data'], void>({
|
||||
query: () => ({
|
||||
method: 'GET',
|
||||
url: '/admin/users/me/permissions',
|
||||
}),
|
||||
transformResponse(res: GetOwnPermissions.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
updateMe: builder.mutation<UpdateMe.Response['data'], UpdateMe.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'PUT',
|
||||
url: '/admin/users/me',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: UpdateMe.Response) {
|
||||
return res.data;
|
||||
},
|
||||
invalidatesTags: ['Me'],
|
||||
}),
|
||||
/**
|
||||
* Permissions
|
||||
*/
|
||||
checkPermissions: builder.query<Check.Response, Check.Request['body']>({
|
||||
query: (permissions) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/permissions/check',
|
||||
data: permissions,
|
||||
}),
|
||||
}),
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
login: builder.mutation<Login.Response['data'], Login.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/login',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: Login.Response) {
|
||||
return res.data;
|
||||
},
|
||||
invalidatesTags: ['Me'],
|
||||
}),
|
||||
logout: builder.mutation<void, void>({
|
||||
query: () => ({
|
||||
method: 'POST',
|
||||
url: '/admin/logout',
|
||||
}),
|
||||
}),
|
||||
resetPassword: builder.mutation<ResetPassword.Response['data'], ResetPassword.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/reset-password',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: ResetPassword.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
renewToken: builder.mutation<RenewToken.Response['data'], RenewToken.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/renew-token',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: RenewToken.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
getRegistrationInfo: builder.query<
|
||||
RegistrationInfo.Response['data'],
|
||||
RegistrationInfo.Request['query']['registrationToken']
|
||||
>({
|
||||
query: (registrationToken) => ({
|
||||
url: '/admin/registration-info',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params: {
|
||||
registrationToken,
|
||||
},
|
||||
getMyPermissions: builder.query<GetOwnPermissions.Response['data'], void>({
|
||||
query: () => ({
|
||||
method: 'GET',
|
||||
url: '/admin/users/me/permissions',
|
||||
}),
|
||||
transformResponse(res: GetOwnPermissions.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
transformResponse(res: RegistrationInfo.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
registerAdmin: builder.mutation<RegisterAdmin.Response['data'], RegisterAdmin.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/register-admin',
|
||||
data: body,
|
||||
updateMe: builder.mutation<UpdateMe.Response['data'], UpdateMe.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'PUT',
|
||||
url: '/admin/users/me',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: UpdateMe.Response) {
|
||||
return res.data;
|
||||
},
|
||||
invalidatesTags: ['Me'],
|
||||
}),
|
||||
transformResponse(res: RegisterAdmin.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
registerUser: builder.mutation<Register.Response['data'], Register.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/register',
|
||||
data: body,
|
||||
/**
|
||||
* Permissions
|
||||
*/
|
||||
checkPermissions: builder.query<Check.Response, Check.Request['body']>({
|
||||
query: (permissions) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/permissions/check',
|
||||
data: permissions,
|
||||
}),
|
||||
}),
|
||||
transformResponse(res: Register.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
forgotPassword: builder.mutation<ForgotPassword.Response, ForgotPassword.Request['body']>({
|
||||
query: (body) => ({
|
||||
url: '/admin/forgot-password',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
login: builder.mutation<Login.Response['data'], Login.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/login',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: Login.Response) {
|
||||
return res.data;
|
||||
},
|
||||
invalidatesTags: ['Me'],
|
||||
}),
|
||||
logout: builder.mutation<void, void>({
|
||||
query: () => ({
|
||||
method: 'POST',
|
||||
url: '/admin/logout',
|
||||
}),
|
||||
}),
|
||||
resetPassword: builder.mutation<
|
||||
ResetPassword.Response['data'],
|
||||
ResetPassword.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/reset-password',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: ResetPassword.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
renewToken: builder.mutation<RenewToken.Response['data'], RenewToken.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/renew-token',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: RenewToken.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
getRegistrationInfo: builder.query<
|
||||
RegistrationInfo.Response['data'],
|
||||
RegistrationInfo.Request['query']['registrationToken']
|
||||
>({
|
||||
query: (registrationToken) => ({
|
||||
url: '/admin/registration-info',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params: {
|
||||
registrationToken,
|
||||
},
|
||||
},
|
||||
}),
|
||||
transformResponse(res: RegistrationInfo.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
registerAdmin: builder.mutation<
|
||||
RegisterAdmin.Response['data'],
|
||||
RegisterAdmin.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/register-admin',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: RegisterAdmin.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
registerUser: builder.mutation<Register.Response['data'], Register.Request['body']>({
|
||||
query: (body) => ({
|
||||
method: 'POST',
|
||||
url: '/admin/register',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: Register.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
forgotPassword: builder.mutation<ForgotPassword.Response, ForgotPassword.Request['body']>({
|
||||
query: (body) => ({
|
||||
url: '/admin/forgot-password',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
}),
|
||||
isSSOLocked: builder.query<IsSSOLocked.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/providers/isSSOLocked',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: IsSSOLocked.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
getProviders: builder.query<GetProviders.Response, void>({
|
||||
query: () => ({
|
||||
url: '/admin/providers',
|
||||
method: 'GET',
|
||||
}),
|
||||
}),
|
||||
getProviderOptions: builder.query<ProvidersOptions.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/providers/options',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: ProvidersOptions.Response) {
|
||||
return res.data;
|
||||
},
|
||||
providesTags: ['ProvidersOptions'],
|
||||
}),
|
||||
updateProviderOptions: builder.mutation<
|
||||
ProvidersOptions.Response['data'],
|
||||
ProvidersOptions.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/providers/options',
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: ProvidersOptions.Response) {
|
||||
return res.data;
|
||||
},
|
||||
invalidatesTags: ['ProvidersOptions'],
|
||||
}),
|
||||
}),
|
||||
isSSOLocked: builder.query<IsSSOLocked.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/providers/isSSOLocked',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: IsSSOLocked.Response) {
|
||||
return res.data;
|
||||
},
|
||||
}),
|
||||
getProviders: builder.query<GetProviders.Response, void>({
|
||||
query: () => ({
|
||||
url: '/admin/providers',
|
||||
method: 'GET',
|
||||
}),
|
||||
}),
|
||||
getProviderOptions: builder.query<ProvidersOptions.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/providers/options',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse(res: ProvidersOptions.Response) {
|
||||
return res.data;
|
||||
},
|
||||
providesTags: ['ProvidersOptions'],
|
||||
}),
|
||||
updateProviderOptions: builder.mutation<
|
||||
ProvidersOptions.Response['data'],
|
||||
ProvidersOptions.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/providers/options',
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse(res: ProvidersOptions.Response) {
|
||||
return res.data;
|
||||
},
|
||||
invalidatesTags: ['ProvidersOptions'],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
const {
|
||||
useCheckPermissionsQuery,
|
||||
|
||||
@ -6,21 +6,38 @@
|
||||
import { adminApi } from './api';
|
||||
|
||||
import type { ContentType } from '../../../shared/contracts/content-types';
|
||||
interface ContentTypes {
|
||||
collectionType: ContentType[];
|
||||
singleType: ContentType[];
|
||||
}
|
||||
|
||||
const contentManager = adminApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
/**
|
||||
* Content Types
|
||||
*/
|
||||
getContentTypes: builder.query<ContentType[], void>({
|
||||
getContentTypes: builder.query<ContentTypes, void>({
|
||||
query: () => ({
|
||||
url: `/content-manager/content-types`,
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse: (res: { data: ContentType[] }) => res.data,
|
||||
transformResponse: (res: { data: ContentType[] }) => {
|
||||
return res.data.reduce<ContentTypes>(
|
||||
(acc, curr) => {
|
||||
if (curr.isDisplayed) {
|
||||
acc[curr.kind].push(curr);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
collectionType: [],
|
||||
singleType: [],
|
||||
}
|
||||
);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
overrideExisting: true,
|
||||
});
|
||||
|
||||
const { useGetContentTypesQuery } = contentManager;
|
||||
|
||||
@ -2,65 +2,76 @@ import * as TransferTokens from '../../../shared/contracts/transfer';
|
||||
|
||||
import { adminApi } from './api';
|
||||
|
||||
const transferTokenService = adminApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getTransferTokens: builder.query<TransferTokens.TokenList.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/transfer/tokens',
|
||||
method: 'GET',
|
||||
const transferTokenService = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['TransferToken'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
regenerateToken: builder.mutation<TransferTokens.TokenRegenerate.Response['data'], string>({
|
||||
query: (url) => ({
|
||||
method: 'POST',
|
||||
url: `${url}/regenerate`,
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenRegenerate.Response) => response.data,
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenList.Response) => response.data,
|
||||
providesTags: (res, _err) => [
|
||||
...(res?.map(({ id }) => ({ type: 'TransferToken' as const, id })) ?? []),
|
||||
{ type: 'TransferToken' as const, id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
getTransferToken: builder.query<
|
||||
TransferTokens.TokenGetById.Response['data'],
|
||||
TransferTokens.TokenGetById.Params['id']
|
||||
>({
|
||||
query: (id) => `/admin/transfer/tokens/${id}`,
|
||||
transformResponse: (response: TransferTokens.TokenGetById.Response) => response.data,
|
||||
providesTags: (res, _err, id) => [{ type: 'TransferToken' as const, id }],
|
||||
}),
|
||||
createTransferToken: builder.mutation<
|
||||
TransferTokens.TokenCreate.Response['data'],
|
||||
TransferTokens.TokenCreate.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/transfer/tokens',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
getTransferTokens: builder.query<TransferTokens.TokenList.Response['data'], void>({
|
||||
query: () => ({
|
||||
url: '/admin/transfer/tokens',
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenList.Response) => response.data,
|
||||
providesTags: (res, _err) => [
|
||||
...(res?.map(({ id }) => ({ type: 'TransferToken' as const, id })) ?? []),
|
||||
{ type: 'TransferToken' as const, id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenCreate.Response) => response.data,
|
||||
invalidatesTags: [{ type: 'TransferToken' as const, id: 'LIST' }],
|
||||
}),
|
||||
deleteTransferToken: builder.mutation<
|
||||
TransferTokens.TokenRevoke.Response['data'],
|
||||
TransferTokens.TokenRevoke.Params['id']
|
||||
>({
|
||||
query: (id) => ({
|
||||
url: `/admin/transfer/tokens/${id}`,
|
||||
method: 'DELETE',
|
||||
getTransferToken: builder.query<
|
||||
TransferTokens.TokenGetById.Response['data'],
|
||||
TransferTokens.TokenGetById.Params['id']
|
||||
>({
|
||||
query: (id) => `/admin/transfer/tokens/${id}`,
|
||||
transformResponse: (response: TransferTokens.TokenGetById.Response) => response.data,
|
||||
providesTags: (res, _err, id) => [{ type: 'TransferToken' as const, id }],
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenRevoke.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, id) => [{ type: 'TransferToken' as const, id }],
|
||||
}),
|
||||
updateTransferToken: builder.mutation<
|
||||
TransferTokens.TokenUpdate.Response['data'],
|
||||
TransferTokens.TokenUpdate.Params & TransferTokens.TokenUpdate.Request['body']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/transfer/tokens/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
createTransferToken: builder.mutation<
|
||||
TransferTokens.TokenCreate.Response['data'],
|
||||
TransferTokens.TokenCreate.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/transfer/tokens',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenCreate.Response) => response.data,
|
||||
invalidatesTags: [{ type: 'TransferToken' as const, id: 'LIST' }],
|
||||
}),
|
||||
deleteTransferToken: builder.mutation<
|
||||
TransferTokens.TokenRevoke.Response['data'],
|
||||
TransferTokens.TokenRevoke.Params['id']
|
||||
>({
|
||||
query: (id) => ({
|
||||
url: `/admin/transfer/tokens/${id}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenRevoke.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, id) => [{ type: 'TransferToken' as const, id }],
|
||||
}),
|
||||
updateTransferToken: builder.mutation<
|
||||
TransferTokens.TokenUpdate.Response['data'],
|
||||
TransferTokens.TokenUpdate.Params & TransferTokens.TokenUpdate.Request['body']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/transfer/tokens/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenUpdate.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'TransferToken' as const, id }],
|
||||
}),
|
||||
transformResponse: (response: TransferTokens.TokenUpdate.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'TransferToken' as const, id }],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
const {
|
||||
useGetTransferTokensQuery,
|
||||
@ -68,6 +79,7 @@ const {
|
||||
useCreateTransferTokenMutation,
|
||||
useDeleteTransferTokenMutation,
|
||||
useUpdateTransferTokenMutation,
|
||||
useRegenerateTokenMutation,
|
||||
} = transferTokenService;
|
||||
|
||||
export {
|
||||
@ -76,4 +88,5 @@ export {
|
||||
useCreateTransferTokenMutation,
|
||||
useDeleteTransferTokenMutation,
|
||||
useUpdateTransferTokenMutation,
|
||||
useRegenerateTokenMutation,
|
||||
};
|
||||
|
||||
@ -6,190 +6,194 @@ import { adminApi } from './api';
|
||||
|
||||
import type { Data } from '@strapi/types';
|
||||
|
||||
const usersService = adminApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
/**
|
||||
* users
|
||||
*/
|
||||
createUser: builder.mutation<Users.Create.Response['data'], Users.Create.Request['body']>({
|
||||
query: (body) => ({
|
||||
url: '/admin/users',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
const usersService = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['LicenseLimits', 'User', 'Role', 'RolePermissions'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
/**
|
||||
* users
|
||||
*/
|
||||
createUser: builder.mutation<Users.Create.Response['data'], Users.Create.Request['body']>({
|
||||
query: (body) => ({
|
||||
url: '/admin/users',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: Users.Create.Response) => response.data,
|
||||
invalidatesTags: ['LicenseLimits', { type: 'User', id: 'LIST' }],
|
||||
}),
|
||||
transformResponse: (response: Users.Create.Response) => response.data,
|
||||
invalidatesTags: ['LicenseLimits', { type: 'User', id: 'LIST' }],
|
||||
}),
|
||||
updateUser: builder.mutation<
|
||||
Users.Update.Response['data'],
|
||||
Omit<Users.Update.Request['body'] & Users.Update.Params, 'blocked'>
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/users/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
updateUser: builder.mutation<
|
||||
Users.Update.Response['data'],
|
||||
Omit<Users.Update.Request['body'] & Users.Update.Params, 'blocked'>
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/users/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
invalidatesTags: (_res, _err, { id }) => [
|
||||
{ type: 'User', id },
|
||||
{ type: 'User', id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
invalidatesTags: (_res, _err, { id }) => [
|
||||
{ type: 'User', id },
|
||||
{ type: 'User', id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
getUsers: builder.query<
|
||||
{
|
||||
users: Users.FindAll.Response['data']['results'];
|
||||
pagination: Users.FindAll.Response['data']['pagination'] | null;
|
||||
},
|
||||
GetUsersParams
|
||||
>({
|
||||
query: ({ id, ...params } = {}) => ({
|
||||
url: `/admin/users/${id ?? ''}`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
getUsers: builder.query<
|
||||
{
|
||||
users: Users.FindAll.Response['data']['results'];
|
||||
pagination: Users.FindAll.Response['data']['pagination'] | null;
|
||||
},
|
||||
}),
|
||||
transformResponse: (res: Users.FindAll.Response | Users.FindOne.Response) => {
|
||||
let users: Users.FindAll.Response['data']['results'] = [];
|
||||
GetUsersParams
|
||||
>({
|
||||
query: ({ id, ...params } = {}) => ({
|
||||
url: `/admin/users/${id ?? ''}`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
}),
|
||||
transformResponse: (res: Users.FindAll.Response | Users.FindOne.Response) => {
|
||||
let users: Users.FindAll.Response['data']['results'] = [];
|
||||
|
||||
if (res.data) {
|
||||
if ('results' in res.data) {
|
||||
if (Array.isArray(res.data.results)) {
|
||||
users = res.data.results;
|
||||
if (res.data) {
|
||||
if ('results' in res.data) {
|
||||
if (Array.isArray(res.data.results)) {
|
||||
users = res.data.results;
|
||||
}
|
||||
} else {
|
||||
users = [res.data];
|
||||
}
|
||||
} else {
|
||||
users = [res.data];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
users,
|
||||
pagination: 'pagination' in res.data ? res.data.pagination : null,
|
||||
};
|
||||
},
|
||||
providesTags: (res, _err, arg) => {
|
||||
if (typeof arg === 'object' && 'id' in arg) {
|
||||
return [{ type: 'User' as const, id: arg.id }];
|
||||
} else {
|
||||
return [
|
||||
...(res?.users.map(({ id }) => ({ type: 'User' as const, id })) ?? []),
|
||||
{ type: 'User' as const, id: 'LIST' },
|
||||
];
|
||||
}
|
||||
},
|
||||
}),
|
||||
deleteManyUsers: builder.mutation<
|
||||
Users.DeleteMany.Response['data'],
|
||||
Users.DeleteMany.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/users/batch-delete',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (res: Users.DeleteMany.Response) => res.data,
|
||||
invalidatesTags: ['LicenseLimits', { type: 'User', id: 'LIST' }],
|
||||
}),
|
||||
/**
|
||||
* roles
|
||||
*/
|
||||
createRole: builder.mutation<Roles.Create.Response['data'], Roles.Create.Request['body']>({
|
||||
query: (body) => ({
|
||||
url: '/admin/roles',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (res: Roles.Create.Response) => res.data,
|
||||
invalidatesTags: [{ type: 'Role', id: 'LIST' }],
|
||||
}),
|
||||
getRoles: builder.query<Roles.FindRoles.Response['data'], GetRolesParams | void>({
|
||||
query: ({ id, ...params } = {}) => ({
|
||||
url: `/admin/roles/${id ?? ''}`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
return {
|
||||
users,
|
||||
pagination: 'pagination' in res.data ? res.data.pagination : null,
|
||||
};
|
||||
},
|
||||
}),
|
||||
transformResponse: (res: Roles.FindRole.Response | Roles.FindRoles.Response) => {
|
||||
let roles: Roles.FindRoles.Response['data'] = [];
|
||||
|
||||
if (res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
roles = res.data;
|
||||
providesTags: (res, _err, arg) => {
|
||||
if (typeof arg === 'object' && 'id' in arg) {
|
||||
return [{ type: 'User' as const, id: arg.id }];
|
||||
} else {
|
||||
roles = [res.data];
|
||||
return [
|
||||
...(res?.users.map(({ id }) => ({ type: 'User' as const, id })) ?? []),
|
||||
{ type: 'User' as const, id: 'LIST' },
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
deleteManyUsers: builder.mutation<
|
||||
Users.DeleteMany.Response['data'],
|
||||
Users.DeleteMany.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: '/admin/users/batch-delete',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (res: Users.DeleteMany.Response) => res.data,
|
||||
invalidatesTags: ['LicenseLimits', { type: 'User', id: 'LIST' }],
|
||||
}),
|
||||
/**
|
||||
* roles
|
||||
*/
|
||||
createRole: builder.mutation<Roles.Create.Response['data'], Roles.Create.Request['body']>({
|
||||
query: (body) => ({
|
||||
url: '/admin/roles',
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (res: Roles.Create.Response) => res.data,
|
||||
invalidatesTags: [{ type: 'Role', id: 'LIST' }],
|
||||
}),
|
||||
getRoles: builder.query<Roles.FindRoles.Response['data'], GetRolesParams | void>({
|
||||
query: ({ id, ...params } = {}) => ({
|
||||
url: `/admin/roles/${id ?? ''}`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
}),
|
||||
transformResponse: (res: Roles.FindRole.Response | Roles.FindRoles.Response) => {
|
||||
let roles: Roles.FindRoles.Response['data'] = [];
|
||||
|
||||
return roles;
|
||||
},
|
||||
providesTags: (res, _err, arg) => {
|
||||
if (typeof arg === 'object' && 'id' in arg) {
|
||||
return [{ type: 'Role' as const, id: arg.id }];
|
||||
} else {
|
||||
return [
|
||||
...(res?.map(({ id }) => ({ type: 'Role' as const, id })) ?? []),
|
||||
{ type: 'Role' as const, id: 'LIST' },
|
||||
];
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateRole: builder.mutation<
|
||||
Roles.Update.Response['data'],
|
||||
Roles.Update.Request['body'] & Roles.Update.Request['params']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/roles/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (res: Roles.Create.Response) => res.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'Role' as const, id }],
|
||||
}),
|
||||
getRolePermissions: builder.query<
|
||||
Roles.GetPermissions.Response['data'],
|
||||
GetRolePermissionsParams
|
||||
>({
|
||||
query: ({ id, ...params }) => ({
|
||||
url: `/admin/roles/${id}/permissions`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
if (res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
roles = res.data;
|
||||
} else {
|
||||
roles = [res.data];
|
||||
}
|
||||
}
|
||||
|
||||
return roles;
|
||||
},
|
||||
providesTags: (res, _err, arg) => {
|
||||
if (typeof arg === 'object' && 'id' in arg) {
|
||||
return [{ type: 'Role' as const, id: arg.id }];
|
||||
} else {
|
||||
return [
|
||||
...(res?.map(({ id }) => ({ type: 'Role' as const, id })) ?? []),
|
||||
{ type: 'Role' as const, id: 'LIST' },
|
||||
];
|
||||
}
|
||||
},
|
||||
}),
|
||||
transformResponse: (res: Roles.GetPermissions.Response) => res.data,
|
||||
providesTags: (_res, _err, { id }) => [{ type: 'RolePermissions' as const, id }],
|
||||
}),
|
||||
updateRolePermissions: builder.mutation<
|
||||
Roles.UpdatePermissions.Response['data'],
|
||||
Roles.UpdatePermissions.Request['body'] & Roles.UpdatePermissions.Request['params']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/roles/${id}/permissions`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
updateRole: builder.mutation<
|
||||
Roles.Update.Response['data'],
|
||||
Roles.Update.Request['body'] & Roles.Update.Request['params']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/roles/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (res: Roles.Create.Response) => res.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'Role' as const, id }],
|
||||
}),
|
||||
transformResponse: (res: Roles.UpdatePermissions.Response) => res.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'RolePermissions' as const, id }],
|
||||
}),
|
||||
/**
|
||||
* Permissions
|
||||
*/
|
||||
getRolePermissionLayout: builder.query<
|
||||
Permissions.GetAll.Response['data'],
|
||||
Permissions.GetAll.Request['params']
|
||||
>({
|
||||
query: (params) => ({
|
||||
url: '/admin/permissions',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
getRolePermissions: builder.query<
|
||||
Roles.GetPermissions.Response['data'],
|
||||
GetRolePermissionsParams
|
||||
>({
|
||||
query: ({ id, ...params }) => ({
|
||||
url: `/admin/roles/${id}/permissions`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
}),
|
||||
transformResponse: (res: Roles.GetPermissions.Response) => res.data,
|
||||
providesTags: (_res, _err, { id }) => [{ type: 'RolePermissions' as const, id }],
|
||||
}),
|
||||
updateRolePermissions: builder.mutation<
|
||||
Roles.UpdatePermissions.Response['data'],
|
||||
Roles.UpdatePermissions.Request['body'] & Roles.UpdatePermissions.Request['params']
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/roles/${id}/permissions`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (res: Roles.UpdatePermissions.Response) => res.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'RolePermissions' as const, id }],
|
||||
}),
|
||||
/**
|
||||
* Permissions
|
||||
*/
|
||||
getRolePermissionLayout: builder.query<
|
||||
Permissions.GetAll.Response['data'],
|
||||
Permissions.GetAll.Request['params']
|
||||
>({
|
||||
query: (params) => ({
|
||||
url: '/admin/permissions',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
}),
|
||||
transformResponse: (res: Permissions.GetAll.Response) => res.data,
|
||||
}),
|
||||
transformResponse: (res: Permissions.GetAll.Response) => res.data,
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
type GetUsersParams =
|
||||
| Users.FindOne.Params
|
||||
|
||||
@ -2,85 +2,89 @@ import * as Webhooks from '../../../shared/contracts/webhooks';
|
||||
|
||||
import { adminApi } from './api';
|
||||
|
||||
const webhooksSerivce = adminApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getWebhooks: builder.query<
|
||||
Webhooks.GetWebhooks.Response['data'],
|
||||
Webhooks.GetWebhook.Params | void
|
||||
>({
|
||||
query: (args) => ({
|
||||
url: `/admin/webhooks/${args?.id ?? ''}`,
|
||||
method: 'GET',
|
||||
const webhooksSerivce = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['Webhook'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getWebhooks: builder.query<
|
||||
Webhooks.GetWebhooks.Response['data'],
|
||||
Webhooks.GetWebhook.Params | void
|
||||
>({
|
||||
query: (args) => ({
|
||||
url: `/admin/webhooks/${args?.id ?? ''}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
transformResponse: (
|
||||
response: Webhooks.GetWebhooks.Response | Webhooks.GetWebhook.Response
|
||||
) => {
|
||||
if (Array.isArray(response.data)) {
|
||||
return response.data;
|
||||
} else {
|
||||
return [response.data];
|
||||
}
|
||||
},
|
||||
providesTags: (res, _err, arg) => {
|
||||
if (typeof arg === 'object' && 'id' in arg) {
|
||||
return [{ type: 'Webhook' as const, id: arg.id }];
|
||||
} else {
|
||||
return [
|
||||
...(res?.map(({ id }) => ({ type: 'Webhook' as const, id })) ?? []),
|
||||
{ type: 'Webhook' as const, id: 'LIST' },
|
||||
];
|
||||
}
|
||||
},
|
||||
}),
|
||||
transformResponse: (
|
||||
response: Webhooks.GetWebhooks.Response | Webhooks.GetWebhook.Response
|
||||
) => {
|
||||
if (Array.isArray(response.data)) {
|
||||
return response.data;
|
||||
} else {
|
||||
return [response.data];
|
||||
}
|
||||
},
|
||||
providesTags: (res, _err, arg) => {
|
||||
if (typeof arg === 'object' && 'id' in arg) {
|
||||
return [{ type: 'Webhook' as const, id: arg.id }];
|
||||
} else {
|
||||
return [
|
||||
...(res?.map(({ id }) => ({ type: 'Webhook' as const, id })) ?? []),
|
||||
{ type: 'Webhook' as const, id: 'LIST' },
|
||||
];
|
||||
}
|
||||
},
|
||||
}),
|
||||
createWebhook: builder.mutation<
|
||||
Webhooks.CreateWebhook.Response['data'],
|
||||
Omit<Webhooks.CreateWebhook.Request['body'], 'id' | 'isEnabled'>
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: `/admin/webhooks`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
createWebhook: builder.mutation<
|
||||
Webhooks.CreateWebhook.Response['data'],
|
||||
Omit<Webhooks.CreateWebhook.Request['body'], 'id' | 'isEnabled'>
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: `/admin/webhooks`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: Webhooks.CreateWebhook.Response) => response.data,
|
||||
invalidatesTags: [{ type: 'Webhook', id: 'LIST' }],
|
||||
}),
|
||||
transformResponse: (response: Webhooks.CreateWebhook.Response) => response.data,
|
||||
invalidatesTags: [{ type: 'Webhook', id: 'LIST' }],
|
||||
}),
|
||||
updateWebhook: builder.mutation<
|
||||
Webhooks.UpdateWebhook.Response['data'],
|
||||
Webhooks.UpdateWebhook.Request['body'] & Webhooks.UpdateWebhook.Params
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/webhooks/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
updateWebhook: builder.mutation<
|
||||
Webhooks.UpdateWebhook.Response['data'],
|
||||
Webhooks.UpdateWebhook.Request['body'] & Webhooks.UpdateWebhook.Params
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/webhooks/${id}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: Webhooks.UpdateWebhook.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'Webhook', id }],
|
||||
}),
|
||||
transformResponse: (response: Webhooks.UpdateWebhook.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { id }) => [{ type: 'Webhook', id }],
|
||||
}),
|
||||
triggerWebhook: builder.mutation<
|
||||
Webhooks.TriggerWebhook.Response['data'],
|
||||
Webhooks.TriggerWebhook.Params['id']
|
||||
>({
|
||||
query: (webhookId) => ({
|
||||
url: `/admin/webhooks/${webhookId}/trigger`,
|
||||
method: 'POST',
|
||||
triggerWebhook: builder.mutation<
|
||||
Webhooks.TriggerWebhook.Response['data'],
|
||||
Webhooks.TriggerWebhook.Params['id']
|
||||
>({
|
||||
query: (webhookId) => ({
|
||||
url: `/admin/webhooks/${webhookId}/trigger`,
|
||||
method: 'POST',
|
||||
}),
|
||||
transformResponse: (response: Webhooks.TriggerWebhook.Response) => response.data,
|
||||
}),
|
||||
transformResponse: (response: Webhooks.TriggerWebhook.Response) => response.data,
|
||||
}),
|
||||
deleteManyWebhooks: builder.mutation<
|
||||
Webhooks.DeleteWebhooks.Response['data'],
|
||||
Webhooks.DeleteWebhooks.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: `/admin/webhooks/batch-delete`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
deleteManyWebhooks: builder.mutation<
|
||||
Webhooks.DeleteWebhooks.Response['data'],
|
||||
Webhooks.DeleteWebhooks.Request['body']
|
||||
>({
|
||||
query: (body) => ({
|
||||
url: `/admin/webhooks/batch-delete`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
}),
|
||||
transformResponse: (response: Webhooks.DeleteWebhooks.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { ids }) => ids.map((id) => ({ type: 'Webhook', id })),
|
||||
}),
|
||||
transformResponse: (response: Webhooks.DeleteWebhooks.Response) => response.data,
|
||||
invalidatesTags: (_res, _err, { ids }) => ids.map((id) => ({ type: 'Webhook', id })),
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
const {
|
||||
useGetWebhooksQuery,
|
||||
|
||||
@ -1,30 +1,25 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { BaseQueryFn } from '@reduxjs/toolkit/query';
|
||||
|
||||
import {
|
||||
getFetchClient,
|
||||
isFetchError,
|
||||
type FetchOptions,
|
||||
type Method,
|
||||
} from '../utils/getFetchClient';
|
||||
import { getFetchClient, isFetchError, type FetchOptions } from '../utils/getFetchClient';
|
||||
|
||||
import type { ApiError } from '../hooks/useAPIErrorHandler';
|
||||
|
||||
export interface QueryArguments {
|
||||
interface QueryArguments {
|
||||
url: string;
|
||||
method?: Method;
|
||||
method?: 'GET' | 'POST' | 'DELETE' | 'PUT';
|
||||
data?: unknown;
|
||||
config?: FetchOptions;
|
||||
}
|
||||
|
||||
export interface UnknownApiError {
|
||||
interface UnknownApiError {
|
||||
name: 'UnknownError';
|
||||
message: string;
|
||||
details?: unknown;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export type BaseQueryError = ApiError | UnknownApiError;
|
||||
type BaseQueryError = ApiError | UnknownApiError;
|
||||
|
||||
const fetchBaseQuery =
|
||||
(): BaseQueryFn<string | QueryArguments, unknown, BaseQueryError> =>
|
||||
@ -114,3 +109,4 @@ const isBaseQueryError = (error: BaseQueryError | SerializedError): error is Bas
|
||||
};
|
||||
|
||||
export { fetchBaseQuery, isBaseQueryError };
|
||||
export type { BaseQueryError, UnknownApiError, QueryArguments };
|
||||
|
||||
@ -8,35 +8,30 @@ const STORAGE_KEYS = {
|
||||
USER: 'userInfo',
|
||||
};
|
||||
|
||||
type FetchParams = Parameters<typeof fetch>;
|
||||
type FetchURL = FetchParams[0];
|
||||
|
||||
export type Method = 'GET' | 'POST' | 'DELETE' | 'PUT';
|
||||
|
||||
export type FetchResponse<TData = any> = {
|
||||
type FetchResponse<TData = any> = {
|
||||
data: TData;
|
||||
status?: number;
|
||||
};
|
||||
|
||||
export type FetchOptions = {
|
||||
type FetchOptions = {
|
||||
params?: any;
|
||||
signal?: AbortSignal;
|
||||
headers?: Record<string, string>;
|
||||
validateStatus?: ((status: number) => boolean) | null;
|
||||
};
|
||||
|
||||
export type FetchConfig = {
|
||||
type FetchConfig = {
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
type ErrorResponse = {
|
||||
interface ErrorResponse {
|
||||
data: {
|
||||
data?: any;
|
||||
error: ApiError & { status?: number };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export class FetchError extends Error {
|
||||
class FetchError extends Error {
|
||||
public name: string;
|
||||
public message: string;
|
||||
public response?: ErrorResponse;
|
||||
@ -58,7 +53,7 @@ export class FetchError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export const isFetchError = (error: unknown): error is FetchError => {
|
||||
const isFetchError = (error: unknown): error is FetchError => {
|
||||
return error instanceof FetchError;
|
||||
};
|
||||
|
||||
@ -161,7 +156,7 @@ const getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {
|
||||
return url;
|
||||
};
|
||||
|
||||
const addBaseUrl = (url: FetchURL) => {
|
||||
const addBaseUrl = (url: Parameters<typeof fetch>[0]) => {
|
||||
return `${backendURL}${url}`;
|
||||
};
|
||||
|
||||
@ -190,6 +185,7 @@ const getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {
|
||||
method: 'GET',
|
||||
headers,
|
||||
});
|
||||
|
||||
return responseInterceptor<TData>(response, options?.validateStatus);
|
||||
},
|
||||
post: async <TData, TSend = any>(
|
||||
@ -268,4 +264,5 @@ const getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {
|
||||
return fetchClient;
|
||||
};
|
||||
|
||||
export { getFetchClient };
|
||||
export { getFetchClient, isFetchError, FetchError };
|
||||
export type { FetchOptions, FetchResponse, FetchConfig, FetchClient, ErrorResponse };
|
||||
|
||||
@ -89,10 +89,10 @@ const Providers = ({ children, initialEntries, storeConfig, permissions = [] }:
|
||||
},
|
||||
});
|
||||
|
||||
const store = configureStore(
|
||||
// @ts-expect-error – we've not filled up the entire initial state.
|
||||
storeConfig ?? defaultTestStoreConfig
|
||||
);
|
||||
const store = configureStore({
|
||||
...defaultTestStoreConfig,
|
||||
...storeConfig,
|
||||
});
|
||||
|
||||
const allPermissions =
|
||||
typeof permissions === 'function'
|
||||
|
||||
@ -12,12 +12,9 @@ export default {
|
||||
const cm = new ContentManagerPlugin();
|
||||
|
||||
app.addReducers({
|
||||
[contentManagerApi.reducerPath]: contentManagerApi.reducer,
|
||||
[PLUGIN_ID]: reducer,
|
||||
});
|
||||
|
||||
app.addMiddlewares([() => contentManagerApi.middleware]);
|
||||
|
||||
app.addMenuLink({
|
||||
to: PLUGIN_ID,
|
||||
icon: Feather,
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import { adminApi } from '@strapi/admin/strapi-admin';
|
||||
|
||||
import { fetchBaseQuery, type UnknownApiError } from '../utils/api';
|
||||
|
||||
const contentManagerApi = createApi({
|
||||
reducerPath: 'contentManagerApi',
|
||||
baseQuery: fetchBaseQuery(),
|
||||
tagTypes: [
|
||||
const contentManagerApi = adminApi.enhanceEndpoints({
|
||||
addTagTypes: [
|
||||
'ComponentConfiguration',
|
||||
'ContentTypesConfiguration',
|
||||
'ContentTypeSettings',
|
||||
@ -14,7 +10,6 @@ const contentManagerApi = createApi({
|
||||
'HistoryVersion',
|
||||
'Relations',
|
||||
],
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
|
||||
export { contentManagerApi, type UnknownApiError };
|
||||
export { contentManagerApi };
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { BaseQueryFn } from '@reduxjs/toolkit/query';
|
||||
import {
|
||||
getFetchClient,
|
||||
ApiError,
|
||||
isFetchError,
|
||||
type FetchOptions,
|
||||
} from '@strapi/admin/strapi-admin';
|
||||
import { ApiError, type UnknownApiError } from '@strapi/admin/strapi-admin';
|
||||
|
||||
interface Query {
|
||||
plugins?: Record<string, unknown>;
|
||||
@ -47,117 +41,11 @@ const buildValidParams = <TQuery extends Query>(query: TQuery): TransformedQuery
|
||||
return validQueryParams;
|
||||
};
|
||||
|
||||
export interface QueryArguments {
|
||||
url: string;
|
||||
method?: string;
|
||||
data?: unknown;
|
||||
config?: FetchOptions;
|
||||
}
|
||||
|
||||
export interface UnknownApiError {
|
||||
name: 'UnknownError';
|
||||
message: string;
|
||||
details?: unknown;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export type BaseQueryError = ApiError | UnknownApiError;
|
||||
|
||||
const fetchBaseQuery =
|
||||
(): BaseQueryFn<string | QueryArguments, unknown, BaseQueryError> =>
|
||||
async (query, { signal }) => {
|
||||
try {
|
||||
const { get, post, del, put } = getFetchClient();
|
||||
|
||||
if (typeof query === 'string') {
|
||||
const result = await get(query, {
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
} else {
|
||||
const { url, method = 'GET', data, config } = query;
|
||||
|
||||
if (method === 'POST') {
|
||||
const result = await post(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'DELETE') {
|
||||
const result = await del(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'PUT') {
|
||||
const result = await put(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Default is GET.
|
||||
*/
|
||||
const result = await get(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
} catch (err) {
|
||||
/**
|
||||
* Handle error of type FetchError
|
||||
*
|
||||
* This format mimics what we want from an FetchError 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.
|
||||
*/
|
||||
|
||||
if (isFetchError(err)) {
|
||||
if (
|
||||
typeof err.response?.data === 'object' &&
|
||||
err.response?.data !== null &&
|
||||
'error' in err.response?.data
|
||||
) {
|
||||
/**
|
||||
* This will most likely be ApiError
|
||||
*/
|
||||
return { data: undefined, error: err.response?.data.error as any };
|
||||
} else {
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: 'UnknownError',
|
||||
message: 'There was an unknown error response from the API',
|
||||
details: err.response,
|
||||
status: err.status,
|
||||
} as UnknownApiError,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const error = err as Error;
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
} satisfies SerializedError,
|
||||
};
|
||||
}
|
||||
};
|
||||
type BaseQueryError = ApiError | UnknownApiError;
|
||||
|
||||
const isBaseQueryError = (error: BaseQueryError | SerializedError): error is BaseQueryError => {
|
||||
return error.name !== undefined;
|
||||
};
|
||||
|
||||
export { fetchBaseQuery, isBaseQueryError, buildValidParams };
|
||||
export { isBaseQueryError, buildValidParams };
|
||||
export type { BaseQueryError, UnknownApiError };
|
||||
|
||||
@ -15,18 +15,15 @@ import {
|
||||
} from '@strapi/admin/strapi-admin/test';
|
||||
|
||||
import { reducer } from '../src/modules/reducers';
|
||||
import { contentManagerApi } from '../src/services/api';
|
||||
|
||||
const storeConfig: ConfigureStoreOptions = {
|
||||
preloadedState: defaultTestStoreConfig.preloadedState,
|
||||
reducer: {
|
||||
...defaultTestStoreConfig.reducer,
|
||||
[contentManagerApi.reducerPath]: contentManagerApi.reducer,
|
||||
'content-manager': reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...defaultTestStoreConfig.middleware(getDefaultMiddleware),
|
||||
contentManagerApi.middleware,
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import { ReleaseAction } from './components/ReleaseAction';
|
||||
// import { addColumnToTableHook } from './components/ReleaseListCell';
|
||||
import { PERMISSIONS } from './constants';
|
||||
import { pluginId } from './pluginId';
|
||||
import { releaseApi } from './services/release';
|
||||
import { prefixPluginTranslations } from './utils/prefixPluginTranslations';
|
||||
|
||||
import type { StrapiApp } from '@strapi/admin/strapi-admin';
|
||||
@ -35,17 +34,6 @@ const admin: Plugin.Config.AdminInput = {
|
||||
position: 2,
|
||||
});
|
||||
|
||||
/**
|
||||
* For some reason every middleware you pass has to a function
|
||||
* that returns the actual middleware. It's annoying but no one knows why....
|
||||
*/
|
||||
// @ts-expect-error – this API needs to be typed better.
|
||||
app.addMiddlewares([() => releaseApi.middleware]);
|
||||
|
||||
app.addReducers({
|
||||
[releaseApi.reducerPath]: releaseApi.reducer,
|
||||
});
|
||||
|
||||
// Insert the Releases container in the 'right-links' zone of the Content Manager's edit view
|
||||
app.getPlugin('content-manager').injectComponent('editView', 'right-links', {
|
||||
name: `${pluginId}-link`,
|
||||
|
||||
@ -55,6 +55,7 @@ import {
|
||||
releaseApi,
|
||||
} from '../services/release';
|
||||
import { useTypedDispatch } from '../store/hooks';
|
||||
import { isBaseQueryError } from '../utils/api';
|
||||
import { getTimezoneOffset } from '../utils/time';
|
||||
|
||||
import { getBadgeProps } from './ReleasesPage';
|
||||
@ -216,7 +217,6 @@ const ReleaseDetailsLayout = ({
|
||||
const {
|
||||
data,
|
||||
isLoading: isLoadingDetails,
|
||||
isError,
|
||||
error,
|
||||
} = useGetReleaseQuery(
|
||||
{ id: releaseId! },
|
||||
@ -301,13 +301,14 @@ const ReleaseDetailsLayout = ({
|
||||
return <Page.Loading />;
|
||||
}
|
||||
|
||||
if (isError || !release) {
|
||||
if ((isBaseQueryError(error) && 'code' in error) || !release) {
|
||||
return (
|
||||
<Navigate
|
||||
to=".."
|
||||
state={{
|
||||
errors: [
|
||||
{
|
||||
// @ts-expect-error – TODO: fix this weird error flow
|
||||
code: error?.code,
|
||||
},
|
||||
],
|
||||
@ -516,7 +517,6 @@ const ReleaseDetailsBody = ({ releaseId }: ReleaseDetailsBodyProps) => {
|
||||
const {
|
||||
data: releaseData,
|
||||
isLoading: isReleaseLoading,
|
||||
isError: isReleaseError,
|
||||
error: releaseError,
|
||||
} = useGetReleaseQuery({ id: releaseId });
|
||||
const {
|
||||
@ -598,14 +598,14 @@ const ReleaseDetailsBody = ({ releaseId }: ReleaseDetailsBodyProps) => {
|
||||
const contentTypes = releaseMeta?.contentTypes || {};
|
||||
const components = releaseMeta?.components || {};
|
||||
|
||||
if (isReleaseError || !release) {
|
||||
if (isBaseQueryError(releaseError) || !release) {
|
||||
const errorsArray = [];
|
||||
if (releaseError) {
|
||||
if (releaseError && 'code' in releaseError) {
|
||||
errorsArray.push({
|
||||
code: releaseError.code,
|
||||
});
|
||||
}
|
||||
if (releaseActionsError) {
|
||||
if (releaseActionsError && 'code' in releaseActionsError) {
|
||||
errorsArray.push({
|
||||
code: releaseActionsError.code,
|
||||
});
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
import { getFetchClient, type FetchOptions, type FetchError } from '@strapi/admin/strapi-admin';
|
||||
|
||||
export interface QueryArguments<TSend> {
|
||||
url: string;
|
||||
method: 'PUT' | 'GET' | 'POST' | 'DELETE';
|
||||
data?: TSend;
|
||||
config?: FetchOptions;
|
||||
}
|
||||
|
||||
const fetchBaseQuery = async <TData = unknown, TSend = unknown>({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
config,
|
||||
}: QueryArguments<TSend>) => {
|
||||
try {
|
||||
const { get, post, del, put } = getFetchClient();
|
||||
|
||||
if (method === 'POST') {
|
||||
const result = await post<TData, TSend>(url, data, config);
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'DELETE') {
|
||||
const result = await del<TData>(url, config);
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'PUT') {
|
||||
const result = await put<TData>(url, data, config);
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Default is GET.
|
||||
*/
|
||||
const result = await get<TData>(url, config);
|
||||
|
||||
return { data: result.data };
|
||||
} catch (error) {
|
||||
const err = error as FetchError;
|
||||
/**
|
||||
* Handle error of type FetchError
|
||||
*
|
||||
* This format mimics what we want from an FetchError 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.status,
|
||||
code: err.code,
|
||||
response: {
|
||||
data: err.response?.data,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { fetchBaseQuery };
|
||||
@ -1,13 +1,10 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import { adminApi } from '@strapi/admin/strapi-admin';
|
||||
|
||||
import {
|
||||
CreateReleaseAction,
|
||||
CreateManyReleaseActions,
|
||||
DeleteReleaseAction,
|
||||
} from '../../../shared/contracts/release-actions';
|
||||
import { pluginId } from '../pluginId';
|
||||
|
||||
import { fetchBaseQuery } from './baseQuery';
|
||||
|
||||
import type {
|
||||
GetReleaseActions,
|
||||
@ -48,257 +45,262 @@ type GetReleasesTabResponse = GetReleases.Response & {
|
||||
};
|
||||
};
|
||||
|
||||
const releaseApi = createApi({
|
||||
reducerPath: pluginId,
|
||||
baseQuery: fetchBaseQuery,
|
||||
tagTypes: ['Release', 'ReleaseAction', 'EntriesInRelease'],
|
||||
endpoints: (build) => {
|
||||
return {
|
||||
getReleasesForEntry: build.query<
|
||||
GetContentTypeEntryReleases.Response,
|
||||
Partial<GetContentTypeEntryReleases.Request['query']>
|
||||
>({
|
||||
query(params) {
|
||||
return {
|
||||
url: '/content-releases',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
};
|
||||
},
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [
|
||||
...result.data.map(({ id }) => ({ type: 'Release' as const, id })),
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
]
|
||||
: [],
|
||||
}),
|
||||
getReleases: build.query<GetReleasesTabResponse, GetReleasesQueryParams | void>({
|
||||
query(
|
||||
{ page, pageSize, filters } = {
|
||||
page: 1,
|
||||
pageSize: 16,
|
||||
filters: {
|
||||
releasedAt: {
|
||||
$notNull: false,
|
||||
const releaseApi = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['Release', 'ReleaseAction', 'EntriesInRelease'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (build) => {
|
||||
return {
|
||||
getReleasesForEntry: build.query<
|
||||
GetContentTypeEntryReleases.Response,
|
||||
Partial<GetContentTypeEntryReleases.Request['query']>
|
||||
>({
|
||||
query(params) {
|
||||
return {
|
||||
url: '/content-releases',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
},
|
||||
}
|
||||
) {
|
||||
return {
|
||||
url: '/content-releases',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params: {
|
||||
page: page || 1,
|
||||
pageSize: pageSize || 16,
|
||||
filters: filters || {
|
||||
releasedAt: {
|
||||
$notNull: false,
|
||||
};
|
||||
},
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [
|
||||
...result.data.map(({ id }) => ({ type: 'Release' as const, id })),
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
]
|
||||
: [],
|
||||
}),
|
||||
getReleases: build.query<GetReleasesTabResponse, GetReleasesQueryParams | void>({
|
||||
query(
|
||||
{ page, pageSize, filters } = {
|
||||
page: 1,
|
||||
pageSize: 16,
|
||||
filters: {
|
||||
releasedAt: {
|
||||
$notNull: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
) {
|
||||
return {
|
||||
url: '/content-releases',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params: {
|
||||
page: page || 1,
|
||||
pageSize: pageSize || 16,
|
||||
filters: filters || {
|
||||
releasedAt: {
|
||||
$notNull: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
transformResponse(response: GetReleasesTabResponse, meta, arg) {
|
||||
const releasedAtValue = arg?.filters?.releasedAt?.$notNull;
|
||||
const isActiveDoneTab = releasedAtValue === 'true';
|
||||
const newResponse: GetReleasesTabResponse = {
|
||||
...response,
|
||||
meta: {
|
||||
...response.meta,
|
||||
activeTab: isActiveDoneTab ? 'done' : 'pending',
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
transformResponse(response: GetReleasesTabResponse, meta, arg) {
|
||||
const releasedAtValue = arg?.filters?.releasedAt?.$notNull;
|
||||
const isActiveDoneTab = releasedAtValue === 'true';
|
||||
const newResponse: GetReleasesTabResponse = {
|
||||
...response,
|
||||
meta: {
|
||||
...response.meta,
|
||||
activeTab: isActiveDoneTab ? 'done' : 'pending',
|
||||
},
|
||||
};
|
||||
|
||||
return newResponse;
|
||||
},
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [
|
||||
...result.data.map(({ id }) => ({ type: 'Release' as const, id })),
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
]
|
||||
: [{ type: 'Release', id: 'LIST' }],
|
||||
}),
|
||||
getRelease: build.query<GetRelease.Response, GetRelease.Request['params']>({
|
||||
query({ id }) {
|
||||
return {
|
||||
url: `/content-releases/${id}`,
|
||||
method: 'GET',
|
||||
};
|
||||
},
|
||||
providesTags: (result, error, arg) => [{ type: 'Release' as const, id: arg.id }],
|
||||
}),
|
||||
getReleaseActions: build.query<
|
||||
GetReleaseActions.Response,
|
||||
GetReleaseActions.Request['params'] & GetReleaseActions.Request['query']
|
||||
>({
|
||||
query({ releaseId, ...params }) {
|
||||
return {
|
||||
url: `/content-releases/${releaseId}/actions`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
};
|
||||
},
|
||||
providesTags: [{ type: 'ReleaseAction', id: 'LIST' }],
|
||||
}),
|
||||
createRelease: build.mutation<CreateRelease.Response, CreateRelease.Request['body']>({
|
||||
query(data) {
|
||||
return {
|
||||
url: '/content-releases',
|
||||
method: 'POST',
|
||||
data,
|
||||
};
|
||||
},
|
||||
invalidatesTags: [{ type: 'Release', id: 'LIST' }],
|
||||
}),
|
||||
updateRelease: build.mutation<
|
||||
void,
|
||||
UpdateRelease.Request['params'] & UpdateRelease.Request['body']
|
||||
>({
|
||||
query({ id, ...data }) {
|
||||
return {
|
||||
url: `/content-releases/${id}`,
|
||||
method: 'PUT',
|
||||
data,
|
||||
};
|
||||
},
|
||||
invalidatesTags: (result, error, arg) => [{ type: 'Release', id: arg.id }],
|
||||
}),
|
||||
createReleaseAction: build.mutation<
|
||||
CreateReleaseAction.Response,
|
||||
CreateReleaseAction.Request
|
||||
>({
|
||||
query({ body, params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
};
|
||||
},
|
||||
invalidatesTags: [
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
{ type: 'ReleaseAction', id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
createManyReleaseActions: build.mutation<
|
||||
CreateManyReleaseActions.Response,
|
||||
CreateManyReleaseActions.Request
|
||||
>({
|
||||
query({ body, params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions/bulk`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
};
|
||||
},
|
||||
invalidatesTags: [
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
{ type: 'ReleaseAction', id: 'LIST' },
|
||||
{ type: 'EntriesInRelease' },
|
||||
],
|
||||
}),
|
||||
updateReleaseAction: build.mutation<
|
||||
UpdateReleaseAction.Response,
|
||||
UpdateReleaseAction.Request & { query: GetReleaseActions.Request['query'] } & {
|
||||
actionPath: [string, number];
|
||||
}
|
||||
>({
|
||||
query({ body, params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
};
|
||||
},
|
||||
invalidatesTags: () => [{ type: 'ReleaseAction', id: 'LIST' }],
|
||||
async onQueryStarted({ body, params, query, actionPath }, { dispatch, queryFulfilled }) {
|
||||
// We need to mimic the same params received by the getReleaseActions query
|
||||
const paramsWithoutActionId = {
|
||||
releaseId: params.releaseId,
|
||||
...query,
|
||||
};
|
||||
|
||||
const patchResult = dispatch(
|
||||
releaseApi.util.updateQueryData('getReleaseActions', paramsWithoutActionId, (draft) => {
|
||||
const [key, index] = actionPath;
|
||||
const action = draft.data[key][index];
|
||||
|
||||
if (action) {
|
||||
action.type = body.type;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
try {
|
||||
await queryFulfilled;
|
||||
} catch {
|
||||
patchResult.undo();
|
||||
return newResponse;
|
||||
},
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [
|
||||
...result.data.map(({ id }) => ({ type: 'Release' as const, id })),
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
]
|
||||
: [{ type: 'Release', id: 'LIST' }],
|
||||
}),
|
||||
getRelease: build.query<GetRelease.Response, GetRelease.Request['params']>({
|
||||
query({ id }) {
|
||||
return {
|
||||
url: `/content-releases/${id}`,
|
||||
method: 'GET',
|
||||
};
|
||||
},
|
||||
providesTags: (result, error, arg) => [{ type: 'Release' as const, id: arg.id }],
|
||||
}),
|
||||
getReleaseActions: build.query<
|
||||
GetReleaseActions.Response,
|
||||
GetReleaseActions.Request['params'] & GetReleaseActions.Request['query']
|
||||
>({
|
||||
query({ releaseId, ...params }) {
|
||||
return {
|
||||
url: `/content-releases/${releaseId}/actions`,
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
};
|
||||
},
|
||||
providesTags: [{ type: 'ReleaseAction', id: 'LIST' }],
|
||||
}),
|
||||
createRelease: build.mutation<CreateRelease.Response, CreateRelease.Request['body']>({
|
||||
query(data) {
|
||||
return {
|
||||
url: '/content-releases',
|
||||
method: 'POST',
|
||||
data,
|
||||
};
|
||||
},
|
||||
invalidatesTags: [{ type: 'Release', id: 'LIST' }],
|
||||
}),
|
||||
updateRelease: build.mutation<
|
||||
void,
|
||||
UpdateRelease.Request['params'] & UpdateRelease.Request['body']
|
||||
>({
|
||||
query({ id, ...data }) {
|
||||
return {
|
||||
url: `/content-releases/${id}`,
|
||||
method: 'PUT',
|
||||
data,
|
||||
};
|
||||
},
|
||||
invalidatesTags: (result, error, arg) => [{ type: 'Release', id: arg.id }],
|
||||
}),
|
||||
createReleaseAction: build.mutation<
|
||||
CreateReleaseAction.Response,
|
||||
CreateReleaseAction.Request
|
||||
>({
|
||||
query({ body, params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
};
|
||||
},
|
||||
invalidatesTags: [
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
{ type: 'ReleaseAction', id: 'LIST' },
|
||||
],
|
||||
}),
|
||||
createManyReleaseActions: build.mutation<
|
||||
CreateManyReleaseActions.Response,
|
||||
CreateManyReleaseActions.Request
|
||||
>({
|
||||
query({ body, params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions/bulk`,
|
||||
method: 'POST',
|
||||
data: body,
|
||||
};
|
||||
},
|
||||
invalidatesTags: [
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
{ type: 'ReleaseAction', id: 'LIST' },
|
||||
{ type: 'EntriesInRelease' },
|
||||
],
|
||||
}),
|
||||
updateReleaseAction: build.mutation<
|
||||
UpdateReleaseAction.Response,
|
||||
UpdateReleaseAction.Request & { query: GetReleaseActions.Request['query'] } & {
|
||||
actionPath: [string, number];
|
||||
}
|
||||
},
|
||||
}),
|
||||
deleteReleaseAction: build.mutation<
|
||||
DeleteReleaseAction.Response,
|
||||
DeleteReleaseAction.Request
|
||||
>({
|
||||
query({ params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
|
||||
method: 'DELETE',
|
||||
};
|
||||
},
|
||||
invalidatesTags: (result, error, arg) => [
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
{ type: 'Release', id: arg.params.releaseId },
|
||||
{ type: 'ReleaseAction', id: 'LIST' },
|
||||
{ type: 'EntriesInRelease' },
|
||||
],
|
||||
}),
|
||||
publishRelease: build.mutation<PublishRelease.Response, PublishRelease.Request['params']>({
|
||||
query({ id }) {
|
||||
return {
|
||||
url: `/content-releases/${id}/publish`,
|
||||
method: 'POST',
|
||||
};
|
||||
},
|
||||
invalidatesTags: (result, error, arg) => [{ type: 'Release', id: arg.id }],
|
||||
}),
|
||||
deleteRelease: build.mutation<DeleteRelease.Response, DeleteRelease.Request['params']>({
|
||||
query({ id }) {
|
||||
return {
|
||||
url: `/content-releases/${id}`,
|
||||
method: 'DELETE',
|
||||
};
|
||||
},
|
||||
invalidatesTags: () => [{ type: 'Release', id: 'LIST' }, { type: 'EntriesInRelease' }],
|
||||
}),
|
||||
getMappedEntriesInReleases: build.query<
|
||||
MapEntriesToReleases.Response['data'],
|
||||
MapEntriesToReleases.Request['query']
|
||||
>({
|
||||
query(params) {
|
||||
return {
|
||||
url: '/content-releases/mapEntriesToReleases',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
};
|
||||
},
|
||||
transformResponse(response: MapEntriesToReleases.Response) {
|
||||
return response.data;
|
||||
},
|
||||
providesTags: [{ type: 'EntriesInRelease' }],
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
>({
|
||||
query({ body, params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
};
|
||||
},
|
||||
invalidatesTags: () => [{ type: 'ReleaseAction', id: 'LIST' }],
|
||||
async onQueryStarted({ body, params, query, actionPath }, { dispatch, queryFulfilled }) {
|
||||
// We need to mimic the same params received by the getReleaseActions query
|
||||
const paramsWithoutActionId = {
|
||||
releaseId: params.releaseId,
|
||||
...query,
|
||||
};
|
||||
|
||||
const patchResult = dispatch(
|
||||
releaseApi.util.updateQueryData(
|
||||
'getReleaseActions',
|
||||
paramsWithoutActionId,
|
||||
(draft) => {
|
||||
const [key, index] = actionPath;
|
||||
const action = draft.data[key][index];
|
||||
|
||||
if (action) {
|
||||
action.type = body.type;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await queryFulfilled;
|
||||
} catch {
|
||||
patchResult.undo();
|
||||
}
|
||||
},
|
||||
}),
|
||||
deleteReleaseAction: build.mutation<
|
||||
DeleteReleaseAction.Response,
|
||||
DeleteReleaseAction.Request
|
||||
>({
|
||||
query({ params }) {
|
||||
return {
|
||||
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
|
||||
method: 'DELETE',
|
||||
};
|
||||
},
|
||||
invalidatesTags: (result, error, arg) => [
|
||||
{ type: 'Release', id: 'LIST' },
|
||||
{ type: 'Release', id: arg.params.releaseId },
|
||||
{ type: 'ReleaseAction', id: 'LIST' },
|
||||
{ type: 'EntriesInRelease' },
|
||||
],
|
||||
}),
|
||||
publishRelease: build.mutation<PublishRelease.Response, PublishRelease.Request['params']>({
|
||||
query({ id }) {
|
||||
return {
|
||||
url: `/content-releases/${id}/publish`,
|
||||
method: 'POST',
|
||||
};
|
||||
},
|
||||
invalidatesTags: (result, error, arg) => [{ type: 'Release', id: arg.id }],
|
||||
}),
|
||||
deleteRelease: build.mutation<DeleteRelease.Response, DeleteRelease.Request['params']>({
|
||||
query({ id }) {
|
||||
return {
|
||||
url: `/content-releases/${id}`,
|
||||
method: 'DELETE',
|
||||
};
|
||||
},
|
||||
invalidatesTags: () => [{ type: 'Release', id: 'LIST' }, { type: 'EntriesInRelease' }],
|
||||
}),
|
||||
getMappedEntriesInReleases: build.query<
|
||||
MapEntriesToReleases.Response['data'],
|
||||
MapEntriesToReleases.Request['query']
|
||||
>({
|
||||
query(params) {
|
||||
return {
|
||||
url: '/content-releases/mapEntriesToReleases',
|
||||
method: 'GET',
|
||||
config: {
|
||||
params,
|
||||
},
|
||||
};
|
||||
},
|
||||
transformResponse(response: MapEntriesToReleases.Response) {
|
||||
return response.data;
|
||||
},
|
||||
providesTags: [{ type: 'EntriesInRelease' }],
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
useGetReleasesQuery,
|
||||
|
||||
11
packages/core/content-releases/admin/src/utils/api.ts
Normal file
11
packages/core/content-releases/admin/src/utils/api.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { ApiError } from '@strapi/admin/strapi-admin';
|
||||
|
||||
type BaseQueryError = ApiError | SerializedError;
|
||||
|
||||
const isBaseQueryError = (error?: BaseQueryError): error is BaseQueryError => {
|
||||
return typeof error !== 'undefined' && error.name !== undefined;
|
||||
};
|
||||
|
||||
export { isBaseQueryError };
|
||||
export type { BaseQueryError };
|
||||
@ -1,9 +1,7 @@
|
||||
/* eslint-disable check-file/filename-naming-convention */
|
||||
import * as React from 'react';
|
||||
|
||||
import { ConfigureStoreOptions } from '@reduxjs/toolkit';
|
||||
import {
|
||||
defaultTestStoreConfig,
|
||||
render as renderAdmin,
|
||||
server,
|
||||
waitFor,
|
||||
@ -13,19 +11,6 @@ import {
|
||||
} from '@strapi/admin/strapi-admin/test';
|
||||
|
||||
import { PERMISSIONS } from '../src/constants';
|
||||
import { releaseApi } from '../src/services/release';
|
||||
|
||||
const storeConfig: ConfigureStoreOptions = {
|
||||
preloadedState: defaultTestStoreConfig.preloadedState,
|
||||
reducer: {
|
||||
...defaultTestStoreConfig.reducer,
|
||||
[releaseApi.reducerPath]: releaseApi.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...defaultTestStoreConfig.middleware(getDefaultMiddleware),
|
||||
releaseApi.middleware,
|
||||
],
|
||||
};
|
||||
|
||||
const render = (
|
||||
ui: React.ReactElement,
|
||||
@ -33,7 +18,7 @@ const render = (
|
||||
): ReturnType<typeof renderAdmin> =>
|
||||
renderAdmin(ui, {
|
||||
...options,
|
||||
providerOptions: { storeConfig, permissions: Object.values(PERMISSIONS).flat() },
|
||||
providerOptions: { permissions: Object.values(PERMISSIONS).flat() },
|
||||
});
|
||||
|
||||
export { render, waitFor, act, screen, server };
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { PLUGIN_ID, FEATURE_ID } from './constants';
|
||||
import { Panel } from './routes/content-manager/[model]/[id]/components/Panel';
|
||||
import { reviewWorkflowsApi } from './services/api';
|
||||
import { addColumnToTableHook } from './utils/cm-hooks';
|
||||
import { prefixPluginTranslations } from './utils/translations';
|
||||
|
||||
@ -10,13 +9,6 @@ import type { Plugin } from '@strapi/types';
|
||||
const admin: Plugin.Config.AdminInput = {
|
||||
register(app: StrapiApp) {
|
||||
if (window.strapi.features.isEnabled(FEATURE_ID)) {
|
||||
app.addReducers({
|
||||
[reviewWorkflowsApi.reducerPath]: reviewWorkflowsApi.reducer,
|
||||
});
|
||||
|
||||
// @ts-expect-error TS doesn't want you to extend the middleware.
|
||||
app.addMiddlewares([() => reviewWorkflowsApi.middleware]);
|
||||
|
||||
app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', addColumnToTableHook);
|
||||
|
||||
const contentManagerPluginApis = app.getPlugin('content-manager').apis;
|
||||
|
||||
@ -119,10 +119,8 @@ describe('AssigneeSelect', () => {
|
||||
return res.once(
|
||||
ctx.status(500),
|
||||
ctx.json({
|
||||
data: {
|
||||
error: {
|
||||
message: 'Server side error message',
|
||||
},
|
||||
error: {
|
||||
message: 'Server side error message',
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -136,7 +134,7 @@ describe('AssigneeSelect', () => {
|
||||
await waitFor(() => expect(queryByText('Loading content...')).not.toBeInTheDocument());
|
||||
await user.click(getByText('John Doe'));
|
||||
|
||||
await findByText('There was an unknown error response from the API');
|
||||
await findByText('Server side error message');
|
||||
|
||||
console.error = origConsoleError;
|
||||
});
|
||||
|
||||
@ -34,7 +34,7 @@ import { useIntl } from 'react-intl';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { Stage as IStage, StagePermission } from '../../../../../shared/contracts/review-workflows';
|
||||
import { useGetRolesQuery } from '../../../services/admin';
|
||||
import { useGetAdminRolesQuery } from '../../../services/admin';
|
||||
import { AVAILABLE_COLORS, getStageColorByHex } from '../../../utils/colors';
|
||||
import { DRAG_DROP_TYPES } from '../constants';
|
||||
import { useDragAndDrop } from '../hooks/useDragAndDrop';
|
||||
@ -517,7 +517,7 @@ const PermissionsField = ({ disabled, name, placeholder, required }: Permissions
|
||||
const allStages = useForm<WorkflowStage[]>('PermissionsField', (state) => state.values.stages);
|
||||
const onFormValueChange = useForm('PermissionsField', (state) => state.onChange);
|
||||
|
||||
const { data: roles = [], isLoading } = useGetRolesQuery();
|
||||
const { data: roles = [], isLoading } = useGetAdminRolesQuery();
|
||||
|
||||
// Super admins always have permissions to do everything and therefore
|
||||
// there is no point for this role to show up in the role combobox
|
||||
|
||||
@ -8,7 +8,7 @@ type RolesResponse = { data: Roles };
|
||||
const adminApi = reviewWorkflowsApi.injectEndpoints({
|
||||
endpoints(builder) {
|
||||
return {
|
||||
getRoles: builder.query<Roles, void>({
|
||||
getAdminRoles: builder.query<Roles, void>({
|
||||
query: () => ({
|
||||
url: `/admin/roles`,
|
||||
method: 'GET',
|
||||
@ -21,7 +21,7 @@ const adminApi = reviewWorkflowsApi.injectEndpoints({
|
||||
},
|
||||
});
|
||||
|
||||
const { useGetRolesQuery } = adminApi;
|
||||
const { useGetAdminRolesQuery } = adminApi;
|
||||
|
||||
export { useGetRolesQuery };
|
||||
export { useGetAdminRolesQuery };
|
||||
export type { SanitizedAdminUser, Roles };
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import { adminApi } from '@strapi/admin/strapi-admin';
|
||||
|
||||
import { fetchBaseQuery, type UnknownApiError } from '../utils/api';
|
||||
|
||||
const reviewWorkflowsApi = createApi({
|
||||
reducerPath: 'reviewWorkflowsApi',
|
||||
baseQuery: fetchBaseQuery(),
|
||||
tagTypes: ['ReviewWorkflow', 'ReviewWorkflowStages'],
|
||||
endpoints: () => ({}),
|
||||
const reviewWorkflowsApi = adminApi.enhanceEndpoints({
|
||||
addTagTypes: ['ReviewWorkflow', 'ReviewWorkflowStages'],
|
||||
});
|
||||
|
||||
export { reviewWorkflowsApi, type UnknownApiError };
|
||||
export { reviewWorkflowsApi };
|
||||
|
||||
@ -87,6 +87,7 @@ const contentManagerApi = reviewWorkflowsApi.injectEndpoints({
|
||||
},
|
||||
}),
|
||||
}),
|
||||
overrideExisting: true,
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@ -1,119 +1,8 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { BaseQueryFn } from '@reduxjs/toolkit/query';
|
||||
import {
|
||||
getFetchClient,
|
||||
isFetchError,
|
||||
type ApiError,
|
||||
type FetchOptions,
|
||||
} from '@strapi/admin/strapi-admin';
|
||||
|
||||
export interface QueryArguments {
|
||||
url: string;
|
||||
method?: string;
|
||||
data?: unknown;
|
||||
config?: FetchOptions;
|
||||
}
|
||||
|
||||
export interface UnknownApiError {
|
||||
name: 'UnknownError';
|
||||
message: string;
|
||||
details?: unknown;
|
||||
status?: number;
|
||||
}
|
||||
import { type UnknownApiError, type ApiError } from '@strapi/admin/strapi-admin';
|
||||
|
||||
export type BaseQueryError = ApiError | UnknownApiError | SerializedError;
|
||||
|
||||
const fetchBaseQuery =
|
||||
(): BaseQueryFn<string | QueryArguments, unknown, BaseQueryError> =>
|
||||
async (query, { signal }) => {
|
||||
try {
|
||||
const { get, post, del, put } = getFetchClient();
|
||||
|
||||
if (typeof query === 'string') {
|
||||
const result = await get(query, { signal });
|
||||
return { data: result.data };
|
||||
} else {
|
||||
const { url, method = 'GET', data, config } = query;
|
||||
|
||||
if (method === 'POST') {
|
||||
const result = await post(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'DELETE') {
|
||||
const result = await del(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'PUT') {
|
||||
const result = await put(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Default is GET.
|
||||
*/
|
||||
const result = await get(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
} catch (err) {
|
||||
/**
|
||||
* Handle error of type FetchError
|
||||
*
|
||||
* This format mimics what we want from an FetchError 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.
|
||||
*/
|
||||
|
||||
if (isFetchError(err)) {
|
||||
if (
|
||||
typeof err.response?.data === 'object' &&
|
||||
err.response?.data !== null &&
|
||||
'error' in err.response?.data
|
||||
) {
|
||||
/**
|
||||
* This will most likely be ApiError
|
||||
*/
|
||||
return { data: undefined, error: err.response?.data.error };
|
||||
} else {
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: 'UnknownError',
|
||||
message: 'There was an unknown error response from the API',
|
||||
details: err.response,
|
||||
status: err.status,
|
||||
} as UnknownApiError,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const error = err as Error;
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
} satisfies SerializedError,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const isBaseQueryError = (error: BaseQueryError): error is ApiError | UnknownApiError => {
|
||||
return error.name !== undefined;
|
||||
};
|
||||
@ -158,4 +47,4 @@ const buildValidParams = <TQuery extends Query>(query: TQuery): TransformedQuery
|
||||
return validQueryParams;
|
||||
};
|
||||
|
||||
export { fetchBaseQuery, isBaseQueryError, buildValidParams };
|
||||
export { isBaseQueryError, buildValidParams };
|
||||
|
||||
@ -1,45 +1,5 @@
|
||||
/* eslint-disable check-file/filename-naming-convention */
|
||||
import * as React from 'react';
|
||||
|
||||
import { ConfigureStoreOptions } from '@reduxjs/toolkit';
|
||||
import {
|
||||
defaultTestStoreConfig,
|
||||
render as renderAdmin,
|
||||
server,
|
||||
waitFor,
|
||||
act,
|
||||
screen,
|
||||
type RenderOptions,
|
||||
renderHook as renderHookAdmin,
|
||||
} from '@strapi/admin/strapi-admin/test';
|
||||
|
||||
import { reviewWorkflowsApi } from '../src/services/api';
|
||||
|
||||
const storeConfig: ConfigureStoreOptions = {
|
||||
preloadedState: defaultTestStoreConfig.preloadedState,
|
||||
reducer: {
|
||||
...defaultTestStoreConfig.reducer,
|
||||
[reviewWorkflowsApi.reducerPath]: reviewWorkflowsApi.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...defaultTestStoreConfig.middleware(getDefaultMiddleware),
|
||||
reviewWorkflowsApi.middleware,
|
||||
],
|
||||
};
|
||||
|
||||
const render = (
|
||||
ui: React.ReactElement,
|
||||
options: RenderOptions = {}
|
||||
): ReturnType<typeof renderAdmin> =>
|
||||
renderAdmin(ui, {
|
||||
...options,
|
||||
providerOptions: { storeConfig },
|
||||
});
|
||||
|
||||
const renderHook: typeof renderHookAdmin = (hook, options) =>
|
||||
renderHookAdmin(hook, {
|
||||
...options,
|
||||
providerOptions: { storeConfig },
|
||||
});
|
||||
import { render, server, waitFor, act, screen, renderHook } from '@strapi/admin/strapi-admin/test';
|
||||
|
||||
export { renderHook, render, waitFor, act, screen, server };
|
||||
|
||||
@ -2,7 +2,6 @@ import { Information } from '@strapi/icons';
|
||||
|
||||
import { PERMISSIONS } from './constants';
|
||||
import { pluginId } from './pluginId';
|
||||
import { api } from './services/api';
|
||||
import { prefixPluginTranslations } from './utils/prefixPluginTranslations';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@ -23,12 +22,6 @@ export default {
|
||||
position: 9,
|
||||
});
|
||||
|
||||
app.addMiddlewares([() => api.middleware]);
|
||||
|
||||
app.addReducers({
|
||||
[api.reducerPath]: api.reducer,
|
||||
});
|
||||
|
||||
app.registerPlugin({
|
||||
id: pluginId,
|
||||
name: pluginId,
|
||||
|
||||
@ -1,31 +1,10 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
render,
|
||||
waitFor,
|
||||
defaultTestStoreConfig,
|
||||
type RenderOptions,
|
||||
} from '@strapi/strapi/admin/test';
|
||||
import { render, waitFor, type RenderOptions } from '@strapi/strapi/admin/test';
|
||||
|
||||
import { api } from '../../services/api';
|
||||
import { App } from '../App';
|
||||
|
||||
const renderApp = (opts?: RenderOptions) =>
|
||||
render(<App />, {
|
||||
...(opts ?? {}),
|
||||
providerOptions: {
|
||||
...opts?.providerOptions,
|
||||
storeConfig: {
|
||||
...defaultTestStoreConfig,
|
||||
reducer: {
|
||||
...defaultTestStoreConfig.reducer,
|
||||
[api.reducerPath]: api.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
defaultTestStoreConfig.middleware(getDefaultMiddleware).concat(api.middleware),
|
||||
},
|
||||
},
|
||||
});
|
||||
const renderApp = (opts?: RenderOptions) => render(<App />, opts);
|
||||
|
||||
const versions = ['2.0.0', '1.2.0', '1.0.0'];
|
||||
|
||||
|
||||
@ -1,31 +1,12 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { fireEvent, render, waitFor, defaultTestStoreConfig } from '@strapi/strapi/admin/test';
|
||||
import { fireEvent, render, waitFor } from '@strapi/strapi/admin/test';
|
||||
import { rest } from 'msw';
|
||||
|
||||
// @ts-expect-error - js file
|
||||
import { server } from '../../../../tests/server';
|
||||
import { api } from '../../services/api';
|
||||
import { SettingsPage } from '../Settings';
|
||||
|
||||
const renderSettingsPage = () =>
|
||||
render(<SettingsPage />, {
|
||||
providerOptions: {
|
||||
storeConfig: {
|
||||
...defaultTestStoreConfig,
|
||||
reducer: {
|
||||
...defaultTestStoreConfig.reducer,
|
||||
[api.reducerPath]: api.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
defaultTestStoreConfig.middleware(getDefaultMiddleware).concat(api.middleware),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('SettingsPage', () => {
|
||||
it('renders the setting page correctly', async () => {
|
||||
const { getByRole, queryByText, getByText } = renderSettingsPage();
|
||||
const { getByRole, queryByText, getByText } = render(<SettingsPage />);
|
||||
|
||||
await waitFor(() => expect(queryByText('Loading content.')).not.toBeInTheDocument());
|
||||
|
||||
@ -50,7 +31,7 @@ describe('SettingsPage', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const { getByLabelText, queryByText } = renderSettingsPage();
|
||||
const { getByLabelText, queryByText } = render(<SettingsPage />);
|
||||
|
||||
await waitFor(() => expect(queryByText('Loading content.')).not.toBeInTheDocument());
|
||||
|
||||
@ -60,7 +41,7 @@ describe('SettingsPage', () => {
|
||||
});
|
||||
|
||||
it('should render the password field when the Restricted Access checkbox is checked', async () => {
|
||||
const { getByRole, getByLabelText, queryByText } = renderSettingsPage();
|
||||
const { getByRole, getByLabelText, queryByText } = render(<SettingsPage />);
|
||||
|
||||
await waitFor(() => expect(queryByText('Loading content.')).not.toBeInTheDocument());
|
||||
|
||||
@ -72,7 +53,7 @@ describe('SettingsPage', () => {
|
||||
});
|
||||
|
||||
it('should allow me to type a password and save that settings change successfully', async () => {
|
||||
const { getByRole, getByLabelText, queryByText, user, findByText } = renderSettingsPage();
|
||||
const { getByRole, getByLabelText, queryByText, user, findByText } = render(<SettingsPage />);
|
||||
|
||||
await waitFor(() => expect(queryByText('Loading content.')).not.toBeInTheDocument());
|
||||
|
||||
|
||||
@ -1,53 +1,51 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import { adminApi } from '@strapi/admin/strapi-admin';
|
||||
|
||||
import { DocumentInfos } from '../types';
|
||||
import { baseQuery } from '../utils/baseQuery';
|
||||
|
||||
type SettingsInput = {
|
||||
restrictedAccess: boolean;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const api = createApi({
|
||||
reducerPath: 'plugin::documentation',
|
||||
baseQuery: baseQuery(),
|
||||
tagTypes: ['DocumentInfo'],
|
||||
endpoints: (builder) => {
|
||||
return {
|
||||
getInfo: builder.query<DocumentInfos, void>({
|
||||
query: () => '/documentation/getInfos',
|
||||
providesTags: ['DocumentInfo'],
|
||||
}),
|
||||
|
||||
deleteVersion: builder.mutation<void, { version: string }>({
|
||||
query: ({ version }) => ({
|
||||
url: `/documentation/deleteDoc/${version}`,
|
||||
method: 'DELETE',
|
||||
const api = adminApi
|
||||
.enhanceEndpoints({
|
||||
addTagTypes: ['DocumentInfo'],
|
||||
})
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => {
|
||||
return {
|
||||
getInfo: builder.query<DocumentInfos, void>({
|
||||
query: () => '/documentation/getInfos',
|
||||
providesTags: ['DocumentInfo'],
|
||||
}),
|
||||
invalidatesTags: ['DocumentInfo'],
|
||||
}),
|
||||
|
||||
updateSettings: builder.mutation<void, { body: SettingsInput }>({
|
||||
query: ({ body }) => ({
|
||||
url: `/documentation/updateSettings`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
deleteVersion: builder.mutation<void, { version: string }>({
|
||||
query: ({ version }) => ({
|
||||
url: `/documentation/deleteDoc/${version}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: ['DocumentInfo'],
|
||||
}),
|
||||
invalidatesTags: ['DocumentInfo'],
|
||||
}),
|
||||
|
||||
regenerateDoc: builder.mutation<void, { version: string }>({
|
||||
query: ({ version }) => ({
|
||||
url: `/documentation/regenerateDoc`,
|
||||
method: 'POST',
|
||||
data: { version },
|
||||
updateSettings: builder.mutation<void, { body: SettingsInput }>({
|
||||
query: ({ body }) => ({
|
||||
url: `/documentation/updateSettings`,
|
||||
method: 'PUT',
|
||||
data: body,
|
||||
}),
|
||||
invalidatesTags: ['DocumentInfo'],
|
||||
}),
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export { api };
|
||||
regenerateDoc: builder.mutation<void, { version: string }>({
|
||||
query: ({ version }) => ({
|
||||
url: `/documentation/regenerateDoc`,
|
||||
method: 'POST',
|
||||
data: { version },
|
||||
}),
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetInfoQuery,
|
||||
|
||||
@ -1,121 +1,10 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { BaseQueryFn } from '@reduxjs/toolkit/query';
|
||||
import {
|
||||
getFetchClient,
|
||||
isFetchError,
|
||||
type ApiError,
|
||||
type FetchOptions,
|
||||
} from '@strapi/strapi/admin';
|
||||
import { type UnknownApiError, type ApiError } from '@strapi/strapi/admin';
|
||||
|
||||
export interface QueryArguments {
|
||||
url: string;
|
||||
method?: string;
|
||||
data?: unknown;
|
||||
config?: FetchOptions;
|
||||
}
|
||||
|
||||
export interface UnknownApiError {
|
||||
name: 'UnknownError';
|
||||
message: string;
|
||||
details?: unknown;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export type BaseQueryError = ApiError | UnknownApiError | SerializedError;
|
||||
|
||||
const baseQuery =
|
||||
(): BaseQueryFn<string | QueryArguments, unknown, BaseQueryError> =>
|
||||
async (query, { signal }) => {
|
||||
try {
|
||||
const { get, post, del, put } = getFetchClient();
|
||||
|
||||
if (typeof query === 'string') {
|
||||
const result = await get(query, { signal });
|
||||
return { data: result.data };
|
||||
} else {
|
||||
const { url, method = 'GET', data, config } = query;
|
||||
|
||||
if (method === 'POST') {
|
||||
const result = await post(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'DELETE') {
|
||||
const result = await del(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'PUT') {
|
||||
const result = await put(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Default is GET.
|
||||
*/
|
||||
const result = await get(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
} catch (err) {
|
||||
/**
|
||||
* Handle error of type FetchError
|
||||
*
|
||||
* This format mimics what we want from an FetchError 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.
|
||||
*/
|
||||
|
||||
if (isFetchError(err)) {
|
||||
if (
|
||||
typeof err.response?.data === 'object' &&
|
||||
err.response?.data !== null &&
|
||||
'error' in err.response?.data
|
||||
) {
|
||||
/**
|
||||
* This will most likely be ApiError
|
||||
*/
|
||||
return { data: undefined, error: err.response?.data.error };
|
||||
} else {
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: 'UnknownError',
|
||||
message: 'There was an unknown error response from the API',
|
||||
details: err.response?.data,
|
||||
status: err.status,
|
||||
} as UnknownApiError,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const error = err as Error;
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
} satisfies SerializedError,
|
||||
};
|
||||
}
|
||||
};
|
||||
type BaseQueryError = ApiError | UnknownApiError | SerializedError;
|
||||
|
||||
const isBaseQueryError = (error: BaseQueryError): error is ApiError | UnknownApiError => {
|
||||
return error.name !== undefined;
|
||||
};
|
||||
|
||||
export { baseQuery, isBaseQueryError };
|
||||
export { isBaseQueryError };
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
module.exports = {
|
||||
preset: '../../../jest-preset.front.js',
|
||||
displayName: 'Documentation plugin',
|
||||
setupFilesAfterEnv: ['./tests/setup.js'],
|
||||
setupFilesAfterEnv: ['./tests/setup.ts'],
|
||||
};
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const { setupServer } = require('msw/node');
|
||||
const { rest } = require('msw');
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
|
||||
const handlers = [
|
||||
rest.get('*/getInfos', (req, res, ctx) => {
|
||||
@ -32,6 +30,4 @@ const handlers = [
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
|
||||
module.exports = {
|
||||
server,
|
||||
};
|
||||
export { server };
|
||||
@ -1,6 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
const { server } = require('./server');
|
||||
import { server } from './server';
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen();
|
||||
@ -1,12 +1,7 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import { adminApi } from '@strapi/admin/strapi-admin';
|
||||
|
||||
import { fetchBaseQuery, type UnknownApiError } from '../utils/baseQuery';
|
||||
|
||||
const i18nApi = createApi({
|
||||
reducerPath: 'i18nApi',
|
||||
baseQuery: fetchBaseQuery(),
|
||||
tagTypes: ['Locale'],
|
||||
endpoints: () => ({}),
|
||||
const i18nApi = adminApi.enhanceEndpoints({
|
||||
addTagTypes: ['Locale'],
|
||||
});
|
||||
|
||||
export { i18nApi, type UnknownApiError };
|
||||
export { i18nApi };
|
||||
|
||||
@ -3,6 +3,7 @@ import { i18nApi } from './api';
|
||||
import type { CountManyEntriesDraftRelations } from '../../../shared/contracts/content-manager';
|
||||
|
||||
const relationsApi = i18nApi.injectEndpoints({
|
||||
overrideExisting: true,
|
||||
endpoints: (builder) => ({
|
||||
getManyDraftRelationCount: builder.query<
|
||||
CountManyEntriesDraftRelations.Response['data'],
|
||||
|
||||
@ -1,121 +1,10 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { BaseQueryFn } from '@reduxjs/toolkit/query';
|
||||
import {
|
||||
getFetchClient,
|
||||
isFetchError,
|
||||
type ApiError,
|
||||
type FetchOptions,
|
||||
} from '@strapi/admin/strapi-admin';
|
||||
import { type ApiError, type UnknownApiError } from '@strapi/admin/strapi-admin';
|
||||
|
||||
export interface QueryArguments {
|
||||
url: string;
|
||||
method?: string;
|
||||
data?: unknown;
|
||||
config?: FetchOptions;
|
||||
}
|
||||
|
||||
export interface UnknownApiError {
|
||||
name: 'UnknownError';
|
||||
message: string;
|
||||
details?: unknown;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export type BaseQueryError = ApiError | UnknownApiError | SerializedError;
|
||||
|
||||
const fetchBaseQuery =
|
||||
(): BaseQueryFn<string | QueryArguments, unknown, BaseQueryError> =>
|
||||
async (query, { signal }) => {
|
||||
try {
|
||||
const { get, post, del, put } = getFetchClient();
|
||||
|
||||
if (typeof query === 'string') {
|
||||
const result = await get(query, { signal });
|
||||
return { data: result.data };
|
||||
} else {
|
||||
const { url, method = 'GET', data, config } = query;
|
||||
|
||||
if (method === 'POST') {
|
||||
const result = await post(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'DELETE') {
|
||||
const result = await del(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
if (method === 'PUT') {
|
||||
const result = await put(url, data, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Default is GET.
|
||||
*/
|
||||
const result = await get(url, {
|
||||
...config,
|
||||
signal,
|
||||
});
|
||||
return { data: result.data };
|
||||
}
|
||||
} catch (err) {
|
||||
/**
|
||||
* Handle error of type FetchError
|
||||
*
|
||||
* This format mimics what we want from an FetchError 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.
|
||||
*/
|
||||
|
||||
if (isFetchError(err)) {
|
||||
if (
|
||||
typeof err.response?.data === 'object' &&
|
||||
err.response?.data !== null &&
|
||||
'error' in err.response?.data
|
||||
) {
|
||||
/**
|
||||
* This will most likely be ApiError
|
||||
*/
|
||||
return { data: undefined, error: err.response?.data.error };
|
||||
} else {
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: 'UnknownError',
|
||||
message: 'There was an unknown error response from the API',
|
||||
details: err.response,
|
||||
status: err.status,
|
||||
} as UnknownApiError,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const error = err as Error;
|
||||
return {
|
||||
data: undefined,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
} satisfies SerializedError,
|
||||
};
|
||||
}
|
||||
};
|
||||
type BaseQueryError = ApiError | UnknownApiError | SerializedError;
|
||||
|
||||
const isBaseQueryError = (error: BaseQueryError): error is ApiError | UnknownApiError => {
|
||||
return error.name !== undefined;
|
||||
};
|
||||
|
||||
export { fetchBaseQuery, isBaseQueryError };
|
||||
export { isBaseQueryError };
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
/* eslint-disable check-file/filename-naming-convention */
|
||||
import * as React from 'react';
|
||||
|
||||
import { ConfigureStoreOptions } from '@reduxjs/toolkit';
|
||||
import {
|
||||
renderHook as renderHookAdmin,
|
||||
render as renderAdmin,
|
||||
defaultTestStoreConfig,
|
||||
waitFor,
|
||||
act,
|
||||
screen,
|
||||
@ -13,35 +11,22 @@ import {
|
||||
} from '@strapi/admin/strapi-admin/test';
|
||||
|
||||
import { PERMISSIONS } from '../src/constants';
|
||||
import { i18nApi } from '../src/services/api';
|
||||
|
||||
import { server } from './server';
|
||||
|
||||
const storeConfig: ConfigureStoreOptions = {
|
||||
preloadedState: defaultTestStoreConfig.preloadedState,
|
||||
reducer: {
|
||||
...defaultTestStoreConfig.reducer,
|
||||
[i18nApi.reducerPath]: i18nApi.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...defaultTestStoreConfig.middleware(getDefaultMiddleware),
|
||||
i18nApi.middleware,
|
||||
],
|
||||
};
|
||||
|
||||
const render = (
|
||||
ui: React.ReactElement,
|
||||
options: RenderOptions = {}
|
||||
): ReturnType<typeof renderAdmin> =>
|
||||
renderAdmin(ui, {
|
||||
...options,
|
||||
providerOptions: { storeConfig, permissions: Object.values(PERMISSIONS).flat() },
|
||||
providerOptions: { permissions: Object.values(PERMISSIONS).flat() },
|
||||
});
|
||||
|
||||
const renderHook: typeof renderHookAdmin = (hook, options) =>
|
||||
renderHookAdmin(hook, {
|
||||
...options,
|
||||
providerOptions: { storeConfig, permissions: Object.values(PERMISSIONS).flat() },
|
||||
providerOptions: { permissions: Object.values(PERMISSIONS).flat() },
|
||||
});
|
||||
|
||||
export { render, renderHook, waitFor, server, act, screen };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user