[Refactor] Settings, PhotoCapture, User 클린 아키텍처 전환#27
Conversation
- Settings: Views/ → Presentation/Views/ 폴더 재구성 - PhotoCapture: PHCaptureImageView → Presentation/Views/ 이동
- Domain: UserInfo 모델, UserRepositoryProtocol, Login/FetchUser/Logout UseCase - Data: LoginResponseDTO, UserInfoResponseDTO(toDomain), UserRepository, UserAPITarget 이동 - AuthedProvider async 확장 추가 - 미사용 DTO 삭제 (LoginRequestDto, JoinRequestDto, KakaoLoginRequestDto 등)
- LoginViewModel: @observable + async/await + UseCase 주입으로 전환 - LoginView: @EnvironmentObject → @bindable constructor injection - UserUseCaseProvider + MockUserUseCaseProvider 추가 - AppContainer에 userRepository, userUseCaseProvider DI 등록 - ContentView/App: @StateObject 제거, Factory DI 기반 주입으로 전환
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
WalkthroughUser 기능이 클린 아키텍처로 완전 리팩토링됩니다. Domain/Data/Presentation 3계층 분리, ChangesUser 기능 클린 아키텍처 전환
Sequence DiagramsequenceDiagram
participant 사용자
participant LoginView
participant LoginViewModel
participant UserUseCaseProvider
participant LoginUseCase
participant UserRepository
participant PlainProvider as plainProvider
participant TokenStore
사용자->>LoginView: 로그인 입력 & 버튼 클릭
LoginView->>LoginViewModel: Task { await login() }
LoginViewModel->>UserUseCaseProvider: login()
UserUseCaseProvider->>LoginUseCase: 유스케이스 인스턴스 반환
LoginView->>LoginViewModel: await execute(loginId:password:)
LoginViewModel->>LoginUseCase: await execute(loginId:password:)
LoginUseCase->>UserRepository: await login(loginId:password:)
UserRepository->>PlainProvider: 로그인 요청
PlainProvider-->>UserRepository: LoginResponseDTO
UserRepository->>TokenStore: save(tokenPair)
TokenStore-->>UserRepository: 저장 완료
UserRepository-->>LoginUseCase: 성공
LoginUseCase-->>LoginViewModel: 성공
LoginViewModel->>LoginViewModel: fetchUser() 호출
LoginViewModel->>UserUseCaseProvider: fetchUser()
UserUseCaseProvider->>UserRepository: await fetchUser()
UserRepository->>UserRepository: authedProvider 요청
UserRepository-->>LoginViewModel: UserInfo 반환
LoginViewModel->>LoginViewModel: isLoggedIn = true
LoginViewModel-->>LoginView: 상태 업데이트
LoginView-->>사용자: 메인 화면으로 전환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 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 unit tests (beta)
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: 4
🤖 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 `@Rephoto_iOS/Core/NetworkAdapter/AuthedProvider`+Async.swift:
- Around line 12-22: AuthedProvider currently wraps a completion-handler API
with withCheckedThrowingContinuation; refactor it into an actor-based
async-native implementation by changing the final class AuthedProvider to actor,
replacing any completion-handler methods (including the underlying request(_:
UserAPITarget) that is being bridged) with native async/await signatures (e.g.,
request(_: UserAPITarget) async throws -> Response), and move the token refresh
synchronization (remove NSLock and any queue usage) into actor-scoped logic so
refresh is single-threaded via actor isolation; after this, remove the
continuation bridge in AuthedProvider+Async.swift and update call sites to call
the new async request directly.
In `@Rephoto_iOS/Features/User/Data/Repositories/UserRepository.swift`:
- Around line 41-44: The logout() implementation can throw before
tokenStore.clear() runs; ensure local token is always removed by adding a
cleanup path (use defer) that calls tokenStore.clear() at the start of logout(),
then perform the authedProvider.request(.logout) and rethrow any error after the
defer runs; reference logout(), authedProvider.request(.logout) and
tokenStore.clear() when making the change.
In `@Rephoto_iOS/Features/User/Domain/Interfaces/UserRepositoryProtocol.swift`:
- Line 8: Remove the unnecessary Foundation import to preserve Domain layer
purity: open the UserRepositoryProtocol.swift and delete the line "import
Foundation", then verify that the protocol declaration UserRepositoryProtocol
and any referenced types (methods, associated types, return types, parameters)
do not rely on Foundation types; if any Foundation types are used, replace them
with pure Swift equivalents or move those definitions out of the Domain layer
before removing the import.
In `@Rephoto_iOS/Features/User/Domain/Models/UserInfo.swift`:
- Line 8: Remove the unnecessary Foundation dependency in the UserInfo domain
model: delete the `import Foundation` line in UserInfo.swift (and ensure the
`UserInfo` type does not use any Foundation types like Date, UUID, NSString,
etc. — if it does, replace them with pure Swift equivalents or move those fields
out of the Domain layer). This keeps the Domain layer pure Swift per the
`**/*Domain/**/*.swift` guideline.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 506df9b4-9ae1-4d22-8a45-d0c5130ac8b6
📒 Files selected for processing (34)
Rephoto_iOS/App/ContentView.swiftRephoto_iOS/App/Rephoto_iOSApp.swiftRephoto_iOS/Core/DIContainer/AppContainer.swiftRephoto_iOS/Core/NetworkAdapter/AuthedProvider+Async.swiftRephoto_iOS/Features/PhotoCapture/Presentation/Views/PHCaptureImageView.swiftRephoto_iOS/Features/Settings/Presentation/Views/HelpView.swiftRephoto_iOS/Features/Settings/Presentation/Views/SettingsView.swiftRephoto_iOS/Features/Settings/Presentation/Views/TrashView.swiftRephoto_iOS/Features/User/Data/DTO/LoginResponseDTO.swiftRephoto_iOS/Features/User/Data/DTO/UserInfoResponseDTO.swiftRephoto_iOS/Features/User/Data/Repositories/UserRepository.swiftRephoto_iOS/Features/User/Data/Targets/UserAPITarget.swiftRephoto_iOS/Features/User/Domain/Interfaces/UserRepositoryProtocol.swiftRephoto_iOS/Features/User/Domain/Models/UserInfo.swiftRephoto_iOS/Features/User/Domain/UseCases/FetchUserUseCaseProtocol.swiftRephoto_iOS/Features/User/Domain/UseCases/Implementations/FetchUserUseCase.swiftRephoto_iOS/Features/User/Domain/UseCases/Implementations/LoginUseCase.swiftRephoto_iOS/Features/User/Domain/UseCases/Implementations/LogoutUseCase.swiftRephoto_iOS/Features/User/Domain/UseCases/LoginUseCaseProtocol.swiftRephoto_iOS/Features/User/Domain/UseCases/LogoutUseCaseProtocol.swiftRephoto_iOS/Features/User/Model/LoginModel.swiftRephoto_iOS/Features/User/Model/UserModel.swiftRephoto_iOS/Features/User/Presentation/Preview/MockUserUseCaseProvider.swiftRephoto_iOS/Features/User/Presentation/Provider/UserUseCaseProvider.swiftRephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swiftRephoto_iOS/Features/User/Presentation/Views/LoginView.swiftRephoto_iOS/Features/User/ViewModel/LoginViewModel.swiftRephoto_iOS/Network/DTOs/Auth/JoinRequestDto.swiftRephoto_iOS/Network/DTOs/Auth/KakaoLoginRequestDto.swiftRephoto_iOS/Network/DTOs/Auth/LoginRequestDto.swiftRephoto_iOS/Network/DTOs/Auth/LoginResponseDto.swiftRephoto_iOS/Network/DTOs/User/User.swiftRephoto_iOS/Network/DTOs/User/UserInfoResponseDto.swiftRephoto_iOS/Network/DTOs/User/UserUpdateRequestDto.swift
💤 Files with no reviewable changes (10)
- Rephoto_iOS/Network/DTOs/User/UserInfoResponseDto.swift
- Rephoto_iOS/Network/DTOs/User/UserUpdateRequestDto.swift
- Rephoto_iOS/Features/User/ViewModel/LoginViewModel.swift
- Rephoto_iOS/Network/DTOs/Auth/LoginResponseDto.swift
- Rephoto_iOS/Network/DTOs/Auth/LoginRequestDto.swift
- Rephoto_iOS/Network/DTOs/Auth/KakaoLoginRequestDto.swift
- Rephoto_iOS/Network/DTOs/User/User.swift
- Rephoto_iOS/Network/DTOs/Auth/JoinRequestDto.swift
- Rephoto_iOS/Features/User/Model/UserModel.swift
- Rephoto_iOS/Features/User/Model/LoginModel.swift
- PHCaptureImageView(UIViewControllerRepresentable) 삭제, PhotoCapture 폴더 제거 - HomeView: SwiftUI PhotosPicker modifier로 교체 - HomeViewModel: handlePickedPhotos()에서 TaskGroup 병렬 메타데이터 추출 - PhotoMetadataExtractor: EXIF/GPS async 추출 서비스 (Home/Domain/Services) - Date+Photo: static DateFormatter (Utilities/Extensions)
✨ PR 유형
어떤 변경 사항이 있나요??
🛠️ 작업내용
Home, Search에 이어 나머지 Feature 전체를 Clean Architecture로 전환.
Settings & PhotoCapture
Presentation/Views/로 정리 (도메인 로직 없음)User (핵심)
UserInfo모델,UserRepositoryProtocol,Login/FetchUser/LogoutUseCaseLoginResponseDTO,UserInfoResponseDTO(toDomain),UserRepository,UserAPITarget이동UserUseCaseProvider,LoginViewModel(@observable + async/await),LoginView(@bindable)AuthedProvider+Async확장 추가 (completion → async 브릿지)DI & 앱 진입점
AppContainer에userRepository,userUseCaseProvider등록Rephoto_iOSApp:@StateObject→ Factory@Injected전환ContentView:@EnvironmentObject→ constructor injection 전환삭제된 파일
LoginRequestDto,JoinRequestDto,KakaoLoginRequestDto,UserUpdateRequestDto,User.swiftLoginModel,UserModelNetwork/Targets/UserAPITarget.swift(→User/Data/Targets/로 이동)Closes #26
📋 추후 진행 상황
📌 리뷰 포인트
UserRepository가plainProvider(비인증)와AuthedProvider(인증) 둘 다 사용하는 구조hasTokens,setOnRefreshFailed가 Repository/Provider에 임시 노출 — Step 2에서 Core 이동 예정✅ Checklist
Summary by CodeRabbit
릴리스 노트
리팩토링