Repository: team-faster/faster
Pull request #92: [user] feat: 회원 소프트 삭제
Author: login: LimdaeIl
Labels: name: ✨ feature
description: 기능 구현
Created: 2025-03-17T14:25:59Z | Merged: 2025-03-18T03:12:59Z

## #️⃣연관된 이슈
- 이슈 번호: #78 

## 변경 타입
- [x] 신규 기능 추가/수정
## 변경 내용

- **to-be**(변경 후 설명을 여기에 작성)

  - [x] TODO 회원 id 로 삭제

## 체크리스트
- [x] 코드가 제대로 동작하는지 확인했습니다.
- [ ] 관련 테스트를 추가했습니다.
- [ ] 문서(코드, 주석, README 등)를 업데이트했습니다.

## 코멘트
- (추가적인 설명이나 코멘트가 필요한 경우 여기에 작성)


Diff:
diff --git a/user/src/main/java/com/faster/user/app/auth/application/usecase/AuthServiceImpl.java b/user/src/main/java/com/faster/user/app/auth/application/usecase/AuthServiceImpl.java
index f1691a49..bb5980ed 100644
--- a/user/src/main/java/com/faster/user/app/auth/application/usecase/AuthServiceImpl.java
+++ b/user/src/main/java/com/faster/user/app/auth/application/usecase/AuthServiceImpl.java
@@ -12,7 +12,7 @@
 import com.faster.user.app.auth.presentation.dto.CreateUserResponseDto;
 import com.faster.user.app.auth.presentation.dto.SignInUserResponseDto;
 import com.faster.user.app.user.domain.entity.User;
-import com.faster.user.app.user.infrastructure.persistence.jpa.UserRepositoryAdapter;
+import com.faster.user.app.user.domain.repository.UserRepository;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.crypto.password.PasswordEncoder;
@@ -23,12 +23,12 @@
 @Service
 public class AuthServiceImpl implements AuthService {
 
-  private final UserRepositoryAdapter userRepositoryAdapter;
+  private final UserRepository userRepository;
   private final PasswordEncoder passwordEncoder;
   private final JwtProvider jwtProvider;
 
   private void findUserByUsernameOrSlackId(String username, String slackId) {
-    Optional<User> existUser = userRepositoryAdapter.findUserByUsernameOrSlackId(username, slackId);
+    Optional<User> existUser = userRepository.findUserByUsernameOrSlackId(username, slackId);
 
     if (existUser.isPresent()) {
       User user = existUser.get();
@@ -42,7 +42,7 @@ private void findUserByUsernameOrSlackId(String username, String slackId) {
   }
 
   private User getUserByUsername(String username) {
-    return userRepositoryAdapter.findUserByUsername(username)
+    return userRepository.findUserByUsername(username)
         .orElseThrow(() -> new CustomException(SIGN_IN_INVALID_USERNAME));
   }
 
@@ -58,7 +58,7 @@ public CreateUserResponseDto createUser(CreateUserRequestDto requestDto) {
         requestDto.slackId()
     );
 
-    User savedUser = userRepositoryAdapter.save(newUser);
+    User savedUser = userRepository.save(newUser);
 
     return CreateUserResponseDto.from(savedUser);
   }
diff --git a/user/src/main/java/com/faster/user/app/global/response/enums/UserResponseCode.java b/user/src/main/java/com/faster/user/app/global/response/enums/UserResponseCode.java
index 6c4bfbde..6b1f45e4 100644
--- a/user/src/main/java/com/faster/user/app/global/response/enums/UserResponseCode.java
+++ b/user/src/main/java/com/faster/user/app/global/response/enums/UserResponseCode.java
@@ -10,7 +10,8 @@
 public enum UserResponseCode implements ErrorCode {
 
   USER_UPDATED(HttpStatus.OK.value(), "회원 정보를 성공적으로 수정했습니다.", HttpStatus.OK),
-  USER_FOUND(HttpStatus.OK.value(), "회원 정보를 성공적으로 조회했습니다.", HttpStatus.OK);
+  USER_FOUND(HttpStatus.OK.value(), "회원 정보를 성공적으로 조회했습니다.", HttpStatus.OK),
+  USER_SOFT_DELETED(HttpStatus.OK.value(), "회원을 성공적으로 소프트 삭제했습니다.", HttpStatus.OK);
 
   private final int code;
   private final String message;
diff --git a/user/src/main/java/com/faster/user/app/user/application/dto/DeleteUserRequestDto.java b/user/src/main/java/com/faster/user/app/user/application/dto/DeleteUserRequestDto.java
new file mode 100644
index 00000000..dcbc9e63
--- /dev/null
+++ b/user/src/main/java/com/faster/user/app/user/application/dto/DeleteUserRequestDto.java
@@ -0,0 +1,12 @@
+package com.faster.user.app.user.application.dto;
+
+import java.time.LocalDateTime;
+
+
+public record DeleteUserRequestDto(
+    Long deleterId) {
+
+  public static DeleteUserRequestDto of(DeleteUserRequestDto requestDto) {
+    return new DeleteUserRequestDto(requestDto.deleterId());
+  }
+}
diff --git a/user/src/main/java/com/faster/user/app/user/application/usecase/UserService.java b/user/src/main/java/com/faster/user/app/user/application/usecase/UserService.java
index 8a3382c4..57cfed54 100644
--- a/user/src/main/java/com/faster/user/app/user/application/usecase/UserService.java
+++ b/user/src/main/java/com/faster/user/app/user/application/usecase/UserService.java
@@ -2,6 +2,8 @@
 
 
 import com.common.response.PageResponse;
+import com.faster.user.app.user.presentation.dto.DeleteUserResponseDto;
+import com.faster.user.app.user.application.dto.DeleteUserRequestDto;
 import com.faster.user.app.user.application.dto.UpdateUserRoleRequestDto;
 import com.faster.user.app.user.presentation.dto.GetAllUserResponseDto;
 import com.faster.user.app.user.presentation.dto.UpdateUserRoleResponseDto;
@@ -12,4 +14,6 @@ public interface UserService {
 
   PageResponse<GetAllUserResponseDto> getAllUsers(String username, String name, String slackId, Integer page,
                                                   Integer size);
+
+  DeleteUserResponseDto deleteUserByUserId(Long userId, DeleteUserRequestDto requestDto);
 }
\ No newline at end of file
diff --git a/user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java b/user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java
index 23b7ce76..4359952e 100644
--- a/user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java
+++ b/user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java
@@ -2,13 +2,16 @@
 
 import com.common.exception.CustomException;
 import com.common.response.PageResponse;
+import com.faster.user.app.user.presentation.dto.DeleteUserResponseDto;
 import com.faster.user.app.global.exception.enums.UserErrorCode;
+import com.faster.user.app.user.application.dto.DeleteUserRequestDto;
 import com.faster.user.app.user.application.dto.UpdateUserRoleRequestDto;
 import com.faster.user.app.user.domain.entity.User;
-import com.faster.user.app.user.infrastructure.persistence.jpa.UserRepositoryAdapter;
+import com.faster.user.app.user.domain.repository.UserRepository;
 import com.faster.user.app.user.infrastructure.persistence.jpa.dto.QUserProjection;
 import com.faster.user.app.user.presentation.dto.GetAllUserResponseDto;
 import com.faster.user.app.user.presentation.dto.UpdateUserRoleResponseDto;
+import java.time.LocalDateTime;
 import lombok.RequiredArgsConstructor;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageRequest;
@@ -20,10 +23,10 @@
 @Service
 public class UserServiceImpl implements UserService {
 
-  private final UserRepositoryAdapter userRepositoryAdapter;
+  private final UserRepository userRepository;
 
   private User getUserByUserId(Long userId) {
-    return userRepositoryAdapter.findById(userId)
+    return userRepository.findById(userId)
         .orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND_BY_ID));
   }
 
@@ -33,7 +36,7 @@ public UpdateUserRoleResponseDto updateUserRoleByUserId(Long userId, UpdateUserR
     User userByUserId = getUserByUserId(userId);
 
     userByUserId.updateUserRole(requestDto.role());
-    User user = userRepositoryAdapter.save(userByUserId);
+    User user = userRepository.save(userByUserId);
 
     return UpdateUserRoleResponseDto.from(user);
   }
@@ -42,7 +45,7 @@ public UpdateUserRoleResponseDto updateUserRoleByUserId(Long userId, UpdateUserR
   public PageResponse<GetAllUserResponseDto> getAllUsers(String username, String name, String slackId, Integer page,
                                                          Integer size) {
     Pageable pageable = PageRequest.of(page, size);
-    Page<QUserProjection> pageResult = userRepositoryAdapter.searchUsers(username, name, slackId, pageable);
+    Page<QUserProjection> pageResult = userRepository.searchUsers(username, name, slackId, pageable);
 
     return PageResponse.from(pageResult)
         .map(user -> new GetAllUserResponseDto(
@@ -60,4 +63,11 @@ public PageResponse<GetAllUserResponseDto> getAllUsers(String username, String n
         ));
   }
 
+  @Override
+  public DeleteUserResponseDto deleteUserByUserId(Long userId, DeleteUserRequestDto requestDto) {
+    User user = getUserByUserId(userId);
+    user.delete(LocalDateTime.now(), requestDto.deleterId());
+
+    return DeleteUserResponseDto.from(user.getId());
+  }
 }
diff --git a/user/src/main/java/com/faster/user/app/user/domain/entity/User.java b/user/src/main/java/com/faster/user/app/user/domain/entity/User.java
index af7c1430..b19170bb 100644
--- a/user/src/main/java/com/faster/user/app/user/domain/entity/User.java
+++ b/user/src/main/java/com/faster/user/app/user/domain/entity/User.java
@@ -2,6 +2,7 @@
 
 import com.common.domain.BaseEntity;
 import com.common.resolver.dto.UserRole;
+import com.faster.user.app.user.application.dto.DeleteUserRequestDto;
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
 import jakarta.persistence.EnumType;
@@ -10,6 +11,7 @@
 import jakarta.persistence.GenerationType;
 import jakarta.persistence.Id;
 import jakarta.persistence.Table;
+import java.time.LocalDateTime;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
diff --git a/user/src/main/java/com/faster/user/app/user/infrastructure/persistence/jpa/UserJpaRepository.java b/user/src/main/java/com/faster/user/app/user/infrastructure/persistence/jpa/UserJpaRepository.java
index 310885d2..b0d02ca8 100644
--- a/user/src/main/java/com/faster/user/app/user/infrastructure/persistence/jpa/UserJpaRepository.java
+++ b/user/src/main/java/com/faster/user/app/user/infrastructure/persistence/jpa/UserJpaRepository.java
@@ -7,7 +7,7 @@
 import org.springframework.stereotype.Repository;
 
 @Repository
-public interface UserJpaRepository extends JpaRepository<User, Long> {
+public interface UserJpaRepository extends JpaRepository<User, Long>, UserRepository, UserRepositoryCustom {
 
   Optional<User> findUserByUsername(String username);
 
diff --git a/user/src/main/java/com/faster/user/app/user/infrastructure/persistence/jpa/UserRepositoryAdapter.java b/user/src/main/java/com/faster/user/app/user/infrastructure/persistence/jpa/UserRepositoryAdapter.java
deleted file mode 100644
index b56d21d4..00000000
--- a/user/src/main/java/com/faster/user/app/user/infrastructure/persistence/jpa/UserRepositoryAdapter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.faster.user.app.user.infrastructure.persistence.jpa;
-
-import com.faster.user.app.user.domain.entity.User;
-import com.faster.user.app.user.domain.repository.UserRepository;
-import com.faster.user.app.user.infrastructure.persistence.jpa.dto.QUserProjection;
-import java.util.Optional;
-import lombok.RequiredArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
-import org.springframework.stereotype.Repository;
-
-@RequiredArgsConstructor
-@Repository
-public class UserRepositoryAdapter implements UserRepository {
-
-  private final UserJpaRepository userJpaRepository;
-  private final UserRepositoryCustom userRepositoryCustom;
-
-  @Override
-  public Optional<User> findById(Long userId) {
-    return userJpaRepository.findById(userId);
-  }
-
-  @Override
-  public Optional<User> findUserByUsername(String username) {
-    return userJpaRepository.findUserByUsername(username);
-  }
-
-  @Override
-  public Optional<User> findUserByUsernameOrSlackId(String username, String slackId) {
-    return userJpaRepository.findUserByUsernameOrSlackId(username, slackId);
-  }
-
-  @Override
-  public User save(User user) {
-    return userJpaRepository.save(user);
-  }
-
-  @Override
-  public Page<QUserProjection> searchUsers(String username, String name, String slackId, Pageable pageable) {
-    return userRepositoryCustom.searchUsers(username, name, slackId, pageable);
-  }
-}
\ No newline at end of file
diff --git a/user/src/main/java/com/faster/user/app/user/presentation/controller/UserController.java b/user/src/main/java/com/faster/user/app/user/presentation/controller/UserController.java
index 802d5254..83182989 100644
--- a/user/src/main/java/com/faster/user/app/user/presentation/controller/UserController.java
+++ b/user/src/main/java/com/faster/user/app/user/presentation/controller/UserController.java
@@ -4,15 +4,17 @@
 import com.common.resolver.dto.UserRole;
 import com.common.response.ApiResponse;
 import com.common.response.PageResponse;
+import com.faster.user.app.user.presentation.dto.DeleteUserResponseDto;
 import com.faster.user.app.global.response.enums.UserResponseCode;
+import com.faster.user.app.user.application.dto.DeleteUserRequestDto;
 import com.faster.user.app.user.application.usecase.UserService;
-import com.faster.user.app.user.infrastructure.persistence.jpa.dto.QUserProjection;
 import com.faster.user.app.user.presentation.dto.GetAllUserResponseDto;
 import lombok.RequiredArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
 import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -47,4 +49,21 @@ public ResponseEntity<ApiResponse<PageResponse<GetAllUserResponseDto>>> getAllUs
             users
         ));
   }
+
+
+  @DeleteMapping("/{userId}")
+  public ResponseEntity<ApiResponse<DeleteUserResponseDto>> updateUserRole(@PathVariable(name = "userId") Long userId,
+                                                                           @RequestBody DeleteUserRequestDto requestDto) {
+    DeleteUserResponseDto deletedUserByUserId = userService.deleteUserByUserId(userId, requestDto);
+
+    return ResponseEntity
+        .status(UserResponseCode.USER_SOFT_DELETED.getStatus())
+        .body(new ApiResponse<>(
+            UserResponseCode.USER_SOFT_DELETED.getMessage(),
+            UserResponseCode.USER_SOFT_DELETED.getStatus().value(),
+            deletedUserByUserId
+        ));
+  }
+
+
 }
\ No newline at end of file
diff --git a/user/src/main/java/com/faster/user/app/user/presentation/dto/DeleteUserResponseDto.java b/user/src/main/java/com/faster/user/app/user/presentation/dto/DeleteUserResponseDto.java
new file mode 100644
index 00000000..f38e92bd
--- /dev/null
+++ b/user/src/main/java/com/faster/user/app/user/presentation/dto/DeleteUserResponseDto.java
@@ -0,0 +1,11 @@
+package com.faster.user.app.user.presentation.dto;
+
+import com.faster.user.app.user.domain.entity.User;
+
+public record DeleteUserResponseDto(
+    Long deletedId
+) {
+  public static DeleteUserResponseDto from(Long deletedId) {
+    return new DeleteUserResponseDto(deletedId);
+  }
+}
diff --git a/user/src/main/java/com/faster/user/app/user/presentation/internal/InternalUserController.java b/user/src/main/java/com/faster/user/app/user/presentation/internal/InternalUserController.java
index a1d5c9f1..adf1cb63 100644
--- a/user/src/main/java/com/faster/user/app/user/presentation/internal/InternalUserController.java
+++ b/user/src/main/java/com/faster/user/app/user/presentation/internal/InternalUserController.java
@@ -34,4 +34,5 @@ public ResponseEntity<ApiResponse<UpdateUserRoleResponseDto>> updateUserRole(
             responseDto
         ));
   }
+
 }


Reviews:
user: login: HanaHww2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T15:30:20Z
user: login: HanaHww2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T15:31:15Z
user: login: HanaHww2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T15:33:13Z
user: login: HanaHww2
body: 수고하셨습니다. (●'◡'●)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T15:34:24Z
user: login: LimdaeIl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T01:31:13Z
user: login: LimdaeIl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T01:35:28Z
user: login: LimdaeIl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T01:58:40Z
user: login: HanaHww2
body: 수고하셨습니다!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T02:12:45Z
user: login: je-pa
body: 고생하셨습니다!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T03:12:24Z

Review comments:
diff_hunk: @@ -0,0 +1,12 @@
+package com.faster.user.app.user.application.dto;
+
+import java.time.LocalDateTime;
+
+
+public record DeleteUserRequestDto(
+    Long deleterId) {
+
+  public static DeleteUserRequestDto of(DeleteUserRequestDto requestDto) {
path: user/src/main/java/com/faster/user/app/user/application/dto/DeleteUserRequestDto.java
user: login: HanaHww2
body: 자기 자신을 다시 반환하는 거 같은데 혹시 제가 잘못 이해한 걸까요?
created_at: 2025-03-17T15:30:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,4 +63,12 @@ public PageResponse<GetAllUserResponseDto> getAllUsers(String username, String n
         ));
   }
 
+  @Override
+  public DeleteUserResponseDto deleteUserByUserId(Long userId, DeleteUserRequestDto requestDto) {
+    User user = getUserByUserId(userId);
+    user.softDeleteUser(requestDto.deleterId(), LocalDateTime.now());
path: user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java
user: login: HanaHww2
body: 저희 BaseEntity 에 보면, delete() 라는 공통 메소드가 있습니다. 한 번 확인 부탁드립니다.
created_at: 2025-03-17T15:31:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,4 +63,12 @@ public PageResponse<GetAllUserResponseDto> getAllUsers(String username, String n
         ));
   }
 
+  @Override
+  public DeleteUserResponseDto deleteUserByUserId(Long userId, DeleteUserRequestDto requestDto) {
+    User user = getUserByUserId(userId);
+    user.softDeleteUser(requestDto.deleterId(), LocalDateTime.now());
+
+    return DeleteUserResponseDto.of(userRepositoryAdapter.save(user).getId());
path: user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java
user: login: HanaHww2
body: 앗, 레포지토리를 인프라스트럭쳐에서 바로 가져오신 거 같네요. 다른 분들 코드 참고해서 도메인에 레포지토리 인터페이스 생성하시고, dip 로 구현해주실 수 있다면 그 방법이 좋을 거 같습니다.
created_at: 2025-03-17T15:33:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,12 @@
+package com.faster.user.app.user.application.dto;
+
+import java.time.LocalDateTime;
+
+
+public record DeleteUserRequestDto(
+    Long deleterId) {
+
+  public static DeleteUserRequestDto of(DeleteUserRequestDto requestDto) {
path: user/src/main/java/com/faster/user/app/user/application/dto/DeleteUserRequestDto.java
user: login: LimdaeIl
body: 맞습니다... 이거 자기 자신을 반환하네요 .. 수정하겠습니다!
created_at: 2025-03-18T01:31:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,4 +63,12 @@ public PageResponse<GetAllUserResponseDto> getAllUsers(String username, String n
         ));
   }
 
+  @Override
+  public DeleteUserResponseDto deleteUserByUserId(Long userId, DeleteUserRequestDto requestDto) {
+    User user = getUserByUserId(userId);
+    user.softDeleteUser(requestDto.deleterId(), LocalDateTime.now());
path: user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java
user: login: LimdaeIl
body: 넵! 다시 수정하겠습니다!
created_at: 2025-03-18T01:35:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,4 +63,12 @@ public PageResponse<GetAllUserResponseDto> getAllUsers(String username, String n
         ));
   }
 
+  @Override
+  public DeleteUserResponseDto deleteUserByUserId(Long userId, DeleteUserRequestDto requestDto) {
+    User user = getUserByUserId(userId);
+    user.softDeleteUser(requestDto.deleterId(), LocalDateTime.now());
+
+    return DeleteUserResponseDto.of(userRepositoryAdapter.save(user).getId());
path: user/src/main/java/com/faster/user/app/user/application/usecase/UserServiceImpl.java
user: login: LimdaeIl
body: 넵 다시 확인하고 수정하겠습니다!
created_at: 2025-03-18T01:58:40Z
author_association: CONTRIBUTOR

PR comments:
user: login: LimdaeIl
created_at: 2025-03-18T02:06:19Z
body: @HanaHww2 @je-pa 다시 수정해서 커밋 올렸습니다!

author_association: CONTRIBUTOR
user: login: LimdaeIl
created_at: 2025-03-18T02:46:01Z
body: @je-pa @HanaHww2 다시 수정해서 올려드렸습니다! 
author_association: CONTRIBUTOR