From 96dcf08b0e9e7203b75cfc40235e99d22b79125c Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Tue, 17 Mar 2026 11:16:57 +0800 Subject: [PATCH] fix: allow republish after review withdrawal --- .../skill/service/SkillPublishService.java | 30 +++++++++- .../service/SkillPublishServiceTest.java | 56 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillPublishService.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillPublishService.java index 5d9ff848..aa032a19 100644 --- a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillPublishService.java +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillPublishService.java @@ -227,8 +227,13 @@ public class SkillPublishService { } // 7. Check version doesn't already exist - if (skillVersionRepository.findBySkillIdAndVersion(skill.getId(), metadata.version()).isPresent()) { - throw new DomainBadRequestException("error.skill.version.exists", metadata.version()); + java.util.Optional existingVersion = skillVersionRepository.findBySkillIdAndVersion(skill.getId(), metadata.version()); + if (existingVersion.isPresent()) { + SkillVersion matchedVersion = existingVersion.get(); + if (matchedVersion.getStatus() == SkillVersionStatus.PUBLISHED) { + throw new DomainBadRequestException("error.skill.version.exists", metadata.version()); + } + deleteReplaceableVersionArtifacts(skill, matchedVersion); } // 8. Create SkillVersion @@ -333,6 +338,27 @@ public class SkillPublishService { return new PublishResult(skill.getId(), skill.getSlug(), version); } + private void deleteReplaceableVersionArtifacts(Skill skill, SkillVersion version) { + if (version.getStatus() == SkillVersionStatus.PUBLISHED) { + throw new DomainBadRequestException("error.skill.version.exists", version.getVersion()); + } + + reviewTaskRepository.findBySkillVersionIdAndStatus(version.getId(), ReviewTaskStatus.PENDING) + .ifPresent(reviewTaskRepository::delete); + + List files = skillFileRepository.findByVersionId(version.getId()); + if (!files.isEmpty()) { + objectStorageService.deleteObjects(files.stream().map(SkillFile::getStorageKey).toList()); + } + objectStorageService.deleteObject(String.format("packages/%d/%d/bundle.zip", skill.getId(), version.getId())); + skillFileRepository.deleteByVersionId(version.getId()); + skillVersionRepository.delete(version); + + if (version.getId().equals(skill.getLatestVersionId())) { + skill.setLatestVersionId(null); + } + } + private String resolveNamespaceSlug(Long namespaceId) { return namespaceRepository.findById(namespaceId) .orElseThrow(() -> new DomainBadRequestException("error.namespace.notFound", namespaceId)) diff --git a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillPublishServiceTest.java b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillPublishServiceTest.java index 7987dae0..16100617 100644 --- a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillPublishServiceTest.java +++ b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillPublishServiceTest.java @@ -142,6 +142,62 @@ class SkillPublishServiceTest { verify(eventPublisher, never()).publishEvent(any()); } + @Test + void testPublishFromEntries_ShouldReplaceDraftVersionWithSameVersion() throws Exception { + String namespaceSlug = "test-ns"; + String publisherId = "user-100"; + String skillMdContent = "---\nname: test-skill\ndescription: Test\nversion: 1.0.0\n---\nBody"; + + PackageEntry skillMd = new PackageEntry("SKILL.md", skillMdContent.getBytes(), skillMdContent.length(), "text/markdown"); + List entries = List.of(skillMd); + + Namespace namespace = new Namespace(namespaceSlug, "Test NS", "user-1"); + setId(namespace, 1L); + NamespaceMember member = mock(NamespaceMember.class); + SkillMetadata metadata = new SkillMetadata("test-skill", "Test", "1.0.0", "Body", Map.of()); + + Skill skill = new Skill(1L, "test-skill", publisherId, SkillVisibility.PUBLIC); + setId(skill, 1L); + SkillVersion draftVersion = new SkillVersion(1L, "1.0.0", publisherId); + draftVersion.setStatus(SkillVersionStatus.DRAFT); + setId(draftVersion, 8L); + SkillFile oldFile = new SkillFile(8L, "SKILL.md", (long) skillMdContent.length(), "text/markdown", "abc", "skills/1/8/SKILL.md"); + + when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace)); + when(namespaceMemberRepository.findByNamespaceIdAndUserId(any(), eq(publisherId))).thenReturn(Optional.of(member)); + when(skillPackageValidator.validate(entries)).thenReturn(ValidationResult.pass()); + when(skillMetadataParser.parse(skillMdContent)).thenReturn(metadata); + when(prePublishValidator.validate(any())).thenReturn(ValidationResult.pass()); + when(skillRepository.findByNamespaceIdAndSlug(any(), eq("test-skill"))).thenReturn(List.of(skill)); + when(skillRepository.findByNamespaceIdAndSlugAndOwnerId(any(), eq("test-skill"), eq(publisherId))).thenReturn(Optional.of(skill)); + when(skillVersionRepository.findBySkillIdAndStatus(1L, SkillVersionStatus.PENDING_REVIEW)).thenReturn(List.of()); + when(skillVersionRepository.findBySkillIdAndVersion(1L, "1.0.0")).thenReturn(Optional.of(draftVersion)); + when(skillFileRepository.findByVersionId(8L)).thenReturn(List.of(oldFile)); + when(skillVersionRepository.save(any(SkillVersion.class))).thenAnswer(invocation -> { + SkillVersion saved = invocation.getArgument(0); + if (saved.getId() == null) { + setId(saved, 10L); + } + return saved; + }); + when(skillRepository.save(any())).thenReturn(skill); + + SkillPublishService.PublishResult result = service.publishFromEntries( + namespaceSlug, + entries, + publisherId, + SkillVisibility.PUBLIC, + Set.of() + ); + + assertEquals("1.0.0", result.version().getVersion()); + assertEquals(SkillVersionStatus.PENDING_REVIEW, result.version().getStatus()); + verify(skillFileRepository).deleteByVersionId(8L); + verify(skillVersionRepository).delete(draftVersion); + verify(objectStorageService).deleteObjects(List.of("skills/1/8/SKILL.md")); + verify(objectStorageService).deleteObject("packages/1/8/bundle.zip"); + } + @Test void testPublishFromEntries_ShouldSlugifyNameBeforeLookupAndResponse() throws Exception { String namespaceSlug = "test-ns";