批量删除

This commit is contained in:
zhx 2026-07-05 14:55:43 +08:00
parent f12b2767c0
commit 4874bba9ac
8 changed files with 413 additions and 54 deletions

View File

@ -15,7 +15,17 @@
"operationId": "listAchievementDefinitions",
"responses": {
"200": {
"$ref": "#/components/responses/EmptyResponse"
"description": "批量创建的礼物",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
},
"x-permission": "achievement:view",
@ -25,7 +35,17 @@
"operationId": "createAchievementDefinition",
"responses": {
"200": {
"$ref": "#/components/responses/EmptyResponse"
"description": "批量删除的资源",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
},
"x-permission": "achievement:create",
@ -2353,6 +2373,21 @@
"x-permissions": ["gift:create"]
}
},
"/admin/gifts/batch": {
"post": {
"operationId": "batchCreateGifts",
"requestBody": {
"$ref": "#/components/requestBodies/BatchCreateGiftsRequest"
},
"responses": {
"200": {
"$ref": "#/components/responses/EmptyResponse"
}
},
"x-permission": "gift:create",
"x-permissions": ["gift:create"]
}
},
"/admin/gifts/{gift_id}": {
"put": {
"operationId": "updateGift",
@ -4205,6 +4240,21 @@
"x-permissions": ["resource:create"]
}
},
"/admin/resources/batch-delete": {
"post": {
"operationId": "batchDeleteResources",
"requestBody": {
"$ref": "#/components/requestBodies/BatchDeleteResourcesRequest"
},
"responses": {
"200": {
"$ref": "#/components/responses/EmptyResponse"
}
},
"x-permission": "resource:delete",
"x-permissions": ["resource:delete"]
}
},
"/admin/resources/{resource_id}": {
"get": {
"operationId": "getResource",
@ -5955,6 +6005,42 @@
}
},
"requestBodies": {
"BatchCreateGiftsRequest": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["items"],
"properties": {
"items": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
}
},
"BatchDeleteResourcesRequest": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["resourceIds"],
"properties": {
"resourceIds": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
},
"BatchStatusRequest": {
"content": {
"application/json": {

View File

@ -1,6 +1,8 @@
import { afterEach, expect, test, vi } from "vitest";
import { setAccessToken } from "@/shared/api/request";
import {
batchCreateGifts,
batchDeleteResources,
createEmojiPack,
createGift,
createResource,
@ -85,6 +87,7 @@ test("resource APIs use generated admin paths", async () => {
status: "active",
});
await deleteResource(11);
await batchDeleteResources([11, 12]);
await listGifts({ page: 1, page_size: 10, region_id: 1001 });
await createGift({
chargeAssetType: "COIN",
@ -104,6 +107,26 @@ test("resource APIs use generated admin paths", async () => {
sortOrder: 0,
status: "active",
});
await batchCreateGifts([
{
chargeAssetType: "COIN",
coinPrice: 20,
cpRelationType: "",
effectiveAtMs: 1777766400000,
effectiveFromMs: 0,
effectiveToMs: 0,
effectTypes: ["animation"],
giftId: "ring",
giftTypeCode: "normal",
name: "Ring",
presentationJson: "{}",
priceVersion: "default",
regionIds: [0],
resourceId: 12,
sortOrder: 1,
status: "active",
},
]);
await enableResource(11);
await disableResource(11);
await createResourceGroup({
@ -180,23 +203,25 @@ test("resource APIs use generated admin paths", async () => {
const [createUrl, createInit] = vi.mocked(fetch).mock.calls[1];
const [updateUrl, updateInit] = vi.mocked(fetch).mock.calls[2];
const [deleteResourceUrl, deleteResourceInit] = vi.mocked(fetch).mock.calls[3];
const [giftListUrl, giftListInit] = vi.mocked(fetch).mock.calls[4];
const [createGiftUrl, createGiftInit] = vi.mocked(fetch).mock.calls[5];
const [enableUrl, enableInit] = vi.mocked(fetch).mock.calls[6];
const [disableUrl, disableInit] = vi.mocked(fetch).mock.calls[7];
const [createGroupUrl, createGroupInit] = vi.mocked(fetch).mock.calls[8];
const [updateGroupUrl, updateGroupInit] = vi.mocked(fetch).mock.calls[9];
const [enableGroupUrl, enableGroupInit] = vi.mocked(fetch).mock.calls[10];
const [disableGroupUrl, disableGroupInit] = vi.mocked(fetch).mock.calls[11];
const [updateGiftUrl, updateGiftInit] = vi.mocked(fetch).mock.calls[12];
const [enableGiftUrl, enableGiftInit] = vi.mocked(fetch).mock.calls[13];
const [disableGiftUrl, disableGiftInit] = vi.mocked(fetch).mock.calls[14];
const [deleteGiftUrl, deleteGiftInit] = vi.mocked(fetch).mock.calls[15];
const [grantListUrl, grantListInit] = vi.mocked(fetch).mock.calls[16];
const [grantTargetUrl, grantTargetInit] = vi.mocked(fetch).mock.calls[17];
const [grantResourceUrl, grantResourceInit] = vi.mocked(fetch).mock.calls[18];
const [grantGroupUrl, grantGroupInit] = vi.mocked(fetch).mock.calls[19];
const [revokeGrantUrl, revokeGrantInit] = vi.mocked(fetch).mock.calls[20];
const [batchDeleteResourceUrl, batchDeleteResourceInit] = vi.mocked(fetch).mock.calls[4];
const [giftListUrl, giftListInit] = vi.mocked(fetch).mock.calls[5];
const [createGiftUrl, createGiftInit] = vi.mocked(fetch).mock.calls[6];
const [batchCreateGiftUrl, batchCreateGiftInit] = vi.mocked(fetch).mock.calls[7];
const [enableUrl, enableInit] = vi.mocked(fetch).mock.calls[8];
const [disableUrl, disableInit] = vi.mocked(fetch).mock.calls[9];
const [createGroupUrl, createGroupInit] = vi.mocked(fetch).mock.calls[10];
const [updateGroupUrl, updateGroupInit] = vi.mocked(fetch).mock.calls[11];
const [enableGroupUrl, enableGroupInit] = vi.mocked(fetch).mock.calls[12];
const [disableGroupUrl, disableGroupInit] = vi.mocked(fetch).mock.calls[13];
const [updateGiftUrl, updateGiftInit] = vi.mocked(fetch).mock.calls[14];
const [enableGiftUrl, enableGiftInit] = vi.mocked(fetch).mock.calls[15];
const [disableGiftUrl, disableGiftInit] = vi.mocked(fetch).mock.calls[16];
const [deleteGiftUrl, deleteGiftInit] = vi.mocked(fetch).mock.calls[17];
const [grantListUrl, grantListInit] = vi.mocked(fetch).mock.calls[18];
const [grantTargetUrl, grantTargetInit] = vi.mocked(fetch).mock.calls[19];
const [grantResourceUrl, grantResourceInit] = vi.mocked(fetch).mock.calls[20];
const [grantGroupUrl, grantGroupInit] = vi.mocked(fetch).mock.calls[21];
const [revokeGrantUrl, revokeGrantInit] = vi.mocked(fetch).mock.calls[22];
expect(String(listUrl)).toContain("/api/v1/admin/resources?");
expect(String(listUrl)).toContain("resource_type=gift");
@ -222,12 +247,18 @@ test("resource APIs use generated admin paths", async () => {
});
expect(String(deleteResourceUrl)).toContain("/api/v1/admin/resources/11");
expect(deleteResourceInit?.method).toBe("DELETE");
expect(String(batchDeleteResourceUrl)).toContain("/api/v1/admin/resources/batch-delete");
expect(batchDeleteResourceInit?.method).toBe("POST");
expect(JSON.parse(String(batchDeleteResourceInit?.body))).toEqual({ resourceIds: [11, 12] });
expect(String(giftListUrl)).toContain("/api/v1/admin/gifts?");
expect(String(giftListUrl)).toContain("region_id=1001");
expect(giftListInit?.method).toBe("GET");
expect(String(createGiftUrl)).toContain("/api/v1/admin/gifts");
expect(createGiftInit?.method).toBe("POST");
expect(JSON.parse(String(createGiftInit?.body))).toMatchObject({ giftId: "rose", resourceId: 11 });
expect(String(batchCreateGiftUrl)).toContain("/api/v1/admin/gifts/batch");
expect(batchCreateGiftInit?.method).toBe("POST");
expect(JSON.parse(String(batchCreateGiftInit?.body))).toMatchObject({ items: [{ giftId: "ring", resourceId: 12 }] });
expect(String(enableUrl)).toContain("/api/v1/admin/resources/11/enable");
expect(enableInit?.method).toBe("POST");
expect(String(disableUrl)).toContain("/api/v1/admin/resources/11/disable");

View File

@ -224,6 +224,10 @@ export interface GiftPayload {
regionIds: number[];
}
export interface BatchCreateGiftsPayload {
items: GiftPayload[];
}
export interface GiftTypePayload {
displayName: string;
tabName: string;
@ -337,6 +341,10 @@ export interface ResourceShopItemsPayload {
items: ResourceShopItemPayload[];
}
export interface BatchDeleteResourcesPayload {
resourceIds: EntityId[];
}
export function listResources(query: PageQuery = {}): Promise<ApiPage<ResourceDto>> {
const endpoint = API_ENDPOINTS.listResources;
return apiRequest<ApiPage<ResourceDto>>(apiEndpointPath(API_OPERATIONS.listResources), {
@ -371,6 +379,17 @@ export function deleteResource(resourceId: EntityId): Promise<ResourceDto> {
});
}
export function batchDeleteResources(resourceIds: EntityId[]): Promise<ResourceDto[]> {
const endpoint = API_ENDPOINTS.batchDeleteResources;
return apiRequest<ResourceDto[], BatchDeleteResourcesPayload>(
apiEndpointPath(API_OPERATIONS.batchDeleteResources),
{
body: { resourceIds },
method: endpoint.method,
},
);
}
export function updateResourceMp4Layouts(items: ResourceMp4LayoutUpdatePayload[]): Promise<ResourceDto[]> {
return apiRequest<ResourceDto[], ResourceMp4LayoutsPayload>("/v1/admin/resources/mp4-layouts/batch", {
body: { items },
@ -548,6 +567,14 @@ export function createGift(payload: GiftPayload): Promise<GiftDto> {
});
}
export function batchCreateGifts(payloads: GiftPayload[]): Promise<GiftDto[]> {
const endpoint = API_ENDPOINTS.batchCreateGifts;
return apiRequest<GiftDto[], BatchCreateGiftsPayload>(apiEndpointPath(API_OPERATIONS.batchCreateGifts), {
body: { items: payloads },
method: endpoint.method,
});
}
export function updateGift(giftId: string, payload: GiftPayload): Promise<GiftDto> {
const endpoint = API_ENDPOINTS.updateGift;
return apiRequest<GiftDto, GiftPayload>(apiEndpointPath(API_OPERATIONS.updateGift, { gift_id: giftId }), {

View File

@ -0,0 +1,117 @@
import { act, renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { useGiftListPage } from "./useResourcePages.js";
const mocks = vi.hoisted(() => ({
batchCreateGifts: vi.fn(),
can: vi.fn(),
createGift: vi.fn(),
listGifts: vi.fn(),
listGiftTypes: vi.fn(),
listResources: vi.fn(),
reload: vi.fn(),
showToast: vi.fn(),
usePaginatedQuery: vi.fn(),
}));
vi.mock("@/app/auth/AuthProvider.jsx", () => ({
useAuth: () => ({ can: mocks.can }),
}));
vi.mock("@/shared/hooks/usePaginatedQuery.js", () => ({
usePaginatedQuery: mocks.usePaginatedQuery,
}));
vi.mock("@/shared/hooks/useRegionOptions.js", () => ({
useRegionOptions: () => ({ loadingRegions: false, regionOptions: [{ label: "全球", value: "0" }] }),
}));
vi.mock("@/shared/ui/ConfirmProvider.jsx", () => ({
useConfirm: () => vi.fn(),
}));
vi.mock("@/shared/ui/ToastProvider.jsx", () => ({
useToast: () => ({ showToast: mocks.showToast }),
}));
vi.mock("@/features/resources/api", async (importOriginal) => ({
...(await importOriginal()),
batchCreateGifts: mocks.batchCreateGifts,
createGift: mocks.createGift,
listGifts: mocks.listGifts,
listGiftTypes: mocks.listGiftTypes,
listResources: mocks.listResources,
}));
beforeEach(() => {
mocks.can.mockReturnValue(true);
mocks.batchCreateGifts.mockResolvedValue([]);
mocks.createGift.mockResolvedValue({});
mocks.listGifts.mockResolvedValue({ items: [], page: 1, pageSize: 100, total: 0 });
mocks.listGiftTypes.mockResolvedValue([]);
mocks.listResources.mockResolvedValue({ items: [], page: 1, pageSize: 100, total: 0 });
mocks.reload.mockResolvedValue(undefined);
mocks.usePaginatedQuery.mockReturnValue({
data: { items: [], page: 1, pageSize: 50, total: 0 },
error: null,
loading: false,
reload: mocks.reload,
});
});
afterEach(() => {
vi.clearAllMocks();
});
test("creating multiple gift drafts calls the batch create endpoint once", async () => {
const { result } = renderHook(() => useGiftListPage());
await act(async () => {
result.current.openCreateGift();
});
await waitFor(() => expect(result.current.activeAction).toBe("create"));
await act(async () => {
result.current.setForm({
...giftDraft({ giftId: "rose", name: "Rose", resourceId: "11" }),
items: [
giftDraft({ giftId: "rose", name: "Rose", resourceId: "11" }),
giftDraft({ giftId: "ring", name: "Ring", resourceId: "12" }),
],
resourceId: "11",
});
});
await act(async () => {
await result.current.submitGift({ preventDefault: vi.fn() });
});
expect(mocks.batchCreateGifts).toHaveBeenCalledTimes(1);
expect(mocks.batchCreateGifts).toHaveBeenCalledWith([
expect.objectContaining({ giftId: "rose", name: "Rose", resourceId: 11 }),
expect.objectContaining({ giftId: "ring", name: "Ring", resourceId: 12 }),
]);
expect(mocks.createGift).not.toHaveBeenCalled();
expect(mocks.reload).toHaveBeenCalledTimes(1);
});
function giftDraft(patch = {}) {
return {
chargeAssetType: "COIN",
coinPrice: "10",
cpRelationType: "",
effectTypes: ["animation"],
effectiveFrom: "",
effectiveTo: "",
enabled: true,
giftId: "gift",
giftTypeCode: "normal",
name: "Gift",
presentationJson: "{}",
priceVersion: "default",
regionIds: ["0"],
resourceId: "1",
sortOrder: "0",
...patch,
};
}

View File

@ -3,6 +3,7 @@ import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { useResourceListPage } from "./useResourcePages.js";
const mocks = vi.hoisted(() => ({
batchDeleteResources: vi.fn(),
can: vi.fn(),
confirm: vi.fn(),
deleteResource: vi.fn(),
@ -29,12 +30,14 @@ vi.mock("@/shared/ui/ToastProvider.jsx", () => ({
vi.mock("@/features/resources/api", async (importOriginal) => ({
...(await importOriginal()),
batchDeleteResources: mocks.batchDeleteResources,
deleteResource: mocks.deleteResource,
}));
beforeEach(() => {
mocks.can.mockReturnValue(true);
mocks.confirm.mockResolvedValue(true);
mocks.batchDeleteResources.mockResolvedValue([]);
mocks.deleteResource.mockResolvedValue({});
mocks.reload.mockResolvedValue(undefined);
mocks.usePaginatedQuery.mockReturnValue({
@ -76,14 +79,14 @@ test("batch resource delete hides successful rows without reloading the list", a
});
expect(deletedResourceIds).toEqual([11, 12]);
expect(mocks.deleteResource).toHaveBeenNthCalledWith(1, 11);
expect(mocks.deleteResource).toHaveBeenNthCalledWith(2, 12);
expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12]);
expect(mocks.deleteResource).not.toHaveBeenCalled();
expect(mocks.reload).not.toHaveBeenCalled();
expect(result.current.data.items).toEqual([]);
expect(mocks.showToast).toHaveBeenCalledWith("已删除 2 个资源", "success");
});
test("batch resource delete continues after an item fails and hides successful rows", async () => {
test("failed batch resource delete keeps all rows visible", async () => {
mocks.usePaginatedQuery.mockReturnValue({
data: {
items: [
@ -99,9 +102,7 @@ test("batch resource delete continues after an item fails and hides successful r
loading: false,
reload: mocks.reload,
});
mocks.deleteResource.mockImplementation((resourceId) =>
resourceId === 12 ? Promise.reject(new Error("delete failed")) : Promise.resolve({}),
);
mocks.batchDeleteResources.mockRejectedValueOnce(new Error("delete failed"));
const { result } = renderHook(() => useResourceListPage());
let deletedResourceIds = [];
@ -113,12 +114,11 @@ test("batch resource delete continues after an item fails and hides successful r
]);
});
expect(deletedResourceIds).toEqual([11, 13]);
expect(mocks.deleteResource).toHaveBeenNthCalledWith(1, 11);
expect(mocks.deleteResource).toHaveBeenNthCalledWith(2, 12);
expect(mocks.deleteResource).toHaveBeenNthCalledWith(3, 13);
expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([12]);
expect(mocks.showToast).toHaveBeenCalledWith("已删除 2 个资源1 个删除失败delete failed", "error");
expect(deletedResourceIds).toEqual([]);
expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12, 13]);
expect(mocks.deleteResource).not.toHaveBeenCalled();
expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([11, 12, 13]);
expect(mocks.showToast).toHaveBeenCalledWith("delete failed", "error");
});
test("failed resource delete keeps the row visible", async () => {

View File

@ -6,6 +6,8 @@ import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import { emptyUserIdentityFilter, userIdentityFilterParams } from "@/shared/ui/userIdentityFilter.js";
import {
batchCreateGifts,
batchDeleteResources as requestBatchDeleteResources,
createEmojiPack,
createGift,
createResource,
@ -593,32 +595,23 @@ export function useResourceListPage() {
}
const deletedResourceIds = [];
const failedDeletes = [];
setLoadingAction(
targetResources.length > 1 ? "resource-delete-batch" : `resource-delete-${targetResources[0].resourceId}`,
);
try {
for (const resource of targetResources) {
try {
await requestDeleteResource(resource.resourceId);
deletedResourceIds.push(resource.resourceId);
} catch (err) {
failedDeletes.push({ err, resource });
}
if (targetResources.length > 1) {
await requestBatchDeleteResources(targetResources.map((resource) => resource.resourceId));
deletedResourceIds.push(...targetResources.map((resource) => resource.resourceId));
} else {
await requestDeleteResource(targetResources[0].resourceId);
deletedResourceIds.push(targetResources[0].resourceId);
}
hideResourceRows(deletedResourceIds);
if (failedDeletes.length) {
const firstError = failedDeletes[0].err?.message || "删除资源失败";
showToast(
deletedResourceIds.length
? `已删除 ${deletedResourceIds.length} 个资源,${failedDeletes.length} 个删除失败:${firstError}`
: firstError,
"error",
);
return deletedResourceIds;
}
showToast(targetResources.length > 1 ? `已删除 ${deletedResourceIds.length} 个资源` : "资源已删除", "success");
return deletedResourceIds;
} catch (err) {
showToast(err.message || "删除资源失败", "error");
return [];
} finally {
setLoadingAction("");
}
@ -1510,9 +1503,12 @@ export function useGiftListPage() {
draft,
payload: buildGiftPayload(parseForm(giftFormSchema, draft)),
}));
for (const { draft, payload } of payloads) {
await createGift(payload);
createdDrafts.push(draft);
if (payloads.length > 1) {
await batchCreateGifts(payloads.map(({ payload }) => payload));
createdDrafts.push(...payloads.map(({ draft }) => draft));
} else {
await createGift(payloads[0].payload);
createdDrafts.push(payloads[0].draft);
}
}
showToast(editing ? "礼物已更新" : `已创建 ${createDrafts.length} 个礼物`, "success");

View File

@ -26,6 +26,8 @@ export const API_OPERATIONS = {
appSetPassword: "appSetPassword",
appUnbanUser: "appUnbanUser",
appUpdateUser: "appUpdateUser",
batchCreateGifts: "batchCreateGifts",
batchDeleteResources: "batchDeleteResources",
batchUpdateUserStatus: "batchUpdateUserStatus",
cancelRoomPin: "cancelRoomPin",
changePassword: "changePassword",
@ -436,6 +438,20 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
permission: "app-user:update",
permissions: ["app-user:update"]
},
batchCreateGifts: {
method: "POST",
operationId: API_OPERATIONS.batchCreateGifts,
path: "/v1/admin/gifts/batch",
permission: "gift:create",
permissions: ["gift:create"]
},
batchDeleteResources: {
method: "POST",
operationId: API_OPERATIONS.batchDeleteResources,
path: "/v1/admin/resources/batch-delete",
permission: "resource:delete",
permissions: ["resource:delete"]
},
batchUpdateUserStatus: {
method: "POST",
operationId: API_OPERATIONS.batchUpdateUserStatus,

View File

@ -1620,6 +1620,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/admin/gifts/batch": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["batchCreateGifts"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/gifts/{gift_id}": {
parameters: {
query?: never;
@ -2660,6 +2676,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/admin/resources/batch-delete": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["batchDeleteResources"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/resources/{resource_id}": {
parameters: {
query?: never;
@ -4601,6 +4633,20 @@ export interface components {
Status: string;
};
requestBodies: {
BatchCreateGiftsRequest: {
content: {
"application/json": {
items: Record<string, never>[];
};
};
};
BatchDeleteResourcesRequest: {
content: {
"application/json": {
resourceIds: number[];
};
};
};
BatchStatusRequest: {
content: {
"application/json": {
@ -4729,7 +4775,15 @@ export interface operations {
};
requestBody?: never;
responses: {
200: components["responses"]["EmptyResponse"];
/** @description 批量创建的礼物 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": Record<string, never>[];
};
};
};
};
createAchievementDefinition: {
@ -4741,7 +4795,15 @@ export interface operations {
};
requestBody?: never;
responses: {
200: components["responses"]["EmptyResponse"];
/** @description 批量删除的资源 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": Record<string, never>[];
};
};
};
};
updateAchievementDefinition: {
@ -6631,6 +6693,18 @@ export interface operations {
200: components["responses"]["EmptyResponse"];
};
};
batchCreateGifts: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: components["requestBodies"]["BatchCreateGiftsRequest"];
responses: {
200: components["responses"]["EmptyResponse"];
};
};
updateGift: {
parameters: {
query?: never;
@ -7873,6 +7947,18 @@ export interface operations {
200: components["responses"]["EmptyResponse"];
};
};
batchDeleteResources: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: components["requestBodies"]["BatchDeleteResourcesRequest"];
responses: {
200: components["responses"]["EmptyResponse"];
};
};
getResource: {
parameters: {
query?: never;