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 @@ -4,10 +4,10 @@
import com.zimdugo.core.exception.BusinessException;
import com.zimdugo.core.exception.ErrorCode;
import com.zimdugo.user.application.UserQueryService;
import com.zimdugo.user.domain.SocialAccountStore;
import com.zimdugo.user.domain.User;
import com.zimdugo.user.domain.UserStatus;
import com.zimdugo.user.infrastructure.SocialAccountJpaRepository;
import com.zimdugo.user.infrastructure.UserJpaRepository;
import com.zimdugo.user.domain.UserStore;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -20,8 +20,8 @@ public class AccountWithdrawalService {
private final AccessTokenValidationService accessTokenValidationService;
private final JwtTokenProvider jwtTokenProvider;
private final UserQueryService userQueryService;
private final UserJpaRepository userJpaRepository;
private final SocialAccountJpaRepository socialAccountJpaRepository;
private final UserStore userStore;
private final SocialAccountStore socialAccountStore;
private final RefreshTokenRepository refreshTokenRepository;

public void withdraw(String accessToken) {
Expand All @@ -39,9 +39,9 @@ public void withdraw(String accessToken) {
}

user.changeStatus(UserStatus.DELETED);
userJpaRepository.save(user);
userStore.store(user);

socialAccountJpaRepository.deleteAllByUserId(userId);
socialAccountStore.deleteAllByUserId(userId);
refreshTokenRepository.deleteAllByUserId(userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
import com.zimdugo.auth.domain.OAuth2UserInfo;
import com.zimdugo.auth.domain.OAuth2UserInfoFactory;
import com.zimdugo.user.domain.SocialAccount;
import com.zimdugo.user.domain.SocialAccountReader;
import com.zimdugo.user.domain.SocialAccountStore;
import com.zimdugo.user.domain.User;
import com.zimdugo.user.domain.UserRole;
import com.zimdugo.user.domain.UserStatus;
import com.zimdugo.user.infrastructure.SocialAccountJpaRepository;
import com.zimdugo.user.infrastructure.UserJpaRepository;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.zimdugo.user.domain.UserStore;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
Expand All @@ -23,13 +21,18 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
@Transactional
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

private final UserJpaRepository userJpaRepository;
private final SocialAccountJpaRepository socialAccountJpaRepository;
private final UserStore userStore;
private final SocialAccountReader socialAccountReader;
private final SocialAccountStore socialAccountStore;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Expand All @@ -45,51 +48,67 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic

Map<String, Object> attributes = new HashMap<>(oAuth2User.getAttributes());
attributes.put("userId", user.getId());
attributes.put("email", user.getEmail());
attributes.put("email", user.getEmail()); // null 가능
attributes.put("nickname", user.getNickname());
attributes.put("role", user.getRoleOrDefault().name());

String nameAttributeKey = resolveNameAttributeKey(user);
String nameAttributeKey = resolveNameAttributeKey(user, userInfo);

return new DefaultOAuth2User(
List.of(new SimpleGrantedAuthority(toAuthority(user.getRoleOrDefault()))),
attributes,
nameAttributeKey
List.of(new SimpleGrantedAuthority(toAuthority(user.getRoleOrDefault()))),
attributes,
nameAttributeKey
);
}

private void validateRequiredFields(OAuth2UserInfo userInfo, String registrationId) {
if (userInfo.getProviderUserId() == null || userInfo.getProviderUserId().isBlank()) {
throw new OAuth2AuthenticationException(
new OAuth2Error("invalid_user_info"),
registrationId + " 사용자 식별자(providerUserId) 가져오지 못했습니다."
new OAuth2Error("invalid_user_info"),
registrationId + " 사용자 식별값(providerUserId) 가져오지 못했습니다."
);
}
}

private User findOrCreateUser(OAuth2UserInfo userInfo) {
return socialAccountJpaRepository
return socialAccountReader
.findByProviderAndProviderUserId(userInfo.getProvider(), userInfo.getProviderUserId())
.map(SocialAccount::getUser)
.map(socialAccount -> syncAndGetUser(socialAccount, userInfo))
.orElseGet(() -> createNewUser(userInfo));
}

private User syncAndGetUser(SocialAccount socialAccount, OAuth2UserInfo userInfo) {
socialAccount.updateProviderProfile(
normalize(userInfo.getEmail()),
normalize(userInfo.getProfileImageUrl())
);
SocialAccount saved = socialAccountStore.store(socialAccount);
return saved.getUser();
}

private User createNewUser(OAuth2UserInfo userInfo) {
String email = normalize(userInfo.getEmail());
String nickname = resolveNickname(userInfo);
String profileImageUrl = normalize(userInfo.getProfileImageUrl());

User user = new User(email, nickname, profileImageUrl, UserStatus.ACTIVE);
User savedUser = userJpaRepository.save(user);
User user = new User(
email, // 카카오는 null일 수 있음
nickname,
profileImageUrl,
UserStatus.ACTIVE
);

User savedUser = userStore.store(user);

SocialAccount socialAccount = new SocialAccount(
savedUser,
userInfo.getProvider(),
userInfo.getProviderUserId(),
email,
profileImageUrl
savedUser,
userInfo.getProvider(),
userInfo.getProviderUserId(),
email, // null 가능
profileImageUrl
);
socialAccountJpaRepository.save(socialAccount);

socialAccountStore.store(socialAccount);

return savedUser;
}
Expand All @@ -116,7 +135,7 @@ private String normalize(String value) {
return trimmed.isBlank() ? null : trimmed;
}

private String resolveNameAttributeKey(User user) {
private String resolveNameAttributeKey(User user, OAuth2UserInfo userInfo) {
if (user.getEmail() != null && !user.getEmail().isBlank()) {
return "email";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.zimdugo.auth.domain;

import com.zimdugo.user.domain.AuthProvider;
import com.zimdugo.identity.domain.AuthProvider;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.zimdugo.auth.domain;

import com.zimdugo.user.domain.AuthProvider;
import com.zimdugo.identity.domain.AuthProvider;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.zimdugo.auth.domain;

import com.zimdugo.user.domain.AuthProvider;
import com.zimdugo.identity.domain.AuthProvider;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.zimdugo.auth.domain;

import com.zimdugo.user.domain.AuthProvider;
import com.zimdugo.identity.domain.AuthProvider;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.zimdugo.auth.domain;

import com.zimdugo.user.domain.AuthProvider;
import com.zimdugo.identity.domain.AuthProvider;

public interface OAuth2UserInfo {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.zimdugo.user.domain;
package com.zimdugo.identity.domain;

public enum AuthProvider {
GOOGLE,
KAKAO,
NAVER,
FACEBOOK
}
}
37 changes: 18 additions & 19 deletions src/main/java/com/zimdugo/user/application/UserQueryService.java
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
package com.zimdugo.user.application;

import com.zimdugo.user.domain.SocialAccount;
import com.zimdugo.user.domain.SocialAccountReader;
import com.zimdugo.user.domain.User;
import com.zimdugo.user.infrastructure.SocialAccountJpaRepository;
import com.zimdugo.user.infrastructure.UserJpaRepository;
import com.zimdugo.user.domain.UserReader;
import com.zimdugo.core.exception.BusinessException;
import com.zimdugo.core.exception.ErrorCode;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserQueryService {

private final UserJpaRepository userJpaRepository;
private final SocialAccountJpaRepository socialAccountJpaRepository;
private final UserReader userReader;
private final SocialAccountReader socialAccountReader;

public UserProfileResponse getProfile(Long userId) {
User user = findById(userId);

List<SocialAccount> socialAccounts =
socialAccountJpaRepository.findAllByUserId(userId);
List<SocialAccount> socialAccounts = socialAccountReader.findAllByUserId(userId);

List<String> providers = socialAccounts.stream()
.map(sa -> sa.getProvider().name().toLowerCase())
.toList();
.map(sa -> sa.getProvider().name().toLowerCase())
.toList();

return new UserProfileResponse(
user.getId(),
user.getEmail(),
user.getNickname(),
user.getProfileImageUrl(),
user.getStatus().name(),
providers
user.getId(),
user.getEmail(),
user.getNickname(),
user.getProfileImageUrl(),
user.getStatus().name(),
providers
);
}

// AuthController에서 재발급 시 User 조회용
public User findById(Long userId) {
return userJpaRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("user not found. id=" + userId));
return userReader.findById(userId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
}
}
77 changes: 25 additions & 52 deletions src/main/java/com/zimdugo/user/domain/SocialAccount.java
Original file line number Diff line number Diff line change
@@ -1,78 +1,51 @@
package com.zimdugo.user.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import com.zimdugo.identity.domain.AuthProvider;
import java.time.LocalDateTime;
import lombok.Getter;

@Entity
@Table(
name = "social_account",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_provider_provider_user_id",
columnNames = {"provider", "provider_user_id"}
)
}
)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SocialAccount {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@JoinColumn(name = "user_id", nullable = false)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private User user;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private AuthProvider provider;

@Column(name = "provider_user_id", nullable = false, length = 100)
private String providerUserId;

@Column(length = 100)
private String providerEmail;

@Column(length = 255)
private String providerProfileImageUrl;

@Column(nullable = false)
private LocalDateTime linkedAt;

public SocialAccount(
User user,
AuthProvider provider,
String providerUserId,
String providerEmail,
String providerProfileImageUrl
User user,
AuthProvider provider,
String providerUserId,
String providerEmail,
String providerProfileImageUrl
) {
this(null, user, provider, providerUserId, providerEmail, providerProfileImageUrl, null);
}

@SuppressWarnings("checkstyle:ParameterNumber")
public SocialAccount(
Long id,
User user,
AuthProvider provider,
String providerUserId,
String providerEmail,
String providerProfileImageUrl,
LocalDateTime linkedAt
) {
this.id = id;
this.user = user;
this.provider = provider;
this.providerUserId = providerUserId;
this.providerEmail = providerEmail;
this.providerProfileImageUrl = providerProfileImageUrl;
this.linkedAt = linkedAt;
}

@PrePersist
protected void onCreate() {
this.linkedAt = LocalDateTime.now();
public void updateProviderProfile(String providerEmail, String providerProfileImageUrl) {
this.providerEmail = providerEmail;
this.providerProfileImageUrl = providerProfileImageUrl;
}
}
Loading
Loading