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
13 changes: 13 additions & 0 deletions common/src/main/java/com/yapp/ndgl/common/type/SocialProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yapp.ndgl.common.type;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum SocialProvider {
KAKAO("카카오"),
APPLE("애플");

private final String label;
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
package com.yapp.ndgl.domain.user.entity;

import com.yapp.ndgl.common.type.SocialProvider;
import com.yapp.ndgl.domain.common.entity.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "users")
@Table(
name = "users",
uniqueConstraints = @UniqueConstraint(columnNames = {"provider", "provider_id"})
)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserEntity extends BaseEntity {

@Column(nullable = false, unique = true, length = 36)
private String uuid;

@Column(nullable = false, length = 500)
@Column(length = 500)
private String fcmToken;

@Column(length = 100)
Expand All @@ -36,6 +43,16 @@ public class UserEntity extends BaseEntity {
@Column(nullable = false, length = 50)
private String nickname;

@Enumerated(EnumType.STRING)
@Column(length = 20)
private SocialProvider provider;

@Column(name = "provider_id", length = 100)
private String providerId;

@Column(length = 200)
private String email;

@Builder
public UserEntity(
final String uuid,
Expand All @@ -44,7 +61,10 @@ public UserEntity(
final String deviceOs,
final String deviceOsVersion,
final String appVersion,
final String nickname
final String nickname,
final SocialProvider provider,
final String providerId,
final String email
) {
this.uuid = uuid;
this.fcmToken = fcmToken;
Expand All @@ -53,5 +73,8 @@ public UserEntity(
this.deviceOsVersion = deviceOsVersion;
this.appVersion = appVersion;
this.nickname = nickname;
this.provider = provider;
this.providerId = providerId;
this.email = email;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

import org.springframework.data.jpa.repository.JpaRepository;

import com.yapp.ndgl.common.type.SocialProvider;
import com.yapp.ndgl.domain.user.entity.UserEntity;

public interface UserRepository extends JpaRepository<UserEntity, Long> {

Optional<UserEntity> findByUuid(String uuid);
Optional<UserEntity> findByUuid(String uuid);

Optional<UserEntity> findByProviderAndProviderId(SocialProvider provider, String providerId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,60 @@
import java.time.LocalDateTime;
import java.util.UUID;

import com.yapp.ndgl.common.type.SocialProvider;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class User {

private final Long id;
private final String uuid;
private final String fcmToken;
private final String deviceModel;
private final String deviceOs;
private final String deviceOsVersion;
private final String appVersion;
private final String nickname;
private final LocalDateTime createdAt;
private final LocalDateTime updatedAt;
private final Long id;
private final String uuid;
private final String fcmToken;
private final String deviceModel;
private final String deviceOs;
private final String deviceOsVersion;
private final String appVersion;
private final String nickname;
private final SocialProvider provider;
private final String providerId;
private final String email;
private final LocalDateTime createdAt;
private final LocalDateTime updatedAt;

public static User create(
final String fcmToken,
final String deviceModel,
final String deviceOs,
final String deviceOsVersion,
final String appVersion,
final String nickname
) {
String uuid = UUID.randomUUID().toString();
LocalDateTime now = LocalDateTime.now();
public static User create(
final String fcmToken,
final String deviceModel,
final String deviceOs,
final String deviceOsVersion,
final String appVersion,
final String nickname
) {
return User.builder()
.uuid(UUID.randomUUID().toString())
.fcmToken(fcmToken)
.deviceModel(deviceModel)
.deviceOs(deviceOs)
.deviceOsVersion(deviceOsVersion)
.appVersion(appVersion)
.nickname(nickname)
.build();
}

return User.builder()
.uuid(uuid)
.fcmToken(fcmToken)
.deviceModel(deviceModel)
.deviceOs(deviceOs)
.deviceOsVersion(deviceOsVersion)
.appVersion(appVersion)
.nickname(nickname)
.createdAt(now)
.updatedAt(now)
.build();
}
public static User createSocialUser(
final SocialProvider provider,
final String providerId,
final String email,
final String nickname
) {
return User.builder()
.uuid(UUID.randomUUID().toString())
.provider(provider)
.providerId(providerId)
.email(email)
.nickname(nickname)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,36 @@

public class UserMapper {

public static UserEntity toEntity(final User user) {
return UserEntity.builder()
.uuid(user.getUuid())
.fcmToken(user.getFcmToken())
.deviceModel(user.getDeviceModel())
.deviceOs(user.getDeviceOs())
.deviceOsVersion(user.getDeviceOsVersion())
.appVersion(user.getAppVersion())
.nickname(user.getNickname())
.build();
}
public static UserEntity toEntity(final User user) {
return UserEntity.builder()
.uuid(user.getUuid())
.fcmToken(user.getFcmToken())
.deviceModel(user.getDeviceModel())
.deviceOs(user.getDeviceOs())
.deviceOsVersion(user.getDeviceOsVersion())
.appVersion(user.getAppVersion())
.nickname(user.getNickname())
.provider(user.getProvider())
.providerId(user.getProviderId())
.email(user.getEmail())
.build();
}

public static User toDomain(final UserEntity entity) {
return User.builder()
.id(entity.getId())
.uuid(entity.getUuid())
.fcmToken(entity.getFcmToken())
.deviceModel(entity.getDeviceModel())
.deviceOs(entity.getDeviceOs())
.deviceOsVersion(entity.getDeviceOsVersion())
.appVersion(entity.getAppVersion())
.nickname(entity.getNickname())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.build();
}
public static User toDomain(final UserEntity entity) {
return User.builder()
.id(entity.getId())
.uuid(entity.getUuid())
.fcmToken(entity.getFcmToken())
.deviceModel(entity.getDeviceModel())
.deviceOs(entity.getDeviceOs())
.deviceOsVersion(entity.getDeviceOsVersion())
.appVersion(entity.getAppVersion())
.nickname(entity.getNickname())
.provider(entity.getProvider())
.providerId(entity.getProviderId())
.email(entity.getEmail())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.yapp.ndgl.domain.user.service;

import java.util.Optional;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.yapp.ndgl.common.exception.GlobalException;
import com.yapp.ndgl.common.exception.UserErrorCode;
import com.yapp.ndgl.common.type.SocialProvider;
import com.yapp.ndgl.domain.user.User;
import com.yapp.ndgl.domain.user.mapper.UserMapper;
import com.yapp.ndgl.domain.user.UserNicknameGenerator;
import com.yapp.ndgl.domain.user.entity.UserEntity;
import com.yapp.ndgl.domain.user.mapper.UserMapper;
import com.yapp.ndgl.domain.user.repository.UserRepository;

import lombok.RequiredArgsConstructor;
Expand All @@ -28,24 +31,34 @@ public User createUser(
final String deviceOsVersion,
final String appVersion) {
String nickname = UserNicknameGenerator.generate();

User user = User.create(
fcmToken,
deviceModel,
deviceOs,
deviceOsVersion,
appVersion,
nickname
);

User user = User.create(fcmToken, deviceModel, deviceOs, deviceOsVersion, appVersion, nickname);
UserEntity savedUserEntity = userRepository.save(UserMapper.toEntity(user));
return UserMapper.toDomain(savedUserEntity);
}

@Transactional
public User createSocialUser(
final SocialProvider provider,
final String providerId,
final String email) {
String nickname = UserNicknameGenerator.generate();
User user = User.createSocialUser(provider, providerId, email, nickname);
UserEntity savedEntity = userRepository.save(UserMapper.toEntity(user));
return UserMapper.toDomain(savedEntity);
}
Comment on lines +39 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# createSocialUser / findByProviderAndProviderId 호출부 확인
rg -nP -C5 '\b(createSocialUser|findByProviderAndProviderId)\s*\(' --type=java -g '!**/UserDomainService.java'

Repository: YAPP-Github/NDGL-BE

Length of output: 2037


🏁 Script executed:

#!/bin/bash
rg -nP '\.createSocialUser\s*\(' --type=java
rg -nP '\.findByProviderAndProviderId\s*\(' --type=java

Repository: YAPP-Github/NDGL-BE

Length of output: 417


🏁 Script executed:

#!/bin/bash
# 1) 해당 서비스의 createSocialUser/중복선검사 흐름 확인
sed -n '1,140p' domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/service/UserDomainService.java

# 2) DataIntegrityViolationException/ConstraintViolationException 예외 매핑 확인
rg -n --hidden --no-ignore-vcs 'DataIntegrityViolationException|ConstraintViolationException|CommonErrorCode\.DATA_INTEGRITY_VIOLATION' --type=java

# 3) 유니크 제약(provider, provider_id) 실제 어노테이션 확인
rg -n --hidden --no-ignore-vcs 'provider[_-]?provider_id|provider_id|providerId|`@UniqueConstraint`|uniqueConstraints\s*=' \
  --type=java domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user

Repository: YAPP-Github/NDGL-BE

Length of output: 5768


🏁 Script executed:

#!/bin/bash
# ApiExceptionHandler: DataIntegrityViolationException 처리 로직 확인
sed -n '130,210p' application/src/main/java/com/yapp/ndgl/application/common/exception/ApiExceptionHandler.java

# CommonErrorCode: DATA_INTEGRITY_VIOLATION의 상태코드/메시지 확인
rg -n "DATA_INTEGRITY_VIOLATION" -S application/src/main/java domain/src/main/java

Repository: YAPP-Github/NDGL-BE

Length of output: 3813


소셜 계정 중복 생성 시 예외 처리(409/500) 및 레이스 대응 확인 필요
UserEntity(provider, provider_id) 유니크 제약이 있고, createSocialUser는 별도 선검사 없이 바로 save를 수행하므로 동시 요청 시 DataIntegrityViolationException이 발생할 수 있습니다. 전역 ApiExceptionHandlerDataIntegrityViolationExceptioncauseorg.hibernate.exception.ConstraintViolationException일 때만 CommonErrorCode.DATA_INTEGRITY_VIOLATION409(CONFLICT) 처리하고, 그 외에는 500으로 떨어집니다. 레이스 상황에서 실제로 해당 cause가 항상 매핑되는지(또는 호출부/도메인에서 일관된 예외로 변환하는지) 확인이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/service/UserDomainService.java`
around lines 39 - 48, createSocialUser currently calls
userRepository.save(UserMapper.toEntity(user)) without handling
concurrent-creation constraint violations, so a DataIntegrityViolationException
during save can map to 500 instead of 409; wrap the save call in a try/catch
inside createSocialUser, catch DataIntegrityViolationException, inspect the
cause chain for org.hibernate.exception.ConstraintViolationException or
SQLIntegrityConstraintViolationException (or the constraint name/message
referring to provider/provider_id), and convert it into a domain/API conflict
error (e.g., throw an ApiException or specific ConflictException that maps to
CommonErrorCode.DATA_INTEGRITY_VIOLATION / 409). Reference: createSocialUser,
User.createSocialUser, userRepository.save, UserEntity, ApiExceptionHandler,
CommonErrorCode.DATA_INTEGRITY_VIOLATION.


@Transactional(readOnly = true)
public User findByUuid(final String uuid) {
return userRepository.findByUuid(uuid)
.map(UserMapper::toDomain)
.orElseThrow(() -> new GlobalException(UserErrorCode.NOT_FOUND_USER));
}

@Transactional(readOnly = true)
public Optional<User> findByProviderAndProviderId(
final SocialProvider provider,
final String providerId) {
return userRepository.findByProviderAndProviderId(provider, providerId)
.map(UserMapper::toDomain);
}
}
Loading