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
50 changes: 41 additions & 9 deletions contract/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ components:
type: "string"
detail: {}
id:
description: "활동 행 식별자. 목록 렌더링용 키이며 어떤 조회 파라미터도 아닙니다."
description: "활동 행의 공개 식별자. 목록 렌더링용 키이며 어떤 조회 파라미터도 아닙니다."
format: "uuid"
type: "string"
ip:
type:
Expand Down Expand Up @@ -370,6 +371,11 @@ components:
type:
- "string"
- "null"
createdByName:
description: "매핑을 만든 관리자 이름"
type:
- "string"
- "null"
ctMax:
description: "동시 연결 상한 오버라이드 (null = 에이전트 기본, 0 = 해제)"
format: "int32"
Expand Down Expand Up @@ -421,6 +427,11 @@ components:
type:
- "string"
- "null"
suspendedByName:
description: "정지한 관리자 이름 (자동 정지면 null)"
type:
- "string"
- "null"
suspendedReason:
description: "정지 사유 (SUSPENDED일 때)"
type:
Expand Down Expand Up @@ -956,7 +967,8 @@ components:
type: "string"
detail: {}
id:
description: "감사 로그 행 식별자. 목록 렌더링용 키이며 어떤 조회 파라미터도 아닙니다."
description: "감사 로그 행의 공개 식별자. 목록 렌더링용 키이며 어떤 조회 파라미터도 아닙니다."
format: "uuid"
type: "string"
ip:
type:
Expand Down Expand Up @@ -1276,9 +1288,7 @@ components:
displayName:
maxLength: 100
minLength: 0
type:
- "string"
- "null"
type: "string"
extraNote:
maxLength: 2000
minLength: 0
Expand Down Expand Up @@ -1312,6 +1322,7 @@ components:
format: "uuid"
type: "string"
required:
- "displayName"
- "orgId"
- "purpose"
- "type"
Expand Down Expand Up @@ -3063,9 +3074,7 @@ components:
format: "date-time"
type: "string"
displayName:
type:
- "string"
- "null"
type: "string"
extraNote:
type:
- "string"
Expand Down Expand Up @@ -3126,6 +3135,7 @@ components:
type: "string"
required:
- "createdAt"
- "displayName"
- "id"
- "orgName"
- "purpose"
Expand Down Expand Up @@ -4169,6 +4179,10 @@ components:
type:
- "string"
- "null"
actorName:
type:
- "string"
- "null"
changedAt:
format: "date-time"
type: "string"
Expand Down Expand Up @@ -4523,6 +4537,9 @@ components:
grantedImageId:
format: "uuid"
type: "string"
grantedImageName:
description: "승인된 OS 이미지의 표시 이름. 카탈로그에서 내려간 이미지도 이름이 남습니다."
type: "string"
grantedMemoryMb:
format: "int32"
type: "integer"
Expand All @@ -4534,9 +4551,15 @@ components:
type:
- "string"
- "null"
nodeName:
description: "배치된 노드의 이름. nodeId가 있을 때 함께 있습니다."
type:
- "string"
- "null"
required:
- "grantedDiskGb"
- "grantedImageId"
- "grantedImageName"
- "grantedMemoryMb"
- "grantedVcpu"
type: "object"
Expand Down Expand Up @@ -4659,13 +4682,21 @@ components:
type:
- "string"
- "null"
flavorName:
description: "요청한 사양 프리셋의 표시 이름. flavorId가 있을 때 함께 있습니다."
type:
- "string"
- "null"
granted:
anyOf:
- $ref: "#/components/schemas/VmGrantedSpecResponse"
- type: "null"
imageId:
format: "uuid"
type: "string"
imageName:
description: "요청한 OS 이미지의 표시 이름. 카탈로그에서 내려간 이미지도 이름이 남습니다."
type: "string"
reqDiskGb:
format: "int32"
type: "integer"
Expand All @@ -4685,6 +4716,7 @@ components:
- "null"
required:
- "imageId"
- "imageName"
- "reqDiskGb"
- "reqMemoryMb"
- "reqVcpu"
Expand Down Expand Up @@ -4987,7 +5019,7 @@ info:
description: "부산대학교 클라우드 플랫폼 Pickle의 REST API. 인증은 JWT Bearer, 오류 응답은 RFC 9457 problem+json(Problem\
\ 스키마)을 따릅니다."
title: "Pickle API"
version: "0.38.0"
version: "0.39.0"
openapi: "3.1.0"
paths:
/admin/announcements:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import kr.ac.pusan.pickle.access.dto.ResourceAccessGrantView;
import kr.ac.pusan.pickle.access.dto.ResourceAccessListResponse;
import kr.ac.pusan.pickle.access.dto.UpdateResourceAccessGrantRequest;
import kr.ac.pusan.pickle.audit.AuditIds;
import kr.ac.pusan.pickle.audit.AuditService;
import kr.ac.pusan.pickle.common.error.ApiException;
import kr.ac.pusan.pickle.common.error.ErrorCodes;
Expand Down Expand Up @@ -60,12 +61,13 @@ public class ResourceAccessGrantService {
private final WorkspaceRepository workspaceRepository;
private final UserRepository userRepository;
private final AuditService auditService;
private final AuditIds auditIds;

public ResourceAccessGrantService(List<ResourceTypeAdapter> adapters,
ResourceAccessResolver resolver, ResourceAccessGrantRepository grantRepository,
WorkspaceMemberRepository workspaceMemberRepository,
WorkspaceRepository workspaceRepository, UserRepository userRepository,
AuditService auditService) {
AuditService auditService, AuditIds auditIds) {
this.adapters = adapters.stream()
.collect(Collectors.toMap(ResourceTypeAdapter::type, Function.identity()));
this.resolver = resolver;
Expand All @@ -74,6 +76,7 @@ public ResourceAccessGrantService(List<ResourceTypeAdapter> adapters,
this.workspaceRepository = workspaceRepository;
this.userRepository = userRepository;
this.auditService = auditService;
this.auditIds = auditIds;
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -203,10 +206,10 @@ private void audit(AuthenticatedUser actor, Managed managed, GrantChange change,
ResourceAccessGrant grant, ResourceRole previousRole, String ip) {
ResourceAccessAudit names = managed.adapter().accessAudit();
Map<String, Object> detail = new LinkedHashMap<>();
detail.put("grantId", grant.getId());
detail.put("grantId", grant.getPublicId());
detail.put("granteeType", grant.getGranteeType().name());
if (grant.getUserId() != null) {
detail.put("granteeUserId", grant.getUserId());
detail.put("granteeUserId", auditIds.user(grant.getUserId()));
}
detail.put("role", grant.getRole().name());
if (previousRole != null) {
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/kr/ac/pusan/pickle/admin/AdminService.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,21 @@ public UserSummaryResponse updateUser(AuthenticatedUser actor, UUID userId,
auditService.recordAfterCommit(actor.id(), actor.role().name(), AuditService.USER_ROLE_UPDATE,
"user", user.getPublicId(),
Map.of("previousRole", previousRole.name(), "role", user.getRole().name(),
"orgId", user.getOrgId() == null ? "null" : String.valueOf(user.getOrgId())), ip);
"orgId", orgPublicIdOrNull(user.getOrgId())), ip);
return UserSummaryResponse.from(user);
}

/**
* The organisation the account now belongs to, named publicly. Map.of
* refuses a null value, so an account under no organisation keeps the
* literal {@code "null"} this field has always carried there.
*/
private String orgPublicIdOrNull(Long orgId) {
return orgId == null ? "null"
: orgRepository.findById(orgId).map(org -> org.getPublicId().toString())
.orElse("null");
}

private static String normalize(String description) {
return Texts.blankToNull(description);
}
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/kr/ac/pusan/pickle/admin/AdminTaskService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.function.Function;
import java.util.stream.Collectors;
import kr.ac.pusan.pickle.admin.dto.AdminTaskResponse;
import kr.ac.pusan.pickle.audit.AuditIds;
import kr.ac.pusan.pickle.audit.AuditService;
import kr.ac.pusan.pickle.auth.dto.MessageResponse;
import kr.ac.pusan.pickle.common.error.ApiException;
Expand Down Expand Up @@ -58,11 +59,13 @@ public class AdminTaskService {
private final DeleteVmJob deleteVmJob;
private final JobScheduler jobScheduler;
private final AuditService auditService;
private final AuditIds auditIds;

public AdminTaskService(ProvisioningTaskRepository taskRepository, VmRepository vmRepository,
OrgRepository orgRepository, WorkspaceRepository workspaceRepository,
ProvisioningService provisioningService,
DeleteVmJob deleteVmJob, JobScheduler jobScheduler, AuditService auditService) {
DeleteVmJob deleteVmJob, JobScheduler jobScheduler, AuditService auditService,
AuditIds auditIds) {
this.taskRepository = taskRepository;
this.vmRepository = vmRepository;
this.orgRepository = orgRepository;
Expand All @@ -71,6 +74,7 @@ public AdminTaskService(ProvisioningTaskRepository taskRepository, VmRepository
this.deleteVmJob = deleteVmJob;
this.jobScheduler = jobScheduler;
this.auditService = auditService;
this.auditIds = auditIds;
}

/**
Expand Down Expand Up @@ -149,7 +153,7 @@ public void afterCommit() {
});
auditService.recordAfterCommit(actor.id(), actor.role().name(), AuditService.TASK_RETRY,
"provisioning_task", task.getPublicId(),
Map.of("vmId", vmId, "kind", task.getKind().name()), ip);
Map.of("vmId", auditIds.vm(vmId), "kind", task.getKind().name()), ip);
return new MessageResponse("작업 재시도를 접수했습니다. 잠시 후 작업 상태가 갱신됩니다.");
}

Expand Down
20 changes: 11 additions & 9 deletions src/main/java/kr/ac/pusan/pickle/admin/AdminUserQueryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,21 @@ public UserAdminDetailResponse getUser(AuthenticatedUser actor, UUID userId) {
memberships, activeVmCount, statusChanges);
}

/** Resolves each transition's actor email in one batch. */
/** Resolves each transition's actor in one batch: id, email and name. */
private List<UserStatusChangeResponse> mapStatusChanges(List<UserStatusChange> changes) {
List<Long> actorIds = changes.stream().map(UserStatusChange::getActorId)
.filter(id -> id != null).distinct().toList();
List<User> actors = userRepository.findAllById(actorIds);
Map<Long, String> emails = actors.stream()
.collect(Collectors.toMap(User::getId, User::getEmail));
Map<Long, UUID> publicIds = actors.stream()
.collect(Collectors.toMap(User::getId, User::getPublicId));
Map<Long, User> actors = userRepository.findAllById(actorIds).stream()
.collect(Collectors.toMap(User::getId, java.util.function.Function.identity()));
return changes.stream()
.map(change -> new UserStatusChangeResponse(change.getFromStatus(), change.getToStatus(),
publicIds.get(change.getActorId()), emails.get(change.getActorId()),
change.getReason(), change.getChangedAt()))
.map(change -> {
User actor = actors.get(change.getActorId());
return new UserStatusChangeResponse(change.getFromStatus(), change.getToStatus(),
actor == null ? null : actor.getPublicId(),
actor == null ? null : actor.getEmail(),
actor == null ? null : actor.getName(),
change.getReason(), change.getChangedAt());
})
.toList();
}

Expand Down
8 changes: 6 additions & 2 deletions src/main/java/kr/ac/pusan/pickle/admin/ApprovalService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import kr.ac.pusan.pickle.access.ResourceType;
import kr.ac.pusan.pickle.admin.dto.ApproveRequestRequest;
import kr.ac.pusan.pickle.admin.dto.RejectRequestRequest;
import kr.ac.pusan.pickle.audit.AuditIds;
import kr.ac.pusan.pickle.audit.AuditService;
import kr.ac.pusan.pickle.common.error.ApiException;
import kr.ac.pusan.pickle.common.error.ErrorCodes;
Expand Down Expand Up @@ -68,6 +69,7 @@ public class ApprovalService {
private final UserRepository userRepository;
private final OrgRepository orgRepository;
private final AuditService auditService;
private final AuditIds auditIds;
private final NotificationService notificationService;

public ApprovalService(RequestRepository requestRepository, RequestReviewRepository reviewRepository,
Expand All @@ -76,7 +78,7 @@ public ApprovalService(RequestRepository requestRepository, RequestReviewReposit
WorkspaceRepository workspaceRepository,
ResourceAccessGrantRepository grantRepository, UserRepository userRepository,
OrgRepository orgRepository,
AuditService auditService, NotificationService notificationService) {
AuditService auditService, AuditIds auditIds, NotificationService notificationService) {
this.requestRepository = requestRepository;
this.reviewRepository = reviewRepository;
this.assembler = assembler;
Expand All @@ -88,6 +90,7 @@ public ApprovalService(RequestRepository requestRepository, RequestReviewReposit
this.userRepository = userRepository;
this.orgRepository = orgRepository;
this.auditService = auditService;
this.auditIds = auditIds;
this.notificationService = notificationService;
}

Expand Down Expand Up @@ -208,7 +211,8 @@ public RequestDetailResponse reject(AuthenticatedUser actor, UUID requestId,
reviewRepository.save(RequestReview.reject(request.getId(), actor.id(), form.comment().strip()));
request.setStatus(RequestStatus.REJECTED);
auditService.recordAfterCommit(actor.id(), actor.role().name(), AuditService.REQUEST_REJECT,
"request", request.getPublicId(), Map.of("workspaceId", request.getWorkspaceId()), ip);
"request", request.getPublicId(),
Map.of("workspaceId", auditIds.workspace(request.getWorkspaceId())), ip);
notificationService.publish(request.getRequesterId(), NotificationEvent.REQUEST_REJECTED,
Map.of("requestId", request.getPublicId(), "comment", form.comment().strip(),
"type", request.getResourceType().name()), null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public record UserStatusChangeResponse(
UserStatus toStatus,
@Nullable UUID actorId,
@Nullable String actorEmail,
@Nullable String actorName,
@Nullable String reason,
Instant changedAt) {
}
Loading