[Feat] Storage Presigned URL API 정의#205
Hidden character warning
Conversation
- Profile → Terms → Content 순서로 온보딩 흐름 변경 - OnboardingTermsScreen 약관 항목 확장 영역 UI 개선 (구분선 full-width, 설명 영역 배경 분리) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 Walkthrough전체 요약프로필 이미지 선택 기능을 갖춘 온보딩 약관 스크린을 추가하고, Presigned URL을 요청할 수 있는 저장소 API 계층을 구현합니다. 온보딩 플로우는 프로필 → 약관 → 콘텐츠 순서로 확장됩니다. 변경 사항Presigned URL 저장소 API 계층
온보딩 플로우 확장
관련 PR
제안 라벨
제안 검수자
리뷰 예상 난이도🎯 3 (Moderate) | ⏱️ ~25 minutes 이 PR은 새로운 저장소 API 계층(데이터 흐름, 도메인 매핑, 의존성 주입)과 새로운 온보딩 약관 스크린(UI 상태 관리, 라우팅 통합) 및 프로필 이미지 선택 기능을 포함하여 여러 계층에 걸친 일관된 변경사항들을 다룹니다. 각 부분의 논리가 명확하지만 파일 수와 기능 범위로 인해 중간 정도의 검토 노력이 필요합니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
app/src/main/java/com/flint/domain/repository/StorageRepository.kt (1)
21-22: ⚡ Quick win
enum.name직접 전송 대신 wire value를 명시적으로 분리해 주세요.Line 21, Line 22처럼
name에 의존하면 enum 리네임만으로 서버 계약이 깨질 수 있습니다. API 전송값은 enum 식별자와 분리해 두는 편이 안전합니다.변경 제안
// app/src/main/java/com/flint/domain/type/FileExtension.kt -enum class FileExtension { - JPG, - JPEG, - PNG, - GIF, - WEBP, - SVG, - PDF, +enum class FileExtension(val wireValue: String) { + JPG("JPG"), + JPEG("JPEG"), + PNG("PNG"), + GIF("GIF"), + WEBP("WEBP"), + SVG("SVG"), + PDF("PDF"), } // app/src/main/java/com/flint/domain/type/StoragePathType.kt -enum class StoragePathType { - USER_PROFILE, - LOGO_IMAGE, - COLLECTION_THUMBNAIL, - COLLECTION_CONTENT, +enum class StoragePathType(val wireValue: String) { + USER_PROFILE("USER_PROFILE"), + LOGO_IMAGE("LOGO_IMAGE"), + COLLECTION_THUMBNAIL("COLLECTION_THUMBNAIL"), + COLLECTION_CONTENT("COLLECTION_CONTENT"), } // app/src/main/java/com/flint/domain/repository/StorageRepository.kt api.getPresignedUrl( - pathType = pathType.name, - extension = extension.name, + pathType = pathType.wireValue, + extension = extension.wireValue, ).data.toModel()🤖 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 `@app/src/main/java/com/flint/domain/repository/StorageRepository.kt` around lines 21 - 22, Don't send enum.name directly; instead send explicit wire values for the enums used when building the payload in StorageRepository (currently where pathType = pathType.name and extension = extension.name). Replace those usages to map the enums to stable wire strings (e.g., add a property or function on the enum like toWireValue() or a `val wireValue` and use pathType.wireValue and extension.wireValue, or a when(...) mapper) so renaming enum constants won't break the API contract.app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt (1)
51-59: ⚡ Quick win사용되지 않는 sharedViewModel
Line 52에서
sharedViewModel을 가져오지만OnboardingTermsRoute에 전달하지 않습니다. 다른 온보딩 화면들(예:OnboardingProfileRouteline 68,OnboardingContentRouteline 79)은 모두 viewModel을 전달하는 반면, 약관 동의 화면은 공유 상태가 필요하지 않은 것으로 보입니다.약관 동의 화면이 공유 ViewModel이 필요하지 않다면 line 52의
sharedViewModel선언을 제거하는 것이 좋습니다.♻️ 제안된 수정
composable<Route.OnboardingTerms> { backStackEntry -> - val sharedViewModel = backStackEntry.sharedViewModel<OnboardingViewModel>(navController) - OnboardingTermsRoute( paddingValues = paddingValues, navigateUp = navController::navigateUp, navigateToOnboardingContent = navController::navigateToOnboardingContent, ) }🤖 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 `@app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt` around lines 51 - 59, Remove the unused sharedViewModel declaration in the composable for Route.OnboardingTerms: the local variable sharedViewModel (created via backStackEntry.sharedViewModel<OnboardingViewModel>(navController)) is not passed into OnboardingTermsRoute and appears unnecessary, so delete that declaration from the composable block to avoid dead code; if the terms screen later needs shared state, pass the same sharedViewModel into OnboardingTermsRoute as done for OnboardingProfileRoute and OnboardingContentRoute.app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt (2)
46-60: ⚡ Quick win상세보기 URL이 비어있음
두 약관 항목의
detailUrl이 모두 빈 문자열입니다. 이로 인해 사용자가 "자세히 보기"(line 228)를 클릭해도 아무 동작도 수행되지 않습니다. 실제 약관 상세 URL이 준비되기 전까지는 해당 링크를 숨기거나 비활성화하는 것을 고려해보세요.🤖 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 `@app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt` around lines 46 - 60, The two TermItem entries in the terms list have empty detailUrl values, so the "자세히 보기" link becomes a no-op; update the code to either provide real URLs or guard the UI: in the OnboardingTermsScreen where you render each TermItem and the "자세히 보기" action, check TermItem.detailUrl for null/empty and if empty either hide the "자세히 보기" button or render it disabled (and prevent navigation in the click handler), or populate detailUrl with the correct agreement URLs; reference the terms list and TermItem.detailUrl and the click handler that opens the detail view to implement the guard.
144-144: 💤 Low value상태 업데이트 패턴 개선 제안
toMutableList().also { it[index] = !it[index] }패턴은 동작하지만, Kotlin의 컬렉션 변환 함수를 사용하면 더 간결합니다.♻️ 더 간결한 구현 제안
-checkedStates = checkedStates.toMutableList().also { it[index] = !it[index] } +checkedStates = checkedStates.mapIndexed { i, checked -> if (i == index) !checked else checked }동일하게 line 147에도 적용할 수 있습니다:
-expandedStates = expandedStates.toMutableList().also { it[index] = !it[index] } +expandedStates = expandedStates.mapIndexed { i, expanded -> if (i == index) !expanded else expanded }🤖 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 `@app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt` at line 144, Replace the mutable-copy pattern used to toggle an item in the checkedStates list (the expression using toMutableList().also { it[index] = !it[index] } in OnboardingTermsScreen) with an immutable collection transformation using mapIndexed to produce a new list where the value at the target index is inverted and all other values are preserved; apply the same mapIndexed-based change to the other occurrence (the similar update at the other toggle site) so state updates are concise and idiomatic.
🤖 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
`@app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt`:
- Around line 229-233: 현재 galleryLauncher.launch(...)를 호출하는 clickAction 핸들러에서
바텀시트를 닫는 처리가 빠져 있습니다; clickAction(예: 해당 블록에서 사용 중인 galleryLauncher,
PickVisualMediaRequest 및 ActivityResultContracts.PickVisualMedia.ImageOnly 호출)
실행 직후 showProfileBottomSheet = false를 설정하여 바텀시트를 명시적으로 닫아 주세요. 동일한 패턴이 존재하는 다른
clickAction(예: 라인 236-239 범위)에도 동일한 showProfileBottomSheet = false 추가를 적용해 주세요.
---
Nitpick comments:
In `@app/src/main/java/com/flint/domain/repository/StorageRepository.kt`:
- Around line 21-22: Don't send enum.name directly; instead send explicit wire
values for the enums used when building the payload in StorageRepository
(currently where pathType = pathType.name and extension = extension.name).
Replace those usages to map the enums to stable wire strings (e.g., add a
property or function on the enum like toWireValue() or a `val wireValue` and use
pathType.wireValue and extension.wireValue, or a when(...) mapper) so renaming
enum constants won't break the API contract.
In
`@app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt`:
- Around line 51-59: Remove the unused sharedViewModel declaration in the
composable for Route.OnboardingTerms: the local variable sharedViewModel
(created via backStackEntry.sharedViewModel<OnboardingViewModel>(navController))
is not passed into OnboardingTermsRoute and appears unnecessary, so delete that
declaration from the composable block to avoid dead code; if the terms screen
later needs shared state, pass the same sharedViewModel into
OnboardingTermsRoute as done for OnboardingProfileRoute and
OnboardingContentRoute.
In
`@app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt`:
- Around line 46-60: The two TermItem entries in the terms list have empty
detailUrl values, so the "자세히 보기" link becomes a no-op; update the code to
either provide real URLs or guard the UI: in the OnboardingTermsScreen where you
render each TermItem and the "자세히 보기" action, check TermItem.detailUrl for
null/empty and if empty either hide the "자세히 보기" button or render it disabled
(and prevent navigation in the click handler), or populate detailUrl with the
correct agreement URLs; reference the terms list and TermItem.detailUrl and the
click handler that opens the detail view to implement the guard.
- Line 144: Replace the mutable-copy pattern used to toggle an item in the
checkedStates list (the expression using toMutableList().also { it[index] =
!it[index] } in OnboardingTermsScreen) with an immutable collection
transformation using mapIndexed to produce a new list where the value at the
target index is inverted and all other values are preserved; apply the same
mapIndexed-based change to the other occurrence (the similar update at the other
toggle site) so state updates are concise and idiomatic.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 07575e41-4d79-4081-a86a-62d7699be174
📒 Files selected for processing (13)
app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.ktapp/src/main/java/com/flint/core/navigation/Route.ktapp/src/main/java/com/flint/data/api/StorageApi.ktapp/src/main/java/com/flint/data/di/ServiceModule.ktapp/src/main/java/com/flint/data/dto/storage/response/PresignedUrlResponseDto.ktapp/src/main/java/com/flint/domain/mapper/storage/PresignedUrlMapper.ktapp/src/main/java/com/flint/domain/model/storage/PresignedUrlModel.ktapp/src/main/java/com/flint/domain/repository/StorageRepository.ktapp/src/main/java/com/flint/domain/type/FileExtension.ktapp/src/main/java/com/flint/domain/type/StoragePathType.ktapp/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.ktapp/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.ktapp/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt
| clickAction = { | ||
| galleryLauncher.launch( | ||
| PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) | ||
| ) | ||
| } |
There was a problem hiding this comment.
메뉴 액션 이후 바텀시트를 명시적으로 닫아주세요.
Line 229, Line 238의 clickAction에서 showProfileBottomSheet = false 처리가 없어 바텀시트가 남을 수 있습니다. 액션 직후 닫기를 명시하는 편이 안전합니다.
수정 예시
MenuBottomSheetData(
label = "갤러리에서 선택",
clickAction = {
+ showProfileBottomSheet = false
galleryLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
}
),
MenuBottomSheetData(
label = "프로필 사진 삭제",
color = FlintTheme.colors.error500,
- clickAction = { selectedImageUri = null }
+ clickAction = {
+ selectedImageUri = null
+ showProfileBottomSheet = false
+ }
),Also applies to: 236-239
🤖 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
`@app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt`
around lines 229 - 233, 현재 galleryLauncher.launch(...)를 호출하는 clickAction 핸들러에서
바텀시트를 닫는 처리가 빠져 있습니다; clickAction(예: 해당 블록에서 사용 중인 galleryLauncher,
PickVisualMediaRequest 및 ActivityResultContracts.PickVisualMedia.ImageOnly 호출)
실행 직후 showProfileBottomSheet = false를 설정하여 바텀시트를 명시적으로 닫아 주세요. 동일한 패턴이 존재하는 다른
clickAction(예: 라인 236-239 범위)에도 동일한 showProfileBottomSheet = false 추가를 적용해 주세요.
📮 관련 이슈
📌 작업 내용
GET /api/v1/storage/presigned-url)생성 파일
StorageApi.ktPresignedUrlResponseDto.ktStoragePathType.kt,FileExtension.ktPresignedUrlModel.ktPresignedUrlMapper.ktStorageRepository.ktServiceModule.kt—provideStorageApi추가사용 예시
storageRepository.getPresignedUrl( pathType = StoragePathType.USER_PROFILE, extension = FileExtension.JPG, )😅 미구현
🫛 To. 리뷰어
Summary by CodeRabbit
릴리스 노트
신기능
변경사항