diff --git a/docs/20260220_삭제닉네임접두사표시정리.md b/docs/20260220_삭제닉네임접두사표시정리.md new file mode 100644 index 00000000..e56b4959 --- /dev/null +++ b/docs/20260220_삭제닉네임접두사표시정리.md @@ -0,0 +1,22 @@ +# 20260220 삭제 닉네임 접두사 표시 정리 + +## 구현 계획 +- [x] 콘텐츠 댓글, 팬톡 응원, 커뮤니티 댓글의 닉네임 표시 흐름(조회/매핑/응답 DTO)을 각각 식별한다. +- [x] 닉네임이 `deleted_`로 시작하는지 판별하고 표시 시 접두사만 제거하는 공통 처리 지점을 설계한다. +- [x] 콘텐츠 댓글 표시 로직에 `deleted_` 접두사 제거 규칙을 적용한다. +- [x] 팬톡 응원 표시 로직에 `deleted_` 접두사 제거 규칙을 적용한다. +- [x] 커뮤니티 댓글 표시 로직에 `deleted_` 접두사 제거 규칙을 적용한다. +- [x] `deleted_` 미포함 닉네임, `deleted_` 포함 닉네임, 접두사만 존재하는 경계 케이스를 기준으로 테스트 케이스를 추가/보강한다. + +## 검증 계획 +- [x] 닉네임 표시에 영향이 있는 테스트를 우선 실행하고 실패 시 원인을 보정한다. +- [x] `./gradlew test`를 실행해 회귀 여부를 확인한다. +- [x] 필요 시 `./gradlew ktlintCheck`로 스타일 규칙 위반 여부를 확인한다. +- [x] `./gradlew build`를 실행해 전체 빌드 성공을 확인한다. + +## 검증 기록 +- [x] 작업 완료 후 검증 결과를 기록한다. + +- 무엇을: `String.removeDeletedNicknamePrefix()` 공통 확장 함수를 추가하고, 콘텐츠 댓글(`AudioContentCommentRepository`), 팬톡 응원(`ExplorerQueryRepository#getCheersList`), 커뮤니티 댓글(`CreatorCommunityCommentRepository`) 응답 닉네임에 동일 규칙을 적용했다. +- 왜: 탈퇴/비활성 사용자 닉네임 저장 정책(`deleted_` 접두사 유지)과 화면 표시 정책(접두사 제거)을 분리해, 사용자에게는 일관된 표시값을 제공하기 위해서다. +- 어떻게 검증했는지: `./gradlew test --tests "kr.co.vividnext.sodalive.extensions.StringExtensionsTest"`, `./gradlew test`, `./gradlew ktlintCheck`, `./gradlew build`를 실행해 모두 `BUILD SUCCESSFUL`을 확인했다. 또한 경계 케이스(`deleted_testUser`, `testUser`, `deleted_`) 단위 테스트를 추가해 기대 출력이 각각 `testUser`, `testUser`, `""`인지 검증했다. diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt index f4e679bf..59f23dd6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt @@ -4,6 +4,7 @@ import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment +import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix import kr.co.vividnext.sodalive.fcm.PushTokenInfo import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken import kr.co.vividnext.sodalive.fcm.QPushTokenInfo @@ -103,8 +104,10 @@ class AudioContentCommentQueryRepositoryImpl( .orderBy(audioContentComment.createdAt.desc()) .fetch() .map { - it.replyCount = commentReplyCountByAudioContentCommentId(it.id) - it + it.copy( + nickname = it.nickname.removeDeletedNicknamePrefix(), + replyCount = commentReplyCountByAudioContentCommentId(it.id) + ) } } @@ -187,6 +190,9 @@ class AudioContentCommentQueryRepositoryImpl( .limit(limit.toLong()) .orderBy(audioContentComment.createdAt.desc()) .fetch() + .map { + it.copy(nickname = it.nickname.removeDeletedNicknamePrefix()) + } } override fun findPushTokenByContentIdAndCommentParentIdMyMemberId( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt index c0c1fae6..f6dace80 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/ExplorerQueryRepository.kt @@ -19,6 +19,7 @@ import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers import kr.co.vividnext.sodalive.explorer.profile.QChannelNotice.channelNotice import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers import kr.co.vividnext.sodalive.explorer.profile.TimeDifferenceResult +import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix import kr.co.vividnext.sodalive.i18n.Lang import kr.co.vividnext.sodalive.i18n.LangContext import kr.co.vividnext.sodalive.i18n.SodaMessageSource @@ -487,7 +488,7 @@ class ExplorerQueryRepository( GetCheersResponseItem( cheersId = it.id!!, memberId = it.member!!.id!!, - nickname = it.member!!.nickname, + nickname = it.member!!.nickname.removeDeletedNicknamePrefix(), profileUrl = if (it.member!!.profileImage != null) { "$cloudFrontHost/${it.member!!.profileImage}" } else { @@ -505,7 +506,7 @@ class ExplorerQueryRepository( GetCheersResponseItem( cheersId = cheers.id!!, memberId = cheers.member!!.id!!, - nickname = cheers.member!!.nickname, + nickname = cheers.member!!.nickname.removeDeletedNicknamePrefix(), profileUrl = if (cheers.member!!.profileImage != null) { "$cloudFrontHost/${cheers.member!!.profileImage}" } else { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/comment/CreatorCommunityCommentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/comment/CreatorCommunityCommentRepository.kt index d6390ff3..f7e12fe1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/comment/CreatorCommunityCommentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/comment/CreatorCommunityCommentRepository.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment +import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix import org.springframework.beans.factory.annotation.Value import org.springframework.data.jpa.repository.JpaRepository import java.time.LocalDateTime @@ -93,8 +94,10 @@ class CreatorCommunityCommentQueryRepositoryImpl( .orderBy(creatorCommunityComment.createdAt.desc()) .fetch() .map { - it.replyCount = commentReplyCountByCommentId(it.id) - it + it.copy( + nickname = it.nickname.removeDeletedNicknamePrefix(), + replyCount = commentReplyCountByCommentId(it.id) + ) } } @@ -174,5 +177,8 @@ class CreatorCommunityCommentQueryRepositoryImpl( .limit(limit) .orderBy(creatorCommunityComment.createdAt.desc()) .fetch() + .map { + it.copy(nickname = it.nickname.removeDeletedNicknamePrefix()) + } } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensions.kt b/src/main/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensions.kt index b609b750..0875e7d4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensions.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensions.kt @@ -5,6 +5,8 @@ import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter +private const val DELETED_NICKNAME_PREFIX = "deleted_" + fun String.convertLocalDateTime(format: String): LocalDateTime { val dateTimeFormatter = DateTimeFormatter.ofPattern(format) return LocalDateTime.parse(this, dateTimeFormatter) @@ -24,3 +26,11 @@ fun String.convertLocalDateTime( .withZoneSameInstant(ZoneId.of("UTC")) .toLocalDateTime() } + +fun String.removeDeletedNicknamePrefix(): String { + return if (startsWith(DELETED_NICKNAME_PREFIX)) { + removePrefix(DELETED_NICKNAME_PREFIX) + } else { + this + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensionsTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensionsTest.kt new file mode 100644 index 00000000..9474f1cb --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensionsTest.kt @@ -0,0 +1,33 @@ +package kr.co.vividnext.sodalive.extensions + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class StringExtensionsTest { + @Test + fun shouldRemoveDeletedPrefixWhenNicknameStartsWithDeletedPrefix() { + val nickname = "deleted_testUser" + + val sanitizedNickname = nickname.removeDeletedNicknamePrefix() + + assertEquals("testUser", sanitizedNickname) + } + + @Test + fun shouldKeepNicknameWhenDeletedPrefixDoesNotExist() { + val nickname = "testUser" + + val sanitizedNickname = nickname.removeDeletedNicknamePrefix() + + assertEquals("testUser", sanitizedNickname) + } + + @Test + fun shouldReturnEmptyStringWhenNicknameContainsOnlyDeletedPrefix() { + val nickname = "deleted_" + + val sanitizedNickname = nickname.removeDeletedNicknamePrefix() + + assertEquals("", sanitizedNickname) + } +}