[Feat] Home api#138
Conversation
# Conflicts: # app/src/main/java/com/flint/presentation/login/LoginScreen.kt
📝 WalkthroughWalkthrough홈 화면용 API/DTO/매퍼/레포지토리와 HomeViewModel 기반 상태 흐름이 추가되었고, 도메인 모델의 ID 타입들이 Long에서 String으로 변경되었습니다. UI 콜백 시그니처와 프리뷰 데이터, 네비게이션 시작점 및 TokenInterceptor의 Authorization 헤더가 수정되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Route as HomeRoute
participant VM as HomeViewModel
participant RepoH as HomeRepository
participant RepoC as ContentRepository
participant RepoCol as CollectionRepository
participant APIH as HomeApi
participant APIC as ContentApi
participant APICo as CollectionApi
participant UI as HomeScreen
Route->>VM: collectAsStateWithLifecycle()
Route->>VM: LaunchedEffect -> getRecommendedCollectionList()
Route->>VM: LaunchedEffect -> getBookmarkedContentList()
Route->>VM: LaunchedEffect -> getRecentCollectionList()
VM->>RepoH: getRecommendedCollectionList()
VM->>RepoC: getBookmarkedContentList()
VM->>RepoCol: getRecentCollectionList()
RepoH->>APIH: getRecommendedCollections()
RepoC->>APIC: getBookmarkedContentList()
RepoCol->>APICo: getRecentCollectionList()
APIH-->>RepoH: BaseResponse<RecommendCollectionResponseDto>
APIC-->>RepoC: BaseResponse<BookmarkedContentListResponseDto>
APICo-->>RepoCol: BaseResponse<RecentCollectionListResponseDto>
RepoH-->>VM: Result<List<CollectionModel>>
RepoC-->>VM: Result<List<ContentModel>>
RepoCol-->>VM: Result<List<CollectionModel>>
VM-->>Route: HomeUiState (combined)
Route->>UI: Render HomeScreen with data
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt (1)
78-79:userId타입 불일치 확인됨 - 수정 필요
AuthorModel.userId는String타입이지만,CollectionDetailScreen의userId파라미터는Long타입으로 정의되어 있습니다.CollectionDetailRoute에서userId = 1L로 전달되고 있으며,CollectionDetailScreen의people: ImmutableList<AuthorModel>파라미터와 타입 불일치가 발생합니다. 일관성을 위해CollectionDetailScreen과CollectionDetailRoute의userId파라미터를String타입으로 변경해야 합니다.
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/core/common/di/interceptor/TokenInterceptor.kt`:
- Around line 29-30: The code contains a hardcoded JWT string and a sample
header call that overwrites the real Authorization header; remove the sample
line that sets the literal token and stop hardcoding credentials in
TokenInterceptor (delete the requestBuilder.header("Authorization", "...")
sample), instead obtain the bearer token from a secure source (e.g., an injected
TokenProvider/getAuthToken(), encrypted storage, or BuildConfig for
non-sensitive dev flags) and set it once via
requestBuilder.header("Authorization", "Bearer " + token) inside
TokenInterceptor.intercept; also ensure conditional logic checks for a non-empty
token before adding the header so you don't overwrite valid headers, and if the
exposed token was real, revoke it and rotate credentials and consider cleaning
git history.
In `@app/src/main/java/com/flint/domain/mapper/content/ContentMapper.kt`:
- Around line 9-18: In BookmarkedContentListResponseDto.toModel(), avoid calling
OttType.valueOf(ottSimple.ottName) directly — make the ottName -> OttType
conversion exception-safe by replacing the valueOf usage in the ottSimpleList
mapping (inside toModel) with a safe lookup that returns a sensible default or
null instead of throwing (e.g., find matching enum by name or use
try/catch/runCatching and fallback to OttType.UNKNOWN or filter out invalid
entries), and ensure the resulting list type (for ContentModel.ottSimpleList) is
adjusted accordingly.
In `@app/src/main/java/com/flint/presentation/home/HomeViewModel.kt`:
- Around line 39-43: The combine lambda parameter names (userInfo, recommended,
favorite) do not match the HomeUiState fields they’re assigned to; update the
lambda parameters and/or assignments so each param clearly corresponds to its
target field (e.g., rename parameters to userInfoLoadState,
recommendedCollectionListLoadState, bookmarkedContentListLoadState or rename to
recommendedLoadState/bookmarkedLoadState/recentLoadState) and then assign them
to HomeUiState(recommendedCollectionListLoadState =
recommendedCollectionListLoadState, bookmarkedContentListLoadState =
bookmarkedContentListLoadState, recentCollectionListLoadState =
recentCollectionListLoadState) so names are consistent with HomeUiState and
improve readability.
- Around line 56-81: The current ViewModel methods
(getRecommendedCollectionList, getBookmarkedContentList,
getRecentCollectionList) only log failures and never update the corresponding
state flows, causing the UI to remain in Loading; update each repository call so
its onFailure handler emits an error state to the appropriate MutableStateFlow
(emit UiState.Error with the exception or its message to
_recommendCollectionListLoadState, _bookmarkedContentListLoadState, and
_recentCollectionListLoadState) and add a missing onFailure handler for
getBookmarkedContentList to mirror the others.
🧹 Nitpick comments (12)
app/src/main/java/com/flint/presentation/main/MainNavigator.kt (1)
35-35: 시작 목적지 변경에 따른 초기 플로우 보장 여부를 확인해주세요.Line 35에서 Home으로 시작점을 바꾸면서 스플래시/인증/온보딩 게이트가 여전히 다른 경로에서 보장되는지 확인이 필요합니다. 그렇지 않다면 앱 상태(인증/온보딩 여부)에 따라 startDestination을 결정하도록 분기하는 구성을 권장합니다.
app/src/main/java/com/flint/core/designsystem/component/listItem/CollectionItem.kt (1)
52-56: thumbnailUrl 공백 대비 fallback을 고려해주세요.
백엔드/레거시 데이터에서 thumbnailUrl이 비어있을 경우 이미지를 유지하려면 기존 collectionImageUrl로 대체하는 처리가 안전합니다.♻️ 제안하는 수정
- NetworkImage( - imageUrl = collectionModel.thumbnailUrl, + val imageUrl = + if (collectionModel.thumbnailUrl.isNotBlank()) { + collectionModel.thumbnailUrl + } else { + collectionModel.collectionImageUrl + } + NetworkImage( + imageUrl = imageUrl, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize(), )app/src/main/java/com/flint/data/dto/content/response/BookmarkedContentListResponseDto.kt (1)
12-21:getOttSimpleListJSON 키/누락 여부 확인백엔드에서 키명이 다르거나 필드가 누락되면 kotlinx.serialization 역직렬화가 실패할 수 있습니다. 스펙 확인 후 누락 가능성이 있으면 기본값/nullable 처리를 고려해주세요.
♻️ 제안 변경
- val getOttSimpleList: List<OttSimpleResponseDto> + val getOttSimpleList: List<OttSimpleResponseDto> = emptyList()app/src/main/java/com/flint/data/dto/home/response/RecommendCollectionResponseDto.kt (1)
20-33:imageList누락 가능성 확인백엔드가
imageList를 누락하거나 null로 내려보낼 수 있다면 역직렬화 실패 가능성이 있습니다. 스펙 확인 후 기본값/nullable 처리를 검토해주세요.♻️ 제안 변경
- val imageList: List<String>, + val imageList: List<String> = emptyList(),app/src/main/java/com/flint/domain/model/collection/CollectionModel.kt (1)
8-18:thumbnailUrl와collectionImageUrl역할 분리 필요이미지 필드가 2개라 사용처 혼동 가능성이 있습니다. 한쪽을 명확히 용도 주석/네이밍(또는 deprecate)로 정리하면 유지보수성이 좋아집니다.
app/src/main/java/com/flint/domain/model/content/ContentModel.kt (1)
7-18:bookmarkId기본값 0은 상태 구분에 모호비북마크 상태를 0으로 표현하면 실제 ID와 충돌할 수 있습니다.
Long?로 명확히 구분하는 편이 안전합니다.♻️ 제안 변경
- val bookmarkId: Long = 0, + val bookmarkId: Long? = null,app/src/main/java/com/flint/presentation/home/uiState/HomeUiState.kt (1)
7-28: 상태 집계 로직이 잘 구현되었습니다.세 개의 로드 상태를 하나의
loadState로 집계하는 로직이 명확합니다. 현재 구현은 모든 데이터가 성공적으로 로드되어야Success를 반환하므로, 홈 화면에서 모든 섹션이 필요한 경우에 적합합니다.향후 부분적 성공(일부 섹션만 로드 성공)을 지원해야 한다면, 각 섹션별로 독립적인 로딩/에러 처리를 고려해볼 수 있습니다:
💡 부분적 성공 처리 예시 (선택사항)
// 개별 섹션의 성공 데이터를 추출하는 헬퍼 프로퍼티 추가 val recommendedCollections: List<CollectionModel>? get() = (recommendedCollectionListLoadState as? UiState.Success)?.data val bookmarkedContents: List<ContentModel>? get() = (bookmarkedContentListLoadState as? UiState.Success)?.data val recentCollections: List<CollectionModel>? get() = (recentCollectionListLoadState as? UiState.Success)?.dataapp/src/main/java/com/flint/data/api/SearchApi.kt (1)
9-14: LGTM!Retrofit API 엔드포인트가 올바르게 정의되었습니다. 커서 기반 페이지네이션과 검색 키워드를 지원합니다.
size파라미터에 기본값을 추가하면 호출 측에서 매번 지정하지 않아도 되어 편리할 수 있습니다:💡 기본값 추가 예시
`@GET`("api/v1/search/bookmarked-contents") suspend fun getBookmarkedContentList( `@Query`("keyword") keyword: String, `@Query`("cursor") cursor: Int, - `@Query`("size") size: Int + `@Query`("size") size: Int = 20 ) : BaseResponse<SearchBookmarkedContentsResponseDto>app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt (1)
8-44: 코드 중복 고려.
RecommendCollectionResponseDto.toModel()과RecentCollectionListResponseDto.toModel()함수의 매핑 로직이 거의 동일합니다. DTO 타입이 다르기 때문에 현재 구현도 괜찮지만, 향후 공통 인터페이스나 헬퍼 함수를 통해 중복을 줄일 수 있습니다.♻️ 선택적 리팩토링 제안
공통 매핑 로직을 private 헬퍼 함수로 추출할 수 있습니다:
private fun mapToCollectionModel( id: String, thumbnailUrl: String, title: String, description: String, imageList: List<String>, bookmarkCount: Int, isBookmarked: Boolean, userId: String, nickname: String, profileUrl: String? ) = CollectionModel( collectionId = id, thumbnailUrl = thumbnailUrl, collectionTitle = title, description = description, imageList = imageList, bookmarkCount = bookmarkCount, isBookmarked = isBookmarked, author = AuthorModel( userId = userId, nickname = nickname, profileUrl = profileUrl ?: "" ) )app/src/main/java/com/flint/presentation/home/HomeViewModel.kt (1)
24-25: 사용되지 않는 의존성.
preferencesManager가 주입되었지만 현재 사용되지 않습니다. 향후 사용 예정이라면 TODO 주석을 추가하거나, 불필요하다면 제거해 주세요.app/src/main/java/com/flint/presentation/home/HomeScreen.kt (2)
35-36: 사용되지 않는 import.
delay와launch가 import되었지만 사용되지 않습니다.🔧 수정 제안
-import kotlinx.coroutines.delay -import kotlinx.coroutines.launch
55-87: Loading 및 Error 상태에 대한 UI 처리가 없습니다.현재
else -> {}로 Loading과 Error 상태를 무시하고 있어, 사용자가 로딩 중이거나 에러 발생 시 빈 화면을 보게 됩니다. PR 설명에 따르면 다른 UI 로직은 별도 이슈로 처리한다고 하니, TODO 주석을 추가하거나 최소한의 로딩 인디케이터를 고려해 주세요.♻️ 개선 제안
when (uiState.loadState) { is UiState.Success -> { // ... existing success handling } - else -> {} + is UiState.Loading -> { + // TODO: 로딩 인디케이터 추가 + Box( + modifier = Modifier.fillMaxSize().padding(paddingValues), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + is UiState.Error -> { + // TODO: 에러 UI 추가 + } }
| // sample | ||
| requestBuilder.header("Authorization", "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOjgwMDAyMTcxMTM1NzExODE5MSwicm9sZSI6IkZMSU5HIiwidHlwZSI6IkFDQ0VTUyIsImlhdCI6MTc2ODgzODU0MCwiZXhwIjoxNzcwMDQ4MTQwfQ.A2XYyu24IoGAXNSQHJ1S-iudWmg8II2_ivI4EdyyWw9KS9oJlxHKOAhcKrsLpLkc9kllZyxwaTJO1t4vI7oZlg") |
There was a problem hiding this comment.
🚨 보안 취약점: 하드코딩된 JWT 토큰이 소스 코드에 노출되었습니다.
이 코드에는 두 가지 심각한 문제가 있습니다:
-
자격 증명 유출: 실제 JWT 토큰이 코드베이스에 커밋되었습니다. 이 토큰에는
userId,role등 민감한 정보가 포함되어 있으며, 만료 시간이 아직 유효합니다. 저장소에 접근할 수 있는 누구나 이 토큰을 악용할 수 있습니다. -
로직 오류:
header()메서드는 동일한 헤더 이름에 대해 값을 덮어씁니다. 따라서 26-28번 라인에서 설정한 정상적인 Bearer 토큰이 30번 라인의 샘플 토큰으로 항상 덮어써져서, 조건부 로직이 무의미해집니다.
이 디버그/개발용 코드는 병합 전에 반드시 제거해야 합니다.
🔐 수정 제안
if (accessToken.isNotEmpty()) {
requestBuilder.header("Authorization", "Bearer $accessToken")
}
- // sample
- requestBuilder.header("Authorization", "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOjgwMDAyMTcxMTM1NzExODE5MSwicm9sZSI6IkZMSU5HIiwidHlwZSI6IkFDQ0VTUyIsImlhdCI6MTc2ODgzODU0MCwiZXhwIjoxNzcwMDQ4MTQwfQ.A2XYyu24IoGAXNSQHJ1S-iudWmg8II2_ivI4EdyyWw9KS9oJlxHKOAhcKrsLpLkc9kllZyxwaTJO1t4vI7oZlg")추가 조치: 이 토큰이 프로덕션 환경에서 사용 중인 실제 토큰이라면, 즉시 해당 토큰을 무효화(revoke)하고 새 토큰을 발급받아야 합니다. Git 히스토리에도 이 토큰이 남아있으므로, 필요시 히스토리 정리도 고려하세요.
🧰 Tools
🪛 Gitleaks (8.30.0)
[high] 30-30: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/core/common/di/interceptor/TokenInterceptor.kt`
around lines 29 - 30, The code contains a hardcoded JWT string and a sample
header call that overwrites the real Authorization header; remove the sample
line that sets the literal token and stop hardcoding credentials in
TokenInterceptor (delete the requestBuilder.header("Authorization", "...")
sample), instead obtain the bearer token from a secure source (e.g., an injected
TokenProvider/getAuthToken(), encrypted storage, or BuildConfig for
non-sensitive dev flags) and set it once via
requestBuilder.header("Authorization", "Bearer " + token) inside
TokenInterceptor.intercept; also ensure conditional logic checks for a non-empty
token before adding the header so you don't overwrite valid headers, and if the
exposed token was real, revoke it and rotate credentials and consider cleaning
git history.
| fun getRecommendedCollectionList() = viewModelScope.launch { | ||
| homeRepository.getRecommendedCollectionList() | ||
| .onSuccess { | ||
| _recommendCollectionListLoadState.emit(UiState.Success(it)) | ||
| } | ||
| .onFailure { | ||
| Log.d("Logd", it.message.toString()) | ||
| } | ||
| } | ||
|
|
||
| fun getBookmarkedContentList() = viewModelScope.launch { | ||
| contentRepository.getBookmarkedContentList() | ||
| .onSuccess { | ||
| _bookmarkedContentListLoadState.emit(UiState.Success(it)) | ||
| } | ||
| } | ||
|
|
||
| fun getRecentCollectionList() = viewModelScope.launch { | ||
| collectionRepository.getRecentCollectionList() | ||
| .onSuccess { | ||
| _recentCollectionListLoadState.emit(UiState.Success(it)) | ||
| } | ||
| .onFailure { | ||
| Log.d("Logd", it.message.toString()) | ||
| } | ||
| } |
There was a problem hiding this comment.
실패 시 에러 상태가 UI에 전달되지 않습니다.
onFailure에서 로그만 남기고 에러 상태를 emit하지 않아, 실패 시 UI가 Loading 상태에 머무릅니다. 또한 getBookmarkedContentList()는 onFailure 핸들러가 없습니다.
🔧 수정 제안
fun getRecommendedCollectionList() = viewModelScope.launch {
homeRepository.getRecommendedCollectionList()
.onSuccess {
_recommendCollectionListLoadState.emit(UiState.Success(it))
}
.onFailure {
- Log.d("Logd", it.message.toString())
+ _recommendCollectionListLoadState.emit(UiState.Error(it))
}
}
fun getBookmarkedContentList() = viewModelScope.launch {
contentRepository.getBookmarkedContentList()
.onSuccess {
_bookmarkedContentListLoadState.emit(UiState.Success(it))
}
+ .onFailure {
+ _bookmarkedContentListLoadState.emit(UiState.Error(it))
+ }
}
fun getRecentCollectionList() = viewModelScope.launch {
collectionRepository.getRecentCollectionList()
.onSuccess {
_recentCollectionListLoadState.emit(UiState.Success(it))
}
.onFailure {
- Log.d("Logd", it.message.toString())
+ _recentCollectionListLoadState.emit(UiState.Error(it))
}
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/home/HomeViewModel.kt` around lines
56 - 81, The current ViewModel methods (getRecommendedCollectionList,
getBookmarkedContentList, getRecentCollectionList) only log failures and never
update the corresponding state flows, causing the UI to remain in Loading;
update each repository call so its onFailure handler emits an error state to the
appropriate MutableStateFlow (emit UiState.Error with the exception or its
message to _recommendCollectionListLoadState, _bookmarkedContentListLoadState,
and _recentCollectionListLoadState) and add a missing onFailure handler for
getBookmarkedContentList to mirror the others.
| .onSuccess { | ||
| _bookmarkedContentListLoadState.emit(UiState.Success(it)) | ||
| } |
There was a problem hiding this comment.
p3
이거 여기서만 .onFailure 없는 이유 있나요?
There was a problem hiding this comment.
다른곳은 테스트 용도로 추가했었는데 여기는 깜빡했네여. 추가하겠습니다~
📮 관련 이슈
📌 작업 내용
📸 스크린샷
😅 미구현
🫛 To. 리뷰어
일단 급하게 Dto랑 Model 형식 맞추기 위해서 PR 올립니다.
웹뷰, 바텀시트 등 기타 로직은 별도 Issue에서 진행하겠습니다
Summary by CodeRabbit
새 기능
리팩터링
기타
✏️ Tip: You can customize this high-level summary in your review settings.