diff --git a/src/main/java/com/dnd/accompany/domain/auth/api/AuthController.java b/src/main/java/com/dnd/accompany/domain/auth/api/AuthController.java new file mode 100644 index 0000000..cc640e7 --- /dev/null +++ b/src/main/java/com/dnd/accompany/domain/auth/api/AuthController.java @@ -0,0 +1,43 @@ +package com.dnd.accompany.domain.auth.api; + +import com.dnd.accompany.domain.auth.dto.AuthUserInfo; +import com.dnd.accompany.domain.auth.dto.Tokens; +import com.dnd.accompany.domain.auth.oauth.dto.LoginRequest; +import com.dnd.accompany.domain.auth.oauth.dto.OAuthUserDataResponse; +import com.dnd.accompany.domain.auth.oauth.dto.OAuthUserInfo; +import com.dnd.accompany.domain.auth.oauth.service.OAuthService; +import com.dnd.accompany.domain.auth.service.TokenService; +import com.dnd.accompany.domain.user.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Auth") +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +public class AuthController { + + private final TokenService tokenService; + private final OAuthService oAuthService; + private final UserService userService; + + + @Operation(summary = "로그인") + @GetMapping("/sign-in") + public ResponseEntity signIn(LoginRequest loginRequest) { + OAuthUserDataResponse oAuthUserData = oAuthService.login(loginRequest); + + OAuthUserInfo oAuthUserInfo = OAuthUserInfo.from(oAuthUserData); + + AuthUserInfo authUserInfo = userService.getOrRegister(oAuthUserInfo); + + Tokens tokens = tokenService.createTokens(authUserInfo); + + return ResponseEntity.ok(tokens); + } +} diff --git a/src/main/java/com/dnd/accompany/domain/auth/oauth/dto/OAuthUserInfo.java b/src/main/java/com/dnd/accompany/domain/auth/oauth/dto/OAuthUserInfo.java new file mode 100644 index 0000000..75b0794 --- /dev/null +++ b/src/main/java/com/dnd/accompany/domain/auth/oauth/dto/OAuthUserInfo.java @@ -0,0 +1,28 @@ +package com.dnd.accompany.domain.auth.oauth.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class OAuthUserInfo { + private String provider; + private String oauthId; + private String nickname; + private String email; + private String appleRefreshToken; + + public static OAuthUserInfo from(OAuthUserDataResponse oAuthUserDataResponse) { + return OAuthUserInfo.builder() + .provider(oAuthUserDataResponse.getProvider()) + .oauthId(oAuthUserDataResponse.getOauthId()) + .nickname(oAuthUserDataResponse.getNickname()) + .email(oAuthUserDataResponse.getEmail()) + .build(); + } +} + diff --git a/src/main/java/com/dnd/accompany/domain/user/infrastructure/UserRepository.java b/src/main/java/com/dnd/accompany/domain/user/infrastructure/UserRepository.java index 0907a30..2d79d9b 100644 --- a/src/main/java/com/dnd/accompany/domain/user/infrastructure/UserRepository.java +++ b/src/main/java/com/dnd/accompany/domain/user/infrastructure/UserRepository.java @@ -4,5 +4,8 @@ import com.dnd.accompany.domain.user.entity.User; +import java.util.Optional; + public interface UserRepository extends JpaRepository { + Optional findUserByProviderAndOauthId(String provider, String oauthId); } diff --git a/src/main/java/com/dnd/accompany/domain/user/service/UserService.java b/src/main/java/com/dnd/accompany/domain/user/service/UserService.java new file mode 100644 index 0000000..510d35a --- /dev/null +++ b/src/main/java/com/dnd/accompany/domain/user/service/UserService.java @@ -0,0 +1,34 @@ +package com.dnd.accompany.domain.user.service; + +import com.dnd.accompany.domain.auth.dto.AuthUserInfo; +import com.dnd.accompany.domain.auth.oauth.dto.OAuthUserInfo; +import com.dnd.accompany.domain.user.entity.User; +import com.dnd.accompany.domain.user.infrastructure.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UserService { + private final UserRepository userRepository; + + @Transactional + public AuthUserInfo getOrRegister(OAuthUserInfo oauthUserInfo) { + User user = userRepository + .findUserByProviderAndOauthId(oauthUserInfo.getProvider(), oauthUserInfo.getOauthId()) + .orElseGet(() -> registerUser(oauthUserInfo)); + + return new AuthUserInfo(user.getId()); + } + + @Transactional + public User registerUser(OAuthUserInfo oauthUserInfo) { + return userRepository.save(User.of( + oauthUserInfo.getEmail(), + oauthUserInfo.getNickname(), + oauthUserInfo.getProvider(), + oauthUserInfo.getOauthId() + )); + } +} diff --git a/src/test/java/com/dnd/accompany/domain/user/service/UserServiceTest.java b/src/test/java/com/dnd/accompany/domain/user/service/UserServiceTest.java new file mode 100644 index 0000000..fb21892 --- /dev/null +++ b/src/test/java/com/dnd/accompany/domain/user/service/UserServiceTest.java @@ -0,0 +1,95 @@ +package com.dnd.accompany.domain.user.service; + +import com.dnd.accompany.domain.auth.dto.AuthUserInfo; +import com.dnd.accompany.domain.auth.oauth.dto.OAuthUserDataResponse; +import com.dnd.accompany.domain.auth.oauth.dto.OAuthUserInfo; +import com.dnd.accompany.domain.user.entity.User; +import com.dnd.accompany.domain.user.infrastructure.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserService userService; + + private OAuthUserDataResponse oAuthUserDataResponse; + + private OAuthUserInfo oauthUserInfo; + + @BeforeEach + void setup() { + oAuthUserDataResponse = OAuthUserDataResponse.builder() + .provider("KAKAO") + .nickname("TESTER1") + .oauthId("KA-123") + .email("test@gmail.com") + .build(); + + oauthUserInfo = OAuthUserInfo.from(oAuthUserDataResponse); + } + + @Test + @DisplayName("getOrRegister 호출 시 User 정보가 없다면 유저 데이터를 저장한다.") + void success() { + //given + User newUser = User.of( + oauthUserInfo.getEmail(), + oauthUserInfo.getNickname(), + oauthUserInfo.getProvider(), + oauthUserInfo.getOauthId() + ); + + ReflectionTestUtils.setField(newUser, "id", 1L); + + when(userRepository.findUserByProviderAndOauthId(oauthUserInfo.getProvider(), oauthUserInfo.getOauthId())).thenReturn(Optional.empty()); + when(userRepository.save(any(User.class))).thenReturn(newUser); + + //when + AuthUserInfo result = userService.getOrRegister(oauthUserInfo); + + //then + verify(userRepository).save(any(User.class)); // Ensure save was called + assertEquals(newUser.getId(), result.getUserId()); + } + + @Test + @DisplayName("getOrRegister 호출 시 User 정보가 있다면 유저 데이터를 저장하지 않는다.") + void success2() { + //given + User existingUser = User.of( + oauthUserInfo.getEmail(), + oauthUserInfo.getNickname(), + oauthUserInfo.getProvider(), + oauthUserInfo.getOauthId() + ); + + ReflectionTestUtils.setField(existingUser, "id", 1L); + + when(userRepository.findUserByProviderAndOauthId(oauthUserInfo.getProvider(), oauthUserInfo.getOauthId())) + .thenReturn(Optional.of(existingUser)); + + //when + AuthUserInfo result = userService.getOrRegister(oauthUserInfo); + + //then + verify(userRepository, never()).save(any(User.class)); + assertEquals(existingUser.getId(), result.getUserId()); + } +} \ No newline at end of file