Refactor/#28 network data layer#29
Conversation
Walkthrough네트워크/데이터 계층을 리팩토링하여 기존 Home/Follow/Travel 서비스와 저장소를 폐기하고, Place/TravelProgram/TravelTemplate/UserTravel 기반의 서비스·저장소·인터페이스·타겟을 새로 도입하고 UseCase 및 Feature DI를 재배치했습니다. Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(100,149,237,0.5) Client
participant Feature as Feature (UI / Interactor)
end
rect rgba(60,179,113,0.5) UseCase
participant Usecase as FollowDetailUsecase
end
rect rgba(255,165,0,0.5) Repos
participant TTRepo as TravelTemplateRepository
participant UTRepo as UserTravelRepository
participant PRepo as PlaceRepository
end
Feature->>Usecase: 요청 (fetchTravelDetail / createUserTravel / fetchPlaceDetail)
Usecase->>TTRepo: fetchTravelDetail(id)
TTRepo-->>Usecase: TravelDetail
Usecase->>TTRepo: fetchPlaces(travelId, day)
TTRepo-->>Usecase: [TravelPlace]
Usecase->>UTRepo: createUserTravel(request) / fetchUpcoming()
UTRepo-->>Usecase: CreateTravelResponse / MyTripSummary
Usecase->>PRepo: fetchPlaceDetail(googlePlaceId) / fetchPlacePhotos(...)
PRepo-->>Usecase: PlaceDetail / [PlacePhoto]
Usecase-->>Feature: 결과 반환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45분 Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 4❌ Failed checks (2 warnings, 2 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Projects/Modules/Networks/Project.swift (1)
16-24:⚠️ Potential issue | 🔴 CriticalNetworks 프레임워크의 Domain 의존성 제거가 불완전합니다.
Project.swift에서
.domain의존성을 제거했으나, Networks 모듈 내 9개 파일에서 여전히import Domain을 사용하고 있습니다:
- Service/AuthService.swift
- Plugin/AuthPlugin.swift
- Plugin/NetworkProviderFactory.swift
- ErrorMapping/Travel/CreateTravelError+Mapping.swift
- ErrorMapping/SignupError+Mapping.swift
- ErrorMapping/Follow/PlacePhotosError+Mapping.swift
- ErrorMapping/Follow/PlaceDetailError+Mapping.swift
- ErrorMapping/Follow/ItineraryError+Mapping.swift
- ErrorMapping/Follow/ContentCardError+Mapping.swift
이 상태에서는 빌드가 실패하거나 Domain이 암묵적 의존성으로 남아있게 됩니다. 각 파일에서 Domain 타입 참조를 제거하거나 해당 타입을 Networks 모듈 또는 Data 레이어로 이동해야 합니다.
🤖 Fix all issues with AI agents
In `@Projects/App/Sources/Application/AppComponent.swift`:
- Around line 16-29: The three computed properties travelTemplateRepository,
travelProgramRepository, and userTravelRepository currently create new
Service/Repository instances on every access; change them to shared
(singleton-like) stored properties so the Service and Repository are
instantiated once and reused—e.g., replace the computed properties with private
lazy or private let stored properties initialized via a shared closure that
calls makeTravelTemplateService(tokenProvider:), makeTravelProgramService(),
makeUserTravelService(tokenProvider:) and constructs
TravelTemplateRepository(service:), TravelProgramRepository(service:), and
UserTravelRepository(service:) respectively to mirror the tokenProvider shared
pattern.
In `@Projects/Modules/Networks/Sources/Service/PlaceService.swift`:
- Around line 34-36: getPlaceDetails is calling the wrong API case
(.getPlacePhotos) causing decoding of PlaceDetailResponse to fail; update the
provider call inside getPlaceDetails to use the correct endpoint case
(.getPlaceDetails) so provider.asyncThowsRequest(.getPlaceDetails(googlePlaceId:
googlePlaceId)) returns a PlaceDetailResponse; verify the call site uses the
same googlePlaceId parameter and that the enum/case names match the PlaceAPI
definition.
In `@Projects/Modules/Networks/Sources/Service/TravelTemplateService.swift`:
- Line 14: The service protocol and implementation use a non-optional day
parameter while the API layer (TravelTemplateAPI.getItinerary(id:day:)) accepts
an optional Int to allow nil -> .requestPlain for full-itinerary requests;
update TravelTemplateServiceProtocol.getItinerary(travelId:day:) signature (and
its concrete TravelTemplateService implementation) to use Int? for the day
parameter, propagate that optional through callers and ensure calls to
TravelTemplateAPI.getItinerary(id:day:) pass the optional so nil triggers the
API's full-schedule request behavior.
In `@Projects/Modules/Networks/Sources/TargetType/PlaceAPI.swift`:
- Line 50: Fix the SwiftLint colon/spacing issues in PlaceAPI.swift: remove the
extra space after the label `parameters:` (ensure only one space after the
colon) and normalize dictionary/type colon styles to no space before and one
space after the colon (e.g., change `[String : String]?` to `[String: String]?`
and ensure any dictionary literals use `key: value` with no leading space before
`:`). Locate uses of `parameters:` and the `[String : String]?` type in this
file and apply these spacing fixes.
- Around line 13-17: PlaceService.getPlaceDetails is calling the wrong API case
due to a copy-paste bug: locate the getPlaceDetails method in PlaceService
(method name: getPlaceDetails) and replace the call that uses
PlaceAPI.getPlacePhotos(googlePlaceId: ...) with
PlaceAPI.getPlaceDetails(googlePlaceId: ...), ensuring the googlePlaceId
parameter is passed through unchanged and any response handling expects place
details rather than photo data.
In `@Projects/Modules/Networks/Sources/TargetType/TravelProgramAPI.swift`:
- Around line 43-44: The dictionary literal in the headers computed property of
TravelProgramAPI uses incorrect colon spacing per SwiftLint; update the headers
implementation (public var headers: [String : String]?) so the colon is attached
to the key (e.g., ["Content-Type": "application/json"]) and ensure any other
dictionary literals in TravelProgramAPI follow the same key: value colon style.
In `@Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift`:
- Line 80: The SwiftLint warning is caused by incorrect spacing in the type
annotation of the headers property; update the declaration for the public var
headers in TravelTemplateAPI.swift to remove the space before the colon so the
dictionary type reads with no space before the colon (i.e., use the correct
`[String: String]?` spacing) to satisfy the lint rule for colon spacing.
- Around line 58-59: The switch over the request task in TravelTemplateAPI.swift
binds an unused variable via the case .getContentCard(let id), causing a
compiler warning; change the pattern to use a wildcard (e.g.,
.getContentCard(_)) or simply .getContentCard to avoid binding id since the ID
is already handled in path—update the case in the task switch within the
TravelTemplateAPI request/task handling to use the wildcard form.
🧹 Nitpick comments (7)
Projects/Domain/Sources/Interface/Place/PlaceRepositoryProtocol.swift (1)
12-12: 임시 반환 타입Int에 대한 후속 작업이 필요합니다.
searchPlaces()가Int를 반환하며//임시주석이 달려 있습니다. 실제 API 스펙이 확정되면 적절한 도메인 모델로 교체해야 합니다. 추적을 위해 이슈를 생성하시겠습니까?Projects/Features/PopularTravelFeature/Sources/Models/PopularTravelPresentationModel.swift (1)
20-20: 오타:viedoType→videoType이 변경 범위 밖이지만,
Category.viedoType과 Line 39의 매핑에서viedoType오타가 존재합니다. 이 PR에서 함께 수정하는 것을 권장합니다.Projects/Data/Sources/DI/PlaceServiceFactory.swift (1)
14-16: SwiftLintimplicit_return경고 —return키워드 제거 가능.단일 표현식 함수이므로
return을 생략할 수 있습니다.♻️ 수정 제안
public func makePlaceService() -> PlaceServiceProtocol { - return PlaceService() + PlaceService() }Projects/Modules/Networks/Sources/TargetType/UserTravelAPI.swift (1)
50-50: SwiftLint: 콜론 스타일 수정 필요딕셔너리 타입에서 콜론은 키 바로 뒤에 위치해야 합니다.
🔧 수정 제안
- public var headers: [String : String]? { + public var headers: [String: String]? {Projects/Data/Sources/Repository/UserTravel /UserTravelRepository.swift (1)
14-22:DateFormatter는 thread-safe하지 않습니다.
UserTravelRepository가 여러 async 컨텍스트에서 동시에 호출될 수 있으므로,DateFormatter의string(from:)호출 시 data race가 발생할 수 있습니다. 대안으로 thread-safe한ISO8601DateFormatter를 사용하거나,dateFormatter를 호출 시점마다 로컬로 생성하는 방법을 고려해 주세요.♻️ ISO8601DateFormatter 사용 제안
public final class UserTravelRepository: UserTravelRepositoryInterface { private let service: UserTravelServiceProtocol - private let dateFormatter: DateFormatter public init(service: UserTravelServiceProtocol) { self.service = service - self.dateFormatter = DateFormatter() - self.dateFormatter.dateFormat = "yyyy-MM-dd" } public func createUserTravel(request: CreateTravelRequest) async throws -> CreateTravelResponse { do { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" let dto = CreateUserTravelRequest(Projects/Data/Sources/Transform/TravelTemplateTransform.swift (1)
96-110:thumbnail이nil일 때 빈 문자열 대신 옵셔널 유지 고려
$0.thumbnail ?? ""는nil일 때 빈 문자열로 대체됩니다. 만약TripInfo.thumbnailUrl이 옵셔널(String?)로 변경 가능하다면, 빈 문자열 대신nil을 전달하는 것이 다운스트림(예: 이미지 로딩)에서 빈 URL 요청을 방지하는 데 더 안전합니다.Projects/Domain/Sources/UseCase/HomeUsecase.swift (1)
11-11: 오래된 주석 정리가 필요합니다.
// MARK: - API 나오기 전 임시— API가 이미 구현된 상태이므로 이 임시 주석은 더 이상 유효하지 않아 보입니다. 정리해 주세요.
| import Moya | ||
|
|
||
| public protocol TravelTemplateServiceProtocol { | ||
| func getItinerary(travelId: Int, day: Int) async throws -> FollowItineraryResponse |
There was a problem hiding this comment.
getItinerary의 day 파라미터 타입이 API 레이어와 불일치합니다.
TravelTemplateServiceProtocol.getItinerary(travelId:day:)에서 day는 Int(필수)이지만, TravelTemplateAPI.getItinerary(id:day:)에서는 Int?(옵셔널)입니다. API 레이어는 day가 nil일 때 .requestPlain을 반환하여 전체 일정 조회를 지원하는데, 서비스 레이어에서 이 옵셔널 동작을 사용할 수 없게 됩니다.
의도적인 것이 아니라면 서비스 프로토콜도 Int?로 맞추는 것이 좋겠습니다.
제안된 수정
public protocol TravelTemplateServiceProtocol {
- func getItinerary(travelId: Int, day: Int) async throws -> FollowItineraryResponse
+ func getItinerary(travelId: Int, day: Int?) async throws -> FollowItineraryResponse
...
}
...
- public func getItinerary(travelId: Int, day: Int) async throws -> FollowItineraryResponse {
+ public func getItinerary(travelId: Int, day: Int?) async throws -> FollowItineraryResponse {
try await provider.asyncThowsRequest(.getItinerary(id: travelId, day: day))
}📝 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.
| func getItinerary(travelId: Int, day: Int) async throws -> FollowItineraryResponse | |
| func getItinerary(travelId: Int, day: Int?) async throws -> FollowItineraryResponse |
🤖 Prompt for AI Agents
In `@Projects/Modules/Networks/Sources/Service/TravelTemplateService.swift` at
line 14, The service protocol and implementation use a non-optional day
parameter while the API layer (TravelTemplateAPI.getItinerary(id:day:)) accepts
an optional Int to allow nil -> .requestPlain for full-itinerary requests;
update TravelTemplateServiceProtocol.getItinerary(travelId:day:) signature (and
its concrete TravelTemplateService implementation) to use Int? for the day
parameter, propagate that optional through callers and ensure calls to
TravelTemplateAPI.getItinerary(id:day:) pass the optional so nil triggers the
API's full-schedule request behavior.
| return .requestPlain | ||
| case .getPlacePhotos(let googlePlaceId), .getPlaceDetails(let googlePlaceId): | ||
| return .requestParameters( | ||
| parameters: ["googlePlaceId": googlePlaceId], |
There was a problem hiding this comment.
SwiftLint: 딕셔너리 리터럴의 콜론 스타일 및 불필요한 공백을 수정하세요.
Line 50에 parameters: 뒤 이중 공백과 Line 56의 [String : String]? 콜론 스타일 경고입니다.
🔧 수정 제안
- parameters: ["googlePlaceId": googlePlaceId],
+ parameters: ["googlePlaceId": googlePlaceId],- public var headers: [String : String]? {
+ public var headers: [String: String]? {📝 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.
| parameters: ["googlePlaceId": googlePlaceId], | |
| parameters: ["googlePlaceId": googlePlaceId], |
🧰 Tools
🪛 SwiftLint (0.63.2)
[Warning] 50-50: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals
(colon)
🤖 Prompt for AI Agents
In `@Projects/Modules/Networks/Sources/TargetType/PlaceAPI.swift` at line 50, Fix
the SwiftLint colon/spacing issues in PlaceAPI.swift: remove the extra space
after the label `parameters:` (ensure only one space after the colon) and
normalize dictionary/type colon styles to no space before and one space after
the colon (e.g., change `[String : String]?` to `[String: String]?` and ensure
any dictionary literals use `key: value` with no leading space before `:`).
Locate uses of `parameters:` and the `[String : String]?` type in this file and
apply these spacing fixes.
| public var headers: [String : String]? { | ||
| ["Content-Type": "application/json"] |
There was a problem hiding this comment.
SwiftLint: 딕셔너리 리터럴의 콜론 스타일을 수정하세요.
SwiftLint 경고에 따라, 딕셔너리 키 뒤의 콜론은 키에 붙여야 합니다.
🔧 수정 제안
- public var headers: [String : String]? {
+ public var headers: [String: String]? {📝 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.
| public var headers: [String : String]? { | |
| ["Content-Type": "application/json"] | |
| public var headers: [String: String]? { | |
| ["Content-Type": "application/json"] |
🧰 Tools
🪛 SwiftLint (0.63.2)
[Warning] 43-43: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals
(colon)
🤖 Prompt for AI Agents
In `@Projects/Modules/Networks/Sources/TargetType/TravelProgramAPI.swift` around
lines 43 - 44, The dictionary literal in the headers computed property of
TravelProgramAPI uses incorrect colon spacing per SwiftLint; update the headers
implementation (public var headers: [String : String]?) so the colon is attached
to the key (e.g., ["Content-Type": "application/json"]) and ensure any other
dictionary literals in TravelProgramAPI follow the same key: value colon style.
| case .getContentCard(let id): | ||
| return .requestPlain |
There was a problem hiding this comment.
사용하지 않는 바인딩 id — 컴파일러 경고가 발생합니다.
task switch에서 getContentCard(let id)로 바인딩하지만 해당 값을 사용하지 않습니다. (ID는 이미 path에서 처리됨). 와일드카드로 변경하세요.
제안된 수정
- case .getContentCard(let id):
+ case .getContentCard:
return .requestPlain📝 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.
| case .getContentCard(let id): | |
| return .requestPlain | |
| case .getContentCard(_): | |
| return .requestPlain |
🤖 Prompt for AI Agents
In `@Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift` around
lines 58 - 59, The switch over the request task in TravelTemplateAPI.swift binds
an unused variable via the case .getContentCard(let id), causing a compiler
warning; change the pattern to use a wildcard (e.g., .getContentCard(_)) or
simply .getContentCard to avoid binding id since the ID is already handled in
path—update the case in the task switch within the TravelTemplateAPI
request/task handling to use the wildcard form.
|
|
||
| public var headers: [String: String]? { | ||
| public var headers: [String : String]? { |
There was a problem hiding this comment.
SwiftLint: 콜론 간격 수정이 필요합니다.
[String : String]?에서 콜론 앞의 공백을 제거해야 합니다.
제안된 수정
- public var headers: [String : String]? {
+ public var headers: [String: String]? {
["Content-Type": "application/json"]
}📝 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.
| public var headers: [String : String]? { | |
| public var headers: [String: String]? { | |
| ["Content-Type": "application/json"] | |
| } |
🧰 Tools
🪛 SwiftLint (0.63.2)
[Warning] 80-80: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals
(colon)
🤖 Prompt for AI Agents
In `@Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift` at line
80, The SwiftLint warning is caused by incorrect spacing in the type annotation
of the headers property; update the declaration for the public var headers in
TravelTemplateAPI.swift to remove the space before the colon so the dictionary
type reads with no space before the colon (i.e., use the correct `[String:
String]?` spacing) to satisfy the lint rule for colon spacing.
There was a problem hiding this comment.
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 (1)
Projects/Features/FollowFeature/Sources/FollowDetailInteractor.swift (1)
184-215:⚠️ Potential issue | 🟡 Minor에러 시 캘린더를 닫는 것이 의도된 동작인지 확인해 주세요.
Line 210에서 에러 발생 시에도
router?.detachTripCalendar()를 호출하여 캘린더를 닫습니다. 사용자가 선택한 날짜 정보가 유실되므로, 실패 시 캘린더를 유지하고 에러 메시지를 보여주는 것이 더 나은 UX일 수 있습니다.
🤖 Fix all issues with AI agents
In `@Projects/Data/Sources/Adapter/TokenProviderAdapter.swift`:
- Around line 20-23: accessToken() currently returns a hardcoded JWT; uncomment
and restore the tokenRepository.get(.accessToken) call and remove the literal
token, and ensure test-only injection by wrapping a fallback debug-only provider
in `#if` DEBUG (read token from a git-ignored xcconfig or environment var) so
production builds never include the secret; specifically update the
accessToken() implementation to call tokenRepository.get(.accessToken) and add a
debug-only code path that reads a config/env value for local testing.
In
`@Projects/Features/FollowFeature/Sources/PlaceDetail/PlaceDetailInteractor.swift`:
- Around line 80-94: The interactor's concurrent fetch block in
PlaceDetailInteractor uses print(error) only on failure and never shows loading
or communicates errors to the presenter; update the flow to call showLoading()
before starting the async let fetches, and in the catch block call hideLoading()
and notify the presenter of the error (e.g., via updatePresenter or a new
presenter method like presentError(_:)/setErrorState) and optionally expose a
retry action so the UI can trigger fetchPlaceDetail/fetchPlacePhotos again;
ensure placeDetail/placePhotos are not left in an inconsistent state when an
error occurs.
In `@Projects/Features/MainFeature/Sources/MainBuilder.swift`:
- Line 24: 클래스 선언의 의존성 목록에서 쉼표 뒤 공백이 빠져 SwiftLint 경고가 발생합니다; MainComponent:
Component<MainDependency>, FollowDetailDependency,
PopularTravelDependency,SearchDependency, SettingDependency, TabBarDependency
선언에서 PopularTravelDependency와 SearchDependency 사이에 공백을 추가하여
"PopularTravelDependency, SearchDependency"로 수정하세요 (수정 대상 심볼: MainComponent,
PopularTravelDependency, SearchDependency).
In `@Projects/Modules/Networks/Sources/Service/PlaceService.swift`:
- Line 14: searchPlaces() currently returns Int as a temporary placeholder;
define a proper response model (e.g., PlaceSearchResponse or reuse an existing
model consistent with getPlacePhotos/getPlaceDetails) in the module, then update
the PlaceRepositoryProtocol declaration for searchPlaces() and its concrete
implementation in PlaceService (and any PlaceAPI helper in PlaceAPI.swift marked
"// 아직 적용x") to return that model instead of Int; also update any callers/tests
to handle the new response type and ensure decoding/mapping logic in the
implementation produces the new response model.
🧹 Nitpick comments (5)
Projects/Modules/Networks/Sources/Service/PlaceService.swift (1)
27-27:asyncThowsRequest메서드명에 오타가 있습니다 (Thows→Throws).메서드 정의(
MoyaProvider+Async.swift라인 111)와 모든 사용처에서asyncThowsRequest로 일관되게 사용되고 있습니다. 올바른 네이밍인asyncThrowsRequest로 메서드명과 모든 호출 위치를 수정해주세요.Projects/Features/FollowFeature/Sources/FollowDetailInteractor.swift (3)
217-228:handleCreateTravelError는 더 이상 호출되지 않는 데드 코드입니다.
Result기반 분기에서do-try-catch로 전환하면서, 이 메서드는 어디에서도 호출되지 않습니다. 향후 구조화된 에러 처리를 위해 보존하려면catch블록에서 활용하고, 그렇지 않으면 제거해 주세요.♻️ catch 블록에서 활용하거나 제거
createUserTravel의 catch 블록에서 활용하는 예시:} catch { + if let travelError = error as? CreateTravelError { + handleCreateTravelError(travelError) + } await MainActor.run { presenter.hideLoading() router?.detachTripCalendar() - print(error) } }
83-107:fetchTravelDetail과fetchPlaces를 동시에 요청할 수 있습니다.현재 순차적으로 호출되고 있지만, 두 요청은 독립적입니다.
PlaceDetailInteractor의async let패턴처럼 병렬로 요청하면 로딩 시간을 단축할 수 있습니다.♻️ async let을 사용한 병렬 fetch 제안
do { - let detail = try await followDetailUsecase.fetchTravelDetail(id: recommendationId) - let places = try await followDetailUsecase.fetchPlaces(travelId: recommendationId, day: 1) + async let detailResult = followDetailUsecase.fetchTravelDetail(id: recommendationId) + async let placesResult = followDetailUsecase.fetchPlaces(travelId: recommendationId, day: 1) + + let (detail, places) = try await (detailResult, placesResult)
9-12:RxSwiftimport와disposeBag제거
RxSwift및DisposeBag이 파일에서 사용되지 않고 있습니다. 모든 서비스 호출이async/await로 구현되어 있으므로, 불필요한 import와 property를 정리해주세요.Projects/Domain/Sources/UseCase/FollowDetailUsecase.swift (1)
19-33:final class대신struct사용을 고려해 보세요.
FollowDetailUsecase는 가변 상태(var)가 없고, 모든 프로퍼티가let으로 선언된 순수 위임(delegation) 객체입니다.struct로 변경하면 힙 할당을 줄이고 의도를 더 명확히 표현할 수 있습니다.단, RIBs DI 그래프에서 참조 시맨틱이 필요하거나 프로토콜이
AnyObject를 요구하는 경우에는 현행class가 적합합니다.
| public func accessToken() -> String? { | ||
| // tokenRepository.get(.accessToken) | ||
| "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzA5NjQzMDUsImV4cCI6MTc3MTA1MDcwNX0.Sn8wNhZ1Ac-ETZDsOiSMMHHaALJNXxNKrbN_-4xD5REcVa2tJ0NiafhTKlbIuYafL1Acd9dDMIHjx3H33c5w8w" | ||
| "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzEyMzU1MDUsImV4cCI6MTc3MTMyMTkwNX0.SfuVfF9FFpnFcUkGM7zC7mlE7-f8zo3NgG5mm86xekLnurFhGgnTIhwpew7FinguOex0smsnx--EHsMaED8D5A" | ||
| } |
There was a problem hiding this comment.
하드코딩된 JWT 토큰이 소스 코드에 커밋되고 있습니다.
tokenRepository.get(.accessToken) 호출이 주석 처리된 상태에서, 실제 JWT 문자열이 소스 코드에 직접 하드코딩되어 있습니다. 이 토큰이 git 히스토리에 남게 되면, 리포지토리에 접근 가능한 누구나 이 토큰을 추출하여 무단 접근에 악용할 수 있습니다.
Gitleaks 정적 분석에서도 이 라인을 high 심각도로 플래그하고 있습니다.
임시 테스트 목적이라면, 최소한 #if DEBUG 컴파일 조건으로 감싸고, 가능하면 gitignore된 xcconfig 또는 환경 변수를 통해 주입하는 방식으로 변경해 주세요.
🔒 제안: DEBUG 조건부 컴파일 및 주석 해제
public func accessToken() -> String? {
-// tokenRepository.get(.accessToken)
- "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzEyMzU1MDUsImV4cCI6MTc3MTMyMTkwNX0.SfuVfF9FFpnFcUkGM7zC7mlE7-f8zo3NgG5mm86xekLnurFhGgnTIhwpew7FinguOex0smsnx--EHsMaED8D5A"
+ `#if` DEBUG
+ // TODO: xcconfig 또는 환경 변수에서 테스트 토큰을 주입하도록 변경
+ return ProcessInfo.processInfo.environment["DEBUG_ACCESS_TOKEN"]
+ `#else`
+ return tokenRepository.get(.accessToken)
+ `#endif`
}Based on learnings: "Do not hardcode temporary/local test secrets in code. Inject secrets via a gitignored xcconfig and wire them into Info.plist, then read only in DEBUG builds."
📝 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.
| public func accessToken() -> String? { | |
| // tokenRepository.get(.accessToken) | |
| "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzA5NjQzMDUsImV4cCI6MTc3MTA1MDcwNX0.Sn8wNhZ1Ac-ETZDsOiSMMHHaALJNXxNKrbN_-4xD5REcVa2tJ0NiafhTKlbIuYafL1Acd9dDMIHjx3H33c5w8w" | |
| "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzEyMzU1MDUsImV4cCI6MTc3MTMyMTkwNX0.SfuVfF9FFpnFcUkGM7zC7mlE7-f8zo3NgG5mm86xekLnurFhGgnTIhwpew7FinguOex0smsnx--EHsMaED8D5A" | |
| } | |
| public func accessToken() -> String? { | |
| `#if` DEBUG | |
| // TODO: xcconfig 또는 환경 변수에서 테스트 토큰을 주입하도록 변경 | |
| return ProcessInfo.processInfo.environment["DEBUG_ACCESS_TOKEN"] | |
| `#else` | |
| return tokenRepository.get(.accessToken) | |
| `#endif` | |
| } |
🧰 Tools
🪛 Gitleaks (8.30.0)
[high] 22-22: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🤖 Prompt for AI Agents
In `@Projects/Data/Sources/Adapter/TokenProviderAdapter.swift` around lines 20 -
23, accessToken() currently returns a hardcoded JWT; uncomment and restore the
tokenRepository.get(.accessToken) call and remove the literal token, and ensure
test-only injection by wrapping a fallback debug-only provider in `#if` DEBUG
(read token from a git-ignored xcconfig or environment var) so production builds
never include the secret; specifically update the accessToken() implementation
to call tokenRepository.get(.accessToken) and add a debug-only code path that
reads a config/env value for local testing.
| do { | ||
| async let detailResult = self.followDetailUsecase.fetchPlaceDetail(googlePlaceId: googlePlaceId) | ||
| async let photosResult = self.followDetailUsecase.fetchPlacePhotos(googlePlaceId: googlePlaceId) | ||
|
|
||
| let (detail, photos) = try await (detailResult, photosResult) | ||
|
|
||
| await MainActor.run { | ||
| self.placeDetail = detail | ||
| self.placePhotos = photos | ||
| self.updatePresenter() | ||
| } | ||
|
|
||
| switch photos { | ||
| case .success(let placePhotos): | ||
| self.placePhotos = placePhotos | ||
| case .failure(let error): | ||
| switch error { | ||
| case .missingParameter(let message): | ||
| print("PlacePhotos 실패 [missingParameter]: \(message)") | ||
| case .serverError(let message): | ||
| print("PlacePhotos 실패 [serverError]: \(message)") | ||
| case .googleApiError(let message): | ||
| print("PlacePhotos 실패 [googleApiError]: \(message)") | ||
| case .unknown(let code, let message): | ||
| print("PlacePhotos 실패 [unknown] code: \(code), message: \(message)") | ||
| } | ||
| } | ||
|
|
||
| updatePresenter() | ||
| } catch { | ||
| print(error) | ||
| } | ||
| } |
There was a problem hiding this comment.
에러 발생 시 사용자에게 피드백이 없습니다.
FollowDetailInteractor에서는 에러 시 hideLoading()을 호출하지만, 여기서는 showLoading()도 없고 catch에서 print(error)만 수행합니다. 네트워크 실패 시 사용자에게 아무 피드백이 없으므로, 최소한 에러 상태를 presenter에 전달하거나 retry 기회를 제공하는 것이 좋습니다.
🤖 Prompt for AI Agents
In
`@Projects/Features/FollowFeature/Sources/PlaceDetail/PlaceDetailInteractor.swift`
around lines 80 - 94, The interactor's concurrent fetch block in
PlaceDetailInteractor uses print(error) only on failure and never shows loading
or communicates errors to the presenter; update the flow to call showLoading()
before starting the async let fetches, and in the catch block call hideLoading()
and notify the presenter of the error (e.g., via updatePresenter or a new
presenter method like presentError(_:)/setErrorState) and optionally expose a
retry action so the UI can trigger fetchPlaceDetail/fetchPlacePhotos again;
ensure placeDetail/placePhotos are not left in an inconsistent state when an
error occurs.
| var followDetailUsecase: FollowDetailUsecaseProtocol { get } | ||
| } | ||
|
|
||
| final class MainComponent: Component<MainDependency>, FollowDetailDependency, PopularTravelDependency,SearchDependency, SettingDependency, TabBarDependency { |
There was a problem hiding this comment.
SwiftLint 경고: 쉼표 뒤 공백 누락
PopularTravelDependency,SearchDependency 사이에 공백이 빠져 있습니다.
🔧 수정 제안
-final class MainComponent: Component<MainDependency>, FollowDetailDependency, PopularTravelDependency,SearchDependency, SettingDependency, TabBarDependency {
+final class MainComponent: Component<MainDependency>, FollowDetailDependency, PopularTravelDependency, SearchDependency, SettingDependency, TabBarDependency {📝 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.
| final class MainComponent: Component<MainDependency>, FollowDetailDependency, PopularTravelDependency,SearchDependency, SettingDependency, TabBarDependency { | |
| final class MainComponent: Component<MainDependency>, FollowDetailDependency, PopularTravelDependency, SearchDependency, SettingDependency, TabBarDependency { |
🧰 Tools
🪛 SwiftLint (0.63.2)
[Warning] 24-24: There should be no space before and one after any comma
(comma)
🤖 Prompt for AI Agents
In `@Projects/Features/MainFeature/Sources/MainBuilder.swift` at line 24, 클래스 선언의
의존성 목록에서 쉼표 뒤 공백이 빠져 SwiftLint 경고가 발생합니다; MainComponent:
Component<MainDependency>, FollowDetailDependency,
PopularTravelDependency,SearchDependency, SettingDependency, TabBarDependency
선언에서 PopularTravelDependency와 SearchDependency 사이에 공백을 추가하여
"PopularTravelDependency, SearchDependency"로 수정하세요 (수정 대상 심볼: MainComponent,
PopularTravelDependency, SearchDependency).
| import Moya | ||
|
|
||
| public protocol PlaceServiceProtocol { | ||
| func searchPlaces() async throws -> Int |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# 프로젝트 구조 확인 및 PlaceAPI.swift 찾기
find . -name "PlaceAPI.swift" -o -name "PlaceService.swift" | head -20Repository: YAPP-Github/27th-App-Team-1-iOS
Length of output: 199
🏁 Script executed:
# PlaceService.swift 전체 내용 확인 (크기 먼저 확인)
wc -l "Projects/Modules/Networks/Sources/Service/PlaceService.swift"Repository: YAPP-Github/27th-App-Team-1-iOS
Length of output: 138
🏁 Script executed:
# 코드베이스에서 searchPlaces 메서드의 모든 정의와 사용 찾기
rg "searchPlaces" --type swift -B 2 -A 5Repository: YAPP-Github/27th-App-Team-1-iOS
Length of output: 6177
🏁 Script executed:
# PlaceService.swift 전체 내용 확인
cat -n "Projects/Modules/Networks/Sources/Service/PlaceService.swift"Repository: YAPP-Github/27th-App-Team-1-iOS
Length of output: 1493
searchPlaces() 반환 타입을 정확한 응답 모델로 변경해야 합니다.
현재 Int 반환 타입은 PlaceAPI.swift에서 // 아직 적용x, PlaceRepositoryProtocol.swift에서 //임시 주석으로 표시되어 있으며, 이는 임시 placeholder입니다. 다른 메서드들(getPlacePhotos, getPlaceDetails)처럼 검색 결과를 담을 수 있는 적절한 응답 모델을 정의하고, 프로토콜(14줄)과 구현체의 반환 타입을 변경해야 합니다.
🤖 Prompt for AI Agents
In `@Projects/Modules/Networks/Sources/Service/PlaceService.swift` at line 14,
searchPlaces() currently returns Int as a temporary placeholder; define a proper
response model (e.g., PlaceSearchResponse or reuse an existing model consistent
with getPlacePhotos/getPlaceDetails) in the module, then update the
PlaceRepositoryProtocol declaration for searchPlaces() and its concrete
implementation in PlaceService (and any PlaceAPI helper in PlaceAPI.swift marked
"// 아직 적용x") to return that model instead of Int; also update any callers/tests
to handle the new response type and ensure decoding/mapping logic in the
implementation produces the new response model.
KimNahun
left a comment
There was a problem hiding this comment.
리팩토링의 방향성과 구조 설계가 좋습니다. Transform을 통한 DTO-Domain 변환도 깔끔합니다.
에러 핸들링이 어떻게 되는지 아직 확 와닿지는 않아서 이 부분이 된다면 좋을 것 같습니다.
| } | ||
|
|
||
| public func searchPlaces() async throws -> Int { | ||
| try await provider.asyncThowsRequest(.searchPlaces) |
| // | ||
| // PlaceRepositoryProtocol.swift | ||
| // Domain | ||
| // | ||
| // Created by 최안용 on 2/14/26. | ||
| // Copyright © 2026 NDGL-iOS. All rights reserved. | ||
| // | ||
|
|
||
| import Foundation | ||
|
|
||
| public protocol PlaceRepositoryInterface { | ||
| func searchPlaces() async throws -> Int //임시 | ||
| func fetchPlacePhotos(googlePlaceId: String) async throws -> [PlacePhoto] | ||
| func fetchPlaceDetail(googlePlaceId: String) async throws -> PlaceDetail | ||
| } |
There was a problem hiding this comment.
프로토콜 네이밍 불일치한 것 같습니다. PlaceRepositoryProtocol, PlaceRepositoryInterface
🔗 연결된 이슈
📄 작업 내용
💻 주요 코드 설명
AppComponent.swiftHomeUsecase.swift👀 기타 더 이야기해볼 점
현재
AuthPlugin및NetworkProviderFactory가 토큰 획득을 위해Domain모듈의TokenProviding인터페이스를 직접 참조하고 있습니다. 이로 인해 인프라 계층(Networks)이 비즈니스 로직 계층(Domain)을 의존하는 현상이 발생하고 있으며, 이는 다음과 같은 잠재적 문제를 야기할 수 있습니다.해당 부분 개선 방안 고민해보면 좋을 것 같습니다.
Summary by CodeRabbit
릴리스 노트