Skip to content
Merged
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 @@ -2,6 +2,7 @@ package org.yapp.infra.user.repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.yapp.domain.user.ProviderType
import org.yapp.infra.user.entity.UserEntity
import java.util.*
Expand All @@ -15,6 +16,12 @@ interface JpaUserRepository : JpaRepository<UserEntity, UUID> {

fun existsByEmail(email: String): Boolean

@Query("SELECT u FROM UserEntity u WHERE u.providerType = :providerType AND u.providerId = :providerId")
fun findByProviderTypeAndProviderIdIncludingDeleted(providerType: ProviderType, providerId: String): UserEntity?
@Query(
value = "SELECT u.* FROM users u WHERE u.provider_type = :#{#providerType.name()} AND u.provider_id = :providerId",
nativeQuery = true
)
fun findByProviderTypeAndProviderIdIncludingDeleted(
@Param("providerType") providerType: ProviderType,
@Param("providerId") providerId: String
): UserEntity?
Comment on lines +19 to +26
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

리그레션 방지 테스트 추가 제안

경계 케이스 중심의 리포지토리/통합 테스트를 추가해 주세요.

  • soft-deleted와 active가 공존할 때: 정책에 맞는 1건만 반환되는지
  • soft-deleted만 존재할 때: 정상 조회되는지
  • 존재하지 않을 때: null 반환되는지
  • Enum 바인딩(SpEL .name())이 실제 저장 값과 일치하는지

필요하시면 테스트 스켈레톤 초안 제공 가능합니다.

🤖 Prompt for AI Agents
In infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt
around lines 19 to 26, add repository/integration tests to prevent regressions:
create tests that (1) insert both an active and a soft-deleted UserEntity with
the same providerType/providerId and assert that the query returns only the
policy-allowed one, (2) insert only a soft-deleted entity and assert it is
returned, (3) assert null is returned when none exist, and (4) verify the enum
binding used in the query (SpEL .name()) matches the stored DB value by
asserting persisted provider_type equals ProviderType.name() for sample entries;
implement cleanup/transactional isolation for each test.

🧹 Nitpick (assertive)

중복 계정 방지용 데이터 모델 가드레일 권장(인덱스/제약)

애플리케이션 레벨 방어(ORDER BY + 1건 제한)와 별개로, 활성 레코드의 중복 생성을 DDL로 차단하는 것을 권장합니다.

  • Postgres 권장: 부분 유니크 인덱스
    • CREATE UNIQUE INDEX CONCURRENTLY ux_users_provider_active ON users(provider_type, provider_id) WHERE deleted_at IS NULL;
  • MySQL 권장: 생성 칼럼(또는 가상 칼럼) 기반 유니크 인덱스
    • ALTER TABLE users ADD COLUMN is_active TINYINT(1) GENERATED ALWAYS AS (CASE WHEN deleted_at IS NULL THEN 1 ELSE 0 END) STORED;
    • CREATE UNIQUE INDEX ux_users_provider_active ON users(provider_type, provider_id, is_active) WHERE is_active = 1;
      • MySQL은 부분 인덱스를 지원하지 않으므로 WHERE 절은 생략하고 3컬럼 유니크로 대체하는 패턴도 흔합니다.

이런 제약을 두면 동일 provider에 대해 활성 상태 중복이 구조적으로 불가능해져, 본 PR과 같은 문제의 재발을 방지할 수 있습니다.

🤖 Prompt for AI Agents
infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt around
lines 19-26: add a DB-level uniqueness constraint to prevent duplicate active
provider accounts; for Postgres create a partial unique index on (provider_type,
provider_id) where deleted_at IS NULL, and for MySQL add a stored/generated
is_active column (true when deleted_at IS NULL) and a unique index on
(provider_type, provider_id, is_active) or equivalent 3-column unique index,
apply these changes via a new migration, and add/adjust integration tests to
verify attempts to insert duplicate active provider records fail at the database
level.

}