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 @@ -2,6 +2,7 @@

import com.bmilab.backend.domain.user.dto.request.RegisterUserRequest;
import com.bmilab.backend.domain.user.dto.request.AdminUpdateUserRequest;
import com.bmilab.backend.domain.user.dto.request.UpdateUserStatusRequest;
import com.bmilab.backend.domain.user.dto.request.UserAccountEmailRequest;
import com.bmilab.backend.domain.user.dto.response.UserDetail;
import com.bmilab.backend.global.exception.ErrorResponse;
Expand Down Expand Up @@ -95,4 +96,49 @@ ResponseEntity<Void> sendAccountEmail(
@PathVariable Long userId,
@RequestBody UserAccountEmailRequest request
);

@Operation(summary = "사용자 퇴사 처리", description = "사용자를 퇴사 처리하는 PATCH API. 퇴사 처리된 사용자는 로그인할 수 없습니다.")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "퇴사 처리 성공"
),
@ApiResponse(
responseCode = "400",
description = "관리자 계정은 퇴사 처리할 수 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
),
@ApiResponse(
responseCode = "404",
description = "사용자 정보를 찾을 수 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
}
)
ResponseEntity<Void> resignUser(@PathVariable Long userId);

@Operation(summary = "사용자 상태 변경", description = "사용자의 상태(재직/휴직/퇴사)를 변경하는 PATCH API")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "상태 변경 성공"
),
@ApiResponse(
responseCode = "400",
description = "관리자 계정은 퇴사 처리할 수 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
),
@ApiResponse(
responseCode = "404",
description = "사용자 정보를 찾을 수 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
}
)
ResponseEntity<Void> updateUserStatus(
@PathVariable Long userId,
@RequestBody UpdateUserStatusRequest request
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.bmilab.backend.domain.user.dto.request.RegisterUserRequest;
import com.bmilab.backend.domain.user.dto.request.AdminUpdateUserRequest;
import com.bmilab.backend.domain.user.dto.request.UpdateUserStatusRequest;
import com.bmilab.backend.domain.user.dto.request.UserAccountEmailRequest;
import com.bmilab.backend.domain.user.dto.response.UserDetail;
import com.bmilab.backend.domain.user.service.AuthService;
Expand Down Expand Up @@ -67,4 +68,21 @@ public ResponseEntity<Void> sendAccountEmail(
userService.sendAccountEmail(userId, request);
return ResponseEntity.ok().build();
}

@PatchMapping("/{userId}/resign")
public ResponseEntity<Void> resignUser(@PathVariable Long userId) {

userService.resignUser(userId);
return ResponseEntity.ok().build();
}

@PatchMapping("/{userId}/status")
public ResponseEntity<Void> updateUserStatus(
@PathVariable Long userId,
@RequestBody @Valid UpdateUserStatusRequest request
) {

userService.updateUserStatus(userId, request.status());
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bmilab.backend.domain.user.dto.request;

import com.bmilab.backend.domain.user.enums.UserStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

public record UpdateUserStatusRequest(
@Schema(description = "사용자 상태", example = "RESIGNED")
@NotNull(message = "사용자 상태는 필수입니다.")
UserStatus status
) {
}
25 changes: 25 additions & 0 deletions src/main/java/com/bmilab/backend/domain/user/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.bmilab.backend.domain.user.entity;

import com.bmilab.backend.domain.user.enums.UserPosition;
import com.bmilab.backend.domain.user.enums.UserStatus;
import com.bmilab.backend.global.entity.BaseTimeEntity;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
Expand Down Expand Up @@ -52,6 +54,13 @@ public class User extends BaseTimeEntity {
@Enumerated(EnumType.STRING)
private Role role;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
@Builder.Default
private UserStatus status = UserStatus.ACTIVE;

@Column(name = "resigned_at")
private LocalDateTime resignedAt;

public void updatePassword(String newPassword) {
this.password = newPassword;
Expand All @@ -76,4 +85,20 @@ public void updateRole(Role role) {
public boolean isAdmin() {
return Role.ADMIN.equals(role);
}

public boolean isActive() {
return UserStatus.ACTIVE.equals(status);
}

public void resign() {
this.status = UserStatus.RESIGNED;
this.resignedAt = LocalDateTime.now();
}

public void updateStatus(UserStatus status) {
this.status = status;
if (status == UserStatus.RESIGNED) {
this.resignedAt = LocalDateTime.now();
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/bmilab/backend/domain/user/enums/UserStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.bmilab.backend.domain.user.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum UserStatus {
ACTIVE("재직"),
ON_LEAVE("휴직"),
RESIGNED("퇴사");

private final String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public enum UserErrorCode implements ErrorCode {
SAME_AS_CURRENT_PASSWORD("새 비밀번호가 현재 비밀번호와 같습니다.", HttpStatus.BAD_REQUEST),
INVALID_PROFILE_IMAGE_FILE_TYPE("프로필 이미지의 파일 형식이 잘못되었습니다.", HttpStatus.BAD_REQUEST),
DUPLICATE_EMAIL("이미 가입된 이메일입니다.", HttpStatus.CONFLICT),
USER_NOT_ACTIVE("비활성화된 계정입니다.", HttpStatus.FORBIDDEN),
CANNOT_RESIGN_ADMIN("관리자 계정은 퇴사 처리할 수 없습니다.", HttpStatus.BAD_REQUEST),
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.bmilab.backend.domain.user.entity.UserInfo;
import com.bmilab.backend.domain.user.entity.UserProjectCategory;
import com.bmilab.backend.domain.user.entity.UserSubAffiliation;
import com.bmilab.backend.domain.user.enums.UserStatus;
import com.bmilab.backend.domain.user.event.UserEducationUpdateEvent;
import com.bmilab.backend.domain.user.exception.UserErrorCode;
import com.bmilab.backend.domain.user.repository.UserEducationRepository;
Expand Down Expand Up @@ -287,6 +288,30 @@ public void deleteUserById(Long userId) {
userRepository.deleteById(userId);
}

@Transactional
public void resignUser(Long userId) {

User user = findUserById(userId);

if (user.isAdmin()) {
throw new ApiException(UserErrorCode.CANNOT_RESIGN_ADMIN);
}

user.resign();
}

@Transactional
public void updateUserStatus(Long userId, UserStatus status) {

User user = findUserById(userId);

if (user.isAdmin() && status == UserStatus.RESIGNED) {
throw new ApiException(UserErrorCode.CANNOT_RESIGN_ADMIN);
}

user.updateStatus(status);
}

public SearchUserResponse searchUsers(String keyword) {

List<User> users = userRepository.searchUsersByKeyword(keyword);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bmilab.backend.global.security;

import com.bmilab.backend.domain.user.entity.User;
import com.bmilab.backend.domain.user.exception.UserErrorCode;
import com.bmilab.backend.domain.user.repository.UserRepository;
import com.bmilab.backend.global.exception.ApiException;
import com.bmilab.backend.global.exception.GlobalErrorCode;
Expand All @@ -21,6 +22,10 @@ public UserDetails loadUserByUsername(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new ApiException(GlobalErrorCode.SECURITY_USER_NOT_FOUND));

if (!user.isActive()) {
throw new ApiException(UserErrorCode.USER_NOT_ACTIVE);
}

return new UserAuthInfo(user, email);
} catch(UsernameNotFoundException exception) {
throw new ApiException(GlobalErrorCode.SECURITY_USER_NOT_FOUND, exception);
Expand Down
Loading