Skip to content

찌르기 쿨타임 처리 수정#171

Open
chanho0908 wants to merge 15 commits into
developfrom
fix/#170-photolog-poke-cooldown
Open

찌르기 쿨타임 처리 수정#171
chanho0908 wants to merge 15 commits into
developfrom
fix/#170-photolog-poke-cooldown

Conversation

@chanho0908

@chanho0908 chanho0908 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

이슈 번호

#170

작업내용

  • 찌르기 쿨타임 중 인증샷 상세 화면에서 토스트가 표시되지 않는 문제, 시간 경과 후 자동으로 해제되도록 수정
  • 찌르기 쿨타임 저장 기준을 goalId 단독에서 goalId + targetDate 복합키로 변경해, 같은 목표라도 날짜별로 독립적인 쿨타임을 갖도록 수정
  • Room poke_history 스키마 변경에 따라 DB version을 2로 올리고 MIGRATION_1_2를 추가
    • 기존 poke_history는 날짜 정보가 없는 로컬 쿨타임 캐시라 drop/recreate 처리
  • 홈 화면과 인증샷 상세 화면의 찌르기 호출부에서 각각 선택 날짜/상세 날짜를 전달하도록 수정

결과물

123.mp4

리뷰어에게 추가로 요구하는 사항 (선택)

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

이 PR은 포크(찌르기) 기능의 쿨다운 정책을 전면 변경합니다. 기존에는 같은 목표에 대해 전역 쿨다운이 적용되었지만, 이제는 같은 목표라도 날짜가 다르면 독립적인 쿨다운이 적용됩니다. 이를 위해 PokeHistoryEntity의 기본키를 (goalId, targetDate) 복합키로 재정의하고 데이터베이스 마이그레이션을 추가한 뒤, 저장소·UseCase·ViewModel·UI 전계층을 일관성 있게 업데이트합니다. 또한 ViewModel의 쿨다운 처리를 Job 기반 타이머로 재구성하고, 새로운 테스트를 추가하여 날짜별 독립 쿨다운 동작을 검증합니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


주요 변경 사항 분석

💾 데이터 계층: 복합 기본키 도입

변경 사항:

  • PokeHistoryEntity@PrimaryKey가 필드 레벨에서 엔터티 레벨 primaryKeys = ["goalId", "targetDate"]로 이동
  • 데이터베이스 버전 1 → 2, 마이그레이션 SQL로 테이블 재구성
  • PokeHistoryDao.findByGoalId()findPokeHistoryEntity(goalId, targetDate) 변경

검토 포인트:

  • ✅ 마이그레이션 SQL이 기존 데이터를 안전하게 처리하는지 확인하셨나요? 현재 구현은 테이블을 DROP한 뒤 재생성하므로, 기존 데이터 보존이 필요하다면 ALTER TABLE 기반 마이그레이션을 고려해야 합니다.
  • ✅ 복합 기본키 설정 시 필드 순서(goalId, targetDate)가 의도한 쿼리 성능(예: goalId 단독 조회)에 미치는 영향을 검토하셨나요?

🏗️ 저장소 및 UseCase: 날짜 파라미터 전파

변경 사항:

  • PokeRepository.findPokeHistory(goalId, targetDate) → 특정 날짜의 이력만 반환
  • PokeRepository.savePokeHistory(goalId, targetDate, pokedAt) → 날짜별 이력 저장
  • PokeGoalUseCase.invoke(goalId, targetDate) → 날짜별 독립 쿨다운 판정

검토 포인트:

  • ✅ 쿨다운 시간 계산 로직이 모두 동일한 targetDate를 기준으로 수행되고 있는지 확인하셨나요? 혼동을 방지하기 위해 메서드 주석에 "targetDate 기준 쿨다운" 명시를 권장합니다.
  • ✅ FakePokeRepository의 PokeHistoryKey 도입이 기존 테스트와 호환되는지 검증했나요?

⏲️ ViewModel: Job 기반 타이머 재설계

변경 사항:

  • pokeCooldownJob 프로퍼티 추가로 이전 타이머 작업 취소 가능
  • startPokeCooldown(remaining) / clearPokeCooldown() 메서드로 일원화
  • 포크 호출 시 isPoking = true 직접 리듀스로 변경

검토 포인트:

  • ✅ 포크 중(isPoking = true)에 사용자가 다시 포크 버튼을 탭할 경우 동작이 명확한지 확인하셨나요? 현재는 !uiState.isPoking으로만 필터링하는데, 네트워크 지연 중 중복 요청을 방지하는 로직이 있는지 검토 권장합니다.
  • delay(remainingMs) 중에 화면이 닫히거나 ViewModel이 cleared된다면 Job 취소가 정상 작동하나요? 타이머 취소 시점을 명확히 기술한 주석 추가를 권장합니다.

🎨 UI 컴포넌트: 파라미터 단순화

변경 사항:

  • PhotologCardContent에서 isPokeDisabled 파라미터 제거
  • 포크 버튼 활성화 조건을 !uiState.isPoking으로 통일

검토 포인트:

  • isPokeDisabled 제거로 이전에 비활성화되던 다른 사유(예: 권한 부족, 로딩 실패)들이 더 이상 고려되지 않는 건 아닐까요? 필요시 isPoking 외 추가 플래그가 필요한지 검토 권장합니다.

✅ 테스트: 기능 검증 강화

변경 사항:

  • PokeGoalUseCaseTest에 새로운 두 가지 시나리오 추가:
    • 같은 목표, 다른 날짜 → 독립적 remainingCooldown 계산
    • 한 날짜 쿨다운 → 다른 날짜 포크 요청 차단 안 함
  • PhotologDetailViewModelTest 신규 작성

검토 포인트:

  • ✅ 매우 좋은 시나리오 커버입니다! 다만 다음의 엣지 케이스도 고려할 가치가 있을까요?
    • targetDate 형식 불일치(예: "2026-06-01" vs "2026-6-1") → 쿨다운 판정 오류
    • 자정 기준 날짜 변경 중 포크 중복 요청
    • 극한 지연 환경에서 타이머 정확도 저하

최종 의견

강점:

  • 📊 계층별 책임 분리가 명확하며, 데이터 → 저장소 → UseCase → UI 순서로 일관되게 설계되었습니다.
  • 🧪 새로운 테스트 케이스로 날짜별 독립 쿨다운의 핵심 동작을 잘 검증합니다.
  • 🔧 Job 기반 타이머로 여러 동시 포크 요청에 대한 상태 관리가 개선되었습니다.

개선 권장:

  • 📝 메서드 주석에 targetDate의 의미(예: UTC 기준, ISO 8601 형식 의무)를 명시하여 개발자 간 혼동을 방지하세요.
  • 🛡️ delay(remainingMs) 중 ViewModel 소멸 시 Job 정리 로직을 onCleared()에서 명시적으로 호출하는지 확인하세요.
  • 🧪 targetDate 형식 불일치 시나리오에 대한 통합 테스트 또는 입력 검증 로직 추가를 검토하세요.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed PR 제목이 주요 변경사항을 명확히 요약하고 있습니다. 쿨타임 처리 수정이라는 핵심 목표와 변경내용이 일치합니다.
Description check ✅ Passed PR 설명이 변경사항과 직접 관련되어 있으며, 작업 내용, 결과물, 데이터베이스 마이그레이션, 쿨타임 로직 변경 등을 구체적으로 기술하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/#170-photolog-poke-cooldown

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chanho0908 chanho0908 self-assigned this Jun 7, 2026
@chanho0908 chanho0908 added the Refactor Good for newcomers label Jun 7, 2026
@chanho0908 chanho0908 changed the title 인증샷 상세 찌르기 쿨타임 처리 수정 찌르기 쿨타임 처리 수정 Jun 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
domain/src/main/java/com/twix/domain/usecase/PokeGoalUseCase.kt (1)

31-35: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

시스템 시간 변경 시 쿨타임이 설정값(3시간)을 초과할 수 있습니다.

현재는 pokedAtcurrentTime보다 미래면 elapsedMs가 음수가 되어 remainingMsCOOLDOWN_MS보다 커질 수 있습니다. 사용자 입장에서는 쿨타임이 비정상적으로 길어집니다. remainingMs0..COOLDOWN_MS 범위로 고정해 주세요.

수정 예시
-        val elapsedMs = currentTime - pokedAt
-        val remainingMs = COOLDOWN_MS - elapsedMs
-
-        return remainingMs.coerceAtLeast(0L)
+        val elapsedMs = (currentTime - pokedAt).coerceAtLeast(0L)
+        val remainingMs = COOLDOWN_MS - elapsedMs
+
+        return remainingMs.coerceIn(0L, COOLDOWN_MS)
🤖 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/src/main/java/com/twix/domain/usecase/PokeGoalUseCase.kt` around lines
31 - 35, The current remainingMs calculation can exceed COOLDOWN_MS if pokedAt
is in the future; modify the return in PokeGoalUseCase (where currentTime,
elapsedMs and remainingMs are computed) to clamp remainingMs into the
0..COOLDOWN_MS range—e.g. replace remainingMs.coerceAtLeast(0L) with
remainingMs.coerceIn(0L, COOLDOWN_MS) so the returned cooldown is never negative
or greater than the configured COOLDOWN_MS.
🧹 Nitpick comments (1)
feature/photolog/detail/src/test/java/com/twix/photolog/detail/PhotologDetailViewModelTest.kt (1)

67-88: ⚡ Quick win

Sad Path 테스트가 빠져 있어 회귀를 놓칠 수 있습니다.

현재 케이스는 쿨타임 정상 흐름 검증은 좋지만, pokeGoalResult = AppResult.Error(...) 상황에서 isPoking 복구와 실패 후 상태를 검증하지 않아 회귀 여지가 남습니다.
에러 경로 테스트 1건을 추가해 dispatch(PhotologDetailIntent.Poke) 이후 isPoking == false 및 쿨타임 상태/사이드이펙트 기대값을 함께 확인해보실까요?

As per coding guidelines, "Happy / Sad Path가 모두 커버되는가?" 항목에 따라 실패 경로 검증을 포함하는 것이 좋습니다.

🤖 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
`@feature/photolog/detail/src/test/java/com/twix/photolog/detail/PhotologDetailViewModelTest.kt`
around lines 67 - 88, Add a sad-path unit test that mirrors the existing
cooldown test but configures FakePokeRepository to return AppResult.Error for
the poke call, then dispatch PhotologDetailIntent.Poke and advance the test
dispatcher (runCurrent/runTest); assert that viewModel.uiState.value.isPoking is
false after the failure, verify pokeCooldownRemaining and isPokeDisabled reflect
the expected post-error state, and check pokeRepository.pokeGoalCallCount (and
any error-handling side-effects) to ensure the failure path is covered; use the
same helpers (createViewModel, FakePokeRepository,
runTest/StandardTestDispatcher, PhotologDetailIntent.Poke) so the new test
parallels the happy-path case.

Source: Coding guidelines

🤖 Prompt for all review comments with 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.

Inline comments:
In
`@feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailViewModel.kt`:
- Around line 175-179: The pokeToPartner() coroutine sets isPoking = true then
calls pokeGoalUseCase.invoke(...) but if that call throws the ViewModel can
remain stuck; wrap the invoke + handlePokeResult call in a try/catch inside
pokeToPartner(), on exception call handlePokeError(exception) and ensure
isPoking is cleared (reduce { copy(isPoking = false) }) in both success and
error paths; rethrow CancellationException (if exception is
CancellationException) so cancellations propagate normally.

---

Outside diff comments:
In `@domain/src/main/java/com/twix/domain/usecase/PokeGoalUseCase.kt`:
- Around line 31-35: The current remainingMs calculation can exceed COOLDOWN_MS
if pokedAt is in the future; modify the return in PokeGoalUseCase (where
currentTime, elapsedMs and remainingMs are computed) to clamp remainingMs into
the 0..COOLDOWN_MS range—e.g. replace remainingMs.coerceAtLeast(0L) with
remainingMs.coerceIn(0L, COOLDOWN_MS) so the returned cooldown is never negative
or greater than the configured COOLDOWN_MS.

---

Nitpick comments:
In
`@feature/photolog/detail/src/test/java/com/twix/photolog/detail/PhotologDetailViewModelTest.kt`:
- Around line 67-88: Add a sad-path unit test that mirrors the existing cooldown
test but configures FakePokeRepository to return AppResult.Error for the poke
call, then dispatch PhotologDetailIntent.Poke and advance the test dispatcher
(runCurrent/runTest); assert that viewModel.uiState.value.isPoking is false
after the failure, verify pokeCooldownRemaining and isPokeDisabled reflect the
expected post-error state, and check pokeRepository.pokeGoalCallCount (and any
error-handling side-effects) to ensure the failure path is covered; use the same
helpers (createViewModel, FakePokeRepository, runTest/StandardTestDispatcher,
PhotologDetailIntent.Poke) so the new test parallels the happy-path case.
🪄 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.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 668257ad-95ce-406a-a7cd-339c9c5f44fa

📥 Commits

Reviewing files that changed from the base of the PR and between dbbd63a and cfc3b1a.

📒 Files selected for processing (15)
  • .github/PULL_REQUEST_TEMPLATE.md
  • core/database/src/main/java/com/twix/database/TwixDatabase.kt
  • core/database/src/main/java/com/twix/database/di/DatabaseModule.kt
  • core/database/src/main/java/com/twix/database/poke/PokeHistoryDao.kt
  • core/database/src/main/java/com/twix/database/poke/PokeHistoryEntity.kt
  • data/src/main/java/com/twix/data/repository/DefaultPokeRepository.kt
  • domain/src/main/java/com/twix/domain/repository/PokeRepository.kt
  • domain/src/main/java/com/twix/domain/usecase/PokeGoalUseCase.kt
  • domain/src/test/java/com/twix/domain/fake/FakePokeRepository.kt
  • domain/src/test/java/com/twix/domain/usecase/PokeGoalUseCaseTest.kt
  • feature/main/src/main/java/com/twix/home/HomeViewModel.kt
  • feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailScreen.kt
  • feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailViewModel.kt
  • feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologCardContent.kt
  • feature/photolog/detail/src/test/java/com/twix/photolog/detail/PhotologDetailViewModelTest.kt
💤 Files with no reviewable changes (2)
  • .github/PULL_REQUEST_TEMPLATE.md
  • feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailScreen.kt

@chanho0908 chanho0908 requested a review from dogmania June 7, 2026 00:59

@dogmania dogmania left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

고생하셨습니다!

onClickUpload
} else {
{ if (!isPokeDisabled) onPoke() }
{ if (!uiState.isPoking) onPoke() }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PhotologDetailUiState에 선언되어 있는 isPokeDisabled를 사용하는 건 어떨까요? 지금 isPoking만 바라보고 있으면 쿨다운 상황에서도 클릭 이벤트 자체는 발생할 것 같습니다.

Comment on lines 175 to +178
private fun pokeToPartner() {
viewModelScope.launch {
startPokeLoading()
handlePokeResult(pokeGoalUseCase.invoke(argGoalId))
reduce { copy(isPoking = true) }
handlePokeResult(pokeGoalUseCase.invoke(argGoalId, argTargetDate.toString()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

메서드 초반에 isPoking이나 isPokeDisabled 활용해서 가드 로직을 추가하면 좋을 거 같아요

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactor Good for newcomers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants