Skip to content

[Refactor] LoginViewModel에서 세션/인증 상태 분리 (SessionStore 도입)#39

Open
ddodle wants to merge 3 commits into
mainfrom
refactor/session-store
Open

[Refactor] LoginViewModel에서 세션/인증 상태 분리 (SessionStore 도입)#39
ddodle wants to merge 3 commits into
mainfrom
refactor/session-store

Conversation

@ddodle

@ddodle ddodle commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

✨ PR 유형

  • 코드 리팩토링

🛠️ 작업내용

LoginViewModel에 섞여 있던 폼 상태앱 전역 세션/인증 상태의 관심사를 분리했습니다.

  • SessionStore 신규 — 인증/세션 소유
    • isLoggedIn, userInfo, name
    • restore()(자동 로그인 복원) / login() / logout() / forceLogout()
    • 인증 UseCase 호출을 담당하고 실패는 throw로 알림 (로딩/에러 등 화면 표현 상태는 갖지 않음)
    • refresh 실패 콜백 배선을 이관
  • LoginViewModel 슬림화 — 폼 표현만 (loginId/password/isLoading/errorMessage), 로그인은 session.login()에 위임
  • ContentViewSessionStore@State로 소유, isLoggedIn으로 루트 분기, .task로 자동 로그인 복원을 루트에서 수행
  • LoginViewsession 주입받아 LoginViewModel@State로 생성, 기존 onAppear 제거

흐름

LoginView → LoginViewModel.login() → SessionStore.login() → UseCase → Repository → HTTP
              (로딩/에러 표현)          (인증 + isLoggedIn 전환)

📋 추후 진행 상황

  • 로그아웃/프로필 UI를 SessionStore에 연결
  • SessionStore 단위 테스트 (로그인/복원/강제 로그아웃 시나리오)

📌 리뷰 포인트

  • 관심사 분리 방향(세션=인증 로직·throw, 폼 VM=화면 표현)이 적절한지
  • 로그인 성공 후 fetchUser() 실패를 조용히 무시하도록 변경함 (이미 로그인은 성공, 프로필만 미로딩 → 비치명적 처리). 에러 노출은 추후 프로필 화면 관심사로 분리 예정
  • 자동 로그인 복원(restore)을 LoginView가 아닌 앱 루트(ContentView)에서 수행하도록 이동

✅ Checklist

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

Closes #37

Summary by CodeRabbit

  • New Features
    • 앱의 로그인 상태가 세션 기반으로 개선되어, 실행 시 이전 세션을 자동으로 복원합니다.
    • 로그인 화면에서 입력 검증과 오류 메시지 표시가 강화되었습니다.
  • Bug Fixes
    • 로그인/로그아웃 흐름에서 상태가 더 일관되게 반영되도록 수정했습니다.
    • 세션 갱신 실패 시 자동으로 안전하게 로그아웃되도록 처리했습니다.
  • Tests
    • 세션 복원, 로그인 성공/실패, 로그아웃 동작을 검증하는 테스트를 추가했습니다.

- SessionStore 신규: isLoggedIn/userInfo/name + restore/login/logout/forceLogout
  인증 UseCase 호출 담당, 실패는 throw. refresh-실패 콜백 배선 이관
- LoginViewModel: 폼 표현(loginId/password/isLoading/errorMessage)만 남기고
  로그인은 session.login()에 위임
- ContentView: SessionStore를 소유하고 isLoggedIn으로 루트 분기,
  .task로 자동 로그인 복원(restore)을 루트에서 수행
- LoginView: session 주입받아 LoginViewModel을 @State로 생성, onAppear 제거
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ddodle, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b08fb1d9-2472-4a8a-b8bf-44dde7bc7262

📥 Commits

Reviewing files that changed from the base of the PR and between 07fd387 and c1db7ce.

📒 Files selected for processing (4)
  • Rephoto_iOS/App/ContentView.swift
  • Rephoto_iOS/App/Rephoto_iOSApp.swift
  • Rephoto_iOS/Core/DIContainer/AppContainer.swift
  • Rephoto_iOS/Features/RephotoTabView.swift

Walkthrough

세션/인증 상태를 관리하는 SessionStore가 신규 도입되어 LoginViewModel에서 분리되었다. LoginViewModel은 로그인 폼 상태만 관리하며 로그인 처리를 SessionStore에 위임한다. ContentView와 LoginView는 SessionStore를 주입받도록 변경되었고, SessionStore와 LoginViewModel에 대한 단위 테스트 및 목 프로바이더가 추가되었다.

Changes

세션 분리 리팩토링

Layer / File(s) Summary
SessionStore 신규 구현
Rephoto_iOS/Features/User/Presentation/Session/SessionStore.swift
@Observable @MainActor`` SessionStore가 토큰 복원(restore), 로그인(login), 로그아웃(logout), 강제 로그아웃(forceLogout), 사용자 정보 갱신(refreshUser)을 담당하며 토큰 리프레시 실패 콜백 시 강제 로그아웃을 수행한다.
LoginViewModel의 세션 위임 전환
Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift
init(session: SessionStore)로 변경되고, 기존 isLoggedIn/userInfo/name/onAppear/fetchUser/logout/forceLogout이 제거되며 loginId/password/isLoading/errorMessage/isShowingError 폼 상태와 session.login(id:password:) 호출로 대체되었다.
ContentView/LoginView 세션 연동
Rephoto_iOS/App/ContentView.swift, Rephoto_iOS/Features/User/Presentation/Views/LoginView.swift
ContentView가 SessionStore를 @State로 보유하고 session.isLoggedIn 분기 및 .task { await session.restore() }를 추가했으며, LoginView는 init(session:)으로 LoginViewModel을 생성하고 onAppear .task를 제거했다.
SessionStore/LoginViewModel 테스트 및 목 객체
Rephoto_iOSTests/SessionStoreTests.swift, Rephoto_iOSTests/LoginViewModelTests.swift, Rephoto_iOSTests/MockUserUseCaseProvider.swift
MockUserUseCaseProvider를 추가하고 restore/login/logout/토큰 리프레시 실패 및 로그인 성공/실패/빈값 시나리오에 대한 상태 전이를 검증하는 테스트를 작성했다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ContentView
  participant SessionStore
  participant LoginView
  participant LoginViewModel
  participant UserUseCaseProvider

  ContentView->>SessionStore: init(provider:)
  ContentView->>SessionStore: restore()
  SessionStore->>UserUseCaseProvider: hasTokens()
  alt 토큰 있음
    SessionStore->>UserUseCaseProvider: fetchUser()
    UserUseCaseProvider-->>SessionStore: userInfo
    SessionStore-->>ContentView: isLoggedIn = true
  else 토큰 없음
    SessionStore-->>ContentView: isLoggedIn = false
  end

  ContentView->>LoginView: init(session:)
  LoginView->>LoginViewModel: init(session:)
  LoginViewModel->>SessionStore: login(id:password:)
  SessionStore->>UserUseCaseProvider: login().execute
  UserUseCaseProvider-->>SessionStore: 결과
  SessionStore->>UserUseCaseProvider: fetchUser()
  SessionStore-->>LoginViewModel: 성공/실패
  LoginViewModel-->>LoginView: errorMessage/isLoading 갱신
Loading

Possibly related PRs

  • Grit-23/Rephoto_Frontend_iOS#27: ContentView, LoginViewModel, LoginView의 인증 상태 관리 및 주입 방식을 동일 영역에서 변경한다.
  • Grit-23/Rephoto_Frontend_iOS#29: LoginViewModel의 로그인 상태 초기화/onAppear/forceLogout 흐름과 ContentView의 화면 분기를 함께 수정한다.

Poem

세션 하나 새로 심어, 🌱
로그인 폼은 가벼워졌네
토끼도 상태 정리 좋아해
forceLogout으로 깔끔하게 뛰어놀고
테스트 밭에 당근도 콕콕 🥕
다음 리팩토링도 신나게 폴짝!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning SessionStore와 LoginViewModel 분리는 반영됐지만 RephotoTabView 전환과 AppContainer 등록이 보이지 않아 이슈 범위를 완전히 충족하지 못합니다. RephotoTabView가 SessionStore를 참조하도록 수정하고, AppContainer에 SessionStore를 등록해 링크된 이슈 범위를 완성하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 SessionStore 도입과 LoginViewModel 세션/인증 분리라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed PR 유형, 작업내용, 추후 진행, 리뷰 포인트, 체크리스트가 템플릿 구조에 맞게 대부분 채워져 있습니다.
Out of Scope Changes check ✅ Passed 추가된 테스트와 Mock은 SessionStore/LoginViewModel 리팩토링을 검증하는 지원 변경으로 보이며, 뚜렷한 범위 외 변경은 없습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/session-store

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.

@ddodle ddodle self-assigned this Jul 2, 2026
- MockUserUseCaseProvider: UseCase 결과 stub + 호출 기록 spy
- SessionStoreTests: restore/login/logout/리프레시 실패 콜백 8케이스
- LoginViewModelTests: 입력 검증 및 isLoading/errorMessage 상태 전이 4케이스

@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.

🧹 Nitpick comments (1)
Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift (1)

13-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

가이드라인상 "ViewModel은 UseCase에만 의존" 규칙과의 정합성 확인.

LoginViewModel이 이제 UseCase가 아닌 SessionStore(Presentation 레이어 객체)에 의존합니다. PR 목표상 폼 상태/세션 상태 분리를 위한 의도된 설계이지만, 코딩 가이드라인은 "ViewModel depends only on UseCase"를 명시하고 있어 문자 그대로는 위반입니다. SessionStore를 Domain 레이어의 공유 유스케이스/서비스로 재정의하거나, 가이드라인에 이 패턴(세션 스토어 위임)에 대한 예외를 명시하는 것을 검토해주세요.

As per coding guidelines, **/*.swift: "Maintain Clean Architecture + MVVM pattern with clear separation: Presentation → Domain ← Data layers, where ViewModel depends only on UseCase, and Data implements Domain Protocol".

Also applies to: 24-26, 38-38

🤖 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 `@Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift`
around lines 13 - 14, LoginViewModel currently depends on SessionStore, which
conflicts with the “ViewModel depends only on UseCase” guideline. Update
LoginViewModel to depend on a Domain-layer abstraction instead of the
Presentation-layer SessionStore, or move SessionStore behind a UseCase/service
that the ViewModel can inject; if this dependency is intentional, document the
exception in the MVVM/Clean Architecture guideline and align the initializer and
session-handling members in LoginViewModel accordingly.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift`:
- Around line 13-14: LoginViewModel currently depends on SessionStore, which
conflicts with the “ViewModel depends only on UseCase” guideline. Update
LoginViewModel to depend on a Domain-layer abstraction instead of the
Presentation-layer SessionStore, or move SessionStore behind a UseCase/service
that the ViewModel can inject; if this dependency is intentional, document the
exception in the MVVM/Clean Architecture guideline and align the initializer and
session-handling members in LoginViewModel accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 49159f28-fc7a-4924-82ff-690947c0644e

📥 Commits

Reviewing files that changed from the base of the PR and between 8647a9c and 07fd387.

📒 Files selected for processing (7)
  • Rephoto_iOS/App/ContentView.swift
  • Rephoto_iOS/Features/User/Presentation/Session/SessionStore.swift
  • Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift
  • Rephoto_iOS/Features/User/Presentation/Views/LoginView.swift
  • Rephoto_iOSTests/LoginViewModelTests.swift
  • Rephoto_iOSTests/MockUserUseCaseProvider.swift
  • Rephoto_iOSTests/SessionStoreTests.swift

- AppContainer: sessionStore singleton 등록 (@mainactor 타입이라 assumeIsolated로 해석)
- ContentView: 직접 생성 대신 @injected 주입으로 전환
- Rephoto_iOSApp: userProvider 전달 배선 제거
- RephotoTabView: 세션 주입 + .environment로 하위 탭에 공급
@ddodle

ddodle commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai 수정사항 확인해줘

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Oops, something went wrong! Please try again later. 🐰 💔

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: LoginViewModel에서 세션/인증 상태 분리 (SessionStore 도입)

1 participant