From f0a9714644079c87b0ee7d2a9e726427bb8be78e Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Tue, 17 Mar 2026 15:20:35 +0800 Subject: [PATCH] fix: hide hidden skills from slug resolution --- Makefile | 2 +- scripts/dev-process.sh | 156 ++++++++++++++++++ .../admin/AdminSkillReportControllerTest.java | 13 +- .../service/SkillSearchAppServiceTest.java | 2 + .../service/SkillSlugResolutionService.java | 2 +- .../SkillSlugResolutionServiceTest.java | 10 ++ 6 files changed, 181 insertions(+), 4 deletions(-) create mode 100755 scripts/dev-process.sh diff --git a/Makefile b/Makefile index be760c80..2f77d71c 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ DEV_API_URL := http://localhost:8080 STAGING_API_URL := http://localhost:8080 STAGING_WEB_URL := http://localhost STAGING_SERVER_IMAGE := skillhub-server:staging -DEV_PROCESS := python3 scripts/dev_process.py +DEV_PROCESS := bash scripts/dev-process.sh DEV_SERVER_PREPARE := true DEV_SERVER_CMD := ./scripts/run-dev-app.sh BACKEND_TEST_JAVA_OPTIONS ?= -XX:+EnableDynamicAgentLoading diff --git a/scripts/dev-process.sh b/scripts/dev-process.sh new file mode 100755 index 00000000..b5d2ebeb --- /dev/null +++ b/scripts/dev-process.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + echo "Usage:" >&2 + echo " $0 status --pid-file " >&2 + echo " $0 stop --pid-file " >&2 + echo " $0 start --pid-file --log-file --cwd -- " >&2 + exit 2 +} + +require_value() { + local flag="$1" + local value="${2:-}" + if [[ -z "$value" ]]; then + echo "Missing value for $flag" >&2 + usage + fi +} + +resolve_path() { + local path="$1" + if [[ "$path" = /* ]]; then + printf '%s\n' "$path" + else + printf '%s/%s\n' "$(pwd)" "$path" + fi +} + +is_running() { + local pid_file="$1" + [[ -f "$pid_file" ]] || return 1 + + local pid + pid="$(cat "$pid_file" 2>/dev/null || true)" + [[ "$pid" =~ ^[0-9]+$ ]] || return 1 + + if kill -0 "$pid" 2>/dev/null; then + return 0 + fi + + rm -f "$pid_file" + return 1 +} + +wait_for_exit() { + local pid="$1" + for _ in $(seq 1 50); do + if ! kill -0 "$pid" 2>/dev/null; then + return 0 + fi + sleep 0.1 + done + return 1 +} + +cmd="${1:-}" +[[ -n "$cmd" ]] || usage +shift || true + +pid_file="" +log_file="" +cwd="" + +case "$cmd" in + status|stop) + while [[ $# -gt 0 ]]; do + case "$1" in + --pid-file) + require_value "$1" "${2:-}" + pid_file="$2" + shift 2 + ;; + *) + usage + ;; + esac + done + [[ -n "$pid_file" ]] || usage + ;; + start) + while [[ $# -gt 0 ]]; do + case "$1" in + --pid-file) + require_value "$1" "${2:-}" + pid_file="$2" + shift 2 + ;; + --log-file) + require_value "$1" "${2:-}" + log_file="$2" + shift 2 + ;; + --cwd) + require_value "$1" "${2:-}" + cwd="$2" + shift 2 + ;; + --) + shift + break + ;; + *) + usage + ;; + esac + done + [[ -n "$pid_file" && -n "$log_file" && -n "$cwd" && $# -gt 0 ]] || usage + ;; + *) + usage + ;; +esac + +case "$cmd" in + status) + is_running "$pid_file" + ;; + stop) + if ! is_running "$pid_file"; then + rm -f "$pid_file" + exit 0 + fi + + pid="$(cat "$pid_file")" + kill "$pid" 2>/dev/null || true + if ! wait_for_exit "$pid"; then + kill -9 "$pid" 2>/dev/null || true + wait_for_exit "$pid" || true + fi + rm -f "$pid_file" + ;; + start) + pid_file="$(resolve_path "$pid_file")" + log_file="$(resolve_path "$log_file")" + cwd="$(resolve_path "$cwd")" + mkdir -p "$(dirname "$pid_file")" "$(dirname "$log_file")" + if is_running "$pid_file"; then + echo "Process already running with PID $(cat "$pid_file")" >&2 + exit 1 + fi + + ( + cd "$cwd" + if command -v setsid >/dev/null 2>&1; then + setsid "$@" >>"$log_file" 2>&1 < /dev/null & + else + nohup "$@" >>"$log_file" 2>&1 < /dev/null & + fi + child_pid=$! + disown "$child_pid" 2>/dev/null || true + echo "$child_pid" >"$pid_file" + ) + ;; +esac diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/admin/AdminSkillReportControllerTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/admin/AdminSkillReportControllerTest.java index cb2d74dd..0ff84bb4 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/admin/AdminSkillReportControllerTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/admin/AdminSkillReportControllerTest.java @@ -95,7 +95,7 @@ class AdminSkillReportControllerTest { report.setStatus(com.iflytek.skillhub.domain.report.SkillReportStatus.RESOLVED); when(skillReportService.resolveReport( org.mockito.ArgumentMatchers.eq(99L), - org.mockito.ArgumentMatchers.eq("admin"), + org.mockito.ArgumentMatchers.eq("super-admin"), org.mockito.ArgumentMatchers.eq(SkillReportDisposition.RESOLVE_AND_HIDE), org.mockito.ArgumentMatchers.eq("handled"), org.mockito.ArgumentMatchers.any(), @@ -103,7 +103,7 @@ class AdminSkillReportControllerTest { .thenReturn(report); mockMvc.perform(post("/api/v1/admin/skill-reports/99/resolve") - .with(authentication(adminAuth())) + .with(authentication(superAdminAuth())) .with(csrf()) .contentType(APPLICATION_JSON) .content("{\"comment\":\"handled\",\"disposition\":\"RESOLVE_AND_HIDE\"}")) @@ -147,4 +147,13 @@ class AdminSkillReportControllerTest { principal, null, List.of(new SimpleGrantedAuthority("ROLE_SKILL_ADMIN")) ); } + + private UsernamePasswordAuthenticationToken superAdminAuth() { + PlatformPrincipal principal = new PlatformPrincipal( + "super-admin", "super-admin", "admin@example.com", "", "github", Set.of("SUPER_ADMIN") + ); + return new UsernamePasswordAuthenticationToken( + principal, null, List.of(new SimpleGrantedAuthority("ROLE_SUPER_ADMIN")) + ); + } } diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillSearchAppServiceTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillSearchAppServiceTest.java index ef6b617d..e739ce30 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillSearchAppServiceTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillSearchAppServiceTest.java @@ -82,8 +82,10 @@ class SkillSearchAppServiceTest { void search_shouldFillVisiblePageAcrossArchivedNamespaceResults() { Skill archivedSkill = new Skill(1L, "archived-skill", "owner-1", SkillVisibility.PUBLIC); setField(archivedSkill, "id", 10L); + archivedSkill.setLatestVersionId(110L); Skill visibleSkill = new Skill(2L, "visible-skill", "owner-1", SkillVisibility.PUBLIC); setField(visibleSkill, "id", 11L); + visibleSkill.setLatestVersionId(111L); Namespace archivedNamespace = new Namespace("archived-team", "Archived Team", "owner-1"); setField(archivedNamespace, "id", 1L); diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionService.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionService.java index d7f9d1bf..e4521da3 100644 --- a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionService.java +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionService.java @@ -32,7 +32,7 @@ public class SkillSlugResolutionService { ? Optional.empty() : skills.stream().filter(skill -> currentUserId.equals(skill.getOwnerId())).findFirst(); Optional publishedSkill = skills.stream() - .filter(skill -> skill.getLatestVersionId() != null) + .filter(skill -> skill.getLatestVersionId() != null && !skill.isHidden()) .findFirst(); if (preference == Preference.CURRENT_USER) { diff --git a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionServiceTest.java b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionServiceTest.java index 8fdbdac1..a9233c89 100644 --- a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionServiceTest.java +++ b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillSlugResolutionServiceTest.java @@ -58,6 +58,16 @@ class SkillSlugResolutionServiceTest { service.resolve(1L, "demo", null, SkillSlugResolutionService.Preference.CURRENT_USER)); } + @Test + void throwsWhenOnlyPublishedSkillIsHiddenFromCurrentUser() throws Exception { + Skill hiddenPublishedSkill = createSkill(4L, "demo", "user-2", 44L); + hiddenPublishedSkill.setHidden(true); + when(skillRepository.findByNamespaceIdAndSlug(1L, "demo")).thenReturn(List.of(hiddenPublishedSkill)); + + assertThrows(DomainBadRequestException.class, () -> + service.resolve(1L, "demo", "user-9", SkillSlugResolutionService.Preference.CURRENT_USER)); + } + private Skill createSkill(Long id, String slug, String ownerId, Long latestVersionId) throws Exception { Skill skill = new Skill(1L, slug, ownerId, SkillVisibility.PUBLIC); Field idField = Skill.class.getDeclaredField("id");