diff --git a/server/skillhub-app/src/main/resources/messages.properties b/server/skillhub-app/src/main/resources/messages.properties index 50493763..82b255e5 100644 --- a/server/skillhub-app/src/main/resources/messages.properties +++ b/server/skillhub-app/src/main/resources/messages.properties @@ -185,3 +185,4 @@ promotion.status.invalid=Unsupported promotion status: {0} promotion.sort.field.invalid=Unsupported promotion sort field: {0} promotion.sort.direction.invalid=Unsupported promotion sort direction: {0} promotion.sort.pending_unsupported=Pending promotion requests do not support reviewed-time sorting +error.skill.subscription.noPermission=You do not have permission to subscribe to this skill. diff --git a/server/skillhub-app/src/main/resources/messages_zh.properties b/server/skillhub-app/src/main/resources/messages_zh.properties index febc9c4e..0f2e1808 100644 --- a/server/skillhub-app/src/main/resources/messages_zh.properties +++ b/server/skillhub-app/src/main/resources/messages_zh.properties @@ -185,3 +185,4 @@ promotion.status.invalid=不支持的提升审核状态:{0} promotion.sort.field.invalid=不支持的提升审核排序字段:{0} promotion.sort.direction.invalid=不支持的提升审核排序方向:{0} promotion.sort.pending_unsupported=待审核提升请求不支持按处理时间排序 +error.skill.subscription.noPermission=您没有订阅此技能的权限。 diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/event/SkillVersionYankedEvent.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/event/SkillVersionYankedEvent.java index a9a6d92d..9df82b5d 100644 --- a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/event/SkillVersionYankedEvent.java +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/event/SkillVersionYankedEvent.java @@ -1,3 +1,3 @@ package com.iflytek.skillhub.domain.event; -public record SkillVersionYankedEvent(Long skillId, Long versionId, String actorUserId, boolean wasPublished) {} +public record SkillVersionYankedEvent(Long skillId, Long versionId, String actorUserId) {} 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 552d9e1f..3cad927f 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 @@ -281,7 +281,7 @@ public class SkillGovernanceService { }); auditLogService.record(actorUserId, "YANK_SKILL_VERSION", "SKILL_VERSION", versionId, null, clientIp, userAgent, jsonReason(reason)); eventPublisher.publishEvent(new com.iflytek.skillhub.domain.event.SkillVersionYankedEvent( - version.getSkillId(), versionId, actorUserId, true)); + version.getSkillId(), versionId, actorUserId)); return saved; } diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/social/SubscriptionMetadataAccessPolicy.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/social/SubscriptionMetadataAccessPolicy.java index f71a0eaa..c166ae21 100644 --- a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/social/SubscriptionMetadataAccessPolicy.java +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/social/SubscriptionMetadataAccessPolicy.java @@ -5,18 +5,37 @@ import com.iflytek.skillhub.domain.namespace.NamespaceRole; import com.iflytek.skillhub.domain.namespace.NamespaceStatus; import com.iflytek.skillhub.domain.skill.Skill; import com.iflytek.skillhub.domain.skill.SkillVisibility; +import com.iflytek.skillhub.domain.skill.VisibilityChecker; import com.iflytek.skillhub.domain.user.UserAccount; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** Authorization for metadata exposed by subscriptions and subscriber notifications. */ @Component public class SubscriptionMetadataAccessPolicy { + private final VisibilityChecker visibilityChecker; + + public SubscriptionMetadataAccessPolicy() { + this(new VisibilityChecker()); + } + + @Autowired + public SubscriptionMetadataAccessPolicy(VisibilityChecker visibilityChecker) { + this.visibilityChecker = visibilityChecker; + } public boolean canAccessCurrent(Skill skill, Namespace namespace, UserAccount account, Map namespaceRoles) { - return canAccess(skill, namespace, account, namespaceRoles, false); + if (account == null || !account.isActive() || namespace == null) { + return false; + } + if (namespace.getStatus() == NamespaceStatus.ARCHIVED + && !isOwnerOrManager(skill, account, namespaceRoles)) { + return false; + } + return visibilityChecker.canAccess(skill, account.getId(), namespaceRoles); } public boolean canAccessYankedPublication(Skill skill, Namespace namespace, UserAccount account, @@ -49,4 +68,13 @@ public class SubscriptionMetadataAccessPolicy { case PRIVATE -> owner || manager; }; } + + private boolean isOwnerOrManager(Skill skill, UserAccount account, + Map namespaceRoles) { + if (skill.getOwnerId().equals(account.getId())) { + return true; + } + NamespaceRole role = namespaceRoles == null ? null : namespaceRoles.get(skill.getNamespaceId()); + return role == NamespaceRole.ADMIN || role == NamespaceRole.OWNER; + } } diff --git a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillReviewSubmitServiceTest.java b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillReviewSubmitServiceTest.java index 0b3b47d0..264e129f 100644 --- a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillReviewSubmitServiceTest.java +++ b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillReviewSubmitServiceTest.java @@ -86,7 +86,6 @@ class SkillReviewSubmitServiceTest { assertEquals(SkillVersionStatus.PENDING_REVIEW, version.getStatus()); assertEquals(SkillVisibility.PUBLIC, version.getRequestedVisibility()); verify(reviewTaskRepository).save(any(ReviewTask.class)); - verify(eventPublisher, never()).publishEvent(any()); } @Test @@ -187,7 +186,7 @@ class SkillReviewSubmitServiceTest { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(SkillPublishedEvent.class); - verify(eventPublisher, times(1)).publishEvent(eventCaptor.capture()); + verify(eventPublisher).publishEvent(eventCaptor.capture()); SkillPublishedEvent event = eventCaptor.getValue(); assertEquals(skillId, event.skillId()); assertEquals(versionId, event.versionId()); @@ -259,30 +258,6 @@ class SkillReviewSubmitServiceTest { verify(eventPublisher, never()).publishEvent(any()); } - @Test - @DisplayName("should not publish event when version persistence fails") - void shouldNotPublishEventWhenVersionPersistenceFails() { - // Given - Long skillId = 1L; - Long versionId = 100L; - String userId = "user-1"; - - Skill skill = createSkill(skillId, userId, 10L, SkillVisibility.PRIVATE); - SkillVersion version = createVersion(versionId, skillId, SkillVersionStatus.UPLOADED); - RuntimeException persistenceFailure = new RuntimeException("version save failed"); - - when(skillRepository.findById(skillId)).thenReturn(Optional.of(skill)); - when(skillVersionRepository.findById(versionId)).thenReturn(Optional.of(version)); - when(skillVersionRepository.save(version)).thenThrow(persistenceFailure); - - // When/Then - RuntimeException thrown = assertThrows(RuntimeException.class, - () -> service.confirmPublish(skillId, versionId, userId, Map.of())); - assertSame(persistenceFailure, thrown); - verify(skillRepository, never()).save(any()); - verify(eventPublisher, never()).publishEvent(any()); - } - @Test @DisplayName("should not publish event when skill persistence fails") void shouldNotPublishEventWhenSkillPersistenceFails() {