-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 소셜 로그인으로 회원 가입 지원 및 계정 연결 해제 구현 #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
9434adc
8e6a0b7
9da77cc
37bc253
f477180
2afec7f
ed3f6c4
03ca57d
f8bbb9b
78b5014
e461bbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package com.dreamteam.alter.adapter.inbound.general.user.dto; | ||
|
|
||
| import com.dreamteam.alter.domain.user.type.PlatformType; | ||
| import com.dreamteam.alter.domain.user.type.SocialProvider; | ||
| import com.dreamteam.alter.domain.user.type.UserGender; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.AssertTrue; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import jakarta.validation.constraints.Size; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Schema(description = "소셜 회원가입 요청 DTO") | ||
| public class CreateUserWithSocialRequestDto { | ||
|
|
||
| @NotBlank | ||
| @Size(max = 64) | ||
| @Schema(description = "회원가입 세션 ID", example = "UUID") | ||
| private String signupSessionId; | ||
|
|
||
| @NotNull | ||
| @Schema(description = "소셜 로그인 플랫폼", example = "KAKAO") | ||
| private SocialProvider provider; | ||
|
|
||
| @Valid | ||
| @Schema(description = "OAuth 토큰") | ||
| private OauthLoginTokenDto oauthToken; | ||
|
|
||
| @Schema(description = "OAuth 인가 코드", example = "authorizationCode") | ||
| private String authorizationCode; | ||
|
|
||
| @NotNull | ||
| @Schema(description = "플랫폼 타입", example = "WEB / NATIVE") | ||
| private PlatformType platformType; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @NotBlank | ||
| @Size(max = 12) | ||
| @Schema(description = "성명", example = "김철수") | ||
| private String name; | ||
|
|
||
| @NotBlank | ||
| @Size(max = 64) | ||
| @Schema(description = "닉네임", example = "유땡땡") | ||
| private String nickname; | ||
|
|
||
| @NotNull | ||
| @Schema(description = "성별", example = "GENDER_MALE") | ||
| private UserGender gender; | ||
|
|
||
| @NotBlank | ||
| @Size(min = 8, max = 8) | ||
| @Schema(description = "생년월일", example = "YYYYMMDD") | ||
| private String birthday; | ||
|
|
||
| @AssertTrue(message = "WEB 플랫폼은 authorizationCode가 필수입니다") | ||
| private boolean isWebPlatformValid() { | ||
| if (platformType != PlatformType.WEB) return true; | ||
| return authorizationCode != null && !authorizationCode.isBlank(); | ||
| } | ||
|
|
||
| @AssertTrue(message = "NATIVE 플랫폼은 oauthToken이 필수입니다") | ||
| private boolean isNativePlatformValid() { | ||
| if (platformType != PlatformType.NATIVE) return true; | ||
| return oauthToken != null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.dreamteam.alter.adapter.inbound.general.user.dto; | ||
|
|
||
| import com.dreamteam.alter.domain.user.type.SocialProvider; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Schema(description = "소셜 계정 연동 해제 요청 DTO") | ||
| public class UnlinkSocialAccountRequestDto { | ||
|
|
||
| @NotNull | ||
| @Schema(description = "해제할 소셜 플랫폼", example = "KAKAO") | ||
| private SocialProvider provider; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.dreamteam.alter.adapter.inbound.general.user.dto; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.AssertTrue; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Size; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Schema(description = "비밀번호 변경 요청 DTO") | ||
| public class UpdatePasswordRequestDto { | ||
|
|
||
| @Schema(description = "현재 비밀번호 (비밀번호가 설정된 사용자는 필수, 소셜 전용 사용자는 생략 가능)", example = "currentPass1!") | ||
| private String currentPassword; | ||
|
|
||
| @NotBlank | ||
| @Size(min = 8, max = 16) | ||
| @Schema(description = "새 비밀번호 (8~16자, 영문·숫자·특수문자 각 1개 이상)", example = "newPass1!") | ||
| private String newPassword; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @AssertTrue(message = "새 비밀번호는 현재 비밀번호와 달라야 합니다") | ||
| private boolean isNewPasswordDifferent() { | ||
| if (currentPassword == null || newPassword == null) return true; | ||
| return !currentPassword.equals(newPassword); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.dreamteam.alter.adapter.outbound.user.persistence; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.redis.core.StringRedisTemplate; | ||
| import org.springframework.stereotype.Repository; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import java.time.Duration; | ||
| import java.util.List; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class SignupSessionCacheRepository { | ||
|
|
||
| private final StringRedisTemplate redisTemplate; | ||
|
|
||
| public void save(String key, String value) { | ||
| redisTemplate.opsForValue().set(key, value); | ||
| } | ||
|
|
||
| public void save(String key, String value, Duration ttl) { | ||
| redisTemplate.opsForValue().set(key, value, ttl); | ||
| } | ||
|
|
||
| public String get(String key) { | ||
| return redisTemplate.opsForValue().get(key); | ||
| } | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void delete(String key) { | ||
| redisTemplate.delete(key); | ||
| } | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void deleteAll(List<String> keys) { | ||
| if (!keys.isEmpty()) { | ||
| redisTemplate.delete(keys); | ||
| } | ||
| } | ||
|
Comment on lines
+29
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Redis는 Spring의 JPA 트랜잭션에 참여하지 않으므로
제안: TransactionSynchronization을 활용한 after-commit 패턴// SignupSessionCacheRepository에서 `@Transactional` 제거
public void deleteAll(List<String> keys) {
if (!keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
// 또는 호출 측(CreateUserWithSocial)에서 after-commit 훅 등록
private void scheduleSessionCleanupAfterCommit(List<String> keys) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
`@Override`
public void afterCommit() {
cacheRepository.deleteAll(keys);
}
}
);
}🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.dreamteam.alter.adapter.outbound.user.persistence; | ||
|
|
||
| import com.dreamteam.alter.domain.user.entity.UserSocial; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface UserSocialJpaRepository extends JpaRepository<UserSocial, Long> { | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.