mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
implement react-query for edge-cases (#1711)
When initially implementing react-query, we focussed on core features. This pull request now replaces the remaining apiClient usages in ui-components and ui-webapp with react-query hooks.
This commit is contained in:
committed by
GitHub
parent
2cd46ce8a0
commit
e1239aff92
82
scm-ui/ui-api/src/apiKeys.ts
Normal file
82
scm-ui/ui-api/src/apiKeys.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import { ApiKey, ApiKeyCreation, ApiKeysCollection, ApiKeyWithToken, Me, User } from "@scm-manager/ui-types";
|
||||
import { ApiResult } from "./base";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { requiredLink } from "./links";
|
||||
|
||||
const CONTENT_TYPE_API_KEY = "application/vnd.scmm-apiKey+json;v=2";
|
||||
|
||||
export const useApiKeys = (user: User | Me): ApiResult<ApiKeysCollection> =>
|
||||
useQuery(["user", user.name, "apiKeys"], () => apiClient.get(requiredLink(user, "apiKeys")).then((r) => r.json()));
|
||||
|
||||
const createApiKey =
|
||||
(link: string) =>
|
||||
async (key: ApiKeyCreation): Promise<ApiKeyWithToken> => {
|
||||
const creationResponse = await apiClient.post(link, key, CONTENT_TYPE_API_KEY);
|
||||
const location = creationResponse.headers.get("Location");
|
||||
if (!location) {
|
||||
throw new Error("Server does not return required Location header");
|
||||
}
|
||||
const locationResponse = await apiClient.get(location);
|
||||
const [apiKey, token] = await Promise.all<ApiKey, string>([locationResponse.json(), creationResponse.text()]);
|
||||
return { ...apiKey, token } as ApiKeyWithToken;
|
||||
};
|
||||
|
||||
export const useCreateApiKey = (user: User | Me, apiKeys: ApiKeysCollection) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, data, isLoading, error, reset } = useMutation<ApiKeyWithToken, Error, ApiKeyCreation>(
|
||||
createApiKey(requiredLink(apiKeys, "create")),
|
||||
{
|
||||
onSuccess: () => queryClient.invalidateQueries(["user", user.name, "apiKeys"]),
|
||||
}
|
||||
);
|
||||
return {
|
||||
create: (key: ApiKeyCreation) => mutate(key),
|
||||
isLoading,
|
||||
error,
|
||||
apiKey: data,
|
||||
reset,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteApiKey = (user: User | Me) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, ApiKey>(
|
||||
(apiKey) => {
|
||||
const deleteUrl = requiredLink(apiKey, "delete");
|
||||
return apiClient.delete(deleteUrl);
|
||||
},
|
||||
{
|
||||
onSuccess: () => queryClient.invalidateQueries(["user", user.name, "apiKeys"]),
|
||||
}
|
||||
);
|
||||
return {
|
||||
remove: (apiKey: ApiKey) => mutate(apiKey),
|
||||
isLoading,
|
||||
error,
|
||||
isDeleted: !!data,
|
||||
};
|
||||
};
|
||||
@@ -22,22 +22,24 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { IndexResources, Link } from "@scm-manager/ui-types";
|
||||
import { HalRepresentation, IndexResources, Link } from "@scm-manager/ui-types";
|
||||
import { useQuery } from "react-query";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { useLegacyContext } from "./LegacyContext";
|
||||
import { MissingLinkError, UnauthorizedError } from "./errors";
|
||||
import { requiredLink } from "./links";
|
||||
|
||||
export type ApiResult<T> = {
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
data?: T;
|
||||
};
|
||||
export type DeleteFunction<T> = (entity: T) => void;
|
||||
|
||||
export const useIndex = (): ApiResult<IndexResources> => {
|
||||
const legacy = useLegacyContext();
|
||||
return useQuery<IndexResources, Error>("index", () => apiClient.get("/").then(response => response.json()), {
|
||||
onSuccess: index => {
|
||||
return useQuery<IndexResources, Error>("index", () => apiClient.get("/").then((response) => response.json()), {
|
||||
onSuccess: (index) => {
|
||||
// ensure legacy code is notified
|
||||
if (legacy.onIndexFetched) {
|
||||
legacy.onIndexFetched(index);
|
||||
@@ -49,7 +51,7 @@ export const useIndex = (): ApiResult<IndexResources> => {
|
||||
// This only happens once because the error response automatically invalidates the cookie.
|
||||
// In this event, we have to try the request once again.
|
||||
return error instanceof UnauthorizedError && failureCount === 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -94,7 +96,20 @@ export const useVersion = (): string => {
|
||||
|
||||
export const useIndexJsonResource = <T>(name: string): ApiResult<T> => {
|
||||
const link = useIndexLink(name);
|
||||
return useQuery<T, Error>(name, () => apiClient.get(link!).then(response => response.json()), {
|
||||
enabled: !!link
|
||||
return useQuery<T, Error>(name, () => apiClient.get(link!).then((response) => response.json()), {
|
||||
enabled: !!link,
|
||||
});
|
||||
};
|
||||
|
||||
export const useJsonResource = <T>(entity: HalRepresentation, name: string, key: string[]): ApiResult<T> =>
|
||||
useQuery<T, Error>(key, () => apiClient.get(requiredLink(entity, name)).then((response) => response.json()));
|
||||
|
||||
export function fetchResourceFromLocationHeader(response: Response) {
|
||||
const location = response.headers.get("Location");
|
||||
if (!location) {
|
||||
throw new Error("Server does not return required Location header");
|
||||
}
|
||||
return apiClient.get(location);
|
||||
}
|
||||
|
||||
export const getResponseJson = (response: Response) => response.json();
|
||||
|
||||
@@ -36,9 +36,9 @@ describe("Test branches hooks", () => {
|
||||
type: "hg",
|
||||
_links: {
|
||||
branches: {
|
||||
href: "/hog/branches"
|
||||
}
|
||||
}
|
||||
href: "/hog/branches",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const develop: Branch = {
|
||||
@@ -46,16 +46,16 @@ describe("Test branches hooks", () => {
|
||||
revision: "42",
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/hog/branches/develop"
|
||||
}
|
||||
}
|
||||
href: "/hog/branches/develop",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const branches: BranchCollection = {
|
||||
_embedded: {
|
||||
branches: [develop]
|
||||
branches: [develop],
|
||||
},
|
||||
_links: {}
|
||||
_links: {},
|
||||
};
|
||||
|
||||
const queryClient = createInfiniteCachingClient();
|
||||
@@ -73,7 +73,7 @@ describe("Test branches hooks", () => {
|
||||
fetchMock.getOnce("/api/v2/hog/branches", branches);
|
||||
|
||||
const { result, waitFor } = renderHook(() => useBranches(repository), {
|
||||
wrapper: createWrapper(undefined, queryClient)
|
||||
wrapper: createWrapper(undefined, queryClient),
|
||||
});
|
||||
await waitFor(() => {
|
||||
return !!result.current.data;
|
||||
@@ -94,7 +94,7 @@ describe("Test branches hooks", () => {
|
||||
"repository",
|
||||
"hitchhiker",
|
||||
"heart-of-gold",
|
||||
"branches"
|
||||
"branches",
|
||||
]);
|
||||
expect(data).toEqual(branches);
|
||||
});
|
||||
@@ -105,7 +105,7 @@ describe("Test branches hooks", () => {
|
||||
fetchMock.getOnce("/api/v2/hog/branches/develop", develop);
|
||||
|
||||
const { result, waitFor } = renderHook(() => useBranch(repository, "develop"), {
|
||||
wrapper: createWrapper(undefined, queryClient)
|
||||
wrapper: createWrapper(undefined, queryClient),
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -128,14 +128,14 @@ describe("Test branches hooks", () => {
|
||||
fetchMock.postOnce("/api/v2/hog/branches", {
|
||||
status: 201,
|
||||
headers: {
|
||||
Location: "/hog/branches/develop"
|
||||
}
|
||||
Location: "/hog/branches/develop",
|
||||
},
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/hog/branches/develop", develop);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() => useCreateBranch(repository), {
|
||||
wrapper: createWrapper(undefined, queryClient)
|
||||
wrapper: createWrapper(undefined, queryClient),
|
||||
});
|
||||
|
||||
await act(() => {
|
||||
@@ -160,7 +160,7 @@ describe("Test branches hooks", () => {
|
||||
"hitchhiker",
|
||||
"heart-of-gold",
|
||||
"branch",
|
||||
"develop"
|
||||
"develop",
|
||||
]);
|
||||
expect(branch).toEqual(develop);
|
||||
});
|
||||
@@ -177,11 +177,11 @@ describe("Test branches hooks", () => {
|
||||
describe("useDeleteBranch tests", () => {
|
||||
const deleteBranch = async () => {
|
||||
fetchMock.deleteOnce("/api/v2/hog/branches/develop", {
|
||||
status: 204
|
||||
status: 204,
|
||||
});
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() => useDeleteBranch(repository), {
|
||||
wrapper: createWrapper(undefined, queryClient)
|
||||
wrapper: createWrapper(undefined, queryClient),
|
||||
});
|
||||
|
||||
await act(() => {
|
||||
@@ -198,12 +198,12 @@ describe("Test branches hooks", () => {
|
||||
expect(isDeleted).toBe(true);
|
||||
});
|
||||
|
||||
it("should invalidate branch", async () => {
|
||||
it("should delete branch cache", async () => {
|
||||
queryClient.setQueryData(["repository", "hitchhiker", "heart-of-gold", "branch", "develop"], develop);
|
||||
await deleteBranch();
|
||||
|
||||
const queryState = queryClient.getQueryState(["repository", "hitchhiker", "heart-of-gold", "branch", "develop"]);
|
||||
expect(queryState!.isInvalidated).toBe(true);
|
||||
expect(queryState).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should invalidate cached branches list", async () => {
|
||||
|
||||
@@ -88,7 +88,7 @@ export const useDeleteBranch = (repository: Repository) => {
|
||||
},
|
||||
{
|
||||
onSuccess: async (_, branch) => {
|
||||
await queryClient.invalidateQueries(branchQueryKey(repository, branch));
|
||||
queryClient.removeQueries(branchQueryKey(repository, branch));
|
||||
await queryClient.invalidateQueries(repoQueryKey(repository, "branches"));
|
||||
}
|
||||
}
|
||||
|
||||
33
scm-ui/ui-api/src/fileContent.ts
Normal file
33
scm-ui/ui-api/src/fileContent.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import { File } from "@scm-manager/ui-types";
|
||||
import { useQuery } from "react-query";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { requiredLink } from "./links";
|
||||
import { ApiResult } from "./base";
|
||||
|
||||
export const useFileContent = (file: File): ApiResult<string> => {
|
||||
const selfLink = requiredLink(file, "self");
|
||||
return useQuery(["fileContent", selfLink], () => apiClient.get(selfLink).then((response) => response.text()));
|
||||
};
|
||||
@@ -22,11 +22,93 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { ApiResult, useRequiredIndexLink } from "./base";
|
||||
import { useQuery } from "react-query";
|
||||
import { ApiResult, fetchResourceFromLocationHeader, getResponseJson, useRequiredIndexLink } from "./base";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { Repository, RepositoryCreation, RepositoryType, RepositoryUrlImport } from "@scm-manager/ui-types";
|
||||
import { requiredLink } from "./links";
|
||||
|
||||
export const useImportLog = (logId: string) : ApiResult<string> => {
|
||||
export const useImportLog = (logId: string): ApiResult<string> => {
|
||||
const link = useRequiredIndexLink("importLog").replace("{logId}", logId);
|
||||
return useQuery<string, Error>(["importLog", logId], () => apiClient.get(link).then(response => response.text()));
|
||||
}
|
||||
return useQuery<string, Error>(["importLog", logId], () => apiClient.get(link).then((response) => response.text()));
|
||||
};
|
||||
|
||||
export const useImportRepositoryFromUrl = (repositoryType: RepositoryType) => {
|
||||
const url = requiredLink(repositoryType, "import", "url");
|
||||
const { isLoading, error, data, mutate } = useMutation<Repository, Error, RepositoryUrlImport>((repo) =>
|
||||
apiClient
|
||||
.post(url, repo, "application/vnd.scmm-repository+json;v=2")
|
||||
.then(fetchResourceFromLocationHeader)
|
||||
.then(getResponseJson)
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
importRepositoryFromUrl: (repository: RepositoryUrlImport) => mutate(repository),
|
||||
importedRepository: data,
|
||||
};
|
||||
};
|
||||
|
||||
const importRepository = (url: string, repository: RepositoryCreation, file: File, password?: string) => {
|
||||
return apiClient
|
||||
.postBinary(url, (formData) => {
|
||||
formData.append("bundle", file, file?.name);
|
||||
formData.append("repository", JSON.stringify({ ...repository, password }));
|
||||
})
|
||||
.then(fetchResourceFromLocationHeader)
|
||||
.then(getResponseJson);
|
||||
};
|
||||
|
||||
type ImportRepositoryFromBundleRequest = {
|
||||
repository: RepositoryCreation;
|
||||
file: File;
|
||||
compressed?: boolean;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export const useImportRepositoryFromBundle = (repositoryType: RepositoryType) => {
|
||||
const url = requiredLink(repositoryType, "import", "bundle");
|
||||
const { isLoading, error, data, mutate } = useMutation<Repository, Error, ImportRepositoryFromBundleRequest>(
|
||||
({ repository, file, compressed, password }) =>
|
||||
importRepository(compressed ? url + "?compressed=true" : url, repository, file, password)
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
importRepositoryFromBundle: (repository: RepositoryCreation, file: File, compressed?: boolean, password?: string) =>
|
||||
mutate({
|
||||
repository,
|
||||
file,
|
||||
compressed,
|
||||
password,
|
||||
}),
|
||||
importedRepository: data,
|
||||
};
|
||||
};
|
||||
|
||||
type ImportFullRepositoryRequest = {
|
||||
repository: RepositoryCreation;
|
||||
file: File;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export const useImportFullRepository = (repositoryType: RepositoryType) => {
|
||||
const { isLoading, error, data, mutate } = useMutation<Repository, Error, ImportFullRepositoryRequest>(
|
||||
({ repository, file, password }) =>
|
||||
importRepository(requiredLink(repositoryType, "import", "fullImport"), repository, file, password)
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
importFullRepository: (repository: RepositoryCreation, file: File, password?: string) =>
|
||||
mutate({
|
||||
repository,
|
||||
file,
|
||||
password,
|
||||
}),
|
||||
importedRepository: data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -50,6 +50,9 @@ export * from "./import";
|
||||
export * from "./diff";
|
||||
export * from "./notifications";
|
||||
export * from "./configLink";
|
||||
export * from "./apiKeys";
|
||||
export * from "./publicKeys";
|
||||
export * from "./fileContent";
|
||||
export * from "./history";
|
||||
export * from "./contentType";
|
||||
export * from "./annotations";
|
||||
|
||||
@@ -60,4 +60,40 @@ describe("requireLink tests", () => {
|
||||
};
|
||||
expect(() => requiredLink(object, "spaceship")).toThrowError();
|
||||
});
|
||||
|
||||
it("should return sub-link if it exists", () => {
|
||||
const object = {
|
||||
_links: {
|
||||
spaceship: [
|
||||
{
|
||||
name: "one",
|
||||
href: "/v2/one"
|
||||
},
|
||||
{
|
||||
name: "two",
|
||||
href: "/v2/two"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
expect(requiredLink(object, "spaceship", "one")).toBe("/v2/one");
|
||||
});
|
||||
|
||||
it("should throw error, if sub-link does not exist in link array", () => {
|
||||
const object = {
|
||||
_links: {
|
||||
spaceship: [
|
||||
{
|
||||
name: "one",
|
||||
href: "/v2/one"
|
||||
},
|
||||
{
|
||||
name: "two",
|
||||
href: "/v2/two"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
expect(() => requiredLink(object, "spaceship", "three")).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,14 +21,32 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import { HalRepresentation } from "@scm-manager/ui-types";
|
||||
import { HalRepresentation, Link } from "@scm-manager/ui-types";
|
||||
import { MissingLinkError } from "./errors";
|
||||
|
||||
export const requiredLink = (object: HalRepresentation, name: string) => {
|
||||
export const requiredLink = (object: HalRepresentation, name: string, subName?: string): string => {
|
||||
const link = object._links[name];
|
||||
if (!link) {
|
||||
throw new MissingLinkError(`could not find link with name ${name}`);
|
||||
}
|
||||
if (Array.isArray(link)) {
|
||||
if (subName) {
|
||||
const subLink = link.find((l: Link) => l.name === subName);
|
||||
if (subLink) {
|
||||
return subLink.href;
|
||||
}
|
||||
throw new Error(`could not return href, sub-link ${subName} in ${name} does not exist`);
|
||||
}
|
||||
throw new Error(`could not return href, link ${name} is a multi link`);
|
||||
}
|
||||
return link.href;
|
||||
};
|
||||
|
||||
export const objectLink = (object: HalRepresentation, name: string) => {
|
||||
const link = object._links[name];
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
if (Array.isArray(link)) {
|
||||
throw new Error(`could not return href, link ${name} is a multi link`);
|
||||
}
|
||||
|
||||
@@ -21,18 +21,21 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import { ApiResult, useIndexJsonResource } from "./base";
|
||||
import { ApiResult, useIndexJsonResource, useJsonResource } from "./base";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import {
|
||||
GlobalPermissionsCollection,
|
||||
Group,
|
||||
Namespace,
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry,
|
||||
Repository,
|
||||
RepositoryVerbs
|
||||
RepositoryVerbs,
|
||||
User,
|
||||
} from "@scm-manager/ui-types";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { requiredLink } from "./links";
|
||||
import { objectLink, requiredLink } from "./links";
|
||||
import { repoQueryKey } from "./keys";
|
||||
import { useRepositoryRoles } from "./repository-roles";
|
||||
|
||||
@@ -40,6 +43,9 @@ export const useRepositoryVerbs = (): ApiResult<RepositoryVerbs> => {
|
||||
return useIndexJsonResource<RepositoryVerbs>("repositoryVerbs");
|
||||
};
|
||||
|
||||
/**
|
||||
* *IMPORTANT NOTE:* These are actually *REPOSITORY* permissions.
|
||||
*/
|
||||
export const useAvailablePermissions = () => {
|
||||
const roles = useRepositoryRoles();
|
||||
const verbs = useRepositoryVerbs();
|
||||
@@ -47,14 +53,14 @@ export const useAvailablePermissions = () => {
|
||||
if (roles.data && verbs.data) {
|
||||
data = {
|
||||
repositoryVerbs: verbs.data.verbs,
|
||||
repositoryRoles: roles.data._embedded.repositoryRoles
|
||||
repositoryRoles: roles.data._embedded.repositoryRoles,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isLoading: roles.isLoading || verbs.isLoading,
|
||||
error: roles.error || verbs.error,
|
||||
data
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -73,21 +79,21 @@ const createQueryKey = (namespaceOrRepository: Namespace | Repository) => {
|
||||
export const usePermissions = (namespaceOrRepository: Namespace | Repository): ApiResult<PermissionCollection> => {
|
||||
const link = requiredLink(namespaceOrRepository, "permissions");
|
||||
const queryKey = createQueryKey(namespaceOrRepository);
|
||||
return useQuery<PermissionCollection, Error>(queryKey, () => apiClient.get(link).then(response => response.json()));
|
||||
return useQuery<PermissionCollection, Error>(queryKey, () => apiClient.get(link).then((response) => response.json()));
|
||||
};
|
||||
|
||||
const createPermission = (link: string) => {
|
||||
return (permission: PermissionCreateEntry) => {
|
||||
return apiClient
|
||||
.post(link, permission, "application/vnd.scmm-repositoryPermission+json")
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
const location = response.headers.get("Location");
|
||||
if (!location) {
|
||||
throw new Error("Server does not return required Location header");
|
||||
}
|
||||
return apiClient.get(location);
|
||||
})
|
||||
.then(response => response.json());
|
||||
.then((response) => response.json());
|
||||
};
|
||||
};
|
||||
|
||||
@@ -100,21 +106,21 @@ export const useCreatePermission = (namespaceOrRepository: Namespace | Repositor
|
||||
onSuccess: () => {
|
||||
const queryKey = createQueryKey(namespaceOrRepository);
|
||||
return queryClient.invalidateQueries(queryKey);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
create: (permission: PermissionCreateEntry) => mutate(permission),
|
||||
permission: data
|
||||
permission: data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useUpdatePermission = (namespaceOrRepository: Namespace | Repository) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { isLoading, error, mutate, data } = useMutation<unknown, Error, Permission>(
|
||||
permission => {
|
||||
(permission) => {
|
||||
const link = requiredLink(permission, "update");
|
||||
return apiClient.put(link, permission, "application/vnd.scmm-repositoryPermission+json");
|
||||
},
|
||||
@@ -122,21 +128,21 @@ export const useUpdatePermission = (namespaceOrRepository: Namespace | Repositor
|
||||
onSuccess: () => {
|
||||
const queryKey = createQueryKey(namespaceOrRepository);
|
||||
return queryClient.invalidateQueries(queryKey);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
update: (permission: Permission) => mutate(permission),
|
||||
isUpdated: !!data
|
||||
isUpdated: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeletePermission = (namespaceOrRepository: Namespace | Repository) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { isLoading, error, mutate, data } = useMutation<unknown, Error, Permission>(
|
||||
permission => {
|
||||
(permission) => {
|
||||
const link = requiredLink(permission, "delete");
|
||||
return apiClient.delete(link);
|
||||
},
|
||||
@@ -144,13 +150,53 @@ export const useDeletePermission = (namespaceOrRepository: Namespace | Repositor
|
||||
onSuccess: () => {
|
||||
const queryKey = createQueryKey(namespaceOrRepository);
|
||||
return queryClient.invalidateQueries(queryKey);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
remove: (permission: Permission) => mutate(permission),
|
||||
isDeleted: !!data
|
||||
isDeleted: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
const userPermissionsKey = (user: User) => ["user", user.name, "permissions"];
|
||||
const groupPermissionsKey = (group: Group) => ["group", group.name, "permissions"];
|
||||
|
||||
export const useGroupPermissions = (group: Group) =>
|
||||
useJsonResource<GlobalPermissionsCollection>(group, "permissions", groupPermissionsKey(group));
|
||||
export const useUserPermissions = (user: User) =>
|
||||
useJsonResource<GlobalPermissionsCollection>(user, "permissions", userPermissionsKey(user));
|
||||
export const useAvailableGlobalPermissions = () =>
|
||||
useIndexJsonResource<Omit<GlobalPermissionsCollection, "_links">>("permissions");
|
||||
|
||||
const useSetEntityPermissions = (permissionCollection?: GlobalPermissionsCollection, key?: string[]) => {
|
||||
const queryClient = useQueryClient();
|
||||
const url = permissionCollection ? objectLink(permissionCollection, "overwrite") : null;
|
||||
const { isLoading, error, mutate, data } = useMutation<unknown, Error, string[]>(
|
||||
(permissions) =>
|
||||
apiClient.put(
|
||||
url!,
|
||||
{
|
||||
permissions,
|
||||
},
|
||||
"application/vnd.scmm-permissionCollection+json;v=2"
|
||||
),
|
||||
{
|
||||
onSuccess: () => queryClient.invalidateQueries(key),
|
||||
}
|
||||
);
|
||||
const setPermissions = (permissions: string[]) => mutate(permissions);
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
setPermissions: url ? setPermissions : undefined,
|
||||
isUpdated: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSetUserPermissions = (user: User, permissions?: GlobalPermissionsCollection) =>
|
||||
useSetEntityPermissions(permissions, userPermissionsKey(user));
|
||||
export const useSetGroupPermissions = (group: Group, permissions?: GlobalPermissionsCollection) =>
|
||||
useSetEntityPermissions(permissions, groupPermissionsKey(group));
|
||||
|
||||
83
scm-ui/ui-api/src/publicKeys.ts
Normal file
83
scm-ui/ui-api/src/publicKeys.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import { Me, PublicKey, PublicKeyCreation, PublicKeysCollection, User } from "@scm-manager/ui-types";
|
||||
import { ApiResult } from "./base";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { requiredLink } from "./links";
|
||||
|
||||
export const CONTENT_TYPE_PUBLIC_KEY = "application/vnd.scmm-publicKey+json;v=2";
|
||||
|
||||
export const usePublicKeys = (user: User | Me): ApiResult<PublicKeysCollection> =>
|
||||
useQuery(["user", user.name, "publicKeys"], () =>
|
||||
apiClient.get(requiredLink(user, "publicKeys")).then((r) => r.json())
|
||||
);
|
||||
|
||||
const createPublicKey =
|
||||
(link: string) =>
|
||||
async (key: PublicKeyCreation): Promise<PublicKey> => {
|
||||
const creationResponse = await apiClient.post(link, key, CONTENT_TYPE_PUBLIC_KEY);
|
||||
const location = creationResponse.headers.get("Location");
|
||||
if (!location) {
|
||||
throw new Error("Server does not return required Location header");
|
||||
}
|
||||
const apiKeyResponse = await apiClient.get(location);
|
||||
return apiKeyResponse.json();
|
||||
};
|
||||
|
||||
export const useCreatePublicKey = (user: User | Me, publicKeys: PublicKeysCollection) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, data, isLoading, error, reset } = useMutation<PublicKey, Error, PublicKeyCreation>(
|
||||
createPublicKey(requiredLink(publicKeys, "create")),
|
||||
{
|
||||
onSuccess: () => queryClient.invalidateQueries(["user", user.name, "publicKeys"]),
|
||||
}
|
||||
);
|
||||
return {
|
||||
create: (key: PublicKeyCreation) => mutate(key),
|
||||
isLoading,
|
||||
error,
|
||||
apiKey: data,
|
||||
reset,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeletePublicKey = (user: User | Me) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, PublicKey>(
|
||||
(publicKey) => {
|
||||
const deleteUrl = requiredLink(publicKey, "delete");
|
||||
return apiClient.delete(deleteUrl);
|
||||
},
|
||||
{
|
||||
onSuccess: () => queryClient.invalidateQueries(["user", user.name, "publicKeys"]),
|
||||
}
|
||||
);
|
||||
return {
|
||||
remove: (publicKey: PublicKey) => mutate(publicKey),
|
||||
isLoading,
|
||||
error,
|
||||
isDeleted: !!data,
|
||||
};
|
||||
};
|
||||
@@ -30,17 +30,17 @@ import {
|
||||
Repository,
|
||||
RepositoryCollection,
|
||||
RepositoryCreation,
|
||||
RepositoryTypeCollection
|
||||
RepositoryTypeCollection,
|
||||
} from "@scm-manager/ui-types";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { ApiResult, useIndexJsonResource, useRequiredIndexLink } from "./base";
|
||||
import { createQueryString } from "./utils";
|
||||
import { requiredLink } from "./links";
|
||||
import { objectLink, requiredLink } from "./links";
|
||||
import { repoQueryKey } from "./keys";
|
||||
import { concat } from "./urls";
|
||||
import { useEffect, useState } from "react";
|
||||
import { NotFoundError } from "./errors";
|
||||
import { MissingLinkError, NotFoundError } from "./errors";
|
||||
|
||||
export type UseRepositoriesRequest = {
|
||||
namespace?: Namespace;
|
||||
@@ -56,7 +56,7 @@ export const useRepositories = (request?: UseRepositoriesRequest): ApiResult<Rep
|
||||
const link = namespaceLink || indexLink;
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
sortBy: "namespaceAndName"
|
||||
sortBy: "namespaceAndName",
|
||||
};
|
||||
if (request?.search) {
|
||||
queryParams.q = request.search;
|
||||
@@ -66,7 +66,7 @@ export const useRepositories = (request?: UseRepositoriesRequest): ApiResult<Rep
|
||||
}
|
||||
return useQuery<RepositoryCollection, Error>(
|
||||
["repositories", request?.namespace?.namespace, request?.search || "", request?.page || 0],
|
||||
() => apiClient.get(`${link}?${createQueryString(queryParams)}`).then(response => response.json()),
|
||||
() => apiClient.get(`${link}?${createQueryString(queryParams)}`).then((response) => response.json()),
|
||||
{
|
||||
enabled: !request?.disabled,
|
||||
onSuccess: (repositories: RepositoryCollection) => {
|
||||
@@ -74,7 +74,7 @@ export const useRepositories = (request?: UseRepositoriesRequest): ApiResult<Rep
|
||||
repositories._embedded.repositories.forEach((repository: Repository) => {
|
||||
queryClient.setQueryData(["repository", repository.namespace, repository.name], repository);
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -92,14 +92,14 @@ const createRepository = (link: string) => {
|
||||
}
|
||||
return apiClient
|
||||
.post(createLink, request.repository, "application/vnd.scmm-repository+json;v=2")
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
const location = response.headers.get("Location");
|
||||
if (!location) {
|
||||
throw new Error("Server does not return required Location header");
|
||||
}
|
||||
return apiClient.get(location);
|
||||
})
|
||||
.then(response => response.json());
|
||||
.then((response) => response.json());
|
||||
};
|
||||
};
|
||||
|
||||
@@ -111,10 +111,10 @@ export const useCreateRepository = () => {
|
||||
const { mutate, data, isLoading, error } = useMutation<Repository, Error, CreateRepositoryRequest>(
|
||||
createRepository(link),
|
||||
{
|
||||
onSuccess: repository => {
|
||||
onSuccess: (repository) => {
|
||||
queryClient.setQueryData(["repository", repository.namespace, repository.name], repository);
|
||||
return queryClient.invalidateQueries(["repositories"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
@@ -123,7 +123,7 @@ export const useCreateRepository = () => {
|
||||
},
|
||||
isLoading,
|
||||
error,
|
||||
repository: data
|
||||
repository: data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -133,7 +133,7 @@ export const useRepositoryTypes = () => useIndexJsonResource<RepositoryTypeColle
|
||||
export const useRepository = (namespace: string, name: string): ApiResult<Repository> => {
|
||||
const link = useRequiredIndexLink("repositories");
|
||||
return useQuery<Repository, Error>(["repository", namespace, name], () =>
|
||||
apiClient.get(concat(link, namespace, name)).then(response => response.json())
|
||||
apiClient.get(concat(link, namespace, name)).then((response) => response.json())
|
||||
);
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ export type UseDeleteRepositoryOptions = {
|
||||
export const useDeleteRepository = (options?: UseDeleteRepositoryOptions) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, Repository>(
|
||||
repository => {
|
||||
(repository) => {
|
||||
const link = requiredLink(repository, "delete");
|
||||
return apiClient.delete(link);
|
||||
},
|
||||
@@ -155,21 +155,21 @@ export const useDeleteRepository = (options?: UseDeleteRepositoryOptions) => {
|
||||
}
|
||||
await queryClient.invalidateQueries(repoQueryKey(repository));
|
||||
await queryClient.invalidateQueries(["repositories"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
remove: (repository: Repository) => mutate(repository),
|
||||
isLoading,
|
||||
error,
|
||||
isDeleted: !!data
|
||||
isDeleted: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useUpdateRepository = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, Repository>(
|
||||
repository => {
|
||||
(repository) => {
|
||||
const link = requiredLink(repository, "update");
|
||||
return apiClient.put(link, repository, "application/vnd.scmm-repository+json;v=2");
|
||||
},
|
||||
@@ -177,21 +177,21 @@ export const useUpdateRepository = () => {
|
||||
onSuccess: async (_, repository) => {
|
||||
await queryClient.invalidateQueries(repoQueryKey(repository));
|
||||
await queryClient.invalidateQueries(["repositories"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
update: (repository: Repository) => mutate(repository),
|
||||
isLoading,
|
||||
error,
|
||||
isUpdated: !!data
|
||||
isUpdated: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useArchiveRepository = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, Repository>(
|
||||
repository => {
|
||||
(repository) => {
|
||||
const link = requiredLink(repository, "archive");
|
||||
return apiClient.post(link);
|
||||
},
|
||||
@@ -199,21 +199,21 @@ export const useArchiveRepository = () => {
|
||||
onSuccess: async (_, repository) => {
|
||||
await queryClient.invalidateQueries(repoQueryKey(repository));
|
||||
await queryClient.invalidateQueries(["repositories"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
archive: (repository: Repository) => mutate(repository),
|
||||
isLoading,
|
||||
error,
|
||||
isArchived: !!data
|
||||
isArchived: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useUnarchiveRepository = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, Repository>(
|
||||
repository => {
|
||||
(repository) => {
|
||||
const link = requiredLink(repository, "unarchive");
|
||||
return apiClient.post(link);
|
||||
},
|
||||
@@ -221,35 +221,35 @@ export const useUnarchiveRepository = () => {
|
||||
onSuccess: async (_, repository) => {
|
||||
await queryClient.invalidateQueries(repoQueryKey(repository));
|
||||
await queryClient.invalidateQueries(["repositories"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
unarchive: (repository: Repository) => mutate(repository),
|
||||
isLoading,
|
||||
error,
|
||||
isUnarchived: !!data
|
||||
isUnarchived: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useRunHealthCheck = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, Repository>(
|
||||
repository => {
|
||||
(repository) => {
|
||||
const link = requiredLink(repository, "runHealthCheck");
|
||||
return apiClient.post(link);
|
||||
},
|
||||
{
|
||||
onSuccess: async (_, repository) => {
|
||||
await queryClient.invalidateQueries(repoQueryKey(repository));
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
runHealthCheck: (repository: Repository) => mutate(repository),
|
||||
isLoading,
|
||||
error,
|
||||
isRunning: !!data
|
||||
isRunning: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -258,14 +258,14 @@ export const useExportInfo = (repository: Repository): ApiResult<ExportInfo> =>
|
||||
//TODO Refetch while exporting to update the page
|
||||
const { isLoading, error, data } = useQuery<ExportInfo, Error>(
|
||||
["repository", repository.namespace, repository.name, "exportInfo"],
|
||||
() => apiClient.get(link).then(response => response.json()),
|
||||
() => apiClient.get(link).then((response) => response.json()),
|
||||
{}
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error: error instanceof NotFoundError ? null : error,
|
||||
data
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -308,14 +308,14 @@ export const useExportRepository = () => {
|
||||
const id = setInterval(() => {
|
||||
apiClient
|
||||
.get(infolink)
|
||||
.then(r => r.json())
|
||||
.then((r) => r.json())
|
||||
.then((info: ExportInfo) => {
|
||||
if (info._links.download) {
|
||||
clearInterval(id);
|
||||
resolve(info);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
clearInterval(id);
|
||||
reject(e);
|
||||
});
|
||||
@@ -328,20 +328,49 @@ export const useExportRepository = () => {
|
||||
onSuccess: async (_, { repository }) => {
|
||||
await queryClient.invalidateQueries(repoQueryKey(repository));
|
||||
await queryClient.invalidateQueries(["repositories"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
exportRepository: (repository: Repository, options: ExportOptions) => mutate({ repository, options }),
|
||||
isLoading,
|
||||
error,
|
||||
data
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
export const usePaths = (repository: Repository, revision: string): ApiResult<Paths> => {
|
||||
const link = requiredLink(repository, "paths").replace("{revision}", revision);
|
||||
return useQuery<Paths, Error>(repoQueryKey(repository, "paths", revision), () =>
|
||||
apiClient.get(link).then(response => response.json())
|
||||
apiClient.get(link).then((response) => response.json())
|
||||
);
|
||||
};
|
||||
|
||||
type RenameRepositoryRequest = {
|
||||
name: string;
|
||||
namespace: string;
|
||||
};
|
||||
|
||||
export const useRenameRepository = (repository: Repository) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const url = objectLink(repository, "renameWithNamespace") || objectLink(repository, "rename");
|
||||
|
||||
if (!url) {
|
||||
throw new MissingLinkError(`could not find rename link on repository ${repository.namespace}/${repository.name}`);
|
||||
}
|
||||
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, RenameRepositoryRequest>(
|
||||
({ name, namespace }) => apiClient.post(url, { namespace, name }, "application/vnd.scmm-repository+json;v=2"),
|
||||
{
|
||||
onSuccess: () => queryClient.removeQueries(repoQueryKey(repository))
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
renameRepository: (namespace: string, name: string) => mutate({ namespace, name }),
|
||||
isLoading,
|
||||
error,
|
||||
isRenamed: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
|
||||
import { ApiResult, useRequiredIndexLink } from "./base";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { Link, User, UserCollection, UserCreation } from "@scm-manager/ui-types";
|
||||
import {Link, Me, User, UserCollection, UserCreation} from "@scm-manager/ui-types";
|
||||
import { apiClient } from "./apiclient";
|
||||
import { createQueryString } from "./utils";
|
||||
import { concat } from "./urls";
|
||||
import { requiredLink } from "./links";
|
||||
|
||||
export type UseUsersRequest = {
|
||||
page?: number | string;
|
||||
@@ -48,11 +49,11 @@ export const useUsers = (request?: UseUsersRequest): ApiResult<UserCollection> =
|
||||
|
||||
return useQuery<UserCollection, Error>(
|
||||
["users", request?.search || "", request?.page || 0],
|
||||
() => apiClient.get(`${indexLink}?${createQueryString(queryParams)}`).then(response => response.json()),
|
||||
() => apiClient.get(`${indexLink}?${createQueryString(queryParams)}`).then((response) => response.json()),
|
||||
{
|
||||
onSuccess: (users: UserCollection) => {
|
||||
users._embedded.users.forEach((user: User) => queryClient.setQueryData(["user", user.name], user));
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -60,7 +61,7 @@ export const useUsers = (request?: UseUsersRequest): ApiResult<UserCollection> =
|
||||
export const useUser = (name: string): ApiResult<User> => {
|
||||
const indexLink = useRequiredIndexLink("users");
|
||||
return useQuery<User, Error>(["user", name], () =>
|
||||
apiClient.get(concat(indexLink, name)).then(response => response.json())
|
||||
apiClient.get(concat(indexLink, name)).then((response) => response.json())
|
||||
);
|
||||
};
|
||||
|
||||
@@ -68,14 +69,14 @@ const createUser = (link: string) => {
|
||||
return (user: UserCreation) => {
|
||||
return apiClient
|
||||
.post(link, user, "application/vnd.scmm-user+json;v=2")
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
const location = response.headers.get("Location");
|
||||
if (!location) {
|
||||
throw new Error("Server does not return required Location header");
|
||||
}
|
||||
return apiClient.get(location);
|
||||
})
|
||||
.then(response => response.json());
|
||||
.then((response) => response.json());
|
||||
};
|
||||
};
|
||||
|
||||
@@ -83,23 +84,23 @@ export const useCreateUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const link = useRequiredIndexLink("users");
|
||||
const { mutate, data, isLoading, error } = useMutation<User, Error, UserCreation>(createUser(link), {
|
||||
onSuccess: user => {
|
||||
onSuccess: (user) => {
|
||||
queryClient.setQueryData(["user", user.name], user);
|
||||
return queryClient.invalidateQueries(["users"]);
|
||||
}
|
||||
},
|
||||
});
|
||||
return {
|
||||
create: (user: UserCreation) => mutate(user),
|
||||
isLoading,
|
||||
error,
|
||||
user: data
|
||||
user: data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useUpdateUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, User>(
|
||||
user => {
|
||||
(user) => {
|
||||
const updateUrl = (user._links.update as Link).href;
|
||||
return apiClient.put(updateUrl, user, "application/vnd.scmm-user+json;v=2");
|
||||
},
|
||||
@@ -107,21 +108,21 @@ export const useUpdateUser = () => {
|
||||
onSuccess: async (_, user) => {
|
||||
await queryClient.invalidateQueries(["user", user.name]);
|
||||
await queryClient.invalidateQueries(["users"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
update: (user: User) => mutate(user),
|
||||
isLoading,
|
||||
error,
|
||||
isUpdated: !!data
|
||||
isUpdated: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, User>(
|
||||
user => {
|
||||
(user) => {
|
||||
const deleteUrl = (user._links.delete as Link).href;
|
||||
return apiClient.delete(deleteUrl);
|
||||
},
|
||||
@@ -129,14 +130,14 @@ export const useDeleteUser = () => {
|
||||
onSuccess: async (_, name) => {
|
||||
await queryClient.invalidateQueries(["user", name]);
|
||||
await queryClient.invalidateQueries(["users"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
remove: (user: User) => mutate(user),
|
||||
isLoading,
|
||||
error,
|
||||
isDeleted: !!data
|
||||
isDeleted: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -144,7 +145,7 @@ const convertToInternal = (url: string, newPassword: string) => {
|
||||
return apiClient.put(
|
||||
url,
|
||||
{
|
||||
newPassword
|
||||
newPassword,
|
||||
},
|
||||
"application/vnd.scmm-user+json;v=2"
|
||||
);
|
||||
@@ -167,32 +168,73 @@ export const useConvertToInternal = () => {
|
||||
onSuccess: async (_, { user }) => {
|
||||
await queryClient.invalidateQueries(["user", user.name]);
|
||||
await queryClient.invalidateQueries(["users"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
convertToInternal: (user: User, password: string) => mutate({ user, password }),
|
||||
isLoading,
|
||||
error,
|
||||
isConverted: !!data
|
||||
isConverted: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useConvertToExternal = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, isLoading, error, data } = useMutation<unknown, Error, User>(
|
||||
user => convertToExternal((user._links.convertToExternal as Link).href),
|
||||
(user) => convertToExternal((user._links.convertToExternal as Link).href),
|
||||
{
|
||||
onSuccess: async (_, user) => {
|
||||
await queryClient.invalidateQueries(["user", user.name]);
|
||||
await queryClient.invalidateQueries(["users"]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
convertToExternal: (user: User) => mutate(user),
|
||||
isLoading,
|
||||
error,
|
||||
isConverted: !!data
|
||||
isConverted: !!data,
|
||||
};
|
||||
};
|
||||
|
||||
const CONTENT_TYPE_PASSWORD_OVERWRITE = "application/vnd.scmm-passwordOverwrite+json;v=2";
|
||||
|
||||
export const useSetUserPassword = (user: User) => {
|
||||
const { data, isLoading, error, mutate, reset } = useMutation<unknown, Error, string>((password) =>
|
||||
apiClient.put(
|
||||
requiredLink(user, "password"),
|
||||
{
|
||||
newPassword: password,
|
||||
},
|
||||
CONTENT_TYPE_PASSWORD_OVERWRITE
|
||||
)
|
||||
);
|
||||
return {
|
||||
setPassword: (newPassword: string) => mutate(newPassword),
|
||||
passwordOverwritten: !!data,
|
||||
isLoading,
|
||||
error,
|
||||
reset
|
||||
};
|
||||
};
|
||||
|
||||
const CONTENT_TYPE_PASSWORD_CHANGE = "application/vnd.scmm-passwordChange+json;v=2";
|
||||
|
||||
type ChangeUserPasswordRequest = {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
};
|
||||
|
||||
export const useChangeUserPassword = (user: User | Me) => {
|
||||
const { data, isLoading, error, mutate, reset } = useMutation<unknown, Error, ChangeUserPasswordRequest>((request) =>
|
||||
apiClient.put(requiredLink(user, "password"), request, CONTENT_TYPE_PASSWORD_CHANGE)
|
||||
);
|
||||
return {
|
||||
changePassword: (oldPassword: string, newPassword: string) => mutate({ oldPassword, newPassword }),
|
||||
passwordChanged: !!data,
|
||||
isLoading,
|
||||
error,
|
||||
reset
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user