From 83758b7c5932af66a67803f04e44d6013d50e2e6 Mon Sep 17 00:00:00 2001 From: kwdahun Date: Sun, 24 Aug 2025 00:16:24 +0900 Subject: [PATCH 01/14] [fix] #93 Edit user dashboard API & Add response data for admin request management pannel --- dummy_data.sql | 90 +++++++++++++++++++ .../dashboard/service/DashboardService.java | 9 +- .../dto/response/SaveRequestResponseDTO.java | 58 +++++++++++- .../dto/response/UserServerResponseDTO.java | 8 +- 4 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 dummy_data.sql diff --git a/dummy_data.sql b/dummy_data.sql new file mode 100644 index 00000000..70ca3612 --- /dev/null +++ b/dummy_data.sql @@ -0,0 +1,90 @@ +-- Dummy data for testing +-- Execute in order: resource_groups → nodes → gpus, groups, container_images + +-- 1. Resource Groups +INSERT INTO resource_groups (rsgroup_id, resource_group_name, description, server_name) VALUES +(1, 'RTX 4090 Cluster', 'High-performance GPU cluster with RTX 4090 cards', 'LAB'), +(2, 'RTX 3090 Ti Cluster', 'Mid-range GPU cluster with RTX 3090 Ti cards', 'FARM'), +(3, 'A100 Server', 'Enterprise GPU server with A100 cards', 'LAB'), +(4, 'RTX 3080 Development', 'Development environment with RTX 3080 cards', 'FARM'); + +-- 2. Nodes +INSERT INTO nodes (node_id, rsgroup_id, memory_size_GB, CPU_core_count) VALUES +('LAB1', 1, 128, 32), +('LAB2', 1, 64, 16), +('LAB3', 3, 256, 64), +('LAB4', 3, 512, 96), +('FARM1', 2, 128, 32), +('FARM2', 2, 256, 64), +('FARM6', 4, 64, 16), +('FARM7', 4, 32, 8), +('FARM8', 2, 128, 32), +('FARM9', 4, 64, 16); + +-- 3. GPUs (homogeneous per node) +INSERT INTO gpus (node_id, gpu_model, RAM_GB) VALUES +-- LAB1 (2x RTX 4090) +('LAB1', 'RTX 4090', 24), +('LAB1', 'RTX 4090', 24), +-- LAB2 (1x RTX 4090) +('LAB2', 'RTX 4090', 24), +-- LAB3 (4x A100) +('LAB3', 'A100', 80), +('LAB3', 'A100', 80), +('LAB3', 'A100', 80), +('LAB3', 'A100', 80), +-- LAB4 (8x A100) +('LAB4', 'A100', 80), +('LAB4', 'A100', 80), +('LAB4', 'A100', 80), +('LAB4', 'A100', 80), +('LAB4', 'A100', 80), +('LAB4', 'A100', 80), +('LAB4', 'A100', 80), +('LAB4', 'A100', 80), +-- FARM1 (2x RTX 3090 Ti) +('FARM1', 'RTX 3090 Ti', 24), +('FARM1', 'RTX 3090 Ti', 24), +-- FARM2 (4x RTX 3090 Ti) +('FARM2', 'RTX 3090 Ti', 24), +('FARM2', 'RTX 3090 Ti', 24), +('FARM2', 'RTX 3090 Ti', 24), +('FARM2', 'RTX 3090 Ti', 24), +-- FARM6 (2x RTX 3080) +('FARM6', 'RTX 3080', 10), +('FARM6', 'RTX 3080', 10), +-- FARM7 (1x RTX 3080) +('FARM7', 'RTX 3080', 10), +-- FARM8 (2x RTX 3090 Ti) +('FARM8', 'RTX 3090 Ti', 24), +('FARM8', 'RTX 3090 Ti', 24), +-- FARM9 (2x RTX 3080) +('FARM9', 'RTX 3080', 10), +('FARM9', 'RTX 3080', 10); + +-- 4. Used IDs for groups +INSERT INTO used_ids (id_value) VALUES +(1001), +(1002), +(1003), +(1004), +(1005); + +-- 5. Groups +INSERT INTO `groups` (ubuntu_gid, group_name) VALUES +(1001, 'ml-researchers'), +(1002, 'data-scientists'), +(1003, 'ai-developers'), +(1004, 'gpu-users'), +(1005, 'admin-team'); + +-- 6. Container Images (CUDA versions only) +INSERT INTO container_image (image_name, image_version, cuda_version, description, created_at, updated_at) VALUES +('cuda', '11.8', '11.8', 'CUDA 11.8 development environment', NOW(), NOW()), +('cuda', '12.0', '12.0', 'CUDA 12.0 development environment', NOW(), NOW()), +('cuda', '11.7', '11.7', 'CUDA 11.7 development environment', NOW(), NOW()), +('cuda', '12.1', '12.1', 'CUDA 12.1 development environment', NOW(), NOW()), +('cuda', '11.6', '11.6', 'CUDA 11.6 development environment', NOW(), NOW()), +('cuda', '12.2', '12.2', 'CUDA 12.2 development environment', NOW(), NOW()), +('cuda', '11.5', '11.5', 'CUDA 11.5 development environment', NOW(), NOW()), +('cuda', '12.3', '12.3', 'CUDA 12.3 development environment', NOW(), NOW()); \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java index 1911077c..3f36cc87 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java @@ -1,5 +1,6 @@ package DGU_AI_LAB.admin_be.domain.dashboard.service; +import DGU_AI_LAB.admin_be.domain.containerImage.dto.response.ContainerImageResponseDTO; import DGU_AI_LAB.admin_be.domain.nodes.entity.Node; import DGU_AI_LAB.admin_be.domain.nodes.repository.NodeRepository; import DGU_AI_LAB.admin_be.domain.requests.dto.response.UserServerResponseDTO; @@ -53,12 +54,18 @@ public List getUserServers(Long userId, Status status) { } } + ContainerImageResponseDTO containerImageDTO = null; + if (request.getContainerImage() != null) { + containerImageDTO = ContainerImageResponseDTO.fromEntity(request.getContainerImage()); + } + return UserServerResponseDTO.fromEntity( request, serverAddress, cpuCoreCount, memoryGB, - resourceGroupName + resourceGroupName, + containerImageDTO ); }) .collect(Collectors.toList()); diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java index b1cf14d5..5429e783 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java @@ -2,6 +2,8 @@ import DGU_AI_LAB.admin_be.domain.requests.entity.Request; import DGU_AI_LAB.admin_be.domain.requests.entity.Status; +import DGU_AI_LAB.admin_be.domain.resourceGroups.entity.ResourceGroup; +import DGU_AI_LAB.admin_be.domain.users.entity.User; import com.fasterxml.jackson.annotation.JsonRawValue; import lombok.Builder; @@ -12,23 +14,71 @@ public record SaveRequestResponseDTO( Long requestId, Integer resourceGroupId, + AdminResourceGroupInfo resourceGroup, + AdminUserInfo user, String imageName, String imageVersion, String ubuntuUsername, Long ubuntuUid, List ubuntuGids, - Long volumeSizeByte, + Long volumeSizeGiB, String usagePurpose, @JsonRawValue String formAnswers, LocalDateTime expiresAt, Status status, LocalDateTime approvedAt, - String comment + String comment, + LocalDateTime createdAt, + LocalDateTime updatedAt ) { + + @Builder + public record AdminResourceGroupInfo( + Integer rsgroupId, + String resourceGroupName, + String description, + String serverName + ) { + public static AdminResourceGroupInfo fromEntity(ResourceGroup resourceGroup) { + return AdminResourceGroupInfo.builder() + .rsgroupId(resourceGroup.getRsgroupId()) + .resourceGroupName(resourceGroup.getResourceGroupName()) + .description(resourceGroup.getDescription()) + .serverName(resourceGroup.getServerName()) + .build(); + } + } + + @Builder + public record AdminUserInfo( + Long userId, + String email, + String name, + String studentId, + String department, + String phone, + Boolean isActive + ) { + public static AdminUserInfo fromEntity(User user) { + return AdminUserInfo.builder() + .userId(user.getUserId()) + .email(user.getEmail()) + .name(user.getName()) + .studentId(user.getStudentId()) + .department(user.getDepartment()) + .phone(user.getPhone()) + .isActive(user.getIsActive()) + .build(); + } + } public static SaveRequestResponseDTO fromEntity(Request request) { return SaveRequestResponseDTO.builder() .requestId(request.getRequestId()) .resourceGroupId(request.getResourceGroup() != null ? request.getResourceGroup().getRsgroupId() : null) + .resourceGroup(request.getResourceGroup() != null ? + AdminResourceGroupInfo.fromEntity(request.getResourceGroup()) : null) + .user(request.getUser() != null ? + AdminUserInfo.fromEntity(request.getUser()) : null) .imageName(request.getContainerImage().getImageName()) .imageVersion(request.getContainerImage().getImageVersion()) .ubuntuUsername(request.getUbuntuUsername()) @@ -40,13 +90,15 @@ public static SaveRequestResponseDTO fromEntity(Request request) { .map(rg -> rg.getGroup().getUbuntuGid()) .toList() ) - .volumeSizeByte(request.getVolumeSizeGiB()) + .volumeSizeGiB(request.getVolumeSizeGiB()) .usagePurpose(request.getUsagePurpose()) .formAnswers(request.getFormAnswers()) .expiresAt(request.getExpiresAt()) .status(request.getStatus()) .approvedAt(request.getApprovedAt()) .comment(request.getAdminComment()) + .createdAt(request.getCreatedAt()) + .updatedAt(request.getUpdatedAt()) .build(); } } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java index ad55a06b..46639a50 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java @@ -1,5 +1,6 @@ package DGU_AI_LAB.admin_be.domain.requests.dto.response; +import DGU_AI_LAB.admin_be.domain.containerImage.dto.response.ContainerImageResponseDTO; import DGU_AI_LAB.admin_be.domain.requests.entity.Request; import DGU_AI_LAB.admin_be.domain.requests.entity.Status; import io.swagger.v3.oas.annotations.media.Schema; @@ -25,18 +26,21 @@ public record UserServerResponseDTO( Integer memoryGB, @Schema(description = "할당된 리소스 그룹명 (GPU 스펙 묶음)", example = "RTX 3090 D6 24GB 그룹") String resourceGroupName, + @Schema(description = "컨테이너 이미지 정보") + ContainerImageResponseDTO containerImage, @Schema(description = "서버 신청 상태", example = "FULFILLED", allowableValues = {"PENDING", "FULFILLED", "DENIED", "MODIFICATION_REQUESTED", "MODIFICATION_APPROVED", "MODIFICATION_REJECTED"}) Status status ) { - public static UserServerResponseDTO fromEntity(Request request, String serverAddress, Integer cpuCoreCount, Integer memoryGB, String resourceGroupName) { + public static UserServerResponseDTO fromEntity(Request request, String serverAddress, Integer cpuCoreCount, Integer memoryGB, String resourceGroupName, ContainerImageResponseDTO containerImage) { return UserServerResponseDTO.builder() .requestId(request.getRequestId()) .serverAddress(serverAddress) .expiresAt(request.getExpiresAt()) - .volumeSizeGiB(request.getVolumeSizeGiB() / (1024L * 1024 * 1024)) + .volumeSizeGiB(request.getVolumeSizeGiB()) .cpuCoreCount(cpuCoreCount) .memoryGB(memoryGB) .resourceGroupName(resourceGroupName) + .containerImage(containerImage) .status(request.getStatus()) .build(); } From f33f34c7da063b6286f57b61b44d6ccb2043249b Mon Sep 17 00:00:00 2001 From: kwdahun Date: Sun, 24 Aug 2025 00:29:42 +0900 Subject: [PATCH 02/14] [feat] #93 Add 'ALL' enumeration for listing dashboard requests --- .../domain/dashboard/controller/DashboardController.java | 3 ++- .../domain/dashboard/service/DashboardService.java | 7 ++++++- .../DGU_AI_LAB/admin_be/domain/requests/entity/Status.java | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/controller/DashboardController.java b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/controller/DashboardController.java index 332de01b..114c02aa 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/controller/DashboardController.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/controller/DashboardController.java @@ -31,8 +31,9 @@ public class DashboardController implements DashBoardApi { * GET /api/dashboard/me/servers * * @param principal 현재 로그인한 사용자의 인증 정보 (CustomUserDetails) - * @param status 조회할 서버 요청의 상태 (필수 값: PENDING, FULFILLED, DENIED 등) + * @param status 조회할 서버 요청의 상태 (필수 값: PENDING, FULFILLED, DENIED, ALL) * 사용자의 승인받은 서버 목록 또는 승인 대기중인 신청 목록을 필터링하여 반환합니다. + * 'ALL' 상태를 사용하면 모든 상태의 요청을 반환합니다. */ @GetMapping("/me/servers") public ResponseEntity> getUserServers( diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java index 3f36cc87..3ea60578 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java @@ -32,7 +32,12 @@ public class DashboardService { public List getUserServers(Long userId, Status status) { log.info("[getUserServers] userId={}의 status={} 서버 목록 조회 시작", userId, status); - List requests = requestRepository.findByUserUserIdAndStatus(userId, status); + List requests; + if (status == Status.ALL) { + requests = requestRepository.findAllByUser_UserId(userId); + } else { + requests = requestRepository.findByUserUserIdAndStatus(userId, status); + } return requests.stream() .map(request -> { diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Status.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Status.java index 037daa71..7c2fb5b6 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Status.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Status.java @@ -1,5 +1,5 @@ package DGU_AI_LAB.admin_be.domain.requests.entity; public enum Status { - PENDING, DENIED, FULFILLED + PENDING, DENIED, FULFILLED, ALL } From 7aa782c4a286cb216571680f10f7dde152399550 Mon Sep 17 00:00:00 2001 From: kwdahun Date: Sun, 24 Aug 2025 00:59:03 +0900 Subject: [PATCH 03/14] [fix] #93 Exclude DTO layer from doing business logic --- .../dto/response/SaveRequestResponseDTO.java | 8 ++- .../requests/service/RequestService.java | 54 ++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java index 5429e783..d6f5a582 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java @@ -74,11 +74,9 @@ public static AdminUserInfo fromEntity(User user) { public static SaveRequestResponseDTO fromEntity(Request request) { return SaveRequestResponseDTO.builder() .requestId(request.getRequestId()) - .resourceGroupId(request.getResourceGroup() != null ? request.getResourceGroup().getRsgroupId() : null) - .resourceGroup(request.getResourceGroup() != null ? - AdminResourceGroupInfo.fromEntity(request.getResourceGroup()) : null) - .user(request.getUser() != null ? - AdminUserInfo.fromEntity(request.getUser()) : null) + .resourceGroupId(request.getResourceGroup().getRsgroupId()) + .resourceGroup(AdminResourceGroupInfo.fromEntity(request.getResourceGroup())) + .user(AdminUserInfo.fromEntity(request.getUser())) .imageName(request.getContainerImage().getImageName()) .imageVersion(request.getContainerImage().getImageVersion()) .ubuntuUsername(request.getUbuntuUsername()) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java index f6d0137a..465fc5d6 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java @@ -95,6 +95,18 @@ public SaveRequestResponseDTO createRequest(Long userId, SaveRequestRequestDTO d req.addGroup(g); } } + + // Validate required entities before DTO conversion + if (req.getResourceGroup() == null) { + throw new BusinessException(ErrorCode.RESOURCE_GROUP_NOT_FOUND); + } + if (req.getUser() == null) { + throw new BusinessException(ErrorCode.USER_NOT_FOUND); + } + if (req.getContainerImage() == null) { + throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); + } + return SaveRequestResponseDTO.fromEntity(req); } @@ -158,6 +170,17 @@ public SaveRequestResponseDTO approveRequest(ApproveRequestDTO dto) { throw new BusinessException(ErrorCode.EXTERNAL_API_FAILED); } + // Validate required entities before DTO conversion + if (request.getResourceGroup() == null) { + throw new BusinessException(ErrorCode.RESOURCE_GROUP_NOT_FOUND); + } + if (request.getUser() == null) { + throw new BusinessException(ErrorCode.USER_NOT_FOUND); + } + if (request.getContainerImage() == null) { + throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); + } + return SaveRequestResponseDTO.fromEntity(request); } @@ -174,6 +197,18 @@ public SaveRequestResponseDTO rejectRequest(RejectRequestDTO dto) { request.reject( dto.adminComment() ); + + // Validate required entities before DTO conversion + if (request.getResourceGroup() == null) { + throw new BusinessException(ErrorCode.RESOURCE_GROUP_NOT_FOUND); + } + if (request.getUser() == null) { + throw new BusinessException(ErrorCode.USER_NOT_FOUND); + } + if (request.getContainerImage() == null) { + throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); + } + return SaveRequestResponseDTO.fromEntity(request); } @@ -181,7 +216,7 @@ public SaveRequestResponseDTO rejectRequest(RejectRequestDTO dto) { @Transactional(readOnly = true) public List getAllRequests() { return requestRepository.findAll().stream() - .map(SaveRequestResponseDTO::fromEntity) + .map(this::validateAndConvertToDTO) .toList(); } @@ -192,7 +227,7 @@ public List getRequestsByUserId(Long userId) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } return requestRepository.findAllByUser_UserId(userId).stream() - .map(SaveRequestResponseDTO::fromEntity) + .map(this::validateAndConvertToDTO) .toList(); } @@ -254,4 +289,19 @@ public List getActiveContainers() { .map(ContainerInfoDTO::fromEntity) .toList(); } + + private SaveRequestResponseDTO validateAndConvertToDTO(Request request) { + // Validate required entities before DTO conversion + if (request.getResourceGroup() == null) { + throw new BusinessException(ErrorCode.RESOURCE_GROUP_NOT_FOUND); + } + if (request.getUser() == null) { + throw new BusinessException(ErrorCode.USER_NOT_FOUND); + } + if (request.getContainerImage() == null) { + throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); + } + + return SaveRequestResponseDTO.fromEntity(request); + } } From 74a5a63de719221263e9baf03ff8f646d23029ae Mon Sep 17 00:00:00 2001 From: kwdahun Date: Sun, 24 Aug 2025 14:27:47 +0900 Subject: [PATCH 04/14] [feat] #93 Add imageId to response format --- .../dto/response/SaveRequestResponseDTO.java | 2 ++ .../requests/service/RequestService.java | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java index d6f5a582..808d8c2e 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java @@ -16,6 +16,7 @@ public record SaveRequestResponseDTO( Integer resourceGroupId, AdminResourceGroupInfo resourceGroup, AdminUserInfo user, + Long imageId, String imageName, String imageVersion, String ubuntuUsername, @@ -77,6 +78,7 @@ public static SaveRequestResponseDTO fromEntity(Request request) { .resourceGroupId(request.getResourceGroup().getRsgroupId()) .resourceGroup(AdminResourceGroupInfo.fromEntity(request.getResourceGroup())) .user(AdminUserInfo.fromEntity(request.getUser())) + .imageId(request.getContainerImage().getImageId()) .imageName(request.getContainerImage().getImageName()) .imageVersion(request.getContainerImage().getImageVersion()) .ubuntuUsername(request.getUbuntuUsername()) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java index 465fc5d6..2ed57018 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java @@ -159,16 +159,18 @@ public SaveRequestResponseDTO approveRequest(ApproveRequestDTO dto) { request.getVolumeSizeGiB() ); - HttpEntity entity = new HttpEntity<>(body, headers); - - try { - ResponseEntity res = restTemplate.postForEntity(url, entity, Void.class); - if (!res.getStatusCode().is2xxSuccessful()) { - throw new BusinessException(ErrorCode.EXTERNAL_API_FAILED); - } - } catch (Exception ex) { - throw new BusinessException(ErrorCode.EXTERNAL_API_FAILED); - } + // Should enable when pvc service is ready + +// HttpEntity entity = new HttpEntity<>(body, headers); +// +// try { +// ResponseEntity res = restTemplate.postForEntity(url, entity, Void.class); +// if (!res.getStatusCode().is2xxSuccessful()) { +// throw new BusinessException(ErrorCode.EXTERNAL_API_FAILED); +// } +// } catch (Exception ex) { +// throw new BusinessException(ErrorCode.EXTERNAL_API_FAILED); +// } // Validate required entities before DTO conversion if (request.getResourceGroup() == null) { @@ -190,7 +192,7 @@ public SaveRequestResponseDTO rejectRequest(RejectRequestDTO dto) { Request request = requestRepository.findById(dto.requestId()) .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND)); - if (request.getStatus() != Status.PENDING) { + if (!(request.getStatus() == Status.PENDING || request.getStatus() == Status.FULFILLED)) { throw new BusinessException(ErrorCode.INVALID_REQUEST_STATUS); } From 33b368e699d5951fa138a96f17549b6d67a701bf Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:11:11 +0900 Subject: [PATCH 05/14] =?UTF-8?q?[feat]=20#93=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20DTO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/CreateGroupRequestDTO.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/DGU_AI_LAB/admin_be/domain/groups/dto/request/CreateGroupRequestDTO.java diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/dto/request/CreateGroupRequestDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/dto/request/CreateGroupRequestDTO.java new file mode 100644 index 00000000..70864ee6 --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/dto/request/CreateGroupRequestDTO.java @@ -0,0 +1,16 @@ +package DGU_AI_LAB.admin_be.domain.groups.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Builder; + +@Builder +public record CreateGroupRequestDTO( + @NotNull @Positive + Long ubuntuGid, + + @NotBlank + String groupName +) { +} \ No newline at end of file From aa00bc17bb301843cd8c0498b72e6deabe5abab9 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:11:24 +0900 Subject: [PATCH 06/14] =?UTF-8?q?[feat]=20#93=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=EB=90=9C=20=EA=B7=B8=EB=A3=B9=20=EC=97=90=EB=9F=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java index 26de0a70..da7339ab 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java +++ b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java @@ -97,6 +97,7 @@ public enum ErrorCode { * Group Error */ NO_AVAILABLE_GROUPS(HttpStatus.NOT_FOUND, "존재하는 그룹 정보가 없습니다."), + DUPLICATE_GROUP_ID(HttpStatus.CONFLICT, "중복된 그룹 ID를 할당할 수 없습니다."), /** From 3995b6c22195fa8d2d660a0ec9ef84f5baa15d4f Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:12:25 +0900 Subject: [PATCH 07/14] =?UTF-8?q?[fix]=20#93=20ubuntu=20gid=EC=99=80?= =?UTF-8?q?=EB=8A=94=20=EB=B3=84=EA=B0=9C=EB=A1=9C=20group=5Fid=EB=A5=BC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=ED=95=98=EA=B3=A0=20MapsId=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_be/domain/groups/entity/Group.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java index d960fb43..673953f9 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java @@ -10,18 +10,21 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder -@EqualsAndHashCode(of = "ubuntuGid") +@EqualsAndHashCode(of = "groupId") public class Group { @Id - @Column(name = "ubuntu_gid", nullable = false) - private Long ubuntuGid; + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "group_id") + private Long groupId; @Column(name = "group_name", nullable = false, length = 100) private String groupName; + @Column(name = "ubuntu_gid", unique = true, nullable = false) + private Long ubuntuGid; + @OneToOne(fetch = FetchType.LAZY) - @MapsId - @JoinColumn(name = "ubuntu_gid") + @JoinColumn(name = "ubuntu_gid", referencedColumnName = "id_value", insertable = false, updatable = false) private UsedId usedId; -} +} \ No newline at end of file From 515f8b3b74267dba6869259ea0b63d9fd9e49ce5 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:12:49 +0900 Subject: [PATCH 08/14] =?UTF-8?q?[fix]=20#93=20mapsId=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EA=B7=B8=EB=A3=B9=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/groups/service/GroupService.java | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java index e9bd6228..cbe7d6aa 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java @@ -1,7 +1,11 @@ package DGU_AI_LAB.admin_be.domain.groups.service; +import DGU_AI_LAB.admin_be.domain.groups.dto.request.CreateGroupRequestDTO; import DGU_AI_LAB.admin_be.domain.groups.dto.response.GroupResponseDTO; +import DGU_AI_LAB.admin_be.domain.groups.entity.Group; import DGU_AI_LAB.admin_be.domain.groups.repository.GroupRepository; +import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; +import DGU_AI_LAB.admin_be.domain.usedIds.repository.UsedIdRepository; import DGU_AI_LAB.admin_be.error.ErrorCode; import DGU_AI_LAB.admin_be.error.exception.BusinessException; import lombok.RequiredArgsConstructor; @@ -10,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; @Slf4j @Service @@ -18,9 +23,10 @@ public class GroupService { private final GroupRepository groupRepository; + private final UsedIdRepository usedIdRepository; /** - * 모든 그룹 정보를 조회하는 API + * 모든 그룹 정보 조회 API * GET /api/groups */ public List getAllGroups() { @@ -39,4 +45,43 @@ public List getAllGroups() { log.info("[getAllGroups] 모든 그룹 정보 조회 완료. {}개 그룹", response.size()); return response; } -} + + /** + * 새로운 그룹을 생성하는 API + * POST /api/groups + */ + @Transactional + public GroupResponseDTO createGroup(CreateGroupRequestDTO dto) { + log.info("[createGroup] 그룹 생성 요청 시작: groupName={}, ubuntuGid={}", dto.groupName(), dto.ubuntuGid()); + + // 1. 요청받은 GID가 UsedId에 이미 사용 중인지 확인하고, 없으면 생성 + Optional existingUsedId = usedIdRepository.findById(dto.ubuntuGid()); + UsedId usedId; + if (existingUsedId.isPresent()) { + log.warn("[createGroup] 중복된 GID가 UsedId에 이미 존재합니다: {}", dto.ubuntuGid()); + usedId = existingUsedId.get(); + } else { + // UsedId에 GID가 없으므로 새로 생성하여 저장합니다. + usedId = usedIdRepository.saveAndFlush(UsedId.builder().idValue(dto.ubuntuGid()).build()); + log.info("[createGroup] UsedId에 GID {} 할당 완료", dto.ubuntuGid()); + } + + // 2. 해당 GID의 그룹이 이미 존재하는지 확인 + if (groupRepository.existsByUbuntuGid(dto.ubuntuGid())) { + log.warn("[createGroup] 중복된 GID를 가진 그룹이 이미 존재합니다: {}", dto.ubuntuGid()); + throw new BusinessException(ErrorCode.DUPLICATE_GROUP_ID); + } + + // 3. 그룹 엔티티 생성 및 저장 + Group group = Group.builder() + .groupName(dto.groupName()) + .ubuntuGid(dto.ubuntuGid()) + .usedId(usedId) // 영속화된 UsedId 인스턴스 연결 + .build(); + + group = groupRepository.save(group); + log.info("[createGroup] 그룹 생성 완료: id={}, name={}", group.getGroupId(), group.getGroupName()); + + return GroupResponseDTO.fromEntity(group); + } +} \ No newline at end of file From 63f2418a2cd8d623af60e641918fb78a8f7bde24 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:12:55 +0900 Subject: [PATCH 09/14] =?UTF-8?q?[fix]=20#93=20mapsId=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EA=B7=B8=EB=A3=B9=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_be/domain/groups/repository/GroupRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/repository/GroupRepository.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/repository/GroupRepository.java index d8f1e3d1..2a73c115 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/repository/GroupRepository.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/repository/GroupRepository.java @@ -10,4 +10,5 @@ @Repository public interface GroupRepository extends JpaRepository { List findAllByUbuntuGidIn(Set ubuntuGids); -} + boolean existsByUbuntuGid(Long ubuntuGid); +} \ No newline at end of file From 8c17d5092977dc12428367a51e74d4e1ef3745f5 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:16:36 +0900 Subject: [PATCH 10/14] =?UTF-8?q?[feat/fix]=20#100=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../groups/controller/GroupController.java | 20 ++++++-- .../admin_be/domain/groups/entity/Group.java | 15 +++--- .../domain/groups/service/GroupService.java | 49 +------------------ 3 files changed, 23 insertions(+), 61 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/controller/GroupController.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/controller/GroupController.java index 6096453f..66e97889 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/controller/GroupController.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/controller/GroupController.java @@ -1,14 +1,14 @@ package DGU_AI_LAB.admin_be.domain.groups.controller; +import DGU_AI_LAB.admin_be.domain.groups.dto.request.CreateGroupRequestDTO; import DGU_AI_LAB.admin_be.domain.groups.dto.response.GroupResponseDTO; import DGU_AI_LAB.admin_be.domain.groups.service.GroupService; import DGU_AI_LAB.admin_be.global.common.SuccessResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -23,12 +23,22 @@ public class GroupController { /** * 모든 그룹 정보 조회 API * GET /api/groups - * * 그룹 ID와 그룹명을 반환합니다. */ - @GetMapping("") + @GetMapping public ResponseEntity> getGroups() { List groups = groupService.getAllGroups(); return SuccessResponse.ok(groups); } + + /** + * 새로운 그룹을 생성하는 API + * POST /api/groups + */ + @PostMapping + public ResponseEntity> createGroup(@RequestBody @Valid CreateGroupRequestDTO dto) { + log.info("[createGroup] 새로운 그룹 생성 요청 접수: {}", dto.groupName()); + GroupResponseDTO response = groupService.createGroup(dto); + return SuccessResponse.created(response); + } } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java index 673953f9..d960fb43 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java @@ -10,21 +10,18 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder -@EqualsAndHashCode(of = "groupId") +@EqualsAndHashCode(of = "ubuntuGid") public class Group { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "group_id") - private Long groupId; + @Column(name = "ubuntu_gid", nullable = false) + private Long ubuntuGid; @Column(name = "group_name", nullable = false, length = 100) private String groupName; - @Column(name = "ubuntu_gid", unique = true, nullable = false) - private Long ubuntuGid; - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "ubuntu_gid", referencedColumnName = "id_value", insertable = false, updatable = false) + @MapsId + @JoinColumn(name = "ubuntu_gid") private UsedId usedId; -} \ No newline at end of file +} diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java index cbe7d6aa..e9bd6228 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java @@ -1,11 +1,7 @@ package DGU_AI_LAB.admin_be.domain.groups.service; -import DGU_AI_LAB.admin_be.domain.groups.dto.request.CreateGroupRequestDTO; import DGU_AI_LAB.admin_be.domain.groups.dto.response.GroupResponseDTO; -import DGU_AI_LAB.admin_be.domain.groups.entity.Group; import DGU_AI_LAB.admin_be.domain.groups.repository.GroupRepository; -import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; -import DGU_AI_LAB.admin_be.domain.usedIds.repository.UsedIdRepository; import DGU_AI_LAB.admin_be.error.ErrorCode; import DGU_AI_LAB.admin_be.error.exception.BusinessException; import lombok.RequiredArgsConstructor; @@ -14,7 +10,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Optional; @Slf4j @Service @@ -23,10 +18,9 @@ public class GroupService { private final GroupRepository groupRepository; - private final UsedIdRepository usedIdRepository; /** - * 모든 그룹 정보 조회 API + * 모든 그룹 정보를 조회하는 API * GET /api/groups */ public List getAllGroups() { @@ -45,43 +39,4 @@ public List getAllGroups() { log.info("[getAllGroups] 모든 그룹 정보 조회 완료. {}개 그룹", response.size()); return response; } - - /** - * 새로운 그룹을 생성하는 API - * POST /api/groups - */ - @Transactional - public GroupResponseDTO createGroup(CreateGroupRequestDTO dto) { - log.info("[createGroup] 그룹 생성 요청 시작: groupName={}, ubuntuGid={}", dto.groupName(), dto.ubuntuGid()); - - // 1. 요청받은 GID가 UsedId에 이미 사용 중인지 확인하고, 없으면 생성 - Optional existingUsedId = usedIdRepository.findById(dto.ubuntuGid()); - UsedId usedId; - if (existingUsedId.isPresent()) { - log.warn("[createGroup] 중복된 GID가 UsedId에 이미 존재합니다: {}", dto.ubuntuGid()); - usedId = existingUsedId.get(); - } else { - // UsedId에 GID가 없으므로 새로 생성하여 저장합니다. - usedId = usedIdRepository.saveAndFlush(UsedId.builder().idValue(dto.ubuntuGid()).build()); - log.info("[createGroup] UsedId에 GID {} 할당 완료", dto.ubuntuGid()); - } - - // 2. 해당 GID의 그룹이 이미 존재하는지 확인 - if (groupRepository.existsByUbuntuGid(dto.ubuntuGid())) { - log.warn("[createGroup] 중복된 GID를 가진 그룹이 이미 존재합니다: {}", dto.ubuntuGid()); - throw new BusinessException(ErrorCode.DUPLICATE_GROUP_ID); - } - - // 3. 그룹 엔티티 생성 및 저장 - Group group = Group.builder() - .groupName(dto.groupName()) - .ubuntuGid(dto.ubuntuGid()) - .usedId(usedId) // 영속화된 UsedId 인스턴스 연결 - .build(); - - group = groupRepository.save(group); - log.info("[createGroup] 그룹 생성 완료: id={}, name={}", group.getGroupId(), group.getGroupName()); - - return GroupResponseDTO.fromEntity(group); - } -} \ No newline at end of file +} From a44883c141ae9d42e1824965f52d2e5fbfbd93b1 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:20:52 +0900 Subject: [PATCH 11/14] =?UTF-8?q?[fix]=20#100=20=EB=82=99=EA=B4=80?= =?UTF-8?q?=EC=A0=81=20=EB=9D=BD=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_be/domain/groups/entity/Group.java | 15 +++--- .../domain/groups/service/GroupService.java | 46 ++++++++++++++++++- .../global/auth/SecurityWhitelist.java | 3 +- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java index d960fb43..673953f9 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/entity/Group.java @@ -10,18 +10,21 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder -@EqualsAndHashCode(of = "ubuntuGid") +@EqualsAndHashCode(of = "groupId") public class Group { @Id - @Column(name = "ubuntu_gid", nullable = false) - private Long ubuntuGid; + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "group_id") + private Long groupId; @Column(name = "group_name", nullable = false, length = 100) private String groupName; + @Column(name = "ubuntu_gid", unique = true, nullable = false) + private Long ubuntuGid; + @OneToOne(fetch = FetchType.LAZY) - @MapsId - @JoinColumn(name = "ubuntu_gid") + @JoinColumn(name = "ubuntu_gid", referencedColumnName = "id_value", insertable = false, updatable = false) private UsedId usedId; -} +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java index e9bd6228..96720a17 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/groups/service/GroupService.java @@ -1,7 +1,11 @@ package DGU_AI_LAB.admin_be.domain.groups.service; +import DGU_AI_LAB.admin_be.domain.groups.dto.request.CreateGroupRequestDTO; import DGU_AI_LAB.admin_be.domain.groups.dto.response.GroupResponseDTO; +import DGU_AI_LAB.admin_be.domain.groups.entity.Group; import DGU_AI_LAB.admin_be.domain.groups.repository.GroupRepository; +import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; +import DGU_AI_LAB.admin_be.domain.usedIds.repository.UsedIdRepository; import DGU_AI_LAB.admin_be.error.ErrorCode; import DGU_AI_LAB.admin_be.error.exception.BusinessException; import lombok.RequiredArgsConstructor; @@ -10,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; @Slf4j @Service @@ -18,6 +23,7 @@ public class GroupService { private final GroupRepository groupRepository; + private final UsedIdRepository usedIdRepository; /** * 모든 그룹 정보를 조회하는 API @@ -39,4 +45,42 @@ public List getAllGroups() { log.info("[getAllGroups] 모든 그룹 정보 조회 완료. {}개 그룹", response.size()); return response; } -} + + /** + * 새로운 그룹을 생성하는 API + * POST /api/groups + */ + @Transactional + public GroupResponseDTO createGroup(CreateGroupRequestDTO dto) { + log.info("[createGroup] 그룹 생성 요청 시작: groupName={}, ubuntuGid={}", dto.groupName(), dto.ubuntuGid()); + + // 1. 요청받은 GID로 UsedId가 이미 존재하는지 확인하고, 없으면 생성 + Optional existingUsedId = usedIdRepository.findById(dto.ubuntuGid()); + UsedId usedId; + if (existingUsedId.isPresent()) { + log.warn("[createGroup] 중복된 GID가 UsedId에 이미 존재합니다: {}", dto.ubuntuGid()); + usedId = existingUsedId.get(); + } else { + usedId = usedIdRepository.saveAndFlush(UsedId.builder().idValue(dto.ubuntuGid()).build()); + log.info("[createGroup] UsedId에 GID {} 할당 완료", dto.ubuntuGid()); + } + + // 2. 해당 GID의 그룹이 이미 존재하는지 확인 + if (groupRepository.existsByUbuntuGid(dto.ubuntuGid())) { + log.warn("[createGroup] 중복된 GID를 가진 그룹이 이미 존재합니다: {}", dto.ubuntuGid()); + throw new BusinessException(ErrorCode.DUPLICATE_GROUP_ID); + } + + // 3. 그룹 엔티티 생성 및 저장 + Group group = Group.builder() + .groupName(dto.groupName()) + .ubuntuGid(dto.ubuntuGid()) + .usedId(usedId) + .build(); + + group = groupRepository.save(group); + log.info("[createGroup] 그룹 생성 완료: id={}, name={}", group.getGroupId(), group.getGroupName()); + + return GroupResponseDTO.fromEntity(group); + } +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java b/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java index 54c758b4..29a7715d 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java +++ b/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java @@ -11,6 +11,7 @@ public class SecurityWhitelist { "/api/auth/login", "/api/auth/register", "/api/auth/reissue", "/api/auth/email/**", "/auth/callback/**", - "/actuator/health", "/actuator/info" + "/actuator/health", "/actuator/info", + "/api/groups/**" ); } From 4d905c40668850f52be272ded88f6ea3c44201b4 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Sun, 24 Aug 2025 17:21:34 +0900 Subject: [PATCH 12/14] =?UTF-8?q?[fix]=20#100=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=ED=99=94=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java b/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java index 29a7715d..18306e3c 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java +++ b/src/main/java/DGU_AI_LAB/admin_be/global/auth/SecurityWhitelist.java @@ -11,7 +11,7 @@ public class SecurityWhitelist { "/api/auth/login", "/api/auth/register", "/api/auth/reissue", "/api/auth/email/**", "/auth/callback/**", - "/actuator/health", "/actuator/info", - "/api/groups/**" + "/actuator/health", "/actuator/info" + //"/api/groups/**" ); } From b8eb6a869a3c0c54849eb93258b04c709ac3709a Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Mon, 25 Aug 2025 12:29:27 +0900 Subject: [PATCH 13/14] =?UTF-8?q?[refactor]=20#102=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20username=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_be/domain/requests/service/RequestService.java | 4 ++++ src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java | 1 + 2 files changed, 5 insertions(+) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java index f6d0137a..7e5fe382 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java @@ -68,6 +68,10 @@ public SaveRequestResponseDTO createRequest(Long userId, SaveRequestRequestDTO d ResourceGroup rg = resourceGroupRepository.findById(dto.resourceGroupId()) .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND)); + if (requestRepository.existsByUbuntuUsername(dto.ubuntuUsername())) { + throw new BusinessException(ErrorCode.DUPLICATE_USERNAME); + } + ContainerImage img = containerImageRepository.findById(dto.imageId()) .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND)); diff --git a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java index 26de0a70..4e7facdf 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java +++ b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java @@ -86,6 +86,7 @@ public enum ErrorCode { INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "올바르지 않은 인증 코드입니다."), GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "지정된 그룹을 찾을 수 없습니다."), UID_ALLOCATION_FAILED(HttpStatus.BAD_REQUEST, "UID를 할당에 실패했습니다."), + DUPLICATE_USERNAME(HttpStatus.CONFLICT, "이미 사용하고 있는 username입니다. 같은 사용자이더라도 다른 username을 입력해주세요."), SLACK_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "Slack 사용자를 찾을 수 없습니다."), SLACK_USER_EMAIL_NOT_MATCH(HttpStatus.NOT_FOUND, "이메일이 일치하는 Slack 사용자를 찾을 수 없습니다."), From e65faab9a252883fe6fec6d5adaf6dc4dea26f3b Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Mon, 25 Aug 2025 12:55:11 +0900 Subject: [PATCH 14/14] =?UTF-8?q?[fix]=20#102=20ssh=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20URI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin_be/domain/users/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/AuthController.java b/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/AuthController.java index a668bbf8..753c4286 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/AuthController.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/AuthController.java @@ -43,7 +43,7 @@ public ResponseEntity login(@RequestBody @Valid UserLoginR } /** ssh 로그인 */ - @PostMapping("/users") + @PostMapping("/users/password") public ResponseEntity userAuth(@RequestBody @Valid UserAuthRequestDTO dto) { return ResponseEntity.ok(userService.userAuth(dto)); }