Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.iflytek.skillhub.dto.PromotionResponseDto;
import com.iflytek.skillhub.service.AuditRequestContext;
import com.iflytek.skillhub.service.GovernanceWorkflowAppService;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -79,11 +81,19 @@ public ApiResponse<PromotionResponseDto> rejectPromotion(@PathVariable Long id,
}

@GetMapping
public ApiResponse<PageResponse<PromotionResponseDto>> listPromotions(@RequestParam(defaultValue = "PENDING") String status,
public ApiResponse<PageResponse<PromotionResponseDto>> listPromotions(@Parameter(schema = @Schema(allowableValues = {"PENDING", "APPROVED", "REJECTED"}, defaultValue = "PENDING"))
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@Parameter(schema = @Schema(allowableValues = {"reviewedAt"}))
@RequestParam(required = false) String sortBy,
@Parameter(schema = @Schema(allowableValues = {"ASC", "DESC"}, defaultValue = "DESC"))
@RequestParam(required = false) String sortDirection,
@RequestAttribute("userId") String userId) {
return ok("response.success.read", governanceWorkflowAppService.listPromotions(status, page, size, userId));
return ok(
"response.success.read",
governanceWorkflowAppService.listPromotions(status, page, size, sortBy, sortDirection, userId)
);
}

@GetMapping("/pending")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
public record PromotionResponseDto(
Long id,
Long sourceSkillId,
String sourceSkillDisplayName,
String sourceSkillSummary,
String sourceNamespace,
String sourceSkillSlug,
String sourceVersion,
Integer sourceVersionFileCount,
Long sourceVersionTotalSize,
Long sourceSkillDownloadCount,
Integer sourceSkillStarCount,
String targetNamespace,
Long targetSkillId,
String status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,15 @@ private PromotionResponseDto toPromotionResponse(PromotionRequest request, Promo
return new PromotionResponseDto(
request.getId(),
request.getSourceSkillId(),
skill.getDisplayName() != null ? skill.getDisplayName() : skill.getSlug(),
skill.getSummary(),
sourceNamespace.getSlug(),
skill.getSlug(),
version.getVersion(),
version.getFileCount(),
version.getTotalSize(),
skill.getDownloadCount(),
skill.getStarCount(),
targetNamespace.getSlug(),
request.getTargetSkillId(),
request.getStatus().name(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,13 @@ public PromotionResponseDto rejectPromotion(Long promotionId,
return promotionPortalAppService.rejectPromotion(promotionId, comment, userId, auditContext);
}

public PageResponse<PromotionResponseDto> listPromotions(String status, int page, int size, String userId) {
return promotionPortalAppService.listPromotions(status, page, size, userId);
public PageResponse<PromotionResponseDto> listPromotions(String status,
int page,
int size,
String sortBy,
String sortDirection,
String userId) {
return promotionPortalAppService.listPromotions(status, page, size, sortBy, sortDirection, userId);
}

public PageResponse<PromotionResponseDto> listPendingPromotions(int page, int size, String userId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@
import com.iflytek.skillhub.domain.review.PromotionRequestRepository;
import com.iflytek.skillhub.domain.review.PromotionService;
import com.iflytek.skillhub.domain.review.ReviewTaskStatus;
import com.iflytek.skillhub.domain.shared.exception.DomainBadRequestException;
import com.iflytek.skillhub.domain.shared.exception.DomainForbiddenException;
import com.iflytek.skillhub.domain.shared.exception.DomainNotFoundException;
import com.iflytek.skillhub.dto.PageResponse;
import com.iflytek.skillhub.dto.PromotionResponseDto;
import com.iflytek.skillhub.repository.GovernanceQueryRepository;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.slf4j.MDC;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

@Service
Expand Down Expand Up @@ -98,10 +102,12 @@ public PromotionResponseDto rejectPromotion(Long promotionId,
public PageResponse<PromotionResponseDto> listPromotions(String status,
int page,
int size,
String sortBy,
String sortDirection,
String userId) {
requirePromotionAdmin(userId);
ReviewTaskStatus reviewStatus = ReviewTaskStatus.valueOf(status.toUpperCase());
Page<PromotionRequest> requests = promotionRequestRepository.findByStatus(reviewStatus, PageRequest.of(page, size));
ReviewTaskStatus reviewStatus = parsePromotionStatus(status);
Page<PromotionRequest> requests = findPromotionRequests(reviewStatus, page, size, sortBy, sortDirection);
return PageResponse.from(new PageImpl<>(
governanceQueryRepository.getPromotionResponses(requests.getContent()),
requests.getPageable(),
Expand All @@ -112,7 +118,16 @@ public PageResponse<PromotionResponseDto> listPromotions(String status,
public PageResponse<PromotionResponseDto> listPendingPromotions(int page, int size, String userId) {
requirePromotionAdmin(userId);
Page<PromotionRequest> requests = promotionRequestRepository.findByStatus(
ReviewTaskStatus.PENDING, PageRequest.of(page, size));
ReviewTaskStatus.PENDING,
PageRequest.of(
page,
size,
Sort.by(
new Sort.Order(Sort.Direction.DESC, "submittedAt"),
new Sort.Order(Sort.Direction.DESC, "id")
)
)
);
return PageResponse.from(new PageImpl<>(
governanceQueryRepository.getPromotionResponses(requests.getContent()),
requests.getPageable(),
Expand All @@ -129,6 +144,72 @@ public PromotionResponseDto getPromotionDetail(Long promotionId, String userId)
return governanceQueryRepository.getPromotionResponse(promotion);
}

private ReviewTaskStatus parsePromotionStatus(String status) {
if (status == null) {
return ReviewTaskStatus.PENDING;
}
if (status.isBlank()) {
throw new DomainBadRequestException("promotion.status.invalid", status);
}
try {
ReviewTaskStatus parsed = ReviewTaskStatus.valueOf(status.toUpperCase(Locale.ROOT));
return switch (parsed) {
case PENDING, APPROVED, REJECTED -> parsed;
default -> throw new DomainBadRequestException("promotion.status.invalid", status);
};
} catch (IllegalArgumentException ex) {
throw new DomainBadRequestException("promotion.status.invalid", status);
}
}

private Page<PromotionRequest> findPromotionRequests(ReviewTaskStatus status,
int page,
int size,
String sortBy,
String sortDirection) {
if (status == ReviewTaskStatus.PENDING) {
if (sortBy != null || sortDirection != null) {
throw new DomainBadRequestException("promotion.sort.pending_unsupported");
}
return promotionRequestRepository.findByStatus(
status,
PageRequest.of(
page,
size,
Sort.by(
new Sort.Order(Sort.Direction.DESC, "submittedAt"),
new Sort.Order(Sort.Direction.DESC, "id")
)
)
);
}

if (sortBy != null && (sortBy.isBlank() || !"reviewedAt".equals(sortBy))) {
throw new DomainBadRequestException("promotion.sort.field.invalid", sortBy);
}

Sort.Direction direction = parsePromotionSortDirection(sortDirection);
Pageable pageable = PageRequest.of(page, size);
if (direction == Sort.Direction.ASC) {
return promotionRequestRepository.findHistoryByStatusOrderByReviewedAtAsc(status, pageable);
}
return promotionRequestRepository.findHistoryByStatusOrderByReviewedAtDesc(status, pageable);
}

private Sort.Direction parsePromotionSortDirection(String sortDirection) {
if (sortDirection == null) {
return Sort.Direction.DESC;
}
if (sortDirection.isBlank()) {
throw new DomainBadRequestException("promotion.sort.direction.invalid", sortDirection);
}
try {
return Sort.Direction.valueOf(sortDirection.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) {
throw new DomainBadRequestException("promotion.sort.direction.invalid", sortDirection);
}
}

private void requirePromotionAdmin(String userId) {
Set<String> platformRoles = platformRoles(userId);
if (!platformRoles.contains("SKILL_ADMIN") && !platformRoles.contains("SUPER_ADMIN")) {
Expand Down
4 changes: 4 additions & 0 deletions server/skillhub-app/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,7 @@ validation.auth.password.reset.code.notBlank=Verification code cannot be blank
validation.auth.password.reset.code.invalid=Verification code must be 6 digits
validation.auth.password.reset.newPassword.notBlank=New password cannot be blank
promotion.target_skill_conflict=The target global skill "{0}" already exists
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
4 changes: 4 additions & 0 deletions server/skillhub-app/src/main/resources/messages_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,7 @@ validation.auth.password.reset.code.notBlank=验证码不能为空
validation.auth.password.reset.code.invalid=验证码必须为 6 位数字
validation.auth.password.reset.newPassword.notBlank=新密码不能为空
promotion.target_skill_conflict=目标全局技能“{0}”已存在
promotion.status.invalid=不支持的提升审核状态:{0}
promotion.sort.field.invalid=不支持的提升审核排序字段:{0}
promotion.sort.direction.invalid=不支持的提升审核排序方向:{0}
promotion.sort.pending_unsupported=待审核提升请求不支持按处理时间排序
Loading
Loading