diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/dto/SkillSummaryResponse.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/dto/SkillSummaryResponse.java index e5f51c0d..4cc2e91e 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/dto/SkillSummaryResponse.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/dto/SkillSummaryResponse.java @@ -8,6 +8,7 @@ public record SkillSummaryResponse( String slug, String displayName, String summary, + String status, Long downloadCount, Integer starCount, BigDecimal ratingAvg, diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java index 5a30ad70..1b465f07 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/MySkillAppService.java @@ -124,6 +124,7 @@ public class MySkillAppService { skill.getSlug(), skill.getDisplayName(), skill.getSummary(), + skill.getStatus().name(), skill.getDownloadCount(), skill.getStarCount(), skill.getRatingAvg(), diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillSearchAppService.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillSearchAppService.java index 3746a9b8..7c1b4a88 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillSearchAppService.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillSearchAppService.java @@ -147,6 +147,7 @@ public class SkillSearchAppService { skill.getSlug(), skill.getDisplayName(), skill.getSummary(), + skill.getStatus().name(), skill.getDownloadCount(), skill.getStarCount(), skill.getRatingAvg(), diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubCompatControllerTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubCompatControllerTest.java index b8cdeffe..4a494005 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubCompatControllerTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubCompatControllerTest.java @@ -56,6 +56,7 @@ class ClawHubCompatControllerTest { "my-skill", "My Skill", "test summary", + "ACTIVE", 10L, 5, BigDecimal.valueOf(4.5), diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubRegistryControllerTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubRegistryControllerTest.java index 3738a23e..cdc1d8f1 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubRegistryControllerTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/ClawHubRegistryControllerTest.java @@ -75,6 +75,7 @@ class ClawHubRegistryControllerTest { "global-skill", "Global Skill", "global summary", + "ACTIVE", 10L, 5, BigDecimal.ZERO, @@ -89,6 +90,7 @@ class ClawHubRegistryControllerTest { "team-skill", "Team Skill", "team summary", + "ACTIVE", 20L, 8, BigDecimal.ONE, diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 9507434a..8f920269 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -412,6 +412,35 @@ export const accountApi = { }, } +export const skillLifecycleApi = { + async archiveSkill(namespace: string, slug: string, reason?: string): Promise { + const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace + await fetchJson(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/archive`, { + method: 'POST', + headers: await ensureCsrfHeaders({ + 'Content-Type': 'application/json', + }), + body: JSON.stringify(reason?.trim() ? { reason: reason.trim() } : {}), + }) + }, + + async unarchiveSkill(namespace: string, slug: string): Promise { + const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace + await fetchJson(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/unarchive`, { + method: 'POST', + headers: await ensureCsrfHeaders(), + }) + }, + + async deleteVersion(namespace: string, slug: string, version: string): Promise { + const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace + await fetchJson(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions/${encodeURIComponent(version)}`, { + method: 'DELETE', + headers: await ensureCsrfHeaders(), + }) + }, +} + export const tokenApi = { async getTokens(params?: { page?: number, size?: number }): Promise<{ items: ApiToken[], total: number, page: number, size: number }> { const page = await unwrap<{ items: ApiToken[], total: number, page: number, size: number }>(client.GET('/api/v1/tokens', { diff --git a/web/src/api/types.ts b/web/src/api/types.ts index 67a218a2..2b165998 100644 --- a/web/src/api/types.ts +++ b/web/src/api/types.ts @@ -107,6 +107,7 @@ export interface SkillSummary { slug: string displayName: string summary?: string + status?: string downloadCount: number starCount: number ratingAvg?: number diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 4dceae8d..2c174376 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -190,8 +190,21 @@ "title": "My Skills", "subtitle": "Manage your published skills", "publishNew": "Publish New Skill", + "archive": "Archive", + "unarchive": "Restore", + "statusArchived": "Archived", "statusPendingReview": "Pending Review", "statusPublished": "Published", + "archiveConfirmTitle": "Archive skill", + "archiveConfirmDescription": "After archiving, regular users will no longer be able to view or download \"{{skill}}\". Continue?", + "unarchiveConfirmTitle": "Restore skill", + "unarchiveConfirmDescription": "\"{{skill}}\" will become visible again and can publish new versions after being restored.", + "archiveSuccessTitle": "Skill archived", + "archiveSuccessDescription": "\"{{skill}}\" has been archived.", + "archiveErrorTitle": "Failed to archive skill", + "unarchiveSuccessTitle": "Skill restored", + "unarchiveSuccessDescription": "\"{{skill}}\" has been restored and can publish new versions again.", + "unarchiveErrorTitle": "Failed to restore skill", "emptyTitle": "No skills yet", "emptyDescription": "Start publishing your first skill", "publishSkill": "Publish Skill" @@ -372,10 +385,34 @@ "loginToRate": "Login to star and rate", "install": "Install", "download": "Download", + "lifecycle": "Lifecycle", + "lifecycleHint": "You can archive this skill. Archived skills are hidden from regular users and cannot be downloaded.", + "archivedPublishHint": "This skill is archived. Restore it before publishing a new version.", + "archivedInstallHint": "This skill is archived and not available for public download.", + "statusActive": "Active", + "statusArchived": "Archived", + "statusHidden": "Hidden", "governance": "Governance", "processing": "Processing...", + "archiveSkill": "Archive Skill", "hideSkill": "Hide Skill", "unhideSkill": "Unhide Skill", + "archiveConfirmTitle": "Archive skill", + "archiveConfirmDescription": "After archiving, regular users will no longer be able to view or download \"{{skill}}\". Continue?", + "unarchiveConfirmTitle": "Restore skill", + "unarchiveConfirmDescription": "\"{{skill}}\" will become visible again and can publish new versions after being restored.", + "archiveSuccessTitle": "Skill archived", + "archiveSuccessDescription": "\"{{skill}}\" has been archived.", + "archiveErrorTitle": "Failed to archive skill", + "unarchiveSuccessTitle": "Skill restored", + "unarchiveSuccessDescription": "\"{{skill}}\" has been restored.", + "unarchiveErrorTitle": "Failed to restore skill", + "deleteVersion": "Delete Version", + "deleteVersionConfirmTitle": "Delete version", + "deleteVersionConfirmDescription": "Version {{version}} cannot be recovered after deletion. Continue?", + "deleteVersionSuccessTitle": "Version deleted", + "deleteVersionSuccessDescription": "Version {{version}} has been deleted.", + "deleteVersionErrorTitle": "Failed to delete version", "yankVersion": "Yank Current Version", "reportSkill": "Report Skill", "reportDialogTitle": "Report skill", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 4024fb8a..451e7ef7 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -190,8 +190,21 @@ "title": "我的技能", "subtitle": "管理你发布的技能", "publishNew": "发布新技能", + "archive": "归档", + "unarchive": "恢复", + "statusArchived": "已归档", "statusPendingReview": "审核中", "statusPublished": "已发布", + "archiveConfirmTitle": "确认归档技能", + "archiveConfirmDescription": "归档后普通用户将无法看到或下载“{{skill}}”,确定继续吗?", + "unarchiveConfirmTitle": "确认恢复技能", + "unarchiveConfirmDescription": "恢复后“{{skill}}”会重新对外可见,并允许继续发布新版本。", + "archiveSuccessTitle": "技能已归档", + "archiveSuccessDescription": "“{{skill}}”已归档。", + "archiveErrorTitle": "归档技能失败", + "unarchiveSuccessTitle": "技能已恢复", + "unarchiveSuccessDescription": "“{{skill}}”已恢复,可继续发布新版本。", + "unarchiveErrorTitle": "恢复技能失败", "emptyTitle": "还没有技能", "emptyDescription": "开始发布你的第一个技能吧", "publishSkill": "发布技能" @@ -372,10 +385,34 @@ "loginToRate": "登录后可以收藏和评分", "install": "安装", "download": "下载", + "lifecycle": "生命周期管理", + "lifecycleHint": "你可以归档这个技能,归档后普通用户将无法查看或下载。", + "archivedPublishHint": "该技能已归档,请先恢复后再继续发布新版本。", + "archivedInstallHint": "该技能已归档,普通用户不可下载。", + "statusActive": "正常", + "statusArchived": "已归档", + "statusHidden": "已隐藏", "governance": "治理操作", "processing": "处理中...", + "archiveSkill": "归档技能", "hideSkill": "隐藏技能", "unhideSkill": "恢复技能", + "archiveConfirmTitle": "确认归档技能", + "archiveConfirmDescription": "归档后普通用户将无法看到或下载“{{skill}}”,确定继续吗?", + "unarchiveConfirmTitle": "确认恢复技能", + "unarchiveConfirmDescription": "恢复后“{{skill}}”会重新对外可见,并允许继续发布新版本。", + "archiveSuccessTitle": "技能已归档", + "archiveSuccessDescription": "“{{skill}}”已归档。", + "archiveErrorTitle": "归档技能失败", + "unarchiveSuccessTitle": "技能已恢复", + "unarchiveSuccessDescription": "“{{skill}}”已恢复。", + "unarchiveErrorTitle": "恢复技能失败", + "deleteVersion": "删除版本", + "deleteVersionConfirmTitle": "确认删除版本", + "deleteVersionConfirmDescription": "版本 {{version}} 删除后无法恢复,确定继续吗?", + "deleteVersionSuccessTitle": "版本已删除", + "deleteVersionSuccessDescription": "版本 {{version}} 已删除。", + "deleteVersionErrorTitle": "删除版本失败", "yankVersion": "撤回当前版本", "reportSkill": "举报技能", "reportDialogTitle": "举报技能", diff --git a/web/src/pages/dashboard/my-skills.tsx b/web/src/pages/dashboard/my-skills.tsx index 5a94b3d4..c76261db 100644 --- a/web/src/pages/dashboard/my-skills.tsx +++ b/web/src/pages/dashboard/my-skills.tsx @@ -1,22 +1,32 @@ +import { useState } from 'react' import { useNavigate } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' import { Button } from '@/shared/ui/button' import { Card } from '@/shared/ui/card' import { EmptyState } from '@/shared/components/empty-state' +import { ConfirmDialog } from '@/shared/components/confirm-dialog' import { DashboardPageHeader } from '@/shared/components/dashboard-page-header' -import { useMySkills } from '@/shared/hooks/use-skill-queries' +import { useArchiveSkill, useMySkills, useUnarchiveSkill } from '@/shared/hooks/use-skill-queries' import { formatCompactCount } from '@/shared/lib/number-format' +import { toast } from '@/shared/lib/toast' export function MySkillsPage() { const navigate = useNavigate() const { t } = useTranslation() + const [archiveTarget, setArchiveTarget] = useState<{ namespace: string; slug: string; name: string } | null>(null) + const [unarchiveTarget, setUnarchiveTarget] = useState<{ namespace: string; slug: string; name: string } | null>(null) const { data: skills, isLoading } = useMySkills() + const archiveMutation = useArchiveSkill() + const unarchiveMutation = useUnarchiveSkill() const handleSkillClick = (namespace: string, slug: string) => { navigate({ to: `/space/${namespace}/${slug}` }) } const resolveStatusLabel = (status?: string) => { + if (status === 'ARCHIVED') { + return t('mySkills.statusArchived') + } if (status === 'PENDING_REVIEW') { return t('mySkills.statusPendingReview') } @@ -27,6 +37,9 @@ export function MySkillsPage() { } const resolveStatusClassName = (status?: string) => { + if (status === 'ARCHIVED') { + return 'bg-slate-500/10 text-slate-500 border-slate-500/20' + } if (status === 'PENDING_REVIEW') { return 'bg-amber-500/10 text-amber-500 border-amber-500/20' } @@ -36,6 +49,46 @@ export function MySkillsPage() { return 'bg-secondary/60 text-muted-foreground border-border/40' } + const handleArchiveSkill = async () => { + if (!archiveTarget) { + return + } + try { + await archiveMutation.mutateAsync({ + namespace: archiveTarget.namespace, + slug: archiveTarget.slug, + }) + toast.success( + t('mySkills.archiveSuccessTitle'), + t('mySkills.archiveSuccessDescription', { skill: archiveTarget.name }), + ) + setArchiveTarget(null) + } catch (error) { + toast.error(t('mySkills.archiveErrorTitle'), error instanceof Error ? error.message : '') + throw error + } + } + + const handleUnarchiveSkill = async () => { + if (!unarchiveTarget) { + return + } + try { + await unarchiveMutation.mutateAsync({ + namespace: unarchiveTarget.namespace, + slug: unarchiveTarget.slug, + }) + toast.success( + t('mySkills.unarchiveSuccessTitle'), + t('mySkills.unarchiveSuccessDescription', { skill: unarchiveTarget.name }), + ) + setUnarchiveTarget(null) + } catch (error) { + toast.error(t('mySkills.unarchiveErrorTitle'), error instanceof Error ? error.message : '') + throw error + } + } + if (isLoading) { return (
@@ -79,6 +132,11 @@ export function MySkillsPage() { {skill.latestVersion && ( v{skill.latestVersion} )} + {skill.status ? ( + + {resolveStatusLabel(skill.status)} + + ) : null} {skill.latestVersionStatus ? ( {resolveStatusLabel(skill.latestVersionStatus)} @@ -92,9 +150,42 @@ export function MySkillsPage() {
- - - +
+ {skill.status === 'ARCHIVED' ? ( + + ) : ( + + )} + + + +
))} @@ -110,6 +201,32 @@ export function MySkillsPage() { } /> )} + + { + if (!open) { + setArchiveTarget(null) + } + }} + title={t('mySkills.archiveConfirmTitle')} + description={archiveTarget ? t('mySkills.archiveConfirmDescription', { skill: archiveTarget.name }) : ''} + confirmText={t('mySkills.archive')} + onConfirm={handleArchiveSkill} + /> + + { + if (!open) { + setUnarchiveTarget(null) + } + }} + title={t('mySkills.unarchiveConfirmTitle')} + description={unarchiveTarget ? t('mySkills.unarchiveConfirmDescription', { skill: unarchiveTarget.name }) : ''} + confirmText={t('mySkills.unarchive')} + onConfirm={handleUnarchiveSkill} + /> ) } diff --git a/web/src/pages/skill-detail.tsx b/web/src/pages/skill-detail.tsx index fa5ccd25..d72fa1a5 100644 --- a/web/src/pages/skill-detail.tsx +++ b/web/src/pages/skill-detail.tsx @@ -17,6 +17,7 @@ import { NamespaceBadge } from '@/shared/components/namespace-badge' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/shared/ui/tabs' import { Button } from '@/shared/ui/button' import { Card } from '@/shared/ui/card' +import { ConfirmDialog } from '@/shared/components/confirm-dialog' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/shared/ui/dialog' import { Input } from '@/shared/ui/input' import { Textarea } from '@/shared/ui/textarea' @@ -26,6 +27,9 @@ import { useSkillVersions, useSkillFiles, useSkillReadme, + useArchiveSkill, + useDeleteSkillVersion, + useUnarchiveSkill, } from '@/shared/hooks/use-skill-queries' export function SkillDetailPage() { @@ -36,6 +40,9 @@ export function SkillDetailPage() { const [reportDialogOpen, setReportDialogOpen] = useState(false) const [reportReason, setReportReason] = useState('') const [reportDetails, setReportDetails] = useState('') + const [archiveConfirmOpen, setArchiveConfirmOpen] = useState(false) + const [unarchiveConfirmOpen, setUnarchiveConfirmOpen] = useState(false) + const [deleteVersionTarget, setDeleteVersionTarget] = useState(null) const { namespace, slug } = useParams({ from: '/space/$namespace/$slug' }) const { user, hasRole } = useAuth() @@ -66,6 +73,9 @@ export function SkillDetailPage() { mutationFn: () => adminApi.yankVersion(latestVersion!.id), onSuccess: refreshSkill, }) + const archiveMutation = useArchiveSkill() + const unarchiveMutation = useUnarchiveSkill() + const deleteVersionMutation = useDeleteSkillVersion() const reportMutation = useSubmitSkillReport(namespace, slug) const handleDownload = () => { @@ -126,6 +136,66 @@ export function SkillDetailPage() { navigate({ to: '/search', search: { q: '', sort: 'relevance', page: 0, starredOnly: false } }) } + const resolveSkillStatusLabel = (status?: string) => { + if (status === 'ARCHIVED') { + return t('skillDetail.statusArchived') + } + if (status === 'ACTIVE') { + return t('skillDetail.statusActive') + } + if (status === 'HIDDEN') { + return t('skillDetail.statusHidden') + } + return status ?? '' + } + + const canDeleteVersion = (status?: string) => status === 'DRAFT' || status === 'REJECTED' + + const handleArchive = async () => { + try { + await archiveMutation.mutateAsync({ namespace, slug }) + toast.success( + t('skillDetail.archiveSuccessTitle'), + t('skillDetail.archiveSuccessDescription', { skill: skill?.displayName ?? slug }), + ) + setArchiveConfirmOpen(false) + } catch (error) { + toast.error(t('skillDetail.archiveErrorTitle'), error instanceof Error ? error.message : '') + throw error + } + } + + const handleUnarchive = async () => { + try { + await unarchiveMutation.mutateAsync({ namespace, slug }) + toast.success( + t('skillDetail.unarchiveSuccessTitle'), + t('skillDetail.unarchiveSuccessDescription', { skill: skill?.displayName ?? slug }), + ) + setUnarchiveConfirmOpen(false) + } catch (error) { + toast.error(t('skillDetail.unarchiveErrorTitle'), error instanceof Error ? error.message : '') + throw error + } + } + + const handleDeleteVersion = async () => { + if (!deleteVersionTarget) { + return + } + try { + await deleteVersionMutation.mutateAsync({ namespace, slug, version: deleteVersionTarget }) + toast.success( + t('skillDetail.deleteVersionSuccessTitle'), + t('skillDetail.deleteVersionSuccessDescription', { version: deleteVersionTarget }), + ) + setDeleteVersionTarget(null) + } catch (error) { + toast.error(t('skillDetail.deleteVersionErrorTitle'), error instanceof Error ? error.message : '') + throw error + } + } + if (isLoadingSkill) { return (
@@ -182,6 +252,11 @@ export function SkillDetailPage() {
+ {skill.status && ( + + {resolveSkillStatusLabel(skill.status)} + + )}

{skill.displayName}

{skill.summary && ( @@ -229,10 +304,26 @@ export function SkillDetailPage() { v{version.version} + {version.status && ( + + {version.status} + + )} - - {formatLocalDateTime(version.publishedAt, i18n.language)} - +
+ + {formatLocalDateTime(version.publishedAt, i18n.language)} + + {canDeleteVersion(version.status) && ( + + )} +
{version.changelog && (

{version.changelog}

@@ -303,6 +394,9 @@ export function SkillDetailPage() { {skill.latestVersion && (
{t('skillDetail.install')}
+ {skill.status === 'ARCHIVED' && ( +

{t('skillDetail.archivedInstallHint')}

+ )} @@ -324,6 +418,26 @@ export function SkillDetailPage() { {t('skillDetail.download')} + {user && ( + +
{t('skillDetail.lifecycle')}
+

+ {skill.status === 'ARCHIVED' + ? t('skillDetail.archivedPublishHint') + : t('skillDetail.lifecycleHint')} +

+ {skill.status === 'ARCHIVED' ? ( + + ) : ( + + )} +
+ )} + {governanceVisible && (
{t('skillDetail.governance')}
@@ -377,6 +491,38 @@ export function SkillDetailPage() { + + + + + + { + if (!open) { + setDeleteVersionTarget(null) + } + }} + title={t('skillDetail.deleteVersionConfirmTitle')} + description={deleteVersionTarget ? t('skillDetail.deleteVersionConfirmDescription', { version: deleteVersionTarget }) : ''} + confirmText={t('skillDetail.deleteVersion')} + variant="destructive" + onConfirm={handleDeleteVersion} + /> ) } diff --git a/web/src/shared/hooks/use-skill-queries.ts b/web/src/shared/hooks/use-skill-queries.ts index a23d3c9f..eb2504c3 100644 --- a/web/src/shared/hooks/use-skill-queries.ts +++ b/web/src/shared/hooks/use-skill-queries.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import type { SkillSummary, SkillDetail, SkillVersion, SkillFile, SearchParams, PagedResponse, PublishResult, Namespace, NamespaceMember } from '@/api/types' -import { fetchJson, fetchText, getCsrfHeaders, meApi, WEB_API_PREFIX } from '@/api/client' +import { fetchJson, fetchText, getCsrfHeaders, meApi, skillLifecycleApi, WEB_API_PREFIX } from '@/api/client' const PUBLISH_REQUEST_TIMEOUT_MS = 60_000 @@ -170,3 +170,48 @@ export function usePublishSkill() { }, }) } + +export function useArchiveSkill() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: ({ namespace, slug, reason }: { namespace: string; slug: string; reason?: string }) => + skillLifecycleApi.archiveSkill(namespace, slug, reason), + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: ['skills', 'my'] }) + queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug] }) + queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug, 'versions'] }) + queryClient.invalidateQueries({ queryKey: ['skills'] }) + }, + }) +} + +export function useUnarchiveSkill() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: ({ namespace, slug }: { namespace: string; slug: string }) => + skillLifecycleApi.unarchiveSkill(namespace, slug), + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: ['skills', 'my'] }) + queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug] }) + queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug, 'versions'] }) + queryClient.invalidateQueries({ queryKey: ['skills'] }) + }, + }) +} + +export function useDeleteSkillVersion() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: ({ namespace, slug, version }: { namespace: string; slug: string; version: string }) => + skillLifecycleApi.deleteVersion(namespace, slug, version), + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: ['skills', 'my'] }) + queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug] }) + queryClient.invalidateQueries({ queryKey: ['skills', variables.namespace, variables.slug, 'versions'] }) + queryClient.invalidateQueries({ queryKey: ['skills'] }) + }, + }) +}