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/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 1911077c..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 @@ -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; @@ -31,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 -> { @@ -53,12 +59,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/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/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 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/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 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/domain/requests/dto/response/SaveRequestResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java index b1cf14d5..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 @@ -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, + Long imageId, 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) + .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()) @@ -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(); } 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 } 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..dc7e86c8 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)); @@ -95,6 +99,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); } @@ -147,17 +163,30 @@ 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) { + 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); } @@ -167,13 +196,25 @@ 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); } 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 +222,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 +233,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 +295,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); + } } 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)); } 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..e1089c7f 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 사용자를 찾을 수 없습니다."), @@ -97,6 +98,7 @@ public enum ErrorCode { * Group Error */ NO_AVAILABLE_GROUPS(HttpStatus.NOT_FOUND, "존재하는 그룹 정보가 없습니다."), + DUPLICATE_GROUP_ID(HttpStatus.CONFLICT, "중복된 그룹 ID를 할당할 수 없습니다."), /** 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..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 @@ -12,5 +12,6 @@ public class SecurityWhitelist { "/api/auth/email/**", "/auth/callback/**", "/actuator/health", "/actuator/info" + //"/api/groups/**" ); }