-
Notifications
You must be signed in to change notification settings - Fork 4
[API] home api 통신 #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughHome 화면의 API 연동을 추가하고, ViewModel 기반 UI 상태 관리로 화면 로직을 이관했습니다. 클럽 목록/재실자 조회, 출퇴근 API 호출을 데이터 계층과 리포지토리에 구현했습니다. MPP 공통 BackHandler를 도입하고 플랫폼별 actual을 추가했습니다. Ktor 클라이언트의 인증 헤더 부착 로직을 호스트/경로 기준으로 분기했습니다. Android/iOS에서 명시적으로 평문 트래픽을 허용했습니다. iOS 프로젝트 빌드 설정이 갱신되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as 사용자
participant UI as HomeScreen
participant VM as HomeViewModel
participant Repo as ClubRepository
participant Remote as RemoteClubDataSource
participant API as Server API
User->>UI: 화면 진입
UI->>VM: collect uiState 시작
VM->>Repo: getMyClubs()
Repo->>Remote: GET /clubs/my
Remote->>API: 요청
API-->>Remote: 클럽 목록
Remote-->>Repo: ApiResult.Success
Repo-->>VM: 결과
VM->>VM: 첫 클럽 선택
VM->>Repo: getPresentMembers(clubId)
Repo->>Remote: GET /clubs/{id}/presences
Remote->>API: 요청
API-->>Remote: 재실자 목록
Remote-->>Repo: ApiResult.Success
Repo-->>VM: 결과
VM-->>UI: uiState 업데이트(클럽/재실자/로딩)
User->>UI: 출근/퇴근 토글
UI->>VM: toggleAttendance()
alt 출근 필요
VM->>Repo: checkIn(clubId)
Repo->>Remote: POST /clubs/{id}/check-in
else 퇴근 필요
VM->>Repo: checkOut(clubId)
Repo->>Remote: DELETE /clubs/{id}/check-out
end
Remote->>API: 요청
API-->>Remote: 상태 코드
Remote-->>Repo: ApiResult
Repo-->>VM: 결과
VM->>Repo: getPresentMembers(clubId) 재조회
Repo->>Remote: GET /clubs/{id}/presences
Remote->>API: 요청
API-->>Remote: 재실자 목록
Remote-->>Repo: ApiResult.Success
Repo-->>VM: 결과
VM-->>UI: uiState 업데이트(참여 상태/재실자)
sequenceDiagram
autonumber
participant HC as HttpClient (Ktor)
participant INT as Internal Host(BASE_URL)
participant EXT as External Host
Note over HC: 요청 전 인터셉터
HC->>HC: 요청 URL 파싱(host, path)
alt host == BASE_URL.host
alt path ∈ no-auth 목록(로그인/재발급 등)
HC->>HC: Authorization 미부착
else
HC->>HC: Authorization: Bearer <token> 부착
end
else host != BASE_URL.host
HC->>HC: Authorization 미부착
end
HC-->>INT: 내부호스트 요청(필요 시 인증)
HC-->>EXT: 외부호스트 요청(무인증)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
composeApp/src/androidMain/AndroidManifest.xml(1 hunks)composeApp/src/androidMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt(0 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt(2 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/response/ClubPresencesResponseDto.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/data/remote/RemoteClubDataSource.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/data/repository/ClubRepository.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/HomeScreen.kt(7 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/HomeViewModel.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/component/MyClubSidebar.kt(2 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/component/PresentMembersList.kt(2 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/mock/PresentMember.kt(1 hunks)composeApp/src/iosMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt(1 hunks)composeApp/src/jvmMain/kotlin/org/whosin/client/presentation/component/BackHandler.jvm.kt(1 hunks)composeApp/src/wasmJsMain/kotlin/org/whosin/client/presentation/component/BackHandler.wasmJs.kt(1 hunks)iosApp/iosApp.xcodeproj/project.pbxproj(7 hunks)iosApp/iosApp/Info.plist(1 hunks)
💤 Files with no reviewable changes (1)
- composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt
🧰 Additional context used
🧬 Code graph analysis (2)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/component/PresentMembersList.kt (1)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/component/PresentMembersItem.kt (1)
PresentMembersItem(27-88)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/HomeScreen.kt (6)
composeApp/src/androidMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt (1)
CommonBackHandler(6-9)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt (1)
CommonBackHandler(5-6)composeApp/src/iosMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt (1)
CommonBackHandler(5-7)composeApp/src/jvmMain/kotlin/org/whosin/client/presentation/component/BackHandler.jvm.kt (1)
CommonBackHandler(5-7)composeApp/src/wasmJsMain/kotlin/org/whosin/client/presentation/component/BackHandler.wasmJs.kt (1)
CommonBackHandler(5-7)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/component/PresentMembersList.kt (1)
PresentMembersList(30-67)
🪛 Gitleaks (8.28.0)
composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt
[high] 51-51: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ios-build
🔇 Additional comments (9)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/component/MyClubSidebar.kt (1)
77-79: 더 간결한 Modifier 체인으로 개선됨기존 코드는
background(shape)+clickable(interactionSource, indication=null)로 리플 효과를 억제했지만, 새 코드는clip→background→clickable로 단순화하면서 기본 리플 효과를 사용하게 되었습니다. 이는 더 관용적인(idiomatic) 패턴이며, 사이드바 항목 선택 시 리플 효과가 표시되는 것이 UX 측면에서 일반적으로 적절합니다.다만, 기존에 리플 효과를 의도적으로 제거했던 것이라면
clickable에indication = null파라미터를 추가하여 리플을 억제할 수 있습니다.composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/component/PresentMembersList.kt (1)
25-25: 타입 마이그레이션 잘 완료됨
PresentMember에서PresentMemberUi로의 타입 전환과 필드명 변경(nickname→userName)이 정확하게 적용되었습니다. ViewModel의 UI 모델과 일관성 있게 연결되어 있습니다.Also applies to: 32-32, 61-61
composeApp/src/androidMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt (1)
1-9: Android actual 구현 정확함플랫폼별
BackHandler에 정확히 위임하고 있으며, 시그니처도 expect 선언과 일치합니다.composeApp/src/iosMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt (1)
1-7: iOS actual 구현 적절함iOS는 시스템 백 버튼이 없으므로 빈 구현이 적절합니다.
composeApp/src/wasmJsMain/kotlin/org/whosin/client/presentation/component/BackHandler.wasmJs.kt (1)
1-7: WASM/JS actual 구현 적절함WASM/JS 타겟에서는 네이티브 백 버튼 개념이 없으므로 빈 구현이 적절합니다.
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/component/BackHandler.kt (1)
1-6: 멀티플랫폼 BackHandler API 선언 정확함expect 함수 시그니처가 명확하며, 각 플랫폼별 actual 구현이 제공되어 있습니다.
composeApp/src/jvmMain/kotlin/org/whosin/client/presentation/component/BackHandler.jvm.kt (1)
1-7: JVM actual 구현 적절함데스크톱 환경에서는 시스템 백 버튼이 없으므로 빈 구현이 적절합니다.
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/mock/PresentMember.kt (1)
3-11: Mock 데이터가 새 타입으로 정확히 업데이트됨
PresentMember를 제거하고PresentMemberUi로 마이그레이션이 올바르게 완료되었습니다. 생성자 호출과 필드명이 일관성 있게 적용되어 있습니다.iosApp/iosApp/Info.plist (1)
8-20: iOS ATS 예외 설정 확인 필요
Info.plist에서whosinroom.store에 대한 ATS 예외를 추가해 암호화되지 않은 HTTP 통신을 허용하고 있습니다. 현재 해당 도메인이 HTTP/HTTPS 모두에서 연결되지 않고 SSL/TLS 인증서가 없어 보안 위험이 큽니다.
- 도메인 주소 정확성 및 HTTPS 지원 계획 확인
- 프로덕션 전까지 ATS 예외 제거 및 HTTPS 전용 연결 유지 권장
| android:supportsRtl="true" | ||
| android:theme="@android:style/Theme.Material.Light.NoActionBar"> | ||
| android:theme="@android:style/Theme.Material.Light.NoActionBar" | ||
| android:usesCleartextTraffic="true"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
평문 트래픽 허용은 보안 위험이 있습니다.
android:usesCleartextTraffic="true" 설정은 암호화되지 않은 HTTP 통신을 허용합니다. iOS 측 Info.plist와 동일하게, 이는 중간자 공격에 노출될 수 있는 보안 위험이 있습니다.
개발 단계라면 임시로 사용할 수 있지만, 프로덕션 릴리스 전에는 반드시:
- 백엔드 API를 HTTPS로 전환
- 이 설정을
false로 변경하거나 제거
해야 합니다. Android 9(API 28) 이상에서는 기본적으로 cleartext 트래픽이 차단되므로, 보안을 위해 HTTPS 사용을 권장합니다.
AndroidManifest.xml(14행) cleartext 트래픽 허용 설정 제거
android:usesCleartextTraffic="true"는 중간자 공격 등에 취약하므로 개발용 임시 설정을 제외하고 프로덕션 전에는 false로 변경하거나 제거하고, 백엔드 API를 HTTPS로 전환해야 합니다.
🤖 Prompt for AI Agents
In composeApp/src/androidMain/AndroidManifest.xml around line 14, remove or set
android:usesCleartextTraffic="false" instead of true so the app does not allow
cleartext (HTTP) traffic in production; ensure backend endpoints use HTTPS and,
if necessary for development, gate any temporary cleartext allowance behind a
debug-only manifest or network security config not shipped to production.
| val accessToken = tokenManager.getAccessToken() ?: "eyJhbGciOiJIUzI1NiJ9.eyJ0b2tlblR5cGUiOiJhY2Nlc3MiLCJ1c2VySWQiOjUsInByb3ZpZGVySWQiOiJsb2NhbGhvc3QiLCJuYW1lIjoi7Iug7KKF7JykIiwicm9sZSI6IlJPTEVfTUVNQkVSIiwiaWF0IjoxNzU5MzgyMzg3LCJleHAiOjE3NTk5ODcxODd9.kT9IH60aCA-6ByEITb-_qPAJY0Oik1bbPKqcBWXzHIk" | ||
| val refreshToken = tokenManager.getRefreshToken() ?: "no_token" | ||
| BearerTokens(accessToken = accessToken, refreshToken = refreshToken) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
하드코드된 JWT 토큰을 즉시 제거해주세요
loadTokens 기본값으로 실제 JWT를 넣어 둔 상태입니다. 이는 비공개 자격 증명이 저장소에 노출되는 심각한 보안 사고이자, 토큰이 없어도 항상 해당 계정으로 인증 요청을 날리게 만들어 시스템 오용 위험까지 초래합니다. 토큰이 없을 때는 전송을 건너뛰도록 처리하고, 하드코드된 문자열을 삭제해 주세요.
- val accessToken = tokenManager.getAccessToken() ?: "eyJhbGciOiJIUzI1NiJ9.eyJ0b2tlblR5cGUiOiJhY2Nlc3MiLCJ1c2VySWQiOjUsInByb3ZpZGVySWQiOiJsb2NhbGhvc3QiLCJuYW1lIjoi7Iug7KKF7JykIiwicm9sZSI6IlJPTEVfTUVNQkVSIiwiaWF0IjoxNzU5MzgyMzg3LCJleHAiOjE3NTk5ODcxODd9.kT9IH60aCA-6ByEITb-_qPAJY0Oik1bbPKqcBWXzHIk"
- val refreshToken = tokenManager.getRefreshToken() ?: "no_token"
- BearerTokens(accessToken = accessToken, refreshToken = refreshToken)
+ val accessToken = tokenManager.getAccessToken()
+ val refreshToken = tokenManager.getRefreshToken()
+ if (accessToken.isNullOrBlank() || refreshToken.isNullOrBlank()) {
+ return@loadTokens null
+ }
+ BearerTokens(accessToken = accessToken, refreshToken = refreshToken)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val accessToken = tokenManager.getAccessToken() ?: "eyJhbGciOiJIUzI1NiJ9.eyJ0b2tlblR5cGUiOiJhY2Nlc3MiLCJ1c2VySWQiOjUsInByb3ZpZGVySWQiOiJsb2NhbGhvc3QiLCJuYW1lIjoi7Iug7KKF7JykIiwicm9sZSI6IlJPTEVfTUVNQkVSIiwiaWF0IjoxNzU5MzgyMzg3LCJleHAiOjE3NTk5ODcxODd9.kT9IH60aCA-6ByEITb-_qPAJY0Oik1bbPKqcBWXzHIk" | |
| val refreshToken = tokenManager.getRefreshToken() ?: "no_token" | |
| BearerTokens(accessToken = accessToken, refreshToken = refreshToken) | |
| } | |
| val accessToken = tokenManager.getAccessToken() | |
| val refreshToken = tokenManager.getRefreshToken() | |
| if (accessToken.isNullOrBlank() || refreshToken.isNullOrBlank()) { | |
| return@loadTokens null | |
| } | |
| BearerTokens(accessToken = accessToken, refreshToken = refreshToken) |
🧰 Tools
🪛 Gitleaks (8.28.0)
[high] 51-51: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt
around lines 51-54, remove the hardcoded JWT and the "no_token" default and
change the logic so that when tokenManager.getAccessToken() or getRefreshToken()
returns null you do not construct or return BearerTokens; instead return null
(or an empty/absent marker) so the caller can skip attaching Authorization
headers. Delete the literal JWT string and "no_token", make the function return
a nullable BearerTokens (or Optional-equivalent) and ensure callers handle the
null case by not sending auth headers or requests that require auth.
| is ApiResult.Success -> { | ||
| val clubs = result.data.data.userClubs.map { ClubUi(it.clubId, it.clubName) } | ||
| _uiState.update { it.copy(isClubsLoading = false, clubs = clubs) } | ||
| // 첫 번째 클럽을 자동으로 선택하고 멤버 목록 로드 | ||
| clubs.firstOrNull()?.let { firstClub -> | ||
| onClubSelected(firstClub) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
성공 시 오류 메시지 초기화가 누락됐습니다.
이전 호출이 실패하면 errorMessage가 채워진 상태로 남는데, 이후 성공 케이스에서 errorMessage를 null로 되돌리지 않아 UI에 오류가 계속 노출됩니다. 성공 분기에서 errorMessage = null을 함께 세팅해 주세요.
🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/home/HomeViewModel.kt
around lines 49-55, the success branch updates isClubsLoading and clubs but does
not clear a previous errorMessage; modify the _uiState.update call in the
ApiResult.Success branch to also set errorMessage = null so any prior error is
cleared when clubs load successfully (i.e., include errorMessage = null
alongside isClubsLoading = false and clubs = clubs).
| GENERATE_INFOPLIST_FILE = YES; | ||
| INFOPLIST_FILE = iosApp/Info.plist; | ||
| INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; | ||
| INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; | ||
| INFOPLIST_KEY_UILaunchScreen_Generation = YES; | ||
| INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; | ||
| INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; | ||
| LD_RUNPATH_SEARCH_PATHS = ( | ||
| "$(inherited)", | ||
| "@executable_path/Frameworks", | ||
| ); | ||
| SWIFT_EMIT_LOC_STRINGS = YES; | ||
| SWIFT_VERSION = 5.0; | ||
| TARGETED_DEVICE_FAMILY = "1,2"; | ||
| }; | ||
| name = Debug; | ||
| }; | ||
| 803A8C7FA01596D81A28696E /* Release */ = { | ||
| isa = XCBuildConfiguration; | ||
| buildSettings = { | ||
| ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | ||
| ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; | ||
| CODE_SIGN_IDENTITY = "Apple Development"; | ||
| CODE_SIGN_STYLE = Automatic; | ||
| DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; | ||
| ENABLE_PREVIEWS = YES; | ||
| GENERATE_INFOPLIST_FILE = YES; | ||
| INFOPLIST_FILE = iosApp/Info.plist; | ||
| INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Info.plist 자동 생성으로 ATS 예외가 사라집니다
GENERATE_INFOPLIST_FILE = YES로 바뀌면서 기존 iosApp/Info.plist에 정의해 둔 NSAppTransportSecurity 예외가 빌드 산출물에서 빠집니다. HTTP 엔드포인트 호출이 다시 막혀서 이번 PR 목적(홈 API 연동)이 바로 깨집니다. 수동 Info.plist를 그대로 쓰도록 원래대로 NO로 돌려주세요.
- GENERATE_INFOPLIST_FILE = YES;
+ GENERATE_INFOPLIST_FILE = NO;위 변경을 Debug/Release 모두에 적용하면 됩니다.
🤖 Prompt for AI Agents
In iosApp/iosApp.xcodeproj/project.pbxproj around lines 179 to 207,
GENERATE_INFOPLIST_FILE was set to YES for the Debug and Release build
configurations which causes the manually maintained iosApp/Info.plist (and its
NSAppTransportSecurity HTTP exceptions) to be ignored; revert these entries to
use the manual Info.plist by setting GENERATE_INFOPLIST_FILE = NO for both Debug
and Release so the existing NSAppTransportSecurity exceptions remain included in
the build artifacts.
🚀 이슈번호
✏️ 변경사항
📷 스크린샷
안드로이드
1000032994.mp4
iOS
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2025-10-02.at.18.03.32.mov
🎸 기타
Summary by CodeRabbit
신규 기능
버그 수정
스타일