diff --git a/src/main/java/com/retrip/auth/application/config/SecurityConfig.java b/src/main/java/com/retrip/auth/application/config/SecurityConfig.java index 0d56265..a4368a9 100644 --- a/src/main/java/com/retrip/auth/application/config/SecurityConfig.java +++ b/src/main/java/com/retrip/auth/application/config/SecurityConfig.java @@ -54,7 +54,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, LoginAuthentic .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); http.authorizeHttpRequests(auth -> { auth - .requestMatchers(HttpMethod.POST, "users").permitAll() + .requestMatchers("users").permitAll() .requestMatchers( "/swagger-ui/**", "/v3/api-docs/**", diff --git a/src/main/java/com/retrip/auth/application/in/MemberService.java b/src/main/java/com/retrip/auth/application/in/MemberService.java index 515484f..1eab147 100644 --- a/src/main/java/com/retrip/auth/application/in/MemberService.java +++ b/src/main/java/com/retrip/auth/application/in/MemberService.java @@ -1,6 +1,7 @@ package com.retrip.auth.application.in; import com.retrip.auth.application.in.request.MemberCreateRequest; +import com.retrip.auth.application.in.request.MemberDeleteRequest; import com.retrip.auth.application.in.request.MemberUpdateRequest; import com.retrip.auth.application.in.response.MemberCreateResponse; import com.retrip.auth.application.in.response.MemberUpdateResponse; @@ -8,7 +9,6 @@ import com.retrip.auth.application.out.repository.MemberRepository; import com.retrip.auth.domain.entity.Member; import com.retrip.auth.domain.exception.MemberNotFoundException; -import com.retrip.auth.domain.exception.common.EntityNotFoundException; import com.retrip.auth.domain.vo.MemberEmail; import com.sun.jdi.request.DuplicateRequestException; import jakarta.transaction.Transactional; @@ -28,7 +28,7 @@ public class MemberService implements ManageMemberUseCase { @Override public MemberCreateResponse createUser(MemberCreateRequest request) { String encode = passwordEncoder.encode(request.password()); - boolean isDuplicate = !memberRepository.findByEmail(new MemberEmail(request.email())).isEmpty(); + boolean isDuplicate = !memberRepository.findByEmailAndIsDeletedFalse(new MemberEmail(request.email())).isEmpty(); if (isDuplicate) { throw new DuplicateRequestException("Email already exists"); } @@ -38,27 +38,34 @@ public MemberCreateResponse createUser(MemberCreateRequest request) { @Override public MemberUpdateResponse updateUser(MemberUpdateRequest request) { - String encodePassword = passwordEncoder.encode(request.password()); String encodeNewPassword = passwordEncoder.encode(request.newPassword()); Member member = findByEmail(request.email()); - if(isUpdatable(member.getPassword().getValue(), encodePassword)) { //여기에 패스워드 검증 로직이 들어가는게 맞는지..? + if (isSamePassword(member.getPassword().getValue(), request.password())) { //여기에 패스워드 검증 로직이 들어가는게 맞는지..? member.update(encodeNewPassword, request.name()); } return MemberUpdateResponse.of(member); } + @Override + public void deleteUser(MemberDeleteRequest request) { + Member member = findByEmail(request.email()); + if (isSamePassword(member.getPassword().getValue(), request.password())) { //여기에 패스워드 검증 로직이 들어가는게 맞는지..? + member.delete(); + } + } + + private Member findByEmail(String email) { - return memberRepository.findByEmail(new MemberEmail(email)) + return memberRepository.findByEmailAndIsDeletedFalse(new MemberEmail(email)) .stream().findAny().orElseThrow(MemberNotFoundException::new); } - private boolean isUpdatable(String password, String inputPassword) { - if (!passwordEncoder.matches(password, inputPassword)) { + private boolean isSamePassword(String password, String inputPassword) { + if (!passwordEncoder.matches(inputPassword, password)) { throw new BadCredentialsException("Bad credentials"); } return true; } - } diff --git a/src/main/java/com/retrip/auth/application/in/request/MemberDeleteRequest.java b/src/main/java/com/retrip/auth/application/in/request/MemberDeleteRequest.java new file mode 100644 index 0000000..5c5328c --- /dev/null +++ b/src/main/java/com/retrip/auth/application/in/request/MemberDeleteRequest.java @@ -0,0 +1,12 @@ +package com.retrip.auth.application.in.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "Member 삭제 Request") +public record MemberDeleteRequest( + @Schema(description = "이메일") + String email, + @Schema(description = "비밀번호") + String password +) { +} diff --git a/src/main/java/com/retrip/auth/application/in/usercase/ManageMemberUseCase.java b/src/main/java/com/retrip/auth/application/in/usercase/ManageMemberUseCase.java index 4364f47..4c1da0f 100644 --- a/src/main/java/com/retrip/auth/application/in/usercase/ManageMemberUseCase.java +++ b/src/main/java/com/retrip/auth/application/in/usercase/ManageMemberUseCase.java @@ -2,6 +2,7 @@ import com.retrip.auth.application.in.request.MemberCreateRequest; +import com.retrip.auth.application.in.request.MemberDeleteRequest; import com.retrip.auth.application.in.request.MemberUpdateRequest; import com.retrip.auth.application.in.response.MemberCreateResponse; import com.retrip.auth.application.in.response.MemberUpdateResponse; @@ -10,4 +11,6 @@ public interface ManageMemberUseCase { MemberCreateResponse createUser(MemberCreateRequest request); MemberUpdateResponse updateUser(MemberUpdateRequest request); + + void deleteUser(MemberDeleteRequest request); } diff --git a/src/main/java/com/retrip/auth/application/out/repository/MemberRepository.java b/src/main/java/com/retrip/auth/application/out/repository/MemberRepository.java index e064f5a..d4ebab5 100644 --- a/src/main/java/com/retrip/auth/application/out/repository/MemberRepository.java +++ b/src/main/java/com/retrip/auth/application/out/repository/MemberRepository.java @@ -10,5 +10,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { - List findByEmail(MemberEmail email); + List findByEmailAndIsDeletedFalse(MemberEmail email); } diff --git a/src/main/java/com/retrip/auth/domain/entity/Member.java b/src/main/java/com/retrip/auth/domain/entity/Member.java index 5a4fdb8..1dfdc67 100644 --- a/src/main/java/com/retrip/auth/domain/entity/Member.java +++ b/src/main/java/com/retrip/auth/domain/entity/Member.java @@ -34,6 +34,8 @@ public class Member extends BaseEntity { @Embedded private Authorities authorities; + private Boolean isDeleted; + public static Member create(String name, String email, String password, List authorities) { Member member = Member.builder() @@ -41,6 +43,7 @@ public static Member create(String name, String email, String password, List result = ApiResponse.ok(tokenResponse); // JSON 직렬화 - String json = getResponseBody(tokenResponse); + String json = getResponseBody(result); // 응답 Header 설정 response.setContentType("application/json"); @@ -98,7 +97,7 @@ public LoginResponse.TokenResponse generateToken(Authentication authentication) return new LoginResponse.TokenResponse(accessToken, refreshToken); } - private static String getResponseBody(LoginResponse.TokenResponse tokenResponse) throws JsonProcessingException { + private static String getResponseBody(ApiResponse tokenResponse) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.writeValueAsString(tokenResponse); } diff --git a/src/main/java/com/retrip/auth/infra/adapter/in/rest/in/MemberController.java b/src/main/java/com/retrip/auth/infra/adapter/in/rest/in/MemberController.java index d0a88b4..198f34c 100644 --- a/src/main/java/com/retrip/auth/infra/adapter/in/rest/in/MemberController.java +++ b/src/main/java/com/retrip/auth/infra/adapter/in/rest/in/MemberController.java @@ -1,10 +1,12 @@ package com.retrip.auth.infra.adapter.in.rest.in; import com.retrip.auth.application.in.request.MemberCreateRequest; +import com.retrip.auth.application.in.request.MemberDeleteRequest; import com.retrip.auth.application.in.request.MemberUpdateRequest; import com.retrip.auth.application.in.response.MemberCreateResponse; import com.retrip.auth.application.in.response.MemberUpdateResponse; import com.retrip.auth.application.in.usercase.ManageMemberUseCase; +import com.retrip.auth.infra.adapter.in.rest.common.ApiResponse; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -20,17 +22,26 @@ public class MemberController { @PostMapping @Schema(description = "회원 가입") - public MemberCreateResponse createUser( + public ApiResponse createUser( @RequestBody MemberCreateRequest request ){ - return manageMemberUseCase.createUser(request); + return ApiResponse.created(manageMemberUseCase.createUser(request)); } @PutMapping @Schema(description = "회원 정보 수정") - public MemberUpdateResponse updateUser( + public ApiResponse updateUser( @RequestBody MemberUpdateRequest request ){ - return manageMemberUseCase.updateUser(request); + return ApiResponse.ok(manageMemberUseCase.updateUser(request)); + } + + @DeleteMapping() + @Schema(description = "회원 정보 삭제") + public ApiResponse deleteUser( + @RequestBody MemberDeleteRequest request + ){ + manageMemberUseCase.deleteUser(request); + return ApiResponse.noContent(); } } diff --git a/src/main/java/com/retrip/auth/infra/adapter/out/persistence/mysql/query/MemberQuerydslRepository.java b/src/main/java/com/retrip/auth/infra/adapter/out/persistence/mysql/query/MemberQuerydslRepository.java index e1747e5..ce06ba7 100644 --- a/src/main/java/com/retrip/auth/infra/adapter/out/persistence/mysql/query/MemberQuerydslRepository.java +++ b/src/main/java/com/retrip/auth/infra/adapter/out/persistence/mysql/query/MemberQuerydslRepository.java @@ -24,7 +24,7 @@ public Optional findByEmailWithAuthorities(String email) { return Optional.ofNullable(query.selectFrom(member) .leftJoin(member.authorities.values, authority) .fetchJoin() - .where(member.email.value.eq(email)) + .where(member.email.value.eq(email), member.isDeleted.isFalse()) .fetchOne()); } } diff --git a/src/test/java/com/retrip/auth/application/in/MemberServiceTest.java b/src/test/java/com/retrip/auth/application/in/MemberServiceTest.java index 72d8957..cfd0afc 100644 --- a/src/test/java/com/retrip/auth/application/in/MemberServiceTest.java +++ b/src/test/java/com/retrip/auth/application/in/MemberServiceTest.java @@ -34,7 +34,7 @@ class MemberServiceTest extends BaseMemberServiceTest { @Test void 회원가입_중복_회원가입() throws Exception { // given - memberRepository.save(Member.create("test", "test@naver.com", "1234")); + memberRepository.save(Member.create("test", "test@naver.com", passwordEncoder.encode("1234"))); MemberCreateRequest request = new MemberCreateRequest("test@naver.com", "1111", "중복 테스트"); //when @@ -49,7 +49,7 @@ class MemberServiceTest extends BaseMemberServiceTest { @Test void 회원정보_수정_패스워드_달라_실패() throws Exception { // given - memberRepository.save(Member.create("test", "test@naver.com", "1234")); + memberRepository.save(Member.create("test", "test@naver.com", passwordEncoder.encode("1234"))); MemberUpdateRequest request = new MemberUpdateRequest("test@naver.com", "1235", "1111", "수정 테스트"); @@ -78,7 +78,7 @@ class MemberServiceTest extends BaseMemberServiceTest { @Test void 회원정보_수정_성공() throws Exception { // given - memberRepository.save(Member.create("test", "test@naver.com", "1234")); + memberRepository.save(Member.create("test", "test@naver.com", passwordEncoder.encode("1234"))); MemberUpdateRequest request = new MemberUpdateRequest("test@naver.com", "1234", "1111", "수정 테스트"); diff --git a/src/test/java/com/retrip/auth/application/in/factory/BaseMemberServiceTest.java b/src/test/java/com/retrip/auth/application/in/factory/BaseMemberServiceTest.java index 07f7ea0..efac709 100644 --- a/src/test/java/com/retrip/auth/application/in/factory/BaseMemberServiceTest.java +++ b/src/test/java/com/retrip/auth/application/in/factory/BaseMemberServiceTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; @SpringBootTest @Transactional @@ -16,6 +17,10 @@ public class BaseMemberServiceTest { @Autowired protected MemberRepository memberRepository; + + @Autowired + protected PasswordEncoder passwordEncoder; + @BeforeEach void setUp() { } diff --git a/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/JwtAuthenticationFilterTest.java index c14c28a..f939aeb 100644 --- a/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/JwtAuthenticationFilterTest.java @@ -47,8 +47,9 @@ void JwtLogin() throws Exception { // JSON 파싱 ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode = objectMapper.readTree(responseBody); - String accessToken = jsonNode.get("accessToken").asText(); - String refreshToken = jsonNode.get("refreshToken").asText(); + JsonNode data = jsonNode.get("data"); + String accessToken = data.get("accessToken").asText(); + String refreshToken = data.get("refreshToken").asText(); // when & then diff --git a/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/LoginAuthenticationFilterTest.java b/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/LoginAuthenticationFilterTest.java index 2e2b6c7..5483fcf 100644 --- a/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/LoginAuthenticationFilterTest.java +++ b/src/test/java/com/retrip/auth/infra/adapter/in/rest/filter/LoginAuthenticationFilterTest.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.retrip.auth.application.in.request.LoginRequest; import com.retrip.auth.application.in.request.MemberCreateRequest; +import com.retrip.auth.application.in.request.MemberDeleteRequest; +import com.retrip.auth.application.in.request.MemberUpdateRequest; import com.retrip.auth.infra.adapter.in.rest.filter.base.BaseLoginAuthenticationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -12,13 +14,14 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest +@Transactional @AutoConfigureMockMvc(addFilters = true) class LoginAuthenticationFilterTest extends BaseLoginAuthenticationTest { @@ -43,8 +46,10 @@ class LoginAuthenticationFilterTest extends BaseLoginAuthenticationTest { .contentType(MediaType.APPLICATION_JSON) .headers(headers)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.accessToken").isNotEmpty()) - .andExpect(jsonPath("$.refreshToken").isNotEmpty()); + .andExpect(jsonPath("$.success").isBoolean()) + .andExpect(jsonPath("$.status").isNotEmpty()) + .andExpect(jsonPath("$.data.accessToken").isNotEmpty()) + .andExpect(jsonPath("$.data.refreshToken").isNotEmpty()); } @Test @@ -61,7 +66,50 @@ class LoginAuthenticationFilterTest extends BaseLoginAuthenticationTest { .content(json) ) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").isNotEmpty()) - .andExpect(jsonPath("$.email").isNotEmpty()); + .andExpect(jsonPath("$.success").isBoolean()) + .andExpect(jsonPath("$.status").isNotEmpty()) + .andExpect(jsonPath("$.data.id").isNotEmpty()) + .andExpect(jsonPath("$.data.email").isNotEmpty()); + } + @Test + void 유저_수정_성공() throws Exception { + memberRepository.save(member); + + // given + MemberUpdateRequest request = new MemberUpdateRequest("test@naver.com", "1234", "1111", + "수정 테스트"); + //when + String json = new ObjectMapper().writeValueAsString(request); + + // when & then + mockMvc.perform(put("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(json) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").isBoolean()) + .andExpect(jsonPath("$.status").isNotEmpty()) + .andExpect(jsonPath("$.data.id").isNotEmpty()) + .andExpect(jsonPath("$.data.email").isNotEmpty()); + } + + @Test + void 유저_삭제_성공() throws Exception { + memberRepository.save(member); + + // given + MemberDeleteRequest request = new MemberDeleteRequest("test@naver.com", "1234"); + //when + String json = new ObjectMapper().writeValueAsString(request); + + // when & then + mockMvc.perform(delete("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(json) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").isBoolean()) + .andExpect(jsonPath("$.status").isNotEmpty()) + .andExpect(jsonPath("$.message").isNotEmpty()); } }