diff --git a/src/main/java/com/ajou/hertz/domain/user/controller/UserControllerV1.java b/src/main/java/com/ajou/hertz/domain/user/controller/UserControllerV1.java index 5c7e14f..a5537df 100644 --- a/src/main/java/com/ajou/hertz/domain/user/controller/UserControllerV1.java +++ b/src/main/java/com/ajou/hertz/domain/user/controller/UserControllerV1.java @@ -5,6 +5,7 @@ import java.net.URI; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -13,12 +14,14 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import com.ajou.hertz.common.auth.UserPrincipal; import com.ajou.hertz.common.validator.PhoneNumber; import com.ajou.hertz.domain.user.dto.UserDto; import com.ajou.hertz.domain.user.dto.request.SignUpRequest; import com.ajou.hertz.domain.user.dto.response.UserEmailResponse; import com.ajou.hertz.domain.user.dto.response.UserExistenceResponse; import com.ajou.hertz.domain.user.dto.response.UserResponse; +import com.ajou.hertz.domain.user.dto.response.UserWithLinkedAccountInfoResponse; import com.ajou.hertz.domain.user.service.UserCommandService; import com.ajou.hertz.domain.user.service.UserQueryService; @@ -27,6 +30,7 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Email; @@ -43,6 +47,19 @@ public class UserControllerV1 { private final UserCommandService userCommandService; private final UserQueryService userQueryService; + @Operation( + summary = "내 정보 조회", + description = "내 정보를 조회합니다.", + security = @SecurityRequirement(name = "access-token") + ) + @GetMapping(value = "/me", headers = API_MINOR_VERSION_HEADER_NAME + "=" + 1) + public UserWithLinkedAccountInfoResponse getMyInfoV1_1( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + UserDto userDto = userQueryService.getDtoById(userPrincipal.getUserId()); + return UserWithLinkedAccountInfoResponse.from(userDto); + } + @Operation( summary = "회원 존재 여부 조회", description = "전달받은 값들에 일치하는 회원이 존재하는지 확인힙니다." @@ -83,13 +100,10 @@ public UserEmailResponse getUserEmailByPhoneV1_1( ) @ApiResponses({ @ApiResponse(responseCode = "200"), - @ApiResponse( - responseCode = "409", content = @Content, - description = """ -

[2200] 이미 다른 사용자가 사용 중인 이메일로 신규 회원을 등록하려고 하는 경우. -

[2203] 이미 다른 사용자가 사용 중인 전화번호로 신규 회원을 등록하려고 하는 경우. - """ - ) + @ApiResponse(responseCode = "409", content = @Content, description = """ +

[2200] 이미 다른 사용자가 사용 중인 이메일로 신규 회원을 등록하려고 하는 경우. +

[2203] 이미 다른 사용자가 사용 중인 전화번호로 신규 회원을 등록하려고 하는 경우. + """) }) @PostMapping(headers = API_MINOR_VERSION_HEADER_NAME + "=" + 1) public ResponseEntity signUpV1_1( diff --git a/src/main/java/com/ajou/hertz/domain/user/dto/UserDto.java b/src/main/java/com/ajou/hertz/domain/user/dto/UserDto.java index 263799f..15fa641 100644 --- a/src/main/java/com/ajou/hertz/domain/user/dto/UserDto.java +++ b/src/main/java/com/ajou/hertz/domain/user/dto/UserDto.java @@ -1,6 +1,7 @@ package com.ajou.hertz.domain.user.dto; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Set; import com.ajou.hertz.domain.user.constant.Gender; @@ -25,6 +26,7 @@ public class UserDto { private Gender gender; private String phone; private String contactLink; + private LocalDateTime createdAt; public static UserDto from(User user) { return new UserDto( @@ -37,7 +39,8 @@ public static UserDto from(User user) { user.getBirth(), user.getGender(), user.getPhone(), - user.getContactLink() + user.getContactLink(), + user.getCreatedAt() ); } } diff --git a/src/main/java/com/ajou/hertz/domain/user/dto/response/UserResponse.java b/src/main/java/com/ajou/hertz/domain/user/dto/response/UserResponse.java index 43ab75e..79b1a98 100644 --- a/src/main/java/com/ajou/hertz/domain/user/dto/response/UserResponse.java +++ b/src/main/java/com/ajou/hertz/domain/user/dto/response/UserResponse.java @@ -1,27 +1,43 @@ package com.ajou.hertz.domain.user.dto.response; import java.time.LocalDate; +import java.time.LocalDateTime; import com.ajou.hertz.domain.user.constant.Gender; import com.ajou.hertz.domain.user.dto.UserDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class UserResponse { + @Schema(description = "Id of user", example = "1") private Long id; + + @Schema(description = "이메일", example = "example@mail.com") private String email; + + @Schema(description = "프로필 이미지 url", example = "https://user-profile-image") private String profileImageUrl; + + @Schema(description = "생년월일") private LocalDate birth; + + @Schema(description = "성별") private Gender gender; + + @Schema(description = "연락 수단") private String contactLink; + @Schema(description = "계정 생성일자 (가입일)") + private LocalDateTime createdAt; + public static UserResponse from(UserDto userDto) { return new UserResponse( userDto.getId(), @@ -29,7 +45,8 @@ public static UserResponse from(UserDto userDto) { userDto.getProfileImageUrl(), userDto.getBirth(), userDto.getGender(), - userDto.getContactLink() + userDto.getContactLink(), + userDto.getCreatedAt() ); } } diff --git a/src/main/java/com/ajou/hertz/domain/user/dto/response/UserWithLinkedAccountInfoResponse.java b/src/main/java/com/ajou/hertz/domain/user/dto/response/UserWithLinkedAccountInfoResponse.java new file mode 100644 index 0000000..e5d8f56 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/user/dto/response/UserWithLinkedAccountInfoResponse.java @@ -0,0 +1,49 @@ +package com.ajou.hertz.domain.user.dto.response; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.springframework.util.StringUtils; + +import com.ajou.hertz.domain.user.constant.Gender; +import com.ajou.hertz.domain.user.dto.UserDto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class UserWithLinkedAccountInfoResponse extends UserResponse { + + @Schema(description = "카카오 계정 연동 여부", example = "true") + private Boolean isKakaoAccountLinked; + + private UserWithLinkedAccountInfoResponse( + Long id, + String email, + String profileImageUrl, + LocalDate birth, + Gender gender, + String contactLink, + LocalDateTime createdAt, + Boolean isKakaoAccountLinked + ) { + super(id, email, profileImageUrl, birth, gender, contactLink, createdAt); + this.isKakaoAccountLinked = isKakaoAccountLinked; + } + + public static UserWithLinkedAccountInfoResponse from(UserDto userDto) { + return new UserWithLinkedAccountInfoResponse( + userDto.getId(), + userDto.getEmail(), + userDto.getProfileImageUrl(), + userDto.getBirth(), + userDto.getGender(), + userDto.getContactLink(), + userDto.getCreatedAt(), + StringUtils.hasText(userDto.getKakaoUid()) + ); + } +} diff --git a/src/test/java/com/ajou/hertz/config/TestSecurityConfig.java b/src/test/java/com/ajou/hertz/config/TestSecurityConfig.java index c44d26d..e9acd79 100644 --- a/src/test/java/com/ajou/hertz/config/TestSecurityConfig.java +++ b/src/test/java/com/ajou/hertz/config/TestSecurityConfig.java @@ -4,6 +4,7 @@ import java.lang.reflect.Constructor; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Set; import org.springframework.boot.test.context.TestConfiguration; @@ -46,7 +47,8 @@ public void securitySetUp() throws Exception { private UserDto createUserDto() throws Exception { Constructor userResponseConstructor = UserDto.class.getDeclaredConstructor( Long.class, Set.class, String.class, String.class, String.class, - String.class, LocalDate.class, Gender.class, String.class, String.class + String.class, LocalDate.class, Gender.class, String.class, String.class, + LocalDateTime.class ); userResponseConstructor.setAccessible(true); return userResponseConstructor.newInstance( @@ -59,7 +61,8 @@ private UserDto createUserDto() throws Exception { LocalDate.of(2024, 1, 1), Gender.ETC, "01012345678", - "https://contack-link" + "https://contack-link", + LocalDateTime.of(2024, 1, 1, 0, 0) ); } } diff --git a/src/test/java/com/ajou/hertz/unit/common/auth/service/AuthServiceTest.java b/src/test/java/com/ajou/hertz/unit/common/auth/service/AuthServiceTest.java index dec0b03..8e82afd 100644 --- a/src/test/java/com/ajou/hertz/unit/common/auth/service/AuthServiceTest.java +++ b/src/test/java/com/ajou/hertz/unit/common/auth/service/AuthServiceTest.java @@ -109,7 +109,8 @@ private JwtTokenInfoDto createJwtTokenInfoDto() { private UserDto createUserDto(long id) throws Exception { Constructor userResponseConstructor = UserDto.class.getDeclaredConstructor( Long.class, Set.class, String.class, String.class, String.class, - String.class, LocalDate.class, Gender.class, String.class, String.class + String.class, LocalDate.class, Gender.class, String.class, String.class, + LocalDateTime.class ); userResponseConstructor.setAccessible(true); return userResponseConstructor.newInstance( @@ -122,7 +123,8 @@ private UserDto createUserDto(long id) throws Exception { LocalDate.of(2024, 1, 1), Gender.ETC, "01012345678", - "https://contack-link" + "https://contack-link", + LocalDateTime.of(2024, 1, 1, 0, 0) ); } diff --git a/src/test/java/com/ajou/hertz/unit/common/kakao/service/KakaoServiceTest.java b/src/test/java/com/ajou/hertz/unit/common/kakao/service/KakaoServiceTest.java index be7c0f1..33c9d92 100644 --- a/src/test/java/com/ajou/hertz/unit/common/kakao/service/KakaoServiceTest.java +++ b/src/test/java/com/ajou/hertz/unit/common/kakao/service/KakaoServiceTest.java @@ -140,7 +140,8 @@ private void verifyEveryMocksShouldHaveNoMoreInteractions() { private UserDto createUserDto(long id) throws Exception { Constructor userResponseConstructor = UserDto.class.getDeclaredConstructor( Long.class, Set.class, String.class, String.class, String.class, - String.class, LocalDate.class, Gender.class, String.class, String.class + String.class, LocalDate.class, Gender.class, String.class, String.class, + LocalDateTime.class ); userResponseConstructor.setAccessible(true); return userResponseConstructor.newInstance( @@ -153,7 +154,8 @@ private UserDto createUserDto(long id) throws Exception { LocalDate.of(2024, 1, 1), Gender.ETC, "01012345678", - "https://contack-link" + "https://contack-link", + LocalDateTime.of(2024, 1, 1, 0, 0) ); } diff --git a/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerV1Test.java b/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerV1Test.java index 7517076..f6fca33 100644 --- a/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerV1Test.java +++ b/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerV1Test.java @@ -2,11 +2,13 @@ import static com.ajou.hertz.common.constant.GlobalConstants.*; import static org.mockito.BDDMockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.lang.reflect.Constructor; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Set; import org.junit.jupiter.api.DisplayName; @@ -17,6 +19,7 @@ import org.springframework.context.annotation.Import; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.http.MediaType; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.test.context.event.annotation.BeforeTestMethod; import org.springframework.test.web.servlet.MockMvc; @@ -26,6 +29,7 @@ import com.ajou.hertz.common.auth.JwtAuthenticationFilter; import com.ajou.hertz.common.auth.JwtExceptionFilter; import com.ajou.hertz.common.auth.JwtTokenProvider; +import com.ajou.hertz.common.auth.UserPrincipal; import com.ajou.hertz.common.config.SecurityConfig; import com.ajou.hertz.domain.user.constant.Gender; import com.ajou.hertz.domain.user.constant.RoleType; @@ -71,6 +75,25 @@ public void securitySetUp() throws Exception { given(userQueryService.getDtoById(anyLong())).willReturn(createUserDto()); } + @Test + void 내_정보를_조회한다() throws Exception { + // given + long userId = 1L; + UserDto expectedResult = createUserDto(userId); + given(userQueryService.getDtoById(userId)).willReturn(expectedResult); + + // when & then + mvc.perform( + get("/v1/users/me") + .header(API_MINOR_VERSION_HEADER_NAME, 1) + .with(user(createTestUser(userId))) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(expectedResult.getId())); + then(userQueryService).should().getDtoById(userId); + verifyEveryMocksShouldHaveNoMoreInteractions(); + } + @Test void 이메일이_주어지고_주어진_이메일을_사용_중인_회원의_존재_여부를_조회한다() throws Exception { // given @@ -219,7 +242,8 @@ private SignUpRequest createSignUpRequest() throws Exception { private UserDto createUserDto(long id) throws Exception { Constructor userResponseConstructor = UserDto.class.getDeclaredConstructor( Long.class, Set.class, String.class, String.class, String.class, - String.class, LocalDate.class, Gender.class, String.class, String.class + String.class, LocalDate.class, Gender.class, String.class, String.class, + LocalDateTime.class ); userResponseConstructor.setAccessible(true); return userResponseConstructor.newInstance( @@ -232,11 +256,16 @@ private UserDto createUserDto(long id) throws Exception { LocalDate.of(2024, 1, 1), Gender.ETC, "01012345678", - "https://contack-link" + "https://contack-link", + LocalDateTime.of(2024, 1, 1, 0, 0) ); } private UserDto createUserDto() throws Exception { return createUserDto(1L); } + + private UserDetails createTestUser(Long userId) throws Exception { + return new UserPrincipal(createUserDto(userId)); + } } \ No newline at end of file