Skip to content

[Refactor] Settings, PhotoCapture, User 클린 아키텍처 전환#27

Merged
ddodle merged 4 commits into
mainfrom
refactor/remaining-clean-architecture
May 19, 2026
Merged

[Refactor] Settings, PhotoCapture, User 클린 아키텍처 전환#27
ddodle merged 4 commits into
mainfrom
refactor/remaining-clean-architecture

Conversation

@ddodle

@ddodle ddodle commented May 19, 2026

Copy link
Copy Markdown
Collaborator

✨ PR 유형

어떤 변경 사항이 있나요??

  • 코드 리팩토링
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

🛠️ 작업내용

Home, Search에 이어 나머지 Feature 전체를 Clean Architecture로 전환.

Settings & PhotoCapture

  • 폴더 구조만 Presentation/Views/로 정리 (도메인 로직 없음)

User (핵심)

  • Domain: UserInfo 모델, UserRepositoryProtocol, Login/FetchUser/Logout UseCase
  • Data: LoginResponseDTO, UserInfoResponseDTO(toDomain), UserRepository, UserAPITarget 이동
  • Presentation: UserUseCaseProvider, LoginViewModel(@observable + async/await), LoginView(@bindable)
  • AuthedProvider+Async 확장 추가 (completion → async 브릿지)
  • Mock Provider 추가 (Preview 지원)

DI & 앱 진입점

  • AppContaineruserRepository, userUseCaseProvider 등록
  • Rephoto_iOSApp: @StateObject → Factory @Injected 전환
  • ContentView: @EnvironmentObject → constructor injection 전환

삭제된 파일

  • 미사용 DTO: LoginRequestDto, JoinRequestDto, KakaoLoginRequestDto, UserUpdateRequestDto, User.swift
  • 미사용 모델: LoginModel, UserModel
  • Network/Targets/UserAPITarget.swift (→ User/Data/Targets/로 이동)

Closes #26

📋 추후 진행 상황

  • Step 2: Token 관리 — Keychain + Actor 전환

📌 리뷰 포인트

  • UserRepositoryplainProvider(비인증)와 AuthedProvider(인증) 둘 다 사용하는 구조
  • hasTokens, setOnRefreshFailed가 Repository/Provider에 임시 노출 — Step 2에서 Core 이동 예정
  • Auth 인프라(TokenStore, AuthPlugin, AuthedProvider)는 Step 2 대비 현 위치 유지

✅ Checklist

  • 커밋 메시지 컨벤션에 맞게 작성했습니다
  • 유지-보수를 위해 주석처리를 잘 작성하였는가?

Summary by CodeRabbit

릴리스 노트

리팩토링

  • 인증 시스템 개선: 애플리케이션의 의존성 주입 및 인증 흐름을 전면 재구성하여 코드 유지보수성 강화
  • 로그인/로그아웃 처리 개선: 비동기 작업 처리를 통한 더욱 안정적인 사용자 인증 관리
  • 자동 로그인 로직 제거: 토큰 기반 자동 로그인 메커니즘을 명시적인 세션 관리 방식으로 변경

Review Change Stack

ddodle added 3 commits May 19, 2026 18:43
- 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 기반 주입으로 전환
@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@ddodle has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 42 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d8bb6281-aa4f-4286-a974-fff423363445

📥 Commits

Reviewing files that changed from the base of the PR and between fb08641 and fe0933d.

📒 Files selected for processing (4)
  • Rephoto_iOS/Features/Home/Domain/Services/PhotoMetadataExtractor.swift
  • Rephoto_iOS/Features/Home/Presentation/ViewModels/HomeViewModel.swift
  • Rephoto_iOS/Features/Home/Presentation/Views/HomeView.swift
  • Rephoto_iOS/Utilities/Extensions/Date+Photo.swift

Walkthrough

User 기능이 클린 아키텍처로 완전 리팩토링됩니다. Domain/Data/Presentation 3계층 분리, @Observable async/await 기반 재구성, constructor injection으로의 전환, AppContainer 등록, ContentView/Rephoto_iOSApp 진입점 재설정, 레거시 DTO/모델 제거가 이루어집니다.

Changes

User 기능 클린 아키텍처 전환

Layer / File(s) Summary
Domain Layer: 인증 계약 및 모델
Rephoto_iOS/Features/User/Domain/Interfaces/UserRepositoryProtocol.swift, Rephoto_iOS/Features/User/Domain/Models/UserInfo.swift, Rephoto_iOS/Features/User/Domain/UseCases/LoginUseCaseProtocol.swift, Rephoto_iOS/Features/User/Domain/UseCases/FetchUserUseCaseProtocol.swift, Rephoto_iOS/Features/User/Domain/UseCases/LogoutUseCaseProtocol.swift
UserRepositoryProtocol은 로그인/조회/로그아웃과 토큰 관리 메서드를 정의하고, 3개 유스케이스 프로토콜은 각 인증 단계의 async throws 계약을 선언하며, UserInfo는 도메인 데이터 모델입니다.
Domain UseCase 구현
Rephoto_iOS/Features/User/Domain/UseCases/Implementations/LoginUseCase.swift, Rephoto_iOS/Features/User/Domain/UseCases/Implementations/FetchUserUseCase.swift, Rephoto_iOS/Features/User/Domain/UseCases/Implementations/LogoutUseCase.swift
각 유스케이스는 주입된 UserRepositoryProtocol로 execute() 메서드를 구현하고 저장소 호출을 전파합니다.
Data Layer: DTO 및 저장소
Rephoto_iOS/Features/User/Data/DTO/LoginResponseDTO.swift, Rephoto_iOS/Features/User/Data/DTO/UserInfoResponseDTO.swift, Rephoto_iOS/Features/User/Data/Repositories/UserRepository.swift
LoginResponseDTO와 UserInfoResponseDTO는 API 응답을 모델링하고, UserRepository는 plainProvider/authedProvider 호출, JSON 디코딩, 토큰 저장/삭제, 도메인 변환, 갱신 실패 핸들러 주입을 통합 구현합니다.
Network 비동기 적응
Rephoto_iOS/Core/NetworkAdapter/AuthedProvider+Async.swift
AuthedProvider의 콜백 기반 request를 withCheckedThrowingContinuation으로 async throws 메서드로 변환합니다.
Presentation Provider 계층
Rephoto_iOS/Features/User/Presentation/Provider/UserUseCaseProvider.swift
UserUseCaseProviderProtocol은 유스케이스 팩토리, 토큰 상태, 갱신 실패 핸들러 등록을 정의하고, UserUseCaseProvider는 이를 구현하며 저장소 주입으로 각 유스케이스를 생성합니다.
Presentation ViewModel
Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift
@Observable @MainActor LoginViewModel은 provider 주입으로 초기화되며, onAppear/login/fetchUser/logout 메서드와 isLoggedIn/userInfo/errorMessage 상태를 관리하고, 토큰 갱신 실패 시 강제 로그아웃을 수행합니다.
Presentation View 및 프리뷰
Rephoto_iOS/Features/User/Presentation/Views/LoginView.swift, Rephoto_iOS/Features/User/Presentation/Preview/MockUserUseCaseProvider.swift
LoginView는 @Bindable loginVM 바인딩으로 전환되고 onLoginSuccess 콜백이 제거되며, .task에서 async로 상태 로드 및 로그인 처리를 수행합니다. MockUserUseCaseProvider는 DEBUG 빌드에서 테스트/프리뷰용 고정 구현을 제공합니다.
DI 설정 및 앱 진입점
Rephoto_iOS/Core/DIContainer/AppContainer.swift, Rephoto_iOS/App/Rephoto_iOSApp.swift, Rephoto_iOS/App/ContentView.swift
AppContainer에 userRepository와 userUseCaseProvider 팩토리가 등록되고, Rephoto_iOSApp은 @Injected로 provider를 주입받아 ContentView에 전달하며, ContentView는 @State loginVM을 초기화하고 로그인 상태로 분기합니다.
레거시 코드 정리
Rephoto_iOS/Features/User/Model/LoginModel.swift, Rephoto_iOS/Features/User/ViewModel/LoginViewModel.swift, Rephoto_iOS/Network/DTOs/Auth/*.swift, Rephoto_iOS/Network/DTOs/User/*.swift
구 LoginModel, 전 LoginViewModel(ViewModel 디렉토리), 그리고 Network 레이어의 JoinRequestDto, KakaoLoginRequestDto, LoginRequestDto, LoginResponseDto(public), User, UserInfoResponseDto(public), UserUpdateRequestDto가 모두 제거됩니다.

Sequence Diagram

sequenceDiagram
  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-->>사용자: 메인 화면으로 전환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Grit-23/Rephoto_Frontend_iOS#5: ContentView 및 Rephoto_iOSApp의 로그인 게이트 재설정과 LoginViewModel 주입 방식 변경이 직접 관련됨.
  • Grit-23/Rephoto_Frontend_iOS#3: Factory Swift 패키지 의존성 추가로, 본 PR에서 @Injected(.userUseCaseProvider)와 constructor injection 전환의 기반을 제공함.
  • Grit-23/Rephoto_Frontend_iOS#10: Rephoto_iOSApp/ContentView 진입점 재설정 및 의존성 주입 방식 전환이 겹침.

Poem

🐰 토끼의 노래
레거시를 정리하니 깔끔하고 맑아,
계층이 분리되니 구조가 밝아.
UseCase와 Repository 춤을 춘다면,
Async/await 비동기 여름날이 온다! 🌟

🚥 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
Title check ✅ Passed PR 제목이 User 리팩토링의 핵심 내용(Clean Architecture 전환)을 명확하게 전달하며, 포함된 모든 Feature(Settings, PhotoCapture, User)를 언급하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션(PR 유형, 작업내용, 추후 진행 상황, 리뷰 포인트, 체크리스트)을 모두 포함하고 있으며, 구체적인 변경사항과 의도를 충실히 기술하고 있습니다.
Linked Issues check ✅ Passed PR의 모든 주요 변경사항이 #26의 리팩토링 요구사항(Domain/Data/Presentation 분리, @Observable 전환, 생성자 주입, DTO 삭제, DI 등록)을 충족하고 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 Clean Architecture 전환과 직접 관련되어 있으며, #26에서 명시한 범위(Settings/PhotoCapture 폴더 정리, User의 3계층 분리, 미사용 파일 삭제, DI 등록) 내에 있습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/remaining-clean-architecture

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.

@ddodle ddodle self-assigned this May 19, 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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1486085 and fb08641.

📒 Files selected for processing (34)
  • Rephoto_iOS/App/ContentView.swift
  • Rephoto_iOS/App/Rephoto_iOSApp.swift
  • Rephoto_iOS/Core/DIContainer/AppContainer.swift
  • Rephoto_iOS/Core/NetworkAdapter/AuthedProvider+Async.swift
  • Rephoto_iOS/Features/PhotoCapture/Presentation/Views/PHCaptureImageView.swift
  • Rephoto_iOS/Features/Settings/Presentation/Views/HelpView.swift
  • Rephoto_iOS/Features/Settings/Presentation/Views/SettingsView.swift
  • Rephoto_iOS/Features/Settings/Presentation/Views/TrashView.swift
  • Rephoto_iOS/Features/User/Data/DTO/LoginResponseDTO.swift
  • Rephoto_iOS/Features/User/Data/DTO/UserInfoResponseDTO.swift
  • Rephoto_iOS/Features/User/Data/Repositories/UserRepository.swift
  • Rephoto_iOS/Features/User/Data/Targets/UserAPITarget.swift
  • Rephoto_iOS/Features/User/Domain/Interfaces/UserRepositoryProtocol.swift
  • Rephoto_iOS/Features/User/Domain/Models/UserInfo.swift
  • Rephoto_iOS/Features/User/Domain/UseCases/FetchUserUseCaseProtocol.swift
  • Rephoto_iOS/Features/User/Domain/UseCases/Implementations/FetchUserUseCase.swift
  • Rephoto_iOS/Features/User/Domain/UseCases/Implementations/LoginUseCase.swift
  • Rephoto_iOS/Features/User/Domain/UseCases/Implementations/LogoutUseCase.swift
  • Rephoto_iOS/Features/User/Domain/UseCases/LoginUseCaseProtocol.swift
  • Rephoto_iOS/Features/User/Domain/UseCases/LogoutUseCaseProtocol.swift
  • Rephoto_iOS/Features/User/Model/LoginModel.swift
  • Rephoto_iOS/Features/User/Model/UserModel.swift
  • Rephoto_iOS/Features/User/Presentation/Preview/MockUserUseCaseProvider.swift
  • Rephoto_iOS/Features/User/Presentation/Provider/UserUseCaseProvider.swift
  • Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift
  • Rephoto_iOS/Features/User/Presentation/Views/LoginView.swift
  • Rephoto_iOS/Features/User/ViewModel/LoginViewModel.swift
  • Rephoto_iOS/Network/DTOs/Auth/JoinRequestDto.swift
  • Rephoto_iOS/Network/DTOs/Auth/KakaoLoginRequestDto.swift
  • Rephoto_iOS/Network/DTOs/Auth/LoginRequestDto.swift
  • Rephoto_iOS/Network/DTOs/Auth/LoginResponseDto.swift
  • Rephoto_iOS/Network/DTOs/User/User.swift
  • Rephoto_iOS/Network/DTOs/User/UserInfoResponseDto.swift
  • Rephoto_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

Comment thread Rephoto_iOS/Core/NetworkAdapter/AuthedProvider+Async.swift
Comment thread Rephoto_iOS/Features/User/Data/Repositories/UserRepository.swift
Comment thread Rephoto_iOS/Features/User/Domain/Models/UserInfo.swift
- PHCaptureImageView(UIViewControllerRepresentable) 삭제, PhotoCapture 폴더 제거
- HomeView: SwiftUI PhotosPicker modifier로 교체
- HomeViewModel: handlePickedPhotos()에서 TaskGroup 병렬 메타데이터 추출
- PhotoMetadataExtractor: EXIF/GPS async 추출 서비스 (Home/Domain/Services)
- Date+Photo: static DateFormatter (Utilities/Extensions)
@ddodle ddodle merged commit 68c453b into main May 19, 2026
2 checks passed
@ddodle ddodle deleted the refactor/remaining-clean-architecture branch May 19, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

♻️ Refactor: Settings, PhotoCapture, User 클린 아키텍처 전환

1 participant