diff --git a/src/main/java/inha/gdgoc/domain/user/controller/UserAdminController.java b/src/main/java/inha/gdgoc/domain/user/controller/UserAdminController.java index 443f435..c884916 100644 --- a/src/main/java/inha/gdgoc/domain/user/controller/UserAdminController.java +++ b/src/main/java/inha/gdgoc/domain/user/controller/UserAdminController.java @@ -53,4 +53,12 @@ public ResponseEntity> updateUserRole(@AuthenticationPri userAdminService.updateUserRoleWithRules(me, userId, req.role()); return ResponseEntity.ok(ApiResponse.ok("USER_ROLE_UPDATED")); } + + @Operation(summary = "사용자 삭제", security = {@SecurityRequirement(name = "BearerAuth")}) + @PreAuthorize("hasAnyRole('LEAD', 'ORGANIZER', 'ADMIN')") + @DeleteMapping("/{userId}") + public ResponseEntity> deleteUser(@AuthenticationPrincipal CustomUserDetails me, @PathVariable Long userId) { + userAdminService.deleteUserWithRules(me, userId); + return ResponseEntity.ok(ApiResponse.ok("USER_DELETED")); + } } \ No newline at end of file diff --git a/src/main/java/inha/gdgoc/domain/user/service/UserAdminService.java b/src/main/java/inha/gdgoc/domain/user/service/UserAdminService.java index 4f46c26..edf8087 100644 --- a/src/main/java/inha/gdgoc/domain/user/service/UserAdminService.java +++ b/src/main/java/inha/gdgoc/domain/user/service/UserAdminService.java @@ -164,6 +164,63 @@ public void updateUserRoleWithRules(CustomUserDetails me, Long targetUserId, Use userRepository.save(target); } + @Transactional + public void deleteUserWithRules(CustomUserDetails me, Long targetUserId) { + User editor = userRepository.findById(me.getUserId()) + .orElseThrow(() -> new BusinessException(GlobalErrorCode.UNAUTHORIZED_USER)); + + User target = userRepository.findById(targetUserId) + .orElseThrow(() -> new BusinessException(GlobalErrorCode.RESOURCE_NOT_FOUND)); + + // 자기 자신 삭제 금지 + if (Objects.equals(editor.getId(), target.getId())) { + throw new BusinessException(GlobalErrorCode.FORBIDDEN_USER, "자기 자신은 삭제할 수 없습니다."); + } + + UserRole editorRole = editor.getUserRole(); + TeamType editorTeam = editor.getTeam(); + + UserRole targetRole = target.getUserRole(); + TeamType targetTeam = target.getTeam(); + + // 공통: '나'는 대상의 현재 role보다 "엄격히" 높아야 함 + if (!(editorRole.rank() > targetRole.rank())) { + throw new BusinessException(GlobalErrorCode.FORBIDDEN_USER, "동급/상급 사용자는 삭제할 수 없습니다."); + } + + switch (editorRole) { + case ADMIN -> { + // ADMIN: 모두 삭제 가능(단, 자기 자신은 위에서 금지) + // 추가 보호가 필요하면 여기서 ADMIN→ADMIN 삭제 금지도 가능 + } + case ORGANIZER -> { + // ORGANIZER: ADMIN 삭제 불가(공통 검사로 이미 걸러짐). 그 외 삭제 가능 + if (targetRole == UserRole.ADMIN) { + throw new BusinessException(GlobalErrorCode.FORBIDDEN_USER, "ADMIN 사용자는 삭제할 수 없습니다."); + } + } + case LEAD -> { + // LEAD: MEMBER/CORE만 삭제 가능 + if (!(targetRole == UserRole.MEMBER || targetRole == UserRole.CORE)) { + throw new BusinessException(GlobalErrorCode.FORBIDDEN_USER, "LEAD는 MEMBER/CORE만 삭제할 수 있습니다."); + } + + // HR-LEAD 특례: 본인 제외 누구든 팀 무관 삭제 가능 + if (editorTeam == TeamType.HR) { + // 자기 자신은 위에서 이미 금지 + } else { + // 일반 LEAD: 같은 팀만 삭제 가능 + if (editorTeam == null || targetTeam != editorTeam) { + throw new BusinessException(GlobalErrorCode.FORBIDDEN_USER, "다른 팀 사용자는 삭제할 수 없습니다."); + } + } + } + default -> throw new BusinessException(GlobalErrorCode.FORBIDDEN_USER); + } + + userRepository.delete(target); + } + /** * 실제 반영 (역할 변경 후 역할 정책에 따라 팀도 정리) */