Conversation
- password 컬럼 nullable 허용 (소셜 전용 계정은 비밀번호 없음) - createWithSocial() 정적 팩토리 메서드 추가
- CreateUserWithSocialUseCase 인바운드 포트 추가 - CreateUserWithSocial 유스케이스 구현 - Redis 회원가입 세션 검증 - 닉네임·연락처·소셜ID 중복 확인 - 이메일 인증 세션 선택적 처리 - 소셜 인증 후 User 및 UserSocial 생성
- CreateUserWithSocialRequestDto 추가 (소셜 회원가입 요청 DTO) - POST /signup-social 엔드포인트 구현 - UserPublicControllerSpec에 Swagger 문서 추가
- 세션 미존재, 닉네임/연락처/소셜ID 중복 예외 케이스 - 정상 소셜 회원가입 시나리오 - 이메일 인증 세션 포함 시나리오
- 소셜 회원가입 시 별도 이메일 인증 없이 소셜 계정에서 제공하는 이메일을 User.email에 바로 저장하도록 변경 - socialAuthInfo.getEmail()로 이메일을 취득하고 중복 확인 후 저장 - 소셜 계정에 이메일이 없는 경우 null로 저장 (기존 nullable 정책 유지)
- UnlinkSocialAccountUseCase 추가 (DELETE /app/users/social/unlink) - 비밀번호 없는 사용자가 마지막 소셜 계정 해제 시 차단 (A016) - UserSocial hard delete: UserSocialRepository command port + UserSocialJpaRepository 신규 추가 - UserSocialQueryRepository에 findByUserIdAndSocialProvider, countByUserId 추가 - ErrorCode A015(SOCIAL_ACCOUNT_NOT_LINKED), A016(SOCIAL_UNLINK_NOT_ALLOWED), A017(INVALID_CURRENT_PASSWORD) 추가
- UpdatePasswordUseCase 추가 (PUT /app/users/me/password) - 비밀번호 있는 사용자: currentPassword 검증 후 변경 (A017) - 소셜 전용 사용자(password=null): currentPassword 없이 바로 신규 설정
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 Walkthrough🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 16
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSocialController.java`:
- Around line 38-40: The DELETE handler unlinkSocialAccount in
UserSocialController currently accepts a request body
(UnlinkSocialAccountRequestDto), which can be dropped by gateways/proxies;
change the API to accept the provider as a query param instead: replace the
`@RequestBody` UnlinkSocialAccountRequestDto parameter with a `@RequestParam`
SocialProvider provider in the unlinkSocialAccount method signature and update
any call sites and service-layer calls that read from
UnlinkSocialAccountRequestDto to use the provider parameter (and remove or adapt
the DTO) so the controller no longer depends on a DELETE body.
In
`@src/main/java/com/dreamteam/alter/adapter/inbound/general/user/dto/CreateUserWithSocialRequestDto.java`:
- Around line 30-39: CreateUserWithSocialRequestDto currently allows both
oauthToken and authorizationCode to be empty; add platform-specific
required-field validation by implementing a custom bean validation on the DTO
(e.g., an `@AssertTrue` boolean method or a custom ConstraintValidator). Inside
the validator method (in CreateUserWithSocialRequestDto) check platformType
(PlatformType) and enforce the required combination: for WEB require
authorizationCode non-empty, for NATIVE require oauthToken non-null (and
validate its inner fields), and for any other PlatformType enforce the
appropriate rule or a clear failure message; return false (or raise a constraint
violation) with a helpful message referencing oauthToken/authorizationCode when
the rule fails. Ensure validation annotations remain on
oauthToken/authorizationCode and that the error message is meaningful for
clients.
In
`@src/main/java/com/dreamteam/alter/adapter/inbound/general/user/dto/UpdatePasswordRequestDto.java`:
- Around line 14-22: The DTO lacks validation preventing the new password from
matching the current one; update the validation so UpdatePasswordRequestDto
enforces newPassword != currentPassword (or add the check in the UpdatePassword
handling logic) by adding a cross-field validation: implement a custom
constraint or an `@AssertTrue` method on UpdatePasswordRequestDto that compares
currentPassword and newPassword and fails when they are equal, and ensure the
constraint/message is used by the controller/service that invokes UpdatePassword
so identical passwords are rejected.
In
`@src/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/UserSocialQueryRepositoryImpl.java`:
- Around line 22-30: The current implementation in
UserSocialQueryRepositoryImpl::findByUserIdAndSocialProvider uses
queryFactory.selectFrom(QUserSocial.userSocial)...fetchOne(), which will throw
at runtime if multiple rows exist because (user_id, social_provider) is not
constrained as unique; either add a DB/entity-level unique constraint for
(user_id, social_provider) on the UserSocial entity (e.g., via
`@Table`(uniqueConstraints=...)) to guarantee single result, or change the
repository query to a defensive read (e.g., use fetchFirst() or use
fetch().stream().findFirst()) so the method returns the first match without
throwing; update the code in the findByUserIdAndSocialProvider method
accordingly and ensure tests cover duplicate-row behavior.
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java`:
- Around line 24-25: The use case CreateUserWithSocial currently depends on
Spring's StringRedisTemplate and performs Redis deletions before DB commit;
replace the direct dependency by introducing an outbound port interface
SignupSessionPort (e.g., method deleteSignupSession(String key) and any
read/write methods used) and inject that port into CreateUserWithSocial instead
of StringRedisTemplate, update all calls (lines referencing redisTemplate ops)
to call signupSessionPort methods, remove any Spring Data imports, and ensure
Redis cleanup runs only after successful DB commit by registering a transaction
synchronization (e.g., TransactionSynchronizationManager.registerSynchronization
with an afterCommit callback that calls
signupSessionPort.deleteSignupSession(sessionKey)) or using an after-commit
transactional event; update CreateUserWithSocial to use these symbols and remove
direct infra coupling.
- Around line 100-106: The Redis deletions (redisTemplate.delete(sessionIdKey)
and redisTemplate.delete(CONTACT_INDEX_KEY_PREFIX + contact)) are happening
before the DB work (authService.generateAuthorization and
authLogRepository.save) and must be moved to run only after a successful commit;
change this by registering an after-commit hook with
TransactionSynchronizationManager (e.g.,
TransactionSynchronizationManager.registerSynchronization) that performs the two
redisTemplate.delete calls in its afterCommit() method so the Redis cleanup only
executes once the transaction committing the authorization and auth log
succeeds.
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccount.java`:
- Around line 33-40: The current check (userSocialQueryRepository.countByUserId)
followed by userSocialRepository.delete is racy; replace with an atomic
conditional deletion or a pessimistic lock in UnlinkSocialAccount. Implement and
call a repository operation like
userSocialRepository.deleteIfMoreThanOneForUser(userSocial.getId(),
user.getId()) that performs a single SQL conditional DELETE (e.g., DELETE ...
WHERE id = ? AND (SELECT COUNT(*) FROM user_social WHERE user_id = ?) > 1) and
throws CustomException(ErrorCode.SOCIAL_UNLINK_NOT_ALLOWED) if the delete
affected 0 rows, or alternatively load the user's socials with a
PESSIMISTIC_WRITE lock, re-check the count, and then delete to ensure the
check+delete is atomic. Ensure to reference and update calls to
userSocialQueryRepository.countByUserId and userSocialRepository.delete in
UnlinkSocialAccount accordingly.
In `@src/main/java/com/dreamteam/alter/domain/user/entity/User.java`:
- Around line 34-35: LoginWithPassword is calling
passwordEncoder.matches(request.getPassword(), user.getPassword()) without
null-checking user.getPassword(), causing NPE for social-only users; update the
login flow (LoginWithPassword.java) to first check if user.getPassword() == null
and treat that as invalid login, following the pattern from UpdatePassword.java
(i.e., replace the direct matches call with a guard like: if (user.getPassword()
== null || !passwordEncoder.matches(...)) throw INVALID_LOGIN_INFO), referencing
the User.password field and the passwordEncoder usage to locate where to add the
check.
In
`@src/main/java/com/dreamteam/alter/domain/user/port/inbound/CreateUserWithSocialUseCase.java`:
- Around line 3-7: The domain port CreateUserWithSocialUseCase currently depends
on adapter DTOs (CreateUserWithSocialRequestDto, GenerateTokenResponseDto);
replace those with domain-level request/response types (e.g.,
CreateUserWithSocialCommand or CreateUserWithSocialRequest and
DomainGenerateTokenResponse) defined under the domain package, update the
CreateUserWithSocialUseCase.execute signature to use these domain types, and
move mapping responsibilities to the inbound adapter (convert
CreateUserWithSocialRequestDto → domain request and domain response →
GenerateTokenResponseDto in the adapter/controller). Ensure no imports from
adapter.inbound.* remain in the domain port.
In
`@src/main/java/com/dreamteam/alter/domain/user/port/inbound/UnlinkSocialAccountUseCase.java`:
- Around line 3-7: The domain inbound port UnlinkSocialAccountUseCase currently
depends on the adapter DTO UnlinkSocialAccountRequestDto, breaking layer
boundaries; change the signature of UnlinkSocialAccountUseCase.execute(AppActor,
...) to accept a domain-owned command or primitives (e.g., create a domain model
like UnlinkSocialAccountCommand in com.dreamteam.alter.domain.user.command or
use simple params) instead of UnlinkSocialAccountRequestDto, remove the import
of adapter.inbound DTO from the domain interface, and update the inbound adapter
to map UnlinkSocialAccountRequestDto → UnlinkSocialAccountCommand before calling
UnlinkSocialAccountUseCase.execute.
In
`@src/main/java/com/dreamteam/alter/domain/user/port/inbound/UpdatePasswordUseCase.java`:
- Around line 3-7: The domain port UpdatePasswordUseCase currently depends on an
adapter DTO (UpdatePasswordRequestDto); remove this infrastructure dependency by
introducing a domain-level request/command object (e.g., UpdatePasswordCommand
or UpdatePasswordRequest) in the domain package and change the method signature
of UpdatePasswordUseCase.execute(AppActor, UpdatePasswordRequestDto) to use that
domain object instead; then perform mapping from UpdatePasswordRequestDto to the
new domain request inside the inbound adapter layer (where
CreateUserWithSocialUseCase was handled), ensuring UpdatePasswordUseCase, its
implementations, and any domain services only import the new domain request
class and not the adapter DTO.
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.java`:
- Around line 93-124: Replace the four near-identical helpers
(createSocialAuthInfo, createSocialAuthInfoForDuplicateSocialId,
createSocialAuthInfoForDuplicateEmail, createSocialAuthInfoWithoutEmail) with a
single parameterized factory that returns a mocked SocialAuthInfo; e.g., a
method like createSocialAuthInfo(String socialId, `@Nullable` String email,
`@Nullable` String refreshToken, SocialProvider provider) that mocks
SocialAuthInfo and conditionally stubs getProvider(), getSocialId(), getEmail(),
and getRefreshToken() only when non-null (use SocialProvider.KAKAO as default
where callers expect it); update tests to call this new helper and remove the
four old methods so maintenance and variations are centralized around one
function.
- Around line 132-286: Rename the test methods to follow the
action_condition_expectedResult naming convention instead of the current
fails_when... and succeeds_with... forms: update methods like
fails_whenSignupSessionNotFound, fails_whenNicknameDuplicated,
fails_whenContactDuplicated, fails_whenSocialIdAlreadyRegistered,
fails_whenSocialEmailAlreadyExists, succeeds_withValidSocialSignup, and
succeeds_withNoEmailFromSocialAccount to names that start with the action (e.g.,
createUserWithSocial), then the condition, then the expected result (e.g.,
createUserWithSocial_signupSessionNotFound_throwsSignUpSessionNotExist); keep or
adjust `@DisplayName` as needed and update any direct references to these method
names in the test class (e.g., in CreateUserWithSocialTests) so tests compile
and run.
- Around line 182-225: The tests for SOCIAL_ID_DUPLICATED and EMAIL_DUPLICATED
need to also assert that the signup session key is cleaned up from Redis after
the failure; after invoking createUserWithSocial.execute(request) add
verification that the Redis deletion call for "SIGNUP:PENDING:signup-session-id"
was invoked (match the same Redis collaborator used in the test setup — e.g.,
verify(redisTemplate).delete("SIGNUP:PENDING:signup-session-id") or
verify(yourRedisClient).remove(...) depending on implementation) in both failing
tests, and keep the existing assertion that userRepository.save is never called.
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccountTests.java`:
- Around line 83-97: The two tests execute_withLinkedSocial_succeeds and
execute_withSingleSocial_succeeds duplicate the same observable behavior;
consolidate or differentiate them: either remove one and keep a single test that
asserts unlinkSocialAccount.execute(actor, request) does not throw and that
userSocialRepository.delete(userSocial) is invoked, or update
execute_withSingleSocial_succeeds to set up the "single social" precondition and
add an extra verification (e.g., verify that userRepository.countSocialsByUserId
or equivalent is not called/was called as intended) to demonstrate the
special-case behavior; locate the test methods and the calls to
unlinkSocialAccount.execute and userSocialRepository.delete to implement the
change.
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/UpdatePasswordTests.java`:
- Around line 75-79: Remove the fragile verification that all interactions on
the mocked user are exhausted in these negative tests: delete the
then(user).shouldHaveNoMoreInteractions() assertions (seen at the end of the
failing tests) and keep only the essential assertions that the use case throws
CustomException with ErrorCode.INVALID_CURRENT_PASSWORD and that no
updatePasswordRepository.save/update method was called; this avoids failing the
test due to the legitimate user.getPassword() call inside updatePassword.execute
while still verifying that password update logic was not invoked.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 95919fb6-7db0-4216-92e1-8b234d3a12a1
📒 Files selected for processing (25)
src/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserPublicController.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserPublicControllerSpec.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSelfController.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSelfControllerSpec.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSocialController.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSocialControllerSpec.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/dto/CreateUserWithSocialRequestDto.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/dto/UnlinkSocialAccountRequestDto.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/dto/UpdatePasswordRequestDto.javasrc/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/UserSocialJpaRepository.javasrc/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/UserSocialQueryRepositoryImpl.javasrc/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/UserSocialRepositoryImpl.javasrc/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.javasrc/main/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccount.javasrc/main/java/com/dreamteam/alter/application/user/usecase/UpdatePassword.javasrc/main/java/com/dreamteam/alter/common/exception/ErrorCode.javasrc/main/java/com/dreamteam/alter/domain/user/entity/User.javasrc/main/java/com/dreamteam/alter/domain/user/port/inbound/CreateUserWithSocialUseCase.javasrc/main/java/com/dreamteam/alter/domain/user/port/inbound/UnlinkSocialAccountUseCase.javasrc/main/java/com/dreamteam/alter/domain/user/port/inbound/UpdatePasswordUseCase.javasrc/main/java/com/dreamteam/alter/domain/user/port/outbound/UserSocialQueryRepository.javasrc/main/java/com/dreamteam/alter/domain/user/port/outbound/UserSocialRepository.javasrc/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.javasrc/test/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccountTests.javasrc/test/java/com/dreamteam/alter/application/user/usecase/UpdatePasswordTests.java
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java (2)
102-108:⚠️ Potential issue | 🟠 MajorRedis 세션 삭제가 DB 커밋 전에 실행되어 데이터 불일치가 발생할 수 있습니다.
Line 104에서
cacheRepository.deleteAll()호출 후 Line 106-107의authService.generateAuthorization()또는authLogRepository.save()가 실패하면, DB 트랜잭션은 롤백되지만 Redis 키는 이미 삭제된 상태가 됩니다.
TransactionSynchronizationManager.registerSynchronization()을 사용하여 DB 커밋 완료 후에만 Redis 정리가 실행되도록 변경해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java` around lines 102 - 108, The Redis session deletion (cacheRepository.deleteAll called with sessionIdKey and contactKey in CreateUserWithSocial) runs before DB commit causing inconsistency if authService.generateAuthorization or authLogRepository.save fails; change this to register a synchronization via TransactionSynchronizationManager.registerSynchronization so that the actual cacheRepository.deleteAll(Arrays.asList(sessionIdKey, contactKey)) is executed in afterCommit(), ensuring the keys (CONTACT_INDEX_KEY_PREFIX + contact, sessionIdKey) are only removed after the transaction successfully commits.
7-7:⚠️ Potential issue | 🟠 Major애플리케이션 계층에서 어댑터 계층 클래스를 직접 의존합니다.
SignupSessionCacheRepository는adapter.outbound패키지의 구현 클래스입니다. 코딩 가이드라인에 따라 애플리케이션 계층은 인프라에 직접 의존하지 않아야 합니다. 도메인 계층에SignupSessionPort같은 아웃바운드 포트 인터페이스를 정의하고, 해당 포트를 주입받도록 변경해 주세요.As per coding guidelines,
src/main/java/com/dreamteam/alter/application/**: "No direct infrastructure dependencies (no Spring Data, no HTTP clients)."Also applies to: 44-44
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java` at line 7, CreateUserWithSocial currently imports and depends on the infrastructure class SignupSessionCacheRepository; change this to depend on an outbound port interface (e.g., define SignupSessionPort in the domain or application API layer) and inject that port into CreateUserWithSocial instead of SignupSessionCacheRepository. Replace usages of SignupSessionCacheRepository in CreateUserWithSocial with the corresponding methods on SignupSessionPort, update constructor/injection to accept SignupSessionPort, and ensure the adapter class SignupSessionCacheRepository implements SignupSessionPort so wiring remains in configuration/DI.src/test/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccountTests.java (1)
85-101:⚠️ Potential issue | 🟡 Minor동일 행위 테스트가 중복되어 테스트 유지비만 증가합니다.
Line [85]-Line [92]와 Line [94]-Line [101]은 현재 전제와 검증이 동일합니다. 하나를 제거하거나, 두 번째 케이스에 별도 전제/검증을 추가해 의도를 분리해 주세요.
중복 제거 예시 diff
@@ - `@Test` - `@DisplayName`("소셜 계정이 1개뿐이어도 비밀번호 있는 사용자는 해제 가능하다") - void execute_withSingleSocial_succeeds() { - // when & then - assertThatNoException().isThrownBy(() -> unlinkSocialAccount.execute(actor, request)); - then(userSocialRepository).should().delete(userSocial); - then(userSocialQueryRepository).should(never()).countByUserIdForUpdate(any()); - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccountTests.java` around lines 85 - 101, The two tests execute_withLinkedSocial_succeeds and execute_withSingleSocial_succeeds in UnlinkSocialAccountTests are identical; remove the duplicate or make the second actually exercise the "single social account" scenario: update the setup for execute_withSingleSocial_succeeds to arrange a single linked social (e.g., mock userSocialQueryRepository.countByUserIdForUpdate(...) to return 1 and ensure actor has a password) and change assertions to expect any additional behavior (for example assert that countByUserIdForUpdate(...) is invoked and still that userSocialRepository.delete(userSocial) is called), or simply delete the redundant test if no extra behavior is required; keep references to unlinkSocialAccount, userSocialRepository, and userSocialQueryRepository when making changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSocialController.java`:
- Around line 22-26: Remove the `@Resource` annotations from the
UserSocialController fields and rely on constructor injection via
`@RequiredArgsConstructor` instead: delete the `@Resource`(name =
"linkSocialAccount") and `@Resource`(name = "unlinkSocialAccount") annotations on
the LinkSocialAccountUseCase and UnlinkSocialAccountUseCase fields so the final
fields are injected via the generated constructor for UserSocialController.
In
`@src/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/SignupSessionCacheRepository.java`:
- Around line 29-39: The `@Transactional`(propagation = Propagation.REQUIRES_NEW)
on SignupSessionCacheRepository.delete and deleteAll is ineffective for Redis
and can cause DB/Redis inconsistency; remove the `@Transactional` annotations from
SignupSessionCacheRepository.delete and deleteAll and instead schedule Redis
deletions to run after DB commit by registering an after-commit hook from the
caller (e.g., from CreateUserWithSocial) using
TransactionSynchronizationManager.registerSynchronization so that
cacheRepository.deleteAll(keys) is invoked in afterCommit; alternatively keep
simple non-transactional delete/deleteAll methods and call them only from an
after-commit callback to ensure Redis cleanup happens only after successful DB
commit.
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.java`:
- Line 6: 현재 테스트 클래스의 잘못된 import로 컴파일이 실패합니다: replace the import of
SignupSessionCacheRepository from adapter.outbound.cache to the correct package
adapter.outbound.user.persistence; update the import statement referencing
SignupSessionCacheRepository in CreateUserWithSocialTests to use
com.dreamteam.alter.adapter.outbound.user.persistence.SignupSessionCacheRepository
so the compiler can resolve the class.
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/UpdatePasswordTests.java`:
- Around line 36-43: Update UpdatePasswordTests to use Mockito annotations
instead of manual mock() creation: annotate the class with
`@ExtendWith`(MockitoExtension.class) and declare the actor and user fields with
`@Mock` (replace the current private AppActor actor; private User user;), then
remove the manual mock assignments in setUp() (user = mock(User.class); actor =
mock(AppActor.class);) while keeping the stubbing
given(actor.getUser()).willReturn(user) in the `@BeforeEach` setUp method; this
keeps tests consistent with the src/test/** guideline and reduces setup
boilerplate.
---
Duplicate comments:
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java`:
- Around line 102-108: The Redis session deletion (cacheRepository.deleteAll
called with sessionIdKey and contactKey in CreateUserWithSocial) runs before DB
commit causing inconsistency if authService.generateAuthorization or
authLogRepository.save fails; change this to register a synchronization via
TransactionSynchronizationManager.registerSynchronization so that the actual
cacheRepository.deleteAll(Arrays.asList(sessionIdKey, contactKey)) is executed
in afterCommit(), ensuring the keys (CONTACT_INDEX_KEY_PREFIX + contact,
sessionIdKey) are only removed after the transaction successfully commits.
- Line 7: CreateUserWithSocial currently imports and depends on the
infrastructure class SignupSessionCacheRepository; change this to depend on an
outbound port interface (e.g., define SignupSessionPort in the domain or
application API layer) and inject that port into CreateUserWithSocial instead of
SignupSessionCacheRepository. Replace usages of SignupSessionCacheRepository in
CreateUserWithSocial with the corresponding methods on SignupSessionPort, update
constructor/injection to accept SignupSessionPort, and ensure the adapter class
SignupSessionCacheRepository implements SignupSessionPort so wiring remains in
configuration/DI.
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccountTests.java`:
- Around line 85-101: The two tests execute_withLinkedSocial_succeeds and
execute_withSingleSocial_succeeds in UnlinkSocialAccountTests are identical;
remove the duplicate or make the second actually exercise the "single social
account" scenario: update the setup for execute_withSingleSocial_succeeds to
arrange a single linked social (e.g., mock
userSocialQueryRepository.countByUserIdForUpdate(...) to return 1 and ensure
actor has a password) and change assertions to expect any additional behavior
(for example assert that countByUserIdForUpdate(...) is invoked and still that
userSocialRepository.delete(userSocial) is called), or simply delete the
redundant test if no extra behavior is required; keep references to
unlinkSocialAccount, userSocialRepository, and userSocialQueryRepository when
making changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2f1b5940-6414-479d-bc13-f1667231b080
📒 Files selected for processing (13)
src/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSocialController.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/controller/UserSocialControllerSpec.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/dto/CreateUserWithSocialRequestDto.javasrc/main/java/com/dreamteam/alter/adapter/inbound/general/user/dto/UpdatePasswordRequestDto.javasrc/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/SignupSessionCacheRepository.javasrc/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/UserSocialQueryRepositoryImpl.javasrc/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.javasrc/main/java/com/dreamteam/alter/application/user/usecase/LoginWithPassword.javasrc/main/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccount.javasrc/main/java/com/dreamteam/alter/domain/user/port/outbound/UserSocialQueryRepository.javasrc/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.javasrc/test/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccountTests.javasrc/test/java/com/dreamteam/alter/application/user/usecase/UpdatePasswordTests.java
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java (1)
7-7: 🛠️ Refactor suggestion | 🟠 Major애플리케이션 계층의 Redis 접근을 포트로 추상화해 주세요.
StringRedisTemplate직접 의존은 없어졌지만, 지금도CreateUserWithSocial이SignupSessionCacheRepository라는 어댑터 구현체를 직접 주입하고 있습니다. 같은 구현체를SignupSessionCleanupListener도 직접 사용하고 있어서 application 레이어가 adapter 패키지에 컴파일 타임으로 결합됩니다.SignupSessionPort같은 아웃바운드 포트를 경계에 두고, 이 어댑터가 그 포트를 구현하도록 바꾸는 편이 현재 아키텍처 규칙에 맞습니다.As per coding guidelines,
src/main/java/com/dreamteam/alter/adapter/**:Outbound adapters (repositories, external services) implement domain port interfaces.andsrc/main/java/com/dreamteam/alter/application/**:No direct infrastructure dependencies (no Spring Data, no HTTP clients).Also applies to: 46-47
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java` at line 7, Create an outbound port interface (e.g., SignupSessionPort) that defines the Redis/session operations currently used, have the adapter SignupSessionCacheRepository implement that port, and update CreateUserWithSocial and SignupSessionCleanupListener to depend on SignupSessionPort instead of SignupSessionCacheRepository; remove direct imports of the adapter from the application layer and inject the port (constructor) so the adapter remains in the adapter package and only implements the port at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.java`:
- Around line 64-68: 테스트에서 CreateUserWithSocial의 성공 경로를 이벤트 기반으로 갱신하세요:
CreateUserWithSocial 인스턴스에 ApplicationEventPublisher를 목(mock)으로 주입하고, 성공 시
SignupCompletedEvent가 publishEvent(...)로 발행되는지 검증하도록 수정하며
SignupSessionCacheRepository.deleteAll() 호출 검증은 제거합니다; Redis 삭제 로직은 별도
SignupSessionCleanupListener 테스트에서 검증하도록 분리하세요.
---
Duplicate comments:
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java`:
- Line 7: Create an outbound port interface (e.g., SignupSessionPort) that
defines the Redis/session operations currently used, have the adapter
SignupSessionCacheRepository implement that port, and update
CreateUserWithSocial and SignupSessionCleanupListener to depend on
SignupSessionPort instead of SignupSessionCacheRepository; remove direct imports
of the adapter from the application layer and inject the port (constructor) so
the adapter remains in the adapter package and only implements the port at
runtime.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 247451e9-6995-4eaf-9eaf-39f2a8f4ce65
📒 Files selected for processing (5)
src/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/SignupSessionCacheRepository.javasrc/main/java/com/dreamteam/alter/application/user/event/SignupCompletedEvent.javasrc/main/java/com/dreamteam/alter/application/user/event/SignupSessionCleanupListener.javasrc/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.javasrc/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.java
| private static final String KEY_PREFIX = "SIGNUP:PENDING:"; | ||
| private static final String CONTACT_INDEX_KEY_PREFIX = "SIGNUP:CONTACT:"; |
There was a problem hiding this comment.
KEY를 CreateSignupSession.java 에서도 사용중인거같은데
사용하는곳마다 정의하는데 따로 분리해서 해당 키 참조할수있도록 하면 좋을거같습니다
| request.getAuthorizationCode(), | ||
| request.getPlatformType() | ||
| ); | ||
| SocialAuthInfo socialAuthInfo = socialAuthenticationManager.authenticate(socialAuthRequest); |
There was a problem hiding this comment.
트랜잭션 내부에서 외부 API 호출시 DB 커넥션 오래 점유하는 문제가 있을수있으니 트랜잭션 외부로 뺼수있음 좋게습니다
There was a problem hiding this comment.
소셜 플랫폼 통해서 사용자 정보를 조회해와야 해서 트랜잭션 분리는 어렵지 않을까 싶네여
There was a problem hiding this comment.
검증 로직들이 트랜잭션 내부에 있을이유가 있을까요
84번 라인까지 트랜잭션 외부로 뺴고 save()만 트랜잭션 내부로 넣으면 될거같습니다
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java (1)
7-7:⚠️ Potential issue | 🟠 Majoruse case가 adapter 구현체에 직접 의존하고 있습니다.
SignupSessionCacheRepository는adapter.outbound...패키지의 concrete bean이라서, application layer가 인프라 구현을 직접 알아야 하는 구조입니다. 세션 캐시 접근은domain.user.port.outbound쪽 포트로 추상화하고 adapter가 그 포트를 구현하도록 분리해 주세요.As per coding guidelines,
src/main/java/com/dreamteam/alter/application/**:No direct infrastructure dependencies (no Spring Data, no HTTP clients).Also applies to: 29-29
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java` at line 7, CreateUserWithSocial currently imports and depends on the concrete adapter class SignupSessionCacheRepository; replace that direct infra dependency with a domain outbound port: define an interface in domain.user.port.outbound (e.g., SignupSessionCachePort with the needed methods), change CreateUserWithSocial to depend on that interface (constructor/injection and import), and update the adapter SignupSessionCacheRepository to implement the new SignupSessionCachePort; ensure wiring (Spring bean) binds the implementation to the port and update any tests or callers to use the port type instead of the concrete class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateSignupSession.java`:
- Around line 42-68: The current flow around contactIndexKey, existingSessionId,
signupSessionId and sessionKey is non-atomic and allows race conditions when
concurrent CreateSignupSession requests run; change the logic to perform the
read/delete/create/update as an atomic operation in Redis (use a Redis
transaction (MULTI/EXEC) or a Lua script executed via redisTemplate.execute) so
that: check contactIndexKey, delete any existing sessionKey and contactIndexKey,
generate and set the new sessionKey -> contact and contactIndexKey ->
signupSessionId with the same expiration atomically; ensure the Lua script or
transaction references SignupSessionConstants.Session.KEY_PREFIX,
CONTACT_INDEX_KEY_PREFIX, EXPIRATION_MINUTES and uses redisTemplate for
execution so no two requests can create overlapping sessions.
- Around line 42-68: Create an outbound port interface (e.g.,
SignupSessionOutboundPort) and move all direct StringRedisTemplate usage out of
CreateSignupSession: replace calls to redisTemplate.opsForValue().get/delete/set
and references to SignupSessionConstants in CreateSignupSession with port
methods like findSessionIdByContact(String contact), deleteSessionById(String
sessionId), deleteContactIndex(String contact), and createSession(String
sessionId, String contact, long ttlMinutes). Implement the port in an
infrastructure adapter (e.g., RedisSignupSessionAdapter) that contains the
redisTemplate and uses SignupSessionConstants for keys/expiration; inject the
port into CreateSignupSession so the application layer has no direct Spring Data
dependency.
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java`:
- Around line 75-76: In CreateUserWithSocial, protect the business result from
cache cleanup failures by wrapping the
cacheRepository.deleteAll(Arrays.asList(sessionIdKey, contactKey)) calls in a
try/catch (or move them to an async after-commit hook) so any exception from
deleteAll is caught, logged, and swallowed (or retried asynchronously) instead
of being rethrown; ensure the success path after
createUserWithSocialTx.process(...) always returns the intended response and
that only the original business exceptions (e.g., CustomException thrown by
createUserWithSocialTx.process) propagate to the caller.
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTx.java`:
- Around line 19-21: The CreateUserWithSocialTx application service is
registered with an unnamed `@Service` which breaks the project's bean-naming
convention; update the class annotation to a named service (for example change
`@Service` to `@Service`("createUserWithSocialTx")) so the bean name is explicit and
consistent with other use case beans, ensuring dependency injection and bean
identification follow the existing pattern used across classes in the
application layer.
In
`@src/main/java/com/dreamteam/alter/common/constants/SignupSessionConstants.java`:
- Around line 3-12: CreateUser currently duplicates the signup session key
constants; replace the local KEY_PREFIX and CONTACT_INDEX_KEY_PREFIX usages in
CreateUser (the constants and any references within the method(s) around where
they are defined) to use SignupSessionConstants.Session.KEY_PREFIX and
SignupSessionConstants.Session.CONTACT_INDEX_KEY_PREFIX instead, remove the
redundant local constant definitions, and add the appropriate import for
SignupSessionConstants if missing so all signup session key formats are
centralized.
In
`@src/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.java`:
- Line 117: Update the assertions in CreateUserWithSocialTests so we verify the
exact keys passed to cacheRepository.deleteAll instead of anyList(); replace
then(cacheRepository).should().deleteAll(anyList()) at the three occurrences
with either an ArgumentCaptor for List<String> to assert the two expected keys
or with eq(Arrays.asList(expectedKey1, expectedKey2)), where expectedKey1 and
expectedKey2 are built using SignupSessionConstants.Session.KEY_PREFIX combined
with CONTACT_INDEX_KEY_PREFIX (and any session-specific suffix used in the
test); assert equality of the list contents order-insensitively if order is not
guaranteed.
---
Duplicate comments:
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.java`:
- Line 7: CreateUserWithSocial currently imports and depends on the concrete
adapter class SignupSessionCacheRepository; replace that direct infra dependency
with a domain outbound port: define an interface in domain.user.port.outbound
(e.g., SignupSessionCachePort with the needed methods), change
CreateUserWithSocial to depend on that interface (constructor/injection and
import), and update the adapter SignupSessionCacheRepository to implement the
new SignupSessionCachePort; ensure wiring (Spring bean) binds the implementation
to the port and update any tests or callers to use the port type instead of the
concrete class.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: eee92152-05fd-4356-8f0f-23502b679c52
📒 Files selected for processing (5)
src/main/java/com/dreamteam/alter/application/user/usecase/CreateSignupSession.javasrc/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocial.javasrc/main/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTx.javasrc/main/java/com/dreamteam/alter/common/constants/SignupSessionConstants.javasrc/test/java/com/dreamteam/alter/application/user/usecase/CreateUserWithSocialTests.java
- 사용되지 않는 메소드 제거 - import 참조 수정 - 쿼리 개선
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/SignupSessionCacheRepository.java`:
- Around line 9-11: Create a domain port interface named SignupSessionStore
under domain.user.port.outbound and have the outbound adapter
SignupSessionCacheRepository implement that interface; update
SignupSessionCacheRepository signature to "public class
SignupSessionCacheRepository implements SignupSessionStore" (keep `@Repository`
and `@RequiredArgsConstructor`), move any method signatures used by application
layer into the new SignupSessionStore interface, and adjust DI/constructor types
in application layer to depend on SignupSessionStore instead of
SignupSessionCacheRepository so the infrastructure dependency is inverted.
- Around line 27-31: In SignupSessionCacheRepository.deleteAll, calling
keys.isEmpty() without a null check can throw NPE; update the method to first
check for null (e.g., if (keys == null || keys.isEmpty()) return;) before
calling redisTemplate.delete(keys) so null or empty inputs are safely ignored;
ensure the change references the deleteAll method and still invokes
redisTemplate.delete(keys) only when keys is non-null and non-empty.
In
`@src/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/UserSocialQueryRepositoryImpl.java`:
- Around line 27-31: Queries in UserSocialQueryRepositoryImpl (the selectFrom
using qUserSocial and the count query later) are missing the active-user filter,
causing inactive user social links to be included; add the same
UserStatus.ACTIVE predicate used elsewhere by appending
qUserSocial.user.status.eq(UserStatus.ACTIVE) to the where(...) clauses for the
select (the UserSocial query that assigns userSocial) and the corresponding
count query so both retrieval and count use consistent ACTIVE-only filtering.
In `@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUser.java`:
- Around line 5-6: CreateUser currently depends directly on the adapter class
SignupSessionCacheRepository; extract a domain port interface (e.g.,
SignupSessionStore) in the domain/application boundary, have
SignupSessionCacheRepository implement that interface, then change CreateUser to
depend on and inject SignupSessionStore instead of SignupSessionCacheRepository
(remove the adapter import). Also update your DI/Wiring configuration to bind
SignupSessionStore to SignupSessionCacheRepository so the adapter remains the
implementation but the application layer only references the port.
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/CreateUserTx.java`:
- Around line 18-20: CreateUserTx currently lacks the inbound port
implementation and an explicit service bean name; implement the
CreateUserTxUseCase interface on the CreateUserTx class and annotate the class
with a named `@Service` (e.g., `@Service`("createUserTx")) so the application layer
conforms to port-driven design. Add the required process(...) method signature
from CreateUserTxUseCase (GenerateTokenResponseDto process(CreateUserRequestDto
request, String contact, String verifiedEmail)) to CreateUserTx and ensure
constructor injection remains via `@RequiredArgsConstructor`.
In
`@src/main/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccount.java`:
- Line 3: UnlinkSocialAccount currently depends on the inbound adapter DTO
UnlinkSocialAccountRequestDto which breaks the application layer boundary;
refactor by introducing an application-level command or inbound port model
(e.g., UnlinkSocialAccountCommand or
com.dreamteam.alter.application.user.port.inbound.UnlinkSocialAccountRequest)
and change UnlinkSocialAccount to accept that command instead of the adapter
DTO; update the controller/adapter to map incoming UnlinkSocialAccountRequestDto
to the new command before calling UnlinkSocialAccount, and remove the
import/usage of
com.dreamteam.alter.adapter.inbound.general.user.dto.UnlinkSocialAccountRequestDto
from the UnlinkSocialAccount class.
In
`@src/main/java/com/dreamteam/alter/domain/user/port/outbound/UserSocialQueryRepository.java`:
- Line 18: The port method name
UserSocialQueryRepository::countByUserIdForUpdate exposes an infrastructure
detail; rename it to a domain-intent name such as
countLinkedSocialAccountsForUnlink on the UserSocialQueryRepository interface,
update every domain/service call site to use the new method name, and move any
SQL locking (e.g., SELECT ... FOR UPDATE or transactional lock) into the
adapter/implementation class so the port remains an implementation-agnostic
contract; ensure the repository adapter implements the new
countLinkedSocialAccountsForUnlink and performs the necessary DB lock inside its
transactional method.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: bbdaee58-bee8-4160-8a5b-7490e8c086e3
📒 Files selected for processing (6)
src/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/SignupSessionCacheRepository.javasrc/main/java/com/dreamteam/alter/adapter/outbound/user/persistence/UserSocialQueryRepositoryImpl.javasrc/main/java/com/dreamteam/alter/application/user/usecase/CreateUser.javasrc/main/java/com/dreamteam/alter/application/user/usecase/CreateUserTx.javasrc/main/java/com/dreamteam/alter/application/user/usecase/UnlinkSocialAccount.javasrc/main/java/com/dreamteam/alter/domain/user/port/outbound/UserSocialQueryRepository.java
관련 문서
https://www.notion.so/BE-33d86553162880019459d27e67fbd09d?source=copy_link
Summary by CodeRabbit
New Features
Bug Fixes