From 1ba1c9018a4da6ed72d40e6eb5811639e81c3144 Mon Sep 17 00:00:00 2001 From: chenbaowang <49091147+Rsweater@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:19:17 +0800 Subject: [PATCH] feat(api): unify permission checks for hide/unhide/archive operations Add unified permission model for skill lifecycle operations: - Skill owners can hide/unhide/archive their own skills - Namespace admins/owners can manage skills in their namespace - Platform admins retain full access via admin endpoints Changes: - SkillGovernanceService: add permission checks to hideSkill/unhideSkill - SkillLifecycleAppService: add hideSkill/unhideSkill methods - GovernanceWorkflowAppService: add facade methods for hide/unhide - SkillLifecycleController: add POST /{namespace}/{slug}/hide and /unhide endpoints Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../portal/SkillLifecycleController.java | 42 +++++++++++++++-- .../service/GovernanceWorkflowAppService.java | 27 +++++++++-- .../service/SkillLifecycleAppService.java | 46 +++++++++++++++++-- .../skill/service/SkillGovernanceService.java | 15 +++++- 4 files changed, 113 insertions(+), 17 deletions(-) diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillLifecycleController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillLifecycleController.java index b590fa22..e67582f9 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillLifecycleController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillLifecycleController.java @@ -141,11 +141,11 @@ public class SkillLifecycleController extends BaseApiController { @PostMapping("/{namespace}/{slug}/confirm-publish") public ApiResponse confirmPublish(@PathVariable String namespace, - @PathVariable String slug, - @Valid @RequestBody ConfirmPublishRequest request, - @RequestAttribute("userId") String userId, - @RequestAttribute(value = "userNsRoles", required = false) Map userNsRoles, - HttpServletRequest httpRequest) { + @PathVariable String slug, + @Valid @RequestBody ConfirmPublishRequest request, + @RequestAttribute("userId") String userId, + @RequestAttribute(value = "userNsRoles", required = false) Map userNsRoles, + HttpServletRequest httpRequest) { return ok("response.success.updated", governanceWorkflowAppService.confirmPublish( namespace, @@ -155,4 +155,36 @@ public class SkillLifecycleController extends BaseApiController { userNsRoles, AuditRequestContext.from(httpRequest))); } + + @PostMapping("/{namespace}/{slug}/hide") + public ApiResponse hideSkill(@PathVariable String namespace, + @PathVariable String slug, + @RequestBody(required = false) AdminSkillActionRequest request, + @RequestAttribute("userId") String userId, + @RequestAttribute(value = "userNsRoles", required = false) Map userNsRoles, + HttpServletRequest httpRequest) { + return ok("response.success.updated", + governanceWorkflowAppService.hideSkill( + namespace, + slug, + request, + userId, + userNsRoles, + AuditRequestContext.from(httpRequest))); + } + + @PostMapping("/{namespace}/{slug}/unhide") + public ApiResponse unhideSkill(@PathVariable String namespace, + @PathVariable String slug, + @RequestAttribute("userId") String userId, + @RequestAttribute(value = "userNsRoles", required = false) Map userNsRoles, + HttpServletRequest httpRequest) { + return ok("response.success.updated", + governanceWorkflowAppService.unhideSkill( + namespace, + slug, + userId, + userNsRoles, + AuditRequestContext.from(httpRequest))); + } } diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/GovernanceWorkflowAppService.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/GovernanceWorkflowAppService.java index 6cca3952..313a880f 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/GovernanceWorkflowAppService.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/GovernanceWorkflowAppService.java @@ -273,11 +273,11 @@ public class GovernanceWorkflowAppService { } public SkillLifecycleMutationResponse confirmPublish(String namespace, - String slug, - String version, - String userId, - Map userNsRoles, - AuditRequestContext auditContext) { + String slug, + String version, + String userId, + Map userNsRoles, + AuditRequestContext auditContext) { return skillLifecycleAppService.confirmPublish( namespace, slug, @@ -286,4 +286,21 @@ public class GovernanceWorkflowAppService { userNsRoles, auditContext); } + + public SkillLifecycleMutationResponse hideSkill(String namespace, + String slug, + AdminSkillActionRequest request, + String userId, + Map userNsRoles, + AuditRequestContext auditContext) { + return skillLifecycleAppService.hideSkill(namespace, slug, request, userId, userNsRoles, auditContext); + } + + public SkillLifecycleMutationResponse unhideSkill(String namespace, + String slug, + String userId, + Map userNsRoles, + AuditRequestContext auditContext) { + return skillLifecycleAppService.unhideSkill(namespace, slug, userId, userNsRoles, auditContext); + } } diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillLifecycleAppService.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillLifecycleAppService.java index e7f86d45..131499ec 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillLifecycleAppService.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillLifecycleAppService.java @@ -213,11 +213,11 @@ public class SkillLifecycleAppService { @Transactional public SkillLifecycleMutationResponse confirmPublish(String namespace, - String slug, - String version, - String userId, - Map userNamespaceRoles, - AuditRequestContext auditContext) { + String slug, + String version, + String userId, + Map userNamespaceRoles, + AuditRequestContext auditContext) { Skill skill = findSkill(namespace, slug, userId); SkillVersion skillVersion = findVersion(skill.getId(), version); skillReviewSubmitService.confirmPublish( @@ -244,6 +244,42 @@ public class SkillLifecycleAppService { ); } + @Transactional + public SkillLifecycleMutationResponse hideSkill(String namespace, + String slug, + AdminSkillActionRequest request, + String userId, + Map userNamespaceRoles, + AuditRequestContext auditContext) { + Skill skill = findSkill(namespace, slug, userId); + Skill hidden = skillGovernanceService.hideSkill( + skill.getId(), + userId, + normalizeRoles(userNamespaceRoles), + auditContext.clientIp(), + auditContext.userAgent(), + request != null ? request.reason() : null + ); + return new SkillLifecycleMutationResponse(hidden.getId(), null, "HIDE", hidden.getStatus().name()); + } + + @Transactional + public SkillLifecycleMutationResponse unhideSkill(String namespace, + String slug, + String userId, + Map userNamespaceRoles, + AuditRequestContext auditContext) { + Skill skill = findSkill(namespace, slug, userId); + Skill unhidden = skillGovernanceService.unhideSkill( + skill.getId(), + userId, + normalizeRoles(userNamespaceRoles), + auditContext.clientIp(), + auditContext.userAgent() + ); + return new SkillLifecycleMutationResponse(unhidden.getId(), null, "UNHIDE", unhidden.getStatus().name()); + } + private Skill findSkill(String namespaceSlug, String skillSlug, String currentUserId) { String cleanNamespace = namespaceSlug.startsWith("@") ? namespaceSlug.substring(1) : namespaceSlug; Namespace namespace = namespaceRepository.findBySlug(cleanNamespace) diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceService.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceService.java index 0dfcb5f3..86c25f3a 100644 --- a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceService.java +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceService.java @@ -69,9 +69,15 @@ public class SkillGovernanceService { } @Transactional - public Skill hideSkill(Long skillId, String actorUserId, String clientIp, String userAgent, String reason) { + public Skill hideSkill(Long skillId, + String actorUserId, + Map userNamespaceRoles, + String clientIp, + String userAgent, + String reason) { Skill skill = skillRepository.findById(skillId) .orElseThrow(() -> new DomainNotFoundException("error.skill.notFound", skillId)); + assertCanManageLifecycle(skill, actorUserId, userNamespaceRoles); skill.setHidden(true); skill.setHiddenAt(currentInstant()); skill.setHiddenBy(actorUserId); @@ -120,9 +126,14 @@ public class SkillGovernanceService { } @Transactional - public Skill unhideSkill(Long skillId, String actorUserId, String clientIp, String userAgent) { + public Skill unhideSkill(Long skillId, + String actorUserId, + Map userNamespaceRoles, + String clientIp, + String userAgent) { Skill skill = skillRepository.findById(skillId) .orElseThrow(() -> new DomainNotFoundException("error.skill.notFound", skillId)); + assertCanManageLifecycle(skill, actorUserId, userNamespaceRoles); skill.setHidden(false); skill.setHiddenAt(null); skill.setHiddenBy(null);