Skip to content

♻️ :: (#441) 모집의뢰서 필터 Repository/UseCase 기반 수정#442

Merged
leejh08 merged 5 commits into
mainfrom
feature/(#441)-recruitment_filter_refactoring
Jun 25, 2026
Merged

♻️ :: (#441) 모집의뢰서 필터 Repository/UseCase 기반 수정#442
leejh08 merged 5 commits into
mainfrom
feature/(#441)-recruitment_filter_refactoring

Conversation

@juyeong525

@juyeong525 juyeong525 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

개요

Flow가 네비게이션 스택에서 ViewController를 찾아 Reactor에 직접 이벤트를 주입하던 구조(클린아키텍처 위배)를 제거하고, 필터를 Repository/UseCase
경계로 옮겨 저장/로드하도록 수정

  • RecruitmentFilterEntity/Repository/Save·LoadUseCase 추가 (Domain)
  • RecruitmentFilterDTO/LocalDataSource(UserDefaults)/RepositoryImpl 추가 (Data)
  • 적용 시 Repository에 저장, 화면 노출 시 UseCase로 로드(viewWillAppear 변경 감지)
  • RecruitmentFilterStep payload 제거, Flow는 순수 pop만 수행
  • 모집의뢰서/체험형현장실습 필터 공유, dead code(updateFilterOptions) 제거

문제 사항

  • 필터 적용 시 RecruitmentFilterStep.popToRecruitment(jobCode:techCode:...) 라는 payload를 가진 Step으로 필터값이 네비게이션 계층을 타고 흐름
  • RecruitmentFilterFlow가 네비게이션 스택에서 RecruitmentViewController/WinterInternViewController를 직접 찾아 reactor.action.onNext(.updateFilterOptions(...))Presentation Reactor 내부에 직접 이벤트를 주입
  • → Flow(네비게이션)가 목적지 Reactor의 내부 Action을 알고 조작하고, 도메인 데이터가 네비게이션 계층을 통과하며, 무관한 두 화면이 한 Flow 메서드에 강결합 → 클린아키텍처 경계 위반

작업사항

  • 필터를 도메인 객체(RecruitmentFilterEntity)로 만들고 Repository/UseCase 경계를 통해 저장/로드
  • 필터 적용 시 Repository(UserDefaults)에 저장, 화면이 노출될 때 UseCase로 로드해 조회
  • Flow는 순수 pop만 담당, Step payload 제거

확인 및 결정 필요한사항

  • 빌드 테스트 및 정책 확인 필요 (모집의뢰서 / 체험형 겨울인턴 에서 필터를 공유해도 가능한지 정책 확인)
  • 로컬 필터 객체를 동기로 된 UseCase을 통해 가져와서 기존 Reactor에 있는 State와 비교한후 api콜 하는데
    프로젝트 규칙있다면 비동기로 통일 or 동기 선택 필요
  • UseCase 네이밍 규칙 등이 있다면 확인후 수정 필요

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 채용 필터 설정을 저장하고 복원하는 기능 추가
    • 화면 이동 시 필터 설정이 유지되도록 개선
    • CompanyDetail 페이지에서 홈 화면으로 이동하는 옵션 추가
  • Chores

    • 빌드 도구(Tuist) 버전 업데이트: 4.119.2 → 4.200.5
    • Xcode 프로젝트 생성 프로세스 최적화

Flow가 네비게이션 스택에서 ViewController를 찾아 Reactor에 직접 이벤트를
주입하던 구조(클린아키텍처 위배)를 제거하고, 필터를 Repository/UseCase
경계로 옮겨 저장/로드하도록 리팩터링.

- RecruitmentFilterEntity/Repository/Save·LoadUseCase 추가 (Domain)
- RecruitmentFilterDTO/LocalDataSource(UserDefaults)/RepositoryImpl 추가 (Data)
- 적용 시 Repository에 저장, 화면 노출 시 UseCase로 로드(viewWillAppear 변경 감지)
- RecruitmentFilterStep payload 제거, Flow는 순수 pop만 수행
- 모집의뢰서/체험형현장실습 필터 공유, dead code(updateFilterOptions) 제거

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

채용 필터 상태를 UserDefaults에 저장/조회하는 퍼시스턴스 계층(RecruitmentFilterEntity, RecruitmentFilterRepository, 유스케이스, DTO, 로컬 데이터 소스)을 신규 구축하고, RecruitmentFilterStep.popToRecruitment에서 연관값을 제거해 필터 전달 방식을 네비게이션 파라미터에서 유스케이스 기반으로 전환했습니다. Presentation 레이어의 CompanyDetailPreviousViewType 정의가 Core로 이동되었으며, tuist 버전과 Makefile도 업데이트되었습니다.

Changes

채용 필터 퍼시스턴스 아키텍처

Layer / File(s) Summary
필터 도메인 엔티티 및 리포지토리 계약
Projects/Domain/Sources/Entities/Recruitments/RecruitmentFilterEntity.swift, Projects/Domain/Sources/Repositories/RecruitmentFilterRepository.swift
RecruitmentFilterEntity (jobCode/techCode/years/region/status, static empty) 및 RecruitmentFilterRepository 프로토콜(save/fetch)이 신규 추가됨.
필터 저장/조회 유스케이스
Projects/Domain/Sources/UseCases/Recruitments/SaveRecruitmentFilterUseCase.swift, Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentFilterUseCase.swift
리포지토리를 주입받아 execute(filter:) / execute() 로 필터를 저장하고 조회하는 두 유스케이스가 추가됨.
데이터 계층 UserDefaults 퍼시스턴스 구현
Projects/Data/Sources/DTO/Recruitments/RecruitmentFilterDTO.swift, Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift, Projects/Data/Sources/Repositories/RecruitmentFilterRepositoryImpl.swift
RecruitmentFilterDTO (Codable, entity↔DTO 변환), LocalRecruitmentFilterDataSourceImpl (UserDefaults JSON 직렬화, 실패 시 .empty 반환), RecruitmentFilterRepositoryImpl (데이터 소스 위임)이 추가됨.
DI 컨테이너 등록 및 주입
Projects/Data/Sources/DI/DataSourceAssembly.swift, Projects/Data/Sources/DI/RepositoryAssembly.swift, Projects/Data/Sources/DI/UseCaseAssembly.swift, Projects/Presentation/Sources/DI/Assembly/MainPresentationAssembly.swift
로컬 데이터 소스, 리포지토리, 두 유스케이스가 Swinject 컨테이너에 등록되고, RecruitmentReactor/RecruitmentFilterReactor/WinterInternReactor 생성자에 각 유스케이스가 추가 주입됨.
네비게이션 스텝 단순화
Projects/Core/Sources/Steps/RecruitmentFilterStep.swift, Projects/Flow/Sources/Recruitment/RecruitmentFilterFlow.swift
popToRecruitment 케이스에서 연관값(필터 파라미터) 제거, 플로우에서 필터 주입·리액터 액션 디스패치를 삭제하고 단순 pop만 수행하도록 변경.
RecruitmentFilterReactor 필터 저장 통합
Projects/Presentation/Sources/RecruitmentFilter/RecruitmentFilterReactor.swift
saveRecruitmentFilterUseCase 주입 추가, 필터 적용 시 엔티티를 구성해 유스케이스로 저장 후 parameterless popToRecruitment 스텝으로 이동.
RecruitmentReactor 필터 로드 통합
Projects/Presentation/Sources/Recruitment/RecruitmentReactor.swift, Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift
FetchRecruitmentFilterUseCase 주입, updateFilterOptions 액션 제거, viewWillAppear 바인딩 추가, 공통 fetchListMutations(filter:) 헬퍼 도입으로 필터 로드 후 목록 갱신 흐름으로 변경.
WinterInternReactor 필터 로드 통합
Projects/Presentation/Sources/WinterIntern/WinterInternReactor.swift
FetchRecruitmentFilterUseCase 주입 추가, fetchRecruitmentList 시 유스케이스로 필터를 로드해 상태에 반영 후 목록 조회로 흐름 변경, updateFilterOptions 액션 제거.
CompanyDetailPreviousViewType 이동 및 툴링 업데이트
Projects/Core/Sources/Steps/CompanyDetailPreviousViewType.swift, Projects/Presentation/Sources/CompanyDetail/CompanyDetailPreviousViewType.swift, .mise.toml, Makefile
CompanyDetailPreviousViewType 정의를 Presentation에서 Core로 이동하고 home 케이스 추가, tuist 버전 4.200.5로 업데이트, Makefile generate 타겟에 --no-open 플래그 및 Xcode Beta 명시적 오픈 명령 추가.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over RecruitmentFilterReactor,SaveRecruitmentFilterUseCase: 필터 적용 흐름
  end
  participant RFC as RecruitmentFilterReactor
  participant SaveUC as SaveRecruitmentFilterUseCase
  participant Repo as RecruitmentFilterRepositoryImpl
  participant DS as LocalRecruitmentFilterDataSourceImpl
  participant UD as UserDefaults
  participant Flow as RecruitmentFilterFlow

  RFC->>RFC: filterApplyButtonDidTap → build RecruitmentFilterEntity
  RFC->>SaveUC: execute(filter:)
  SaveUC->>Repo: saveRecruitmentFilter(filter)
  Repo->>DS: saveFilter(filter)
  DS->>UD: JSONEncoder → set(data, forKey:)
  RFC->>Flow: steps.accept(.popToRecruitment)
  Flow->>Flow: navigationController.popViewController

  rect rgba(144, 238, 144, 0.5)
    Note over RecruitmentReactor,FetchRecruitmentFilterUseCase: 화면 복귀 후 필터 로드 흐름
  end
  participant RRC as RecruitmentReactor
  participant FetchUC as FetchRecruitmentFilterUseCase

  RRC->>RRC: viewWillAppear
  RRC->>FetchUC: execute()
  FetchUC->>Repo: fetchRecruitmentFilter()
  Repo->>DS: fetchFilter()
  DS->>UD: data(forKey:) → JSONDecoder → toDomain()
  DS-->>FetchUC: RecruitmentFilterEntity
  FetchUC-->>RRC: RecruitmentFilterEntity
  RRC->>RRC: fetchListMutations(filter:) → setFilterOptions + fetchRecruitmentList
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • Team-return/JOBIS-DSM-iOS-v2#412: RecruitmentFilterStep.popToRecruitmentRecruitmentFilterFlow의 동일한 코드 지점을 변경하며, 해당 PR은 region 연관값 추가, 이 PR은 연관값 전체 제거라는 반대 방향으로 연결된다.
  • Team-return/JOBIS-DSM-iOS-v2#440: RecruitmentFilterStep.popToRecruitmentRecruitmentFilterFlow/RecruitmentFilterReactor 네비게이션 시그니처를 동일한 지점에서 변경하며, 이 PR이 파라미터 없는 케이스와 로컬 퍼시스턴스로 확장한 흐름의 전단계에 해당한다.
  • Team-return/JOBIS-DSM-iOS-v2#437: CompanyDetailPreviousViewTypehome 케이스를 동일하게 다루며, 이 PR에서 Presentation 레이어 정의를 삭제하고 Core로 이동하는 작업과 코드 수준에서 연결된다.

Suggested labels

♻️refactor

Suggested reviewers

  • leejh08
  • circle0802

Poem

🐰 토끼가 필터를 저장했어요,
UserDefaults 속 JSON 꾸러미로~
파라미터 없이 pop! 하고 돌아오면,
viewWillAppear에서 필터를 꺼내와요 🌸
클린 아키텍처, 깔끔하게 정돈됐죠! ✨

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약하고 있습니다. '모집의뢰서 필터 Repository/UseCase 기반 수정'은 정확히 이 PR의 핵심 리팩토링(Domain/Data 계층의 Repository와 UseCase 추가를 통한 필터 처리 방식 개선)을 반영합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift (2)

204-204: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

린팅 위반: 라인 길이를 130자 이하로 줄이세요.

현재 140자로 프로젝트 제한(130자)을 초과합니다. 파라미터를 여러 줄로 분리하거나 메서드 이름을 줄여주세요.

제안하는 수정 예시
-    public func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
+    public func collectionSkeletonView(
+        _ skeletonView: UITableView,
+        cellIdentifierForRowAt indexPath: IndexPath
+    ) -> ReusableCellIdentifier {
🤖 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 `@Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift` at
line 204, The method signature collectionSkeletonView(_:cellIdentifierForRowAt:)
exceeds the 130-char line limit; break the declaration across multiple lines to
wrap parameters (e.g., place the first parameter on its own line and the second
parameter on the next line) or shorten the method name if appropriate so the
full signature for public func collectionSkeletonView(_ skeletonView:
UITableView, cellIdentifierForRowAt indexPath: IndexPath) fits under 130
characters while preserving the same parameter types and access level.

173-173: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

린팅 위반: 세로 공백을 제거하세요.

파이프라인에서 감지한 대로 연속된 빈 줄이 2개 있습니다. 1개로 줄여주세요.

🤖 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 `@Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift` at
line 173, There are two consecutive blank lines detected in
RecruitmentViewController.swift (around the RecruitmentViewController class near
the area flagged ~line 173); remove one of the blank lines so there is only a
single empty line between the surrounding code blocks (e.g., between the
preceding and following methods/properties) to satisfy the linter.
🧹 Nitpick comments (3)
Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift (1)

15-18: ⚡ Quick win

인코딩 실패를 로깅하는 것을 권장합니다.

현재 인코딩 실패 시 조용히 반환하여 저장이 실패했는지 확인할 방법이 없습니다. 디버깅을 위해 최소한 로그를 남기는 것이 좋습니다.

📝 개선안
 public func saveFilter(_ filter: RecruitmentFilterEntity) {
-    guard let data = try? JSONEncoder().encode(RecruitmentFilterDTO(entity: filter)) else { return }
+    guard let data = try? JSONEncoder().encode(RecruitmentFilterDTO(entity: filter)) else {
+        print("⚠️ Failed to encode RecruitmentFilterEntity")
+        return
+    }
     userDefaults.set(data, forKey: key)
 }
🤖 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 `@Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift` around
lines 15 - 18, The saveFilter(_:) method in RecruitmentFilterLocal silently
returns when JSONEncoder().encode(RecruitmentFilterDTO(entity: filter)) fails;
modify saveFilter(_:) to catch the encoding error instead of using try? and log
the failure (including the error and the RecruitmentFilterDTO or filter
identifier) before returning so callers and debuggers can see why the save
failed; update the code around RecruitmentFilterDTO(entity: filter),
JSONEncoder(), and userDefaults.set(..., forKey: key) to handle and log the
thrown error (using your app's Logger/print/os_log) while keeping the current
behavior of not crashing.
Projects/Domain/Sources/UseCases/Recruitments/SaveRecruitmentFilterUseCase.swift (1)

1-11: ⚡ Quick win

프로퍼티 선언 위치를 init 앞으로 이동하세요.

LoadRecruitmentFilterUseCase와 동일하게, Line 6의 recruitmentFilterRepository 프로퍼티가 Lines 2-4의 init 메서드 뒤에 선언되어 있습니다. Swift 컨벤션에 따라 프로퍼티를 init 앞에 선언해 주세요.

♻️ 제안하는 프로퍼티 순서 수정
 public struct SaveRecruitmentFilterUseCase {
+    private let recruitmentFilterRepository: RecruitmentFilterRepository
+
     public init(recruitmentFilterRepository: RecruitmentFilterRepository) {
         self.recruitmentFilterRepository = recruitmentFilterRepository
     }
 
-    private let recruitmentFilterRepository: RecruitmentFilterRepository
-
     public func execute(filter: RecruitmentFilterEntity) {
         recruitmentFilterRepository.saveRecruitmentFilter(filter)
     }
🤖 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
`@Projects/Domain/Sources/UseCases/Recruitments/SaveRecruitmentFilterUseCase.swift`
around lines 1 - 11, Move the private property declaration for
recruitmentFilterRepository so it appears before the init in the
SaveRecruitmentFilterUseCase struct (match the ordering used in
LoadRecruitmentFilterUseCase); specifically, declare private let
recruitmentFilterRepository: RecruitmentFilterRepository above the public
init(recruitmentFilterRepository: RecruitmentFilterRepository) initializer and
keep the execute(filter:) method unchanged.
Projects/Domain/Sources/UseCases/Recruitments/LoadRecruitmentFilterUseCase.swift (1)

1-11: ⚡ Quick win

프로퍼티 선언 위치를 init 앞으로 이동하세요.

Swift 코딩 컨벤션에 따르면 프로퍼티는 일반적으로 init 메서드보다 앞에 선언됩니다. 현재 Line 6의 recruitmentFilterRepository 프로퍼티가 Lines 2-4의 init 메서드 뒤에 선언되어 있어 일반적인 구조와 다릅니다.

♻️ 제안하는 프로퍼티 순서 수정
 public struct LoadRecruitmentFilterUseCase {
+    private let recruitmentFilterRepository: RecruitmentFilterRepository
+
     public init(recruitmentFilterRepository: RecruitmentFilterRepository) {
         self.recruitmentFilterRepository = recruitmentFilterRepository
     }
 
-    private let recruitmentFilterRepository: RecruitmentFilterRepository
-
     public func execute() -> RecruitmentFilterEntity {
         recruitmentFilterRepository.fetchRecruitmentFilter()
     }
🤖 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
`@Projects/Domain/Sources/UseCases/Recruitments/LoadRecruitmentFilterUseCase.swift`
around lines 1 - 11, Move the property declaration for
recruitmentFilterRepository to appear before the init within the
LoadRecruitmentFilterUseCase struct: locate the private let
recruitmentFilterRepository: RecruitmentFilterRepository declaration and place
it above public init(recruitmentFilterRepository: RecruitmentFilterRepository)
so properties are declared before initializers; leave execute() and other logic
unchanged.
🤖 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 `@Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift`:
- Line 4: 프로토콜 LocalRecruitmentFilterDataSource의 접근 수준을 public으로 변경하세요: 파일 내
protocol 선언을 internal에서 public으로 바꾸고, 필요하다면 해당 프로토콜이 선언한 모든 메서드/프로퍼티 시그니처도
public으로 노출하여 구현체 LocalRecruitmentFilterDataSourceImpl 및 RepositoryAssembly에서
정상적으로 resolve될 수 있게 만드세요.
- Line 10: The property userDefaults is being instantiated inline
(UserDefaults.standard) — change LocalRecruitmentFilterDataSourceImpl (or the
type in RecruitmentFilterLocal.swift) to accept a UserDefaults instance via its
initializer and store it as a dependency instead of creating it inside the
class; then register and inject the dependency from DataSourceAssembly (e.g.,
container.register(...){ _ in LocalRecruitmentFilterDataSourceImpl(userDefaults:
.standard) }) so tests can provide a mock UserDefaults.

In `@Projects/Domain/Sources/Repositories/RecruitmentFilterRepository.swift`:
- Around line 1-4: Update the RecruitmentFilterRepository protocol to use
Rx-style reactive return types: change saveRecruitmentFilter(_ filter:
RecruitmentFilterEntity) to return a Completable (or equivalent reactive void)
and change fetchRecruitmentFilter() to return Single<RecruitmentFilterEntity>;
locate these members on the RecruitmentFilterRepository protocol and adjust any
conforming implementations and imports (e.g., RxSwift) to return the reactive
types so callers and UseCases remain consistent with the project's
Single/Completable conventions.

In `@Projects/Presentation/Sources/Recruitment/RecruitmentReactor.swift`:
- Around line 148-176: fetchListMutations is missing error handling so a network
failure from fetchRecruitmentListUseCase.execute(...) can leave loading stuck;
update fetchListMutations to mirror updateSortOption by adding a catch (or
catchError) on the execute() observable that returns .just(.setLoading(false))
(or a concat of .setLoading(false) and an optional error mutation) so that
.setLoading(false) is always emitted on failure after the
.setRecruitmentList/.setLoading(false) success path; reference
fetchListMutations and fetchRecruitmentListUseCase.execute(...) when making the
change.

---

Outside diff comments:
In `@Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift`:
- Line 204: The method signature
collectionSkeletonView(_:cellIdentifierForRowAt:) exceeds the 130-char line
limit; break the declaration across multiple lines to wrap parameters (e.g.,
place the first parameter on its own line and the second parameter on the next
line) or shorten the method name if appropriate so the full signature for public
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt
indexPath: IndexPath) fits under 130 characters while preserving the same
parameter types and access level.
- Line 173: There are two consecutive blank lines detected in
RecruitmentViewController.swift (around the RecruitmentViewController class near
the area flagged ~line 173); remove one of the blank lines so there is only a
single empty line between the surrounding code blocks (e.g., between the
preceding and following methods/properties) to satisfy the linter.

---

Nitpick comments:
In `@Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift`:
- Around line 15-18: The saveFilter(_:) method in RecruitmentFilterLocal
silently returns when JSONEncoder().encode(RecruitmentFilterDTO(entity: filter))
fails; modify saveFilter(_:) to catch the encoding error instead of using try?
and log the failure (including the error and the RecruitmentFilterDTO or filter
identifier) before returning so callers and debuggers can see why the save
failed; update the code around RecruitmentFilterDTO(entity: filter),
JSONEncoder(), and userDefaults.set(..., forKey: key) to handle and log the
thrown error (using your app's Logger/print/os_log) while keeping the current
behavior of not crashing.

In
`@Projects/Domain/Sources/UseCases/Recruitments/LoadRecruitmentFilterUseCase.swift`:
- Around line 1-11: Move the property declaration for
recruitmentFilterRepository to appear before the init within the
LoadRecruitmentFilterUseCase struct: locate the private let
recruitmentFilterRepository: RecruitmentFilterRepository declaration and place
it above public init(recruitmentFilterRepository: RecruitmentFilterRepository)
so properties are declared before initializers; leave execute() and other logic
unchanged.

In
`@Projects/Domain/Sources/UseCases/Recruitments/SaveRecruitmentFilterUseCase.swift`:
- Around line 1-11: Move the private property declaration for
recruitmentFilterRepository so it appears before the init in the
SaveRecruitmentFilterUseCase struct (match the ordering used in
LoadRecruitmentFilterUseCase); specifically, declare private let
recruitmentFilterRepository: RecruitmentFilterRepository above the public
init(recruitmentFilterRepository: RecruitmentFilterRepository) initializer and
keep the execute(filter:) method unchanged.
🪄 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: c1b9df61-5d26-49d3-ba8d-f83d66ed7459

📥 Commits

Reviewing files that changed from the base of the PR and between af0be33 and ea5935b.

📒 Files selected for processing (17)
  • Projects/Core/Sources/Steps/RecruitmentFilterStep.swift
  • Projects/Data/Sources/DI/DataSourceAssembly.swift
  • Projects/Data/Sources/DI/RepositoryAssembly.swift
  • Projects/Data/Sources/DI/UseCaseAssembly.swift
  • Projects/Data/Sources/DTO/Recruitments/RecruitmentFilterDTO.swift
  • Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift
  • Projects/Data/Sources/Repositories/RecruitmentFilterRepositoryImpl.swift
  • Projects/Domain/Sources/Entities/Recruitments/RecruitmentFilterEntity.swift
  • Projects/Domain/Sources/Repositories/RecruitmentFilterRepository.swift
  • Projects/Domain/Sources/UseCases/Recruitments/LoadRecruitmentFilterUseCase.swift
  • Projects/Domain/Sources/UseCases/Recruitments/SaveRecruitmentFilterUseCase.swift
  • Projects/Flow/Sources/Recruitment/RecruitmentFilterFlow.swift
  • Projects/Presentation/Sources/DI/Assembly/MainPresentationAssembly.swift
  • Projects/Presentation/Sources/Recruitment/RecruitmentReactor.swift
  • Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift
  • Projects/Presentation/Sources/RecruitmentFilter/RecruitmentFilterReactor.swift
  • Projects/Presentation/Sources/WinterIntern/WinterInternReactor.swift

import Foundation
import Domain

protocol LocalRecruitmentFilterDataSource {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Protocol 접근 레벨을 public으로 변경하세요.

구현체 LocalRecruitmentFilterDataSourceImplpublic인데 protocol은 internal입니다. RepositoryAssembly에서 이 타입을 resolve하려면 protocol도 public이어야 합니다.

🔧 수정안
-protocol LocalRecruitmentFilterDataSource {
+public protocol LocalRecruitmentFilterDataSource {
     func saveFilter(_ filter: RecruitmentFilterEntity)
     func fetchFilter() -> RecruitmentFilterEntity
 }
🤖 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 `@Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift` at line
4, 프로토콜 LocalRecruitmentFilterDataSource의 접근 수준을 public으로 변경하세요: 파일 내 protocol
선언을 internal에서 public으로 바꾸고, 필요하다면 해당 프로토콜이 선언한 모든 메서드/프로퍼티 시그니처도 public으로 노출하여
구현체 LocalRecruitmentFilterDataSourceImpl 및 RepositoryAssembly에서 정상적으로 resolve될 수
있게 만드세요.

}

public struct LocalRecruitmentFilterDataSourceImpl: LocalRecruitmentFilterDataSource {
private let userDefaults = UserDefaults.standard

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

UserDefaults를 직접 생성하지 말고 생성자를 통해 주입하세요.

코딩 가이드라인에 따르면 모든 의존성은 Assembly 클래스에서 등록하고 Container를 통해 주입해야 하며, inline으로 생성하지 말아야 합니다. UserDefaults.standard를 프로퍼티에서 직접 생성하면 테스트 시 목(mock) 객체로 대체할 수 없습니다.

🛠️ 권장 수정안
 public struct LocalRecruitmentFilterDataSourceImpl: LocalRecruitmentFilterDataSource {
-    private let userDefaults = UserDefaults.standard
+    private let userDefaults: UserDefaults
     private let key = "RECRUITMENT_FILTER"
 
-    public init() {}
+    public init(userDefaults: UserDefaults = .standard) {
+        self.userDefaults = userDefaults
+    }

그리고 DataSourceAssembly.swift에서 다음과 같이 등록:

container.register(LocalRecruitmentFilterDataSource.self) { _ in
    LocalRecruitmentFilterDataSourceImpl(userDefaults: .standard)
}

As per coding guidelines: Register all dependencies in Assembly classes and inject through Container, do not create dependencies inline

🤖 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 `@Projects/Data/Sources/DataSource/Local/RecruitmentFilterLocal.swift` at line
10, The property userDefaults is being instantiated inline
(UserDefaults.standard) — change LocalRecruitmentFilterDataSourceImpl (or the
type in RecruitmentFilterLocal.swift) to accept a UserDefaults instance via its
initializer and store it as a dependency instead of creating it inside the
class; then register and inject the dependency from DataSourceAssembly (e.g.,
container.register(...){ _ in LocalRecruitmentFilterDataSourceImpl(userDefaults:
.standard) }) so tests can provide a mock UserDefaults.

Comment on lines +1 to +4
public protocol RecruitmentFilterRepository {
func saveRecruitmentFilter(_ filter: RecruitmentFilterEntity)
func fetchRecruitmentFilter() -> RecruitmentFilterEntity
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: 프로젝트 내 다른 Repository 프로토콜의 메서드 시그니처 패턴 확인

# Test: Repository 프로토콜들의 메서드 반환 타입 조사
rg -n -A 2 'protocol.*Repository' --type swift Projects/Domain/Sources/Repositories/

Repository: Team-return/JOBIS-DSM-iOS-v2

Length of output: 6544


Repository 동기 시그니처를 Rx 패턴(Single/Completable)으로 맞추세요

RecruitmentFilterRepositorysaveRecruitmentFilter(void) / fetchRecruitmentFilter(동기 Entity 반환)가 프로젝트의 다른 Repository 프로토콜들과 불일치합니다. (예: 대부분 Single<...> / Completable 반환)

  • saveRecruitmentFilterCompletable(또는 동일 의미의 반응형 반환)
  • fetchRecruitmentFilterSingle<RecruitmentFilterEntity>로 변경해 UseCase의 sync/async 동작 컨벤션과 일관성을 확보하고, 향후 UserDefaults 외 네트워크/DB 확장 시에도 API 전환 비용을 줄이세요.
🤖 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 `@Projects/Domain/Sources/Repositories/RecruitmentFilterRepository.swift`
around lines 1 - 4, Update the RecruitmentFilterRepository protocol to use
Rx-style reactive return types: change saveRecruitmentFilter(_ filter:
RecruitmentFilterEntity) to return a Completable (or equivalent reactive void)
and change fetchRecruitmentFilter() to return Single<RecruitmentFilterEntity>;
locate these members on the RecruitmentFilterRepository protocol and adjust any
conforming implementations and imports (e.g., RxSwift) to return the reactive
types so callers and UseCases remain consistent with the project's
Single/Completable conventions.

Comment on lines +148 to +176
private func fetchListMutations(filter: RecruitmentFilterEntity) -> Observable<Mutation> {
return .concat([
.just(.setFilterOptions(
jobCode: filter.jobCode,
techCode: filter.techCode,
years: filter.years,
region: filter.region,
status: filter.status
)),
.just(.resetPageCount),
.just(.setLoading(true)),
fetchRecruitmentListUseCase.execute(
page: 1,
jobCode: filter.jobCode,
techCode: filter.techCode,
years: filter.years,
region: filter.region,
status: filter.status,
sortType: currentState.sortType?.rawValue
)
.asObservable()
.flatMap { list -> Observable<Mutation> in
.concat([
.just(.setRecruitmentList(list)),
.just(.setLoading(false))
])
}
])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

fetchListMutations에 에러 핸들링 누락으로 로딩 상태가 stuck될 수 있습니다.

fetchRecruitmentListUseCase.execute() 호출 시 네트워크 오류가 발생하면 .setLoading(false) mutation이 방출되지 않아 UI가 로딩 상태에 고정됩니다. 동일 파일 내 updateSortOption (line 143)에서는 .catch { _ in .just(.setLoading(false)) }로 처리하고 있습니다.

🐛 에러 핸들링 추가 제안
             fetchRecruitmentListUseCase.execute(
                 page: 1,
                 jobCode: filter.jobCode,
                 techCode: filter.techCode,
                 years: filter.years,
                 region: filter.region,
                 status: filter.status,
                 sortType: currentState.sortType?.rawValue
             )
             .asObservable()
             .flatMap { list -> Observable<Mutation> in
                 .concat([
                     .just(.setRecruitmentList(list)),
                     .just(.setLoading(false))
                 ])
             }
+            .catch { _ in .just(.setLoading(false)) }
         ])
     }
🤖 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 `@Projects/Presentation/Sources/Recruitment/RecruitmentReactor.swift` around
lines 148 - 176, fetchListMutations is missing error handling so a network
failure from fetchRecruitmentListUseCase.execute(...) can leave loading stuck;
update fetchListMutations to mirror updateSortOption by adding a catch (or
catchError) on the execute() observable that returns .just(.setLoading(false))
(or a concat of .setLoading(false) and an optional error mutation) so that
.setLoading(false) is always emitted on failure after the
.setRecruitmentList/.setLoading(false) success path; reference
fetchListMutations and fetchRecruitmentListUseCase.execute(...) when making the
change.

leejh08 and others added 4 commits June 16, 2026 10:16
.xcworkspace의 macOS 기본 실행 앱이 VSCode로 등록되어 있고, Xcode/Xcode-beta가
동일 bundle identifier(com.apple.dt.Xcode)를 공유해 시스템 기본앱 설정만으로는
구분이 안 됨. tuist generate --no-open 후 Xcode-beta 경로를 명시적으로 open하도록
변경.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ase로 리네임

프로젝트의 읽기 UseCase는 전부 Fetch* 접두사를 쓰는데(FetchRecruitmentListUseCase,
FetchCodeListUseCase 등) 새로 추가된 필터 조회 UseCase만 Load*였음. 네이밍
컨벤션 일관성을 위해 Fetch*로 통일.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Core/Steps와 Presentation/CompanyDetail에 동일한 이름의 public enum이
각각 선언되어 있어 Core+Presentation을 동시에 import하는 Flow 타겟에서
'CompanyDetailPreviousViewType' is ambiguous for type lookup 에러 발생.

과거 (#421) PreviousViewType enum을 Core로 옮기면서 Presentation 쪽
파일을 삭제하지 않고 남겨둔 게 원인. Presentation 쪽엔 있던 home
케이스가 Core 쪽엔 빠져있어서 Core 버전에 추가하고 Presentation
중복 파일을 삭제.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Xcode 27 beta가 최소 배포 타겟을 15.0으로 올리면서, 그보다 낮은 배포
타겟을 선언한 서드파티 SPM 의존성(Alamofire, RxSwift, Moya, Lottie 등)
전체에서 빌드가 깨지는 문제. Package.swift 설정으로 override 가능할
거라 가정했으나 실제로는 Tuist 자체의 버그였음(tuist/tuist#11163,
PR #11169로 수정, 4.198.0부터 정상화). 기존 핀 버전(4.119.2)이 그보다
훨씬 오래돼 버그를 그대로 갖고 있어 4.200.5로 업그레이드.

tuist install && tuist generate 재생성 정상 완료, 일반 Xcode 빌드도
회귀 없이 BUILD SUCCEEDED 확인.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentFilterUseCase.swift (1)

1-11: ⚠️ Potential issue | 🟠 Major

execute() 메서드의 반환 타입을 프로젝트 컨벤션에 맞춰 Single<RecruitmentFilterEntity>로 변경하세요.

프로젝트의 모든 Fetch*UseCase(예: FetchStudentInfoUseCase, FetchRecruitmentDetailUseCase, FetchCodeListUseCase, FetchNotificationListUseCase 등)는 RxSwift의 Single<T> 또는 Completable을 반환합니다. FetchRecruitmentFilterUseCase만 동기적으로 직접 RecruitmentFilterEntity를 반환하고 있어 불일치합니다. UserDefaults 기반이므로 동기 처리이더라도 Single로 래핑하여 프로젝트의 표준 반응형 패턴을 준수해야 합니다.

🤖 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
`@Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentFilterUseCase.swift`
around lines 1 - 11, The execute() method in FetchRecruitmentFilterUseCase
currently returns RecruitmentFilterEntity directly, but it should return
Single<RecruitmentFilterEntity> to match the project's RxSwift reactive pattern
convention used by other Fetch*UseCase classes. Change the return type of the
execute() method from RecruitmentFilterEntity to
Single<RecruitmentFilterEntity>, and wrap the result from
recruitmentFilterRepository.fetchRecruitmentFilter() in a Single.just() call to
conform to the project's standard reactive return pattern.
🤖 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 `@Makefile`:
- Around line 3-4: Replace the hardcoded Xcode-beta path in the Makefile with a
configurable environment variable approach. Define an XCODE_APP variable that
accepts environment variable input with a sensible default (the standard Xcode
installation path), then modify the open command to use this variable. Add a
check to verify the specified Xcode application path exists before attempting to
open it, and provide a clear error message if the path cannot be found, allowing
developers to override the path by setting XCODE_APP environment variable when
running make generate.

---

Outside diff comments:
In
`@Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentFilterUseCase.swift`:
- Around line 1-11: The execute() method in FetchRecruitmentFilterUseCase
currently returns RecruitmentFilterEntity directly, but it should return
Single<RecruitmentFilterEntity> to match the project's RxSwift reactive pattern
convention used by other Fetch*UseCase classes. Change the return type of the
execute() method from RecruitmentFilterEntity to
Single<RecruitmentFilterEntity>, and wrap the result from
recruitmentFilterRepository.fetchRecruitmentFilter() in a Single.just() call to
conform to the project's standard reactive return pattern.
🪄 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: 54adf0ca-1399-4cbf-9666-913ec1472072

📥 Commits

Reviewing files that changed from the base of the PR and between ea5935b and 09d3ba1.

📒 Files selected for processing (9)
  • .mise.toml
  • Makefile
  • Projects/Core/Sources/Steps/CompanyDetailPreviousViewType.swift
  • Projects/Data/Sources/DI/UseCaseAssembly.swift
  • Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentFilterUseCase.swift
  • Projects/Presentation/Sources/CompanyDetail/CompanyDetailPreviousViewType.swift
  • Projects/Presentation/Sources/DI/Assembly/MainPresentationAssembly.swift
  • Projects/Presentation/Sources/Recruitment/RecruitmentReactor.swift
  • Projects/Presentation/Sources/WinterIntern/WinterInternReactor.swift
💤 Files with no reviewable changes (1)
  • Projects/Presentation/Sources/CompanyDetail/CompanyDetailPreviousViewType.swift
✅ Files skipped from review due to trivial changes (1)
  • .mise.toml
🚧 Files skipped from review as they are similar to previous changes (4)
  • Projects/Data/Sources/DI/UseCaseAssembly.swift
  • Projects/Presentation/Sources/DI/Assembly/MainPresentationAssembly.swift
  • Projects/Presentation/Sources/WinterIntern/WinterInternReactor.swift
  • Projects/Presentation/Sources/Recruitment/RecruitmentReactor.swift

Comment thread Makefile
Comment on lines +3 to +4
tuist generate --no-open
open -a /Applications/Xcode-beta.app *.xcworkspace

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Xcode-beta 경로 하드코딩이 팀 전체의 개발 환경을 제약합니다.

/Applications/Xcode-beta.app 경로가 하드코딩되어 있어 다음 문제가 발생할 수 있습니다:

  • Xcode-beta가 설치되지 않은 팀원의 make generate 실패
  • 안정 버전 Xcode를 사용하려는 팀원의 선택권 제한
  • CI/CD 환경에서 예상치 못한 실패 가능성
  • 오류 발생 시 명확한 피드백 부재

환경 변수를 통해 Xcode 경로를 설정하거나, 경로 존재 여부를 확인한 후 폴백하는 방식으로 개선하는 것을 권장합니다.

🔧 설정 가능한 Xcode 경로 개선안
+XCODE_APP ?= /Applications/Xcode.app
+
 generate:
 	tuist install
 	tuist generate --no-open
-	open -a /Applications/Xcode-beta.app *.xcworkspace
+	`@if` [ -d "$(XCODE_APP)" ]; then \
+		open -a "$(XCODE_APP)" *.xcworkspace; \
+	else \
+		echo "Error: Xcode not found at $(XCODE_APP)"; \
+		echo "Set XCODE_APP environment variable to your Xcode path"; \
+		exit 1; \
+	fi

사용 예시:

# 기본 Xcode 사용
make generate

# Xcode-beta 사용
XCODE_APP=/Applications/Xcode-beta.app make generate
📝 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.

Suggested change
tuist generate --no-open
open -a /Applications/Xcode-beta.app *.xcworkspace
XCODE_APP ?= /Applications/Xcode.app
generate:
tuist install
tuist generate --no-open
`@if` [ -d "$(XCODE_APP)" ]; then \
open -a "$(XCODE_APP)" *.xcworkspace; \
else \
echo "Error: Xcode not found at $(XCODE_APP)"; \
echo "Set XCODE_APP environment variable to your Xcode path"; \
exit 1; \
fi
🤖 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 `@Makefile` around lines 3 - 4, Replace the hardcoded Xcode-beta path in the
Makefile with a configurable environment variable approach. Define an XCODE_APP
variable that accepts environment variable input with a sensible default (the
standard Xcode installation path), then modify the open command to use this
variable. Add a check to verify the specified Xcode application path exists
before attempting to open it, and provide a clear error message if the path
cannot be found, allowing developers to override the path by setting XCODE_APP
environment variable when running make generate.

@leejh08 leejh08 merged commit 4e6f09d into main Jun 25, 2026
3 checks passed
@leejh08 leejh08 deleted the feature/(#441)-recruitment_filter_refactoring branch June 25, 2026 00:54
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.

모집의뢰서/체험형겨울인턴 필터 시 직접 reactor에 이벤트 전송하는 구조 수정

2 participants