mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-07 02:57:51 +00:00
feat(skill): add version management APIs
This commit is contained in:
parent
3f82a88c4e
commit
4ad97aa55a
5 changed files with 141 additions and 1 deletions
|
|
@ -12,10 +12,13 @@ import com.iflytek.skillhub.domain.skill.SkillRepository;
|
|||
import com.iflytek.skillhub.domain.skill.SkillVersion;
|
||||
import com.iflytek.skillhub.domain.skill.SkillVersionRepository;
|
||||
import com.iflytek.skillhub.domain.skill.service.SkillGovernanceService;
|
||||
import com.iflytek.skillhub.domain.skill.service.SkillPublishService;
|
||||
import com.iflytek.skillhub.dto.AdminSkillActionRequest;
|
||||
import com.iflytek.skillhub.dto.ApiResponse;
|
||||
import com.iflytek.skillhub.dto.ApiResponseFactory;
|
||||
import com.iflytek.skillhub.dto.SkillLifecycleMutationResponse;
|
||||
import com.iflytek.skillhub.dto.SkillVersionRereleaseRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
|
|
@ -35,6 +38,7 @@ public class SkillLifecycleController extends BaseApiController {
|
|||
private final SkillVersionRepository skillVersionRepository;
|
||||
private final SkillGovernanceService skillGovernanceService;
|
||||
private final ReviewService reviewService;
|
||||
private final SkillPublishService skillPublishService;
|
||||
private final AuditLogService auditLogService;
|
||||
|
||||
public SkillLifecycleController(NamespaceRepository namespaceRepository,
|
||||
|
|
@ -42,6 +46,7 @@ public class SkillLifecycleController extends BaseApiController {
|
|||
SkillVersionRepository skillVersionRepository,
|
||||
SkillGovernanceService skillGovernanceService,
|
||||
ReviewService reviewService,
|
||||
SkillPublishService skillPublishService,
|
||||
AuditLogService auditLogService,
|
||||
ApiResponseFactory responseFactory) {
|
||||
super(responseFactory);
|
||||
|
|
@ -50,6 +55,7 @@ public class SkillLifecycleController extends BaseApiController {
|
|||
this.skillVersionRepository = skillVersionRepository;
|
||||
this.skillGovernanceService = skillGovernanceService;
|
||||
this.reviewService = reviewService;
|
||||
this.skillPublishService = skillPublishService;
|
||||
this.auditLogService = auditLogService;
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +147,40 @@ public class SkillLifecycleController extends BaseApiController {
|
|||
new SkillLifecycleMutationResponse(skill.getId(), skillVersion.getId(), "WITHDRAW_REVIEW", "DELETED"));
|
||||
}
|
||||
|
||||
@PostMapping("/{namespace}/{slug}/versions/{version}/rerelease")
|
||||
public ApiResponse<SkillLifecycleMutationResponse> rereleaseVersion(@PathVariable String namespace,
|
||||
@PathVariable String slug,
|
||||
@PathVariable String version,
|
||||
@Valid @RequestBody SkillVersionRereleaseRequest request,
|
||||
@RequestAttribute("userId") String userId,
|
||||
@RequestAttribute(value = "userNsRoles", required = false) Map<Long, NamespaceRole> userNsRoles,
|
||||
HttpServletRequest httpRequest) {
|
||||
Skill skill = findSkill(namespace, slug);
|
||||
SkillVersion skillVersion = skillVersionRepository.findBySkillIdAndVersion(skill.getId(), version)
|
||||
.orElseThrow(() -> new DomainBadRequestException("error.skill.version.notFound", version));
|
||||
SkillPublishService.PublishResult result = skillPublishService.rereleasePublishedVersion(
|
||||
skill.getId(),
|
||||
skillVersion.getVersion(),
|
||||
request.targetVersion().trim(),
|
||||
userId,
|
||||
userNsRoles != null ? userNsRoles : Map.of()
|
||||
);
|
||||
auditLogService.record(
|
||||
userId,
|
||||
"RERELEASE_SKILL_VERSION",
|
||||
"SKILL_VERSION",
|
||||
skillVersion.getId(),
|
||||
null,
|
||||
httpRequest.getRemoteAddr(),
|
||||
httpRequest.getHeader("User-Agent"),
|
||||
"{\"sourceVersion\":\"" + version.replace("\"", "\\\"")
|
||||
+ "\",\"targetVersion\":\"" + request.targetVersion().trim().replace("\"", "\\\"") + "\"}"
|
||||
);
|
||||
|
||||
return ok("response.success.updated",
|
||||
new SkillLifecycleMutationResponse(result.skillId(), result.version().getId(), "RERELEASE_VERSION", result.version().getStatus().name()));
|
||||
}
|
||||
|
||||
private Skill findSkill(String namespaceSlug, String skillSlug) {
|
||||
String cleanNamespace = namespaceSlug.startsWith("@") ? namespaceSlug.substring(1) : namespaceSlug;
|
||||
Namespace namespace = namespaceRepository.findBySlug(cleanNamespace)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.iflytek.skillhub.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record SkillVersionRereleaseRequest(
|
||||
@NotBlank(message = "{validation.required}")
|
||||
String targetVersion
|
||||
) {
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import com.iflytek.skillhub.domain.skill.SkillVersion;
|
|||
import com.iflytek.skillhub.domain.skill.SkillVersionRepository;
|
||||
import com.iflytek.skillhub.domain.skill.SkillVersionStatus;
|
||||
import com.iflytek.skillhub.domain.skill.SkillVisibility;
|
||||
import com.iflytek.skillhub.domain.skill.service.SkillPublishService;
|
||||
import com.iflytek.skillhub.domain.skill.service.SkillGovernanceService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -60,6 +61,9 @@ class SkillLifecycleControllerTest {
|
|||
@MockBean
|
||||
private ReviewService reviewService;
|
||||
|
||||
@MockBean
|
||||
private SkillPublishService skillPublishService;
|
||||
|
||||
@MockBean
|
||||
private AuditLogService auditLogService;
|
||||
|
||||
|
|
@ -173,6 +177,45 @@ class SkillLifecycleControllerTest {
|
|||
.andExpect(jsonPath("$.data.status").value("DELETED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rereleaseVersion_returnsUnifiedEnvelope() throws Exception {
|
||||
Namespace namespace = new Namespace("global", "Global", "owner");
|
||||
setNamespaceId(namespace, 1L);
|
||||
Skill skill = new Skill(1L, "demo-skill", "owner", SkillVisibility.PUBLIC);
|
||||
setSkillId(skill, 1L);
|
||||
SkillVersion newVersion = new SkillVersion(1L, "1.2.4", "owner");
|
||||
setSkillVersionId(newVersion, 3L);
|
||||
newVersion.setStatus(SkillVersionStatus.PUBLISHED);
|
||||
|
||||
given(namespaceRepository.findBySlug("global")).willReturn(java.util.Optional.of(namespace));
|
||||
given(skillRepository.findByNamespaceIdAndSlug(1L, "demo-skill")).willReturn(java.util.Optional.of(skill));
|
||||
SkillVersion sourceVersion = new SkillVersion(1L, "1.2.3", "owner");
|
||||
setSkillVersionId(sourceVersion, 2L);
|
||||
sourceVersion.setStatus(SkillVersionStatus.PUBLISHED);
|
||||
given(skillVersionRepository.findBySkillIdAndVersion(1L, "1.2.3")).willReturn(java.util.Optional.of(sourceVersion));
|
||||
given(skillPublishService.rereleasePublishedVersion(
|
||||
eq(1L),
|
||||
eq("1.2.3"),
|
||||
eq("1.2.4"),
|
||||
eq("usr_1"),
|
||||
anyMap()))
|
||||
.willReturn(new SkillPublishService.PublishResult(1L, "demo-skill", newVersion));
|
||||
|
||||
mockMvc.perform(post("/api/web/skills/global/demo-skill/versions/1.2.3/rerelease")
|
||||
.requestAttr("userId", "usr_1")
|
||||
.requestAttr("userNsRoles", java.util.Map.of(1L, NamespaceRole.ADMIN))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"targetVersion\":\"1.2.4\"}")
|
||||
.with(user("usr_1"))
|
||||
.with(csrf()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.skillId").value(1))
|
||||
.andExpect(jsonPath("$.data.versionId").value(3))
|
||||
.andExpect(jsonPath("$.data.action").value("RERELEASE_VERSION"))
|
||||
.andExpect(jsonPath("$.data.status").value("PUBLISHED"));
|
||||
}
|
||||
|
||||
private Skill skillWithStatus(Skill skill, com.iflytek.skillhub.domain.skill.SkillStatus status) {
|
||||
skill.setStatus(status);
|
||||
return skill;
|
||||
|
|
|
|||
|
|
@ -256,8 +256,10 @@ public class SkillQueryService {
|
|||
if (canManageRestrictedSkill(skill, currentUserId, userNsRoles)) {
|
||||
visibleVersions = skillVersionRepository.findBySkillId(skill.getId()).stream()
|
||||
.filter(version -> version.getStatus() == SkillVersionStatus.PUBLISHED
|
||||
|| version.getStatus() == SkillVersionStatus.PENDING_REVIEW
|
||||
|| version.getStatus() == SkillVersionStatus.DRAFT
|
||||
|| version.getStatus() == SkillVersionStatus.REJECTED)
|
||||
|| version.getStatus() == SkillVersionStatus.REJECTED
|
||||
|| version.getStatus() == SkillVersionStatus.YANKED)
|
||||
.sorted(Comparator
|
||||
.comparingInt((SkillVersion version) -> lifecycleListPriority(version.getStatus()))
|
||||
.thenComparing(SkillVersion::getPublishedAt,
|
||||
|
|
@ -435,6 +437,15 @@ public class SkillQueryService {
|
|||
if (status == SkillVersionStatus.REJECTED) {
|
||||
return 1;
|
||||
}
|
||||
if (status == SkillVersionStatus.PENDING_REVIEW) {
|
||||
return 2;
|
||||
}
|
||||
if (status == SkillVersionStatus.DRAFT) {
|
||||
return 3;
|
||||
}
|
||||
if (status == SkillVersionStatus.YANKED) {
|
||||
return 4;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -266,6 +266,43 @@ class SkillQueryServiceTest {
|
|||
assertEquals("README.md", result.get(0).getFilePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testListVersions_ShouldIncludePendingAndRejectedForLifecycleManagers() throws Exception {
|
||||
String namespaceSlug = "test-ns";
|
||||
String skillSlug = "test-skill";
|
||||
String ownerId = "user-100";
|
||||
Map<Long, NamespaceRole> userNsRoles = Map.of(1L, NamespaceRole.OWNER);
|
||||
|
||||
Namespace namespace = new Namespace(namespaceSlug, "Test NS", ownerId);
|
||||
setId(namespace, 1L);
|
||||
Skill skill = new Skill(1L, skillSlug, ownerId, SkillVisibility.PUBLIC);
|
||||
setId(skill, 1L);
|
||||
skill.setStatus(SkillStatus.ACTIVE);
|
||||
|
||||
SkillVersion published = new SkillVersion(1L, "1.0.0", ownerId);
|
||||
setId(published, 10L);
|
||||
published.setStatus(SkillVersionStatus.PUBLISHED);
|
||||
published.setPublishedAt(java.time.LocalDateTime.of(2026, 3, 1, 10, 0));
|
||||
|
||||
SkillVersion pending = new SkillVersion(1L, "1.1.0", ownerId);
|
||||
setId(pending, 11L);
|
||||
pending.setStatus(SkillVersionStatus.PENDING_REVIEW);
|
||||
|
||||
SkillVersion rejected = new SkillVersion(1L, "1.2.0", ownerId);
|
||||
setId(rejected, 12L);
|
||||
rejected.setStatus(SkillVersionStatus.REJECTED);
|
||||
|
||||
when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace));
|
||||
when(skillRepository.findByNamespaceIdAndSlug(1L, skillSlug)).thenReturn(Optional.of(skill));
|
||||
when(visibilityChecker.canAccess(skill, ownerId, userNsRoles)).thenReturn(true);
|
||||
when(skillVersionRepository.findBySkillId(1L)).thenReturn(List.of(pending, published, rejected));
|
||||
|
||||
Page<SkillVersion> result = service.listVersions(namespaceSlug, skillSlug, ownerId, userNsRoles, PageRequest.of(0, 20));
|
||||
|
||||
assertEquals(List.of("1.0.0", "1.2.0", "1.1.0"),
|
||||
result.getContent().stream().map(SkillVersion::getVersion).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testResolveVersion_ShouldReturnLatestWhenHashDoesNotMatch() throws Exception {
|
||||
String namespaceSlug = "test-ns";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue