fix(subscription): reuse visibility policy and localize denial

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-08-27 16:45:55 +08:00
parent bacfd58aa0
commit 1331667496
6 changed files with 34 additions and 29 deletions

View file

@ -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.

View file

@ -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=您没有订阅此技能的权限。

View file

@ -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) {}

View file

@ -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;
}

View file

@ -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<Long, NamespaceRole> 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<Long, NamespaceRole> 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;
}
}

View file

@ -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<SkillPublishedEvent> 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() {