Skip to content

Comments

Feat/#90 온보딩 시 네트워크 연결#101

Merged
soseoyo12 merged 12 commits intodevelopfrom
feat/#90-informationViewAPI
Jan 20, 2026
Merged

Feat/#90 온보딩 시 네트워크 연결#101
soseoyo12 merged 12 commits intodevelopfrom
feat/#90-informationViewAPI

Conversation

@soseoyo12
Copy link
Contributor

@soseoyo12 soseoyo12 commented Jan 18, 2026

🔗 연결된 이슈

📄 작업 내용

  • 온보딩 시 post 요청
image

이건 get이 아니라서 사진이 부실해여,,,

잘 포스트가 됩니다~

@soseoyo12 soseoyo12 requested a review from a team January 18, 2026 17:55
@soseoyo12 soseoyo12 self-assigned this Jan 18, 2026
@soseoyo12 soseoyo12 requested review from sum130, wotjs020708 and y-eonee and removed request for a team January 18, 2026 17:55
@soseoyo12 soseoyo12 linked an issue Jan 18, 2026 that may be closed by this pull request
1 task
@soseoyo12 soseoyo12 added the Feature 새로운 기능 구현 및 API 연결 label Jan 18, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 18, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

프로필 생성 기능을 추가하기 위해 도메인, 데이터, 프레젠테이션 계층에 걸쳐 온보딩 흐름을 확장합니다. 새로운 저장소, 유스케이스, API 엔드포인트 및 관련 DTO를 도입하고, 뷰모델과 뷰를 업데이트하여 네트워크 기반 프로필 생성을 지원합니다.

Changes

Cohort / File(s) 요약
도메인 모델 및 인터페이스
Domain/Model/ProfileEntity.swift, Domain/Interface/OnboardingInterface.swift
프로필 데이터 모델과 프로필 생성을 위한 비동기 인터페이스 추가
도메인 유스케이스
Domain/UseCase/CreateProfileUseCase.swift
프로필 생성 유스케이스 프로토콜 및 구현체(DefaultCreateProfileUseCase) 추가
데이터 저장소
Data/Repository/OnboardingRepository.swift
OnboardingInterface 구현을 통한 기본 온보딩 저장소 추가
데이터 전송 객체
Data/Model/CreateProfileRequestDTO.swift, Data/Model/CreateProfileResponseDTO.swift
프로필 생성 요청/응답 DTO 추가
네트워크 엔드포인트
Data/Network/EndPoint/OnboardingAPI.swift
프로필 생성 API 경로(/api/onboarding/profiles) 정의
의존성 주입
Data/DataDependencyAssembler.swift, Domain/DomainDependencyAssembler.swift, Presentation/PresentationDependencyAssembler.swift
CreateProfileUseCase 및 OnboardingInterface 등록, DI 컨테이너 연결
프레젠테이션 계층
Presentation/Feature/Onboarding/OnboardingViewModel.swift, Presentation/Feature/Onboarding/InformationView.swift, Presentation/Feature/Onboarding/SplashView.swift
비동기 프로필 생성, 로딩 상태 관리, 상태 기반 네비게이션 추가
영속성
Data/Persistence/UserDefaultKey.swift
온보딩 완료 상태 저장을 위한 UserDefaultsKey 추가
자산 제거
Assets.xcassets/Splash/Contents.json
Splash 자산 카탈로그 메타데이터 제거

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant InformationView as InformationView
    participant OnboardingVM as OnboardingViewModel
    participant CreateProfileUC as CreateProfileUseCase
    participant OnboardingRepo as DefaultOnboardingRepository
    participant NetworkService as NetworkService
    participant AppCoordinator as AppCoordinator

    User->>InformationView: "Next" 버튼 클릭
    InformationView->>OnboardingVM: createProfile(name, age)
    activate OnboardingVM
    OnboardingVM->>OnboardingVM: isLoading = true
    OnboardingVM->>CreateProfileUC: execute(name, age)
    deactivate OnboardingVM
    
    activate CreateProfileUC
    CreateProfileUC->>OnboardingRepo: createProfile(name, age)
    deactivate CreateProfileUC
    
    activate OnboardingRepo
    OnboardingRepo->>NetworkService: POST /api/onboarding/profiles
    deactivate OnboardingRepo
    
    activate NetworkService
    Note over NetworkService: 네트워크 요청 실행
    NetworkService-->>OnboardingRepo: ProfileEntity (성공)
    deactivate NetworkService
    
    OnboardingRepo-->>CreateProfileUC: ProfileEntity
    CreateProfileUC-->>OnboardingVM: ProfileEntity
    
    activate OnboardingVM
    OnboardingVM->>OnboardingVM: isLoading = false
    OnboardingVM->>OnboardingVM: isOnboardingCompleted = true
    OnboardingVM-->>InformationView: 상태 업데이트 (isOnboardingCompleted)
    deactivate OnboardingVM
    
    InformationView->>AppCoordinator: navigationToTabbar()
    AppCoordinator->>User: 탭 바 화면으로 이동
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #32: 온보딩 뷰모델, 프레젠테이션 의존성 어셈블러, 온보딩 뷰 및 네비게이션 코드를 직접 수정하여 이 PR에서 도입된 온보딩 추가 기능과 관련
  • PR #101: 온보딩 인터페이스, 저장소, 유스케이스, API, DTO, DI 어셈블러 등 동일한 온보딩 코드 경로를 수정하여 코드 레벨에서 직접 관련
  • PR #35: 온보딩 네트워킹(OnboardingAPI, DTO, NetworkService 연결)이 PR #35에서 도입된 네트워킹 추상화(EndPoint, NetworkService, DefaultNetworkService)에 의존하므로 관련

Suggested reviewers

  • y-eonee
  • wotjs020708
🚥 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 제목 '온보딩 시 네트워크 연결'은 정보 보기 화면에서 네트워크 연결을 구현하는 변경사항의 핵심을 명확하게 요약합니다.
Description check ✅ Passed PR 설명은 이슈 #90 종료 및 온보딩 중 POST 요청 구현에 관한 실질적 정보를 포함하고 있습니다.
Linked Issues check ✅ Passed 변경사항은 이슈 #90의 목표인 '인포메이션 뷰 네트워크 연결'을 완전히 충족합니다. OnboardingAPI, CreateProfileUseCase, 관련 DTO 및 뷰 모델이 POST 요청을 성공적으로 구현합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항은 네트워크 연결 구현과 관련이 있습니다. Contents.json 삭제와 같은 사소한 변경을 제외하고 모두 온보딩 프로필 생성 기능 범위 내에 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#90-informationViewAPI

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Model/OnboardingDTO.swift`:
- Around line 24-29: CreateProfileResponseDTO.toEntity() currently uses an
ISO8601DateFormatter with .withFractionalSeconds which fails when the server
returns timestamps without fractional seconds, causing createdDate to
incorrectly fall back to Date(); update to attempt parsing with multiple
formats: first try ISO8601DateFormatter with [.withInternetDateTime,
.withFractionalSeconds], and if that returns nil try a second
ISO8601DateFormatter without .withFractionalSeconds (or a DateFormatter with
"yyyy-MM-dd'T'HH:mm:ssZ") before falling back to Date(), so createdDate reflects
the actual server time when possible.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/InformationView.swift`:
- Around line 73-79: The button action must re-validate input and UI state
before calling the network: inside the CherrishButton closure, add guards to
ensure the local state is correct (e.g., buttonState equals the actionable state
used when creating the profile), the viewModel is not loading
(viewModel.isLoading is false), and age can be parsed to Int (let ageValue =
Int(age)); only then call await viewModel.createProfile(name: name, age:
ageValue). This prevents accidental requests if CherrishButton does not block
actions.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/OnboardingViewModel.swift`:
- Around line 27-39: In createProfile(name:age:) avoid showing the generic error
message when the task was cancelled by handling cancellation explicitly: inside
the async function (or in the do/catch), detect CancellationError (or check
Task.isCancelled) and return early without setting errorMessage; keep the
existing catch let error as CherrishError branch for domain errors, and ensure
isLoading is cleared in a finally-like path (or after the do/catch) so loading
state is always reset even on cancellation. Reference: createProfile(name:age:),
createProfileUseCase.execute(...), errorMessage, isLoading.
- Around line 18-24: The init currently force-unwraps DIContainer.shared.resolve
for createProfileUseCase which can crash when DI is missing; fix by making
createProfileUseCase a required non-optional parameter (change
init(createProfileUseCase: CreateProfileUseCase, userDefaultService:
UserDefaultService = DefaultUserDefaultService())) so callers/tests must inject
it, or if you need a fallback keep the default but replace the force-unwrap with
a safe check (guard let resolved = DIContainer.shared.resolve(type:
CreateProfileUseCase.self) else { preconditionFailure("CreateProfileUseCase not
registered in DIContainer") }) and assign resolved to self.createProfileUseCase;
update references to the init and tests accordingly.

Comment on lines 24 to 29
extension CreateProfileResponseDTO {
func toEntity() -> ProfileEntity {
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let createdDate = dateFormatter.date(from: date) ?? Date()
return ProfileEntity(id: id, name: name, createdDate: createdDate)
Copy link

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
# Find the file in question
find . -name "OnboardingDTO.swift" -type f 2>/dev/null | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 128


🏁 Script executed:

#!/bin/bash
# List the structure to understand the repository layout
git ls-files | head -30

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 2440


🏁 Script executed:

#!/bin/bash
# Check if the file exists and look at the relevant lines
if [ -f "Cherrish-iOS/Cherrish-iOS/Data/Model/OnboardingDTO.swift" ]; then
  echo "=== File found ==="
  wc -l "Cherrish-iOS/Cherrish-iOS/Data/Model/OnboardingDTO.swift"
  echo ""
  echo "=== Content around lines 24-29 ==="
  sed -n '20,35p' "Cherrish-iOS/Cherrish-iOS/Data/Model/OnboardingDTO.swift"
else
  echo "File not found at expected path"
fi

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 572


🏁 Script executed:

#!/bin/bash
# Look for test files related to OnboardingDTO or CreateProfileResponseDTO
find . -name "*Test*" -o -name "*test*" -o -name "*Mock*" | grep -E "\.(swift|json)$" | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 133


🏁 Script executed:

#!/bin/bash
# Search for CreateProfileResponseDTO usage or tests
rg "CreateProfileResponseDTO" --type swift -A 5 -B 5

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 2674


🏁 Script executed:

#!/bin/bash
# Look for date parsing tests or examples
rg "ISO8601DateFormatter\|date.*format" --type swift -B 2 -A 2 | head -50

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check if there are any fixtures or mock data with date examples
rg "20[0-9]{2}-[0-9]{2}-[0-9]{2}" --type json | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check TestAPI.swift for mock responses
cat -n "Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/TestAPI.swift"

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 381


🏁 Script executed:

#!/bin/bash
# Look for any JSON or mock data files that might show server response format
find . -type f \( -name "*.json" -o -name "*mock*" -o -name "*Mock*" \) | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1882


🏁 Script executed:

#!/bin/bash
# Search for any endpoint definitions that might show the API response format
rg "OnboardingEndPoint\|createProfile" --type swift -A 10 -B 2

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Search for endpoint definitions more broadly
rg "EndPoint" --type swift | grep -i "onboard\|profile" | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 413


🏁 Script executed:

#!/bin/bash
# Look for the endpoint file itself
find . -name "*EndPoint*" -o -name "*Endpoint*" | grep swift

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 209


🏁 Script executed:

#!/bin/bash
# Check the entire OnboardingDTO file
cat -n "Cherrish-iOS/Cherrish-iOS/Data/Model/OnboardingDTO.swift"

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 944


🏁 Script executed:

#!/bin/bash
# Check the OnboardingEndPoint.swift file
cat -n "Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/OnboardingEndPoint.swift"

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1538


🏁 Script executed:

#!/bin/bash
# Search for any backend specifications or API documentation
find . -type f \( -name "*.md" -o -name "README*" -o -name "API*" -o -name "SPEC*" \) | head -20

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 159


🏁 Script executed:

#!/bin/bash
# Check the README for any API specification
cat "README.md"

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 2764


🏁 Script executed:

#!/bin/bash
# Search for any test files or assertions that show expected date format
find . -name "*Test*" -o -name "*Tests*" | xargs grep -l "date\|CreateProfile" 2>/dev/null | head -10

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 52


🌐 Web query:

ISO8601DateFormatter withFractionalSeconds parsing without fractional seconds

💡 Result:

Short answer: ISO8601DateFormatter is (or historically was) strict — enabling .withFractionalSeconds makes the parser expect fractional seconds, and disabling it rejects strings that include them. The usual practical fixes are either try two formatters (one with .withFractionalSeconds and one without) or normalize the input string (strip or add fractional seconds) before parsing. (forums.swift.org)

Minimal example (fallback approach):

  • Try parsing with [.withInternetDateTime, .withFractionalSeconds]; if that returns nil, try [.withInternetDateTime] (or the reverse order).

Alternative approaches:

  • Preprocess the string to add/remove fractional seconds so it matches a single formatter.
  • Use a third‑party ISO8601 parser or a custom Date decoding strategy that attempts multiple formats.
  • Note: newer Swift/Foundation proposals aim to make ISO8601 parsing accept optional fractional seconds regardless of the formatting flag (so future APIs may remove this problem). (forums.swift.org)

If you want, I can paste a short Swift code snippet showing the two‑formatter fallback.

Citations:


🌐 Web query:

iOS Swift ISO8601DateFormatter formatOptions strict parsing behavior

💡 Result:

Short answer: ISO8601DateFormatter’s formatOptions are strict for both formatting and parsing — the parser expects the date string to match the exact components you included in formatOptions. For example, if you set [.withInternetDateTime, .withFractionalSeconds] the formatter will accept strings with fractional seconds but will refuse the same timestamp without them; conversely, using .withInternetDateTime alone won’t accept fractional seconds unless you add .withFractionalSeconds. The same strictness applies to separators/timezone options (colon separators, full time, etc.), so you often need either multiple formatters (fallbacks) or to normalize incoming strings before parsing. [1][2]

References

  • StackOverflow explanation and iOS-version notes about withFractionalSeconds (parsing added iOS 11 / macOS 10.13). [1]
  • Swift forums discussion showing the strict/mandatory behavior and common workarounds (try without/with fractional seconds as fallback or normalize). [2]

Sources
[1] Stack Overflow: “ISO8601DateFormatter doesn't parse ISO date string”.
[2] Swift Forums: “ISO8601DateFormatter fails to parse a valid ISO-8601 date”.


ISO8601 파싱 실패 시 createdDate가 현재 시각으로 덮입니다.

ISO8601DateFormatter는 formatOptions에서 설정한 형식을 엄격하게 검증합니다. .withFractionalSeconds를 지정하면 파서는 입력값에 반드시 소수점 초가 포함되기를 기대합니다. 서버가 소수점 없이 응답하면 파싱이 실패하고 현재 시간으로 폴백되어 데이터 무결성 문제가 발생합니다.

🛠️ 제안 수정
-        let dateFormatter = ISO8601DateFormatter()
-        dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
-        let createdDate = dateFormatter.date(from: date) ?? Date()
+        let formatter = ISO8601DateFormatter()
+        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+        let fallback = ISO8601DateFormatter()
+        fallback.formatOptions = [.withInternetDateTime]
+        let createdDate = formatter.date(from: date)
+            ?? fallback.date(from: date)
+            ?? Date()
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Model/OnboardingDTO.swift` around lines 24 -
29, CreateProfileResponseDTO.toEntity() currently uses an ISO8601DateFormatter
with .withFractionalSeconds which fails when the server returns timestamps
without fractional seconds, causing createdDate to incorrectly fall back to
Date(); update to attempt parsing with multiple formats: first try
ISO8601DateFormatter with [.withInternetDateTime, .withFractionalSeconds], and
if that returns nil try a second ISO8601DateFormatter without
.withFractionalSeconds (or a DateFormatter with "yyyy-MM-dd'T'HH:mm:ssZ") before
falling back to Date(), so createdDate reflects the actual server time when
possible.

Comment on lines 73 to 79
CherrishButton(title: "다음", type: .next, state: $buttonState) {
Task {
guard let ageValue = Int(age) else { return }
await viewModel.createProfile(name: name, age: ageValue)
}
}
.disabled(viewModel.isLoading)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

유효성 검증을 액션 내부에서도 보강해주세요.

CherrishButton.normal 상태에서 액션을 차단하지 않는다면, 잘못된 입력으로 네트워크 요청이 나갈 수 있습니다. 상태와 무관하게 한 번 더 가드하는 편이 안전합니다.

✅ 제안 수정
            CherrishButton(title: "다음", type: .next, state: $buttonState) {
                Task {
+                    let isNameValid = !name.isEmpty && !isNameOverLimit
+                    let isAgeValid = !age.isEmpty && !isAgeOverLimit
+                    guard isNameValid, isAgeValid else { return }
                     guard let ageValue = Int(age) else { return }
                     await viewModel.createProfile(name: name, age: ageValue)
                }
            }
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/InformationView.swift`
around lines 73 - 79, The button action must re-validate input and UI state
before calling the network: inside the CherrishButton closure, add guards to
ensure the local state is correct (e.g., buttonState equals the actionable state
used when creating the profile), the viewModel is not loading
(viewModel.isLoading is false), and age can be parsed to Int (let ageValue =
Int(age)); only then call await viewModel.createProfile(name: name, age:
ageValue). This prevents accidental requests if CherrishButton does not block
actions.

Comment on lines 27 to 39
@MainActor
func createProfile(name: String, age: Int) async {
isLoading = true
errorMessage = nil

do {
let profile = try await createProfileUseCase.execute(name: name, age: age)
_ = userDefaultService.save(profile.id, key: .userID)
isOnboardingCompleted = true
} catch let error as CherrishError {
errorMessage = error.localizedDescription
} catch {
errorMessage = "알 수 없는 오류가 발생했습니다."
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

취소된 작업에 오류 메시지가 노출되지 않도록 분기하세요.

사용자가 화면을 떠나며 Task가 취소될 때도 “알 수 없는 오류”가 표시될 수 있습니다.

🔧 제안 수정안
-        } catch let error as CherrishError {
+        } catch is CancellationError {
+            // 취소는 사용자에게 오류로 노출하지 않음
+        } catch let error as CherrishError {
             errorMessage = error.localizedDescription
         } catch {
             errorMessage = "알 수 없는 오류가 발생했습니다."
         }
📝 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
@MainActor
func createProfile(name: String, age: Int) async {
isLoading = true
errorMessage = nil
do {
let profile = try await createProfileUseCase.execute(name: name, age: age)
_ = userDefaultService.save(profile.id, key: .userID)
isOnboardingCompleted = true
} catch let error as CherrishError {
errorMessage = error.localizedDescription
} catch {
errorMessage = "알 수 없는 오류가 발생했습니다."
`@MainActor`
func createProfile(name: String, age: Int) async {
isLoading = true
errorMessage = nil
do {
let profile = try await createProfileUseCase.execute(name: name, age: age)
_ = userDefaultService.save(profile.id, key: .userID)
isOnboardingCompleted = true
} catch is CancellationError {
// 취소는 사용자에게 오류로 노출하지 않음
} catch let error as CherrishError {
errorMessage = error.localizedDescription
} catch {
errorMessage = "알 수 없는 오류가 발생했습니다."
}
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/OnboardingViewModel.swift`
around lines 27 - 39, In createProfile(name:age:) avoid showing the generic
error message when the task was cancelled by handling cancellation explicitly:
inside the async function (or in the do/catch), detect CancellationError (or
check Task.isCancelled) and return early without setting errorMessage; keep the
existing catch let error as CherrishError branch for domain errors, and ensure
isLoading is cleared in a finally-like path (or after the do/catch) so loading
state is always reset even on cancellation. Reference: createProfile(name:age:),
createProfileUseCase.execute(...), errorMessage, isLoading.

Copy link
Contributor

@y-eonee y-eonee left a comment

Choose a reason for hiding this comment

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

온보딩하는 과정을 찍은 gif를 찍어주세요 ~ ~ 수고하셨습니다
리뷰 확인해주세요 !!

Comment on lines 26 to 29
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let createdDate = dateFormatter.date(from: date) ?? Date()
return ProfileEntity(id: id, name: name, createdDate: createdDate)
Copy link
Contributor

Choose a reason for hiding this comment

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

date가 유저엔티티에 필요하지는 않을 것 같아서 이 부분은 삭제하는게 좋을 것 같아요

Comment on lines 11 to 21
struct CreateProfileRequestDTO: Encodable {
let name: String
let age: Int
}


struct CreateProfileResponseDTO: Decodable {
let id: Int
let name: String
let date: String
}
Copy link
Contributor

Choose a reason for hiding this comment

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

리퀘스트랑 리스폰스는 파일을 분리하면 좋을 것 같아요

Comment on lines 9 to 10


Copy link
Contributor

Choose a reason for hiding this comment

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

줄바꿈삭제해주세요!!


import Alamofire

enum OnboardingEndPoint: EndPoint {
Copy link
Contributor

Choose a reason for hiding this comment

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

OnboardingEndPoint -> OnboardingAPI 로 바꾸면 좋을 것 같아염

struct ProfileEntity {
let id: Int
let name: String
let createdDate: Date
Copy link
Contributor

Choose a reason for hiding this comment

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

요건 지워도될듯!!

Comment on lines 76 to 79
#Preview {
OnboardingContainerView(viewModel: OnboardingViewModel())
.environmentObject(OnboardingCoordinator())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

삭제부탁 ~~

Copy link
Contributor

Choose a reason for hiding this comment

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

프리뷰!!! 아악
KakaoTalk_Photo_2026-01-09-23-22-18 012

Comment on lines 16 to 20
private let userDefaultService: UserDefaultService

init(
createProfileUseCase: CreateProfileUseCase? = nil,
userDefaultService: UserDefaultService = DefaultUserDefaultService()
Copy link
Contributor

Choose a reason for hiding this comment

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

유저디폴트가 프레젠테이션 레이어에서 필요할까요 ~~ ?? ,, 레포지토리 단으로 옮기면 좋을 것 같아요

Comment on lines 22 to 23
self.createProfileUseCase = createProfileUseCase
?? DIContainer.shared.resolve(type: CreateProfileUseCase.self)!
Copy link
Contributor

Choose a reason for hiding this comment

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

이거는 디아이컨테이너에서 해주세요 !!

Comment on lines 34 to 35
_ = userDefaultService.save(profile.id, key: .userID)
isOnboardingCompleted = true
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 데이터레이어로 옮겨주세용

Copy link

@coderabbitai coderabbitai bot left a comment

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 (1)
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)

43-45: 에러 로깅 일관성을 맞춰주세요.

FetchDashboardData 해결 실패 시 다른 guard 문(라인 21-22, 32)과 달리 CherrishLogger.error를 호출하지 않습니다. 일관성을 위해 에러 로깅을 추가하는 것이 좋습니다.

♻️ 제안 수정
         guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else {
+            CherrishLogger.error(CherrishError.DIFailedError)
             return
         }
🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/OnboardingAPI.swift`:
- Around line 48-56: The current bodyParameters implementation in OnboardingAPI
manually builds a dictionary for the .createProfile case; instead, return the
CreateProfileRequestDTO (which conforms to Encodable) so the NetworkService can
encode it automatically: update the bodyParameters accessor (or add a new
bodyEncodable property) to return the request DTO for .createProfile rather than
a [String:Any] literal, and ensure NetworkService sends Encodable bodies (or use
JSONEncoder in the endpoint implementation to produce Data from the DTO).
Reference: OnboardingAPI, bodyParameters, and CreateProfileRequestDTO.
- Around line 12-57: The OnboardingAPI and OnboardingEndPoint types are
duplicated; consolidate them by keeping a single endpoint definition (pick
either OnboardingAPI or OnboardingEndPoint) and remove the other. Update all
references/imports across the codebase to point to the retained type (e.g.,
OnboardingAPI or OnboardingEndPoint), ensure the retained enum includes the
CreateProfile(request: CreateProfileRequestDTO) case and the same
implementations for basePath, path, method, headers, parameterEncoding,
queryParameters, and bodyParameters, and delete the duplicate file to avoid
conflicts and duplicated symbols.

In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/OnboardingRepository.swift`:
- Around line 10-27: DefaultOnboardingRepository.createProfile currently ignores
the result of userDefaultService.save, which can hide persistence failures;
after receiving response (CreateProfileResponseDTO) call
userDefaultService.save(response.id, key: .userID) and check its return/throw
result, and if it indicates failure throw or propagate a Storage/Repository
error from createProfile so callers don't assume success; update createProfile
to either convert a false/save-failure into a thrown error (or adjust signature
if save is async/throws) and include the unique symbols
DefaultOnboardingRepository.createProfile, CreateProfileResponseDTO, and
userDefaultService.save in your change.

In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift`:
- Around line 44-50: There is a duplicate block that re-declares
onboardingRepository and re-registers CreateProfileUseCase; remove the redundant
guard/register block so onboardingRepository is only resolved once and
CreateProfileUseCase is registered a single time (keep the original guard let
onboardingRepository = DIContainer.shared.resolve(type:
OnboardingInterface.self) and the DIContainer.shared.register(type:
CreateProfileUseCase.self) { DefaultCreateProfileUseCase(repository:
onboardingRepository) } registration, and delete the repeated copy).
♻️ Duplicate comments (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/InformationView.swift (1)

80-83: 액션 내부에서 입력 유효성 검증을 보강해주세요.

현재 Int(age) 변환만 검증하고 있습니다. CherrishButton.normal 상태에서 액션을 차단하지 않는다면, 빈 이름이나 제한 초과 입력으로 네트워크 요청이 발생할 수 있습니다.

✅ 제안 수정
             Task {
+                guard !name.isEmpty, !isNameOverLimit else { return }
+                guard !age.isEmpty, !isAgeOverLimit else { return }
                 guard let ageValue = Int(age) else { return }
                 await viewModel.createProfile(name: name, age: ageValue)
             }
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/OnboardingViewModel.swift (2)

21-36: CancellationError 처리를 추가해 주세요.

사용자가 화면을 떠나 Task가 취소될 때 "알 수 없는 오류가 발생했습니다" 메시지가 표시될 수 있습니다.

🔧 CancellationError 처리 추가
         do {
             _ = try await createProfileUseCase.execute(name: name, age: age)
             isOnboardingCompleted = true
+        } catch is CancellationError {
+            // 취소는 사용자에게 오류로 노출하지 않음
         } catch let error as CherrishError {
             errorMessage = error.localizedDescription
         } catch {
             errorMessage = "알 수 없는 오류가 발생했습니다."
         }

23-35: defer를 사용하여 로딩 상태 정리를 보장하세요.

현재 구조에서는 예외 발생 시에도 isLoading = false가 실행되지만, defer를 사용하면 코드 흐름이 더 명확해집니다.

♻️ defer 사용 제안
     func createProfile(name: String, age: Int) async {
         isLoading = true
         errorMessage = nil
+        defer { isLoading = false }
         
         do {
             _ = try await createProfileUseCase.execute(name: name, age: age)
             isOnboardingCompleted = true
+        } catch is CancellationError {
+            // 취소는 오류로 표시하지 않음
         } catch let error as CherrishError {
             errorMessage = error.localizedDescription
         } catch {
             errorMessage = "알 수 없는 오류가 발생했습니다."
         }
-        
-        isLoading = false
     }

Comment on lines +12 to +57
enum OnboardingAPI: EndPoint {
case createProfile(request: CreateProfileRequestDTO)

var basePath: String {
return "/api/onboarding"
}

var path: String {
switch self {
case .createProfile:
return "/profiles"
}
}

var method: HTTPMethod {
switch self {
case .createProfile:
return .post
}
}

var headers: HeaderType {
switch self {
case .createProfile:
return .basic
}
}

var parameterEncoding: ParameterEncoding {
return JSONEncoding.default
}

var queryParameters: [String: String]? {
return nil
}

var bodyParameters: Parameters? {
switch self {
case .createProfile(let request):
return [
"name": request.name,
"age": request.age
]
}
}
}
Copy link

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
# Find OnboardingEndPoint.swift file
fd -t f "OnboardingEndPoint.swift" --exec cat {}

Repository: TEAM-Cherrish/Cherrish-iOS

Length of output: 1139


OnboardingAPI.swiftOnboardingEndPoint.swift는 동일한 기능을 하므로 중복을 제거해야 합니다.

두 파일이 같은 역할을 수행합니다. OnboardingAPIOnboardingEndPoint 모두 동일한 케이스, 동일한 basePath, path, method, headers, parameterEncoding, queryParameters, bodyParameters를 가지고 있습니다. 하나의 파일로 통합하고 다른 하나는 삭제하며, 이를 참조하는 모든 곳에서 임포트를 업데이트하세요.

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/OnboardingAPI.swift` around
lines 12 - 57, The OnboardingAPI and OnboardingEndPoint types are duplicated;
consolidate them by keeping a single endpoint definition (pick either
OnboardingAPI or OnboardingEndPoint) and remove the other. Update all
references/imports across the codebase to point to the retained type (e.g.,
OnboardingAPI or OnboardingEndPoint), ensure the retained enum includes the
CreateProfile(request: CreateProfileRequestDTO) case and the same
implementations for basePath, path, method, headers, parameterEncoding,
queryParameters, and bodyParameters, and delete the duplicate file to avoid
conflicts and duplicated symbols.

Comment on lines +48 to +56
var bodyParameters: Parameters? {
switch self {
case .createProfile(let request):
return [
"name": request.name,
"age": request.age
]
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

DTO의 Encodable 활용을 고려해 주세요.

CreateProfileRequestDTO가 이미 nameage를 가지고 있으므로, 수동으로 딕셔너리를 생성하는 대신 Encodable을 활용하면 유지보수성이 향상됩니다.

♻️ Encodable 활용 제안

만약 NetworkServiceEncodable body를 지원한다면:

     var bodyParameters: Parameters? {
         switch self {
         case .createProfile(let request):
-            return [
-                "name": request.name,
-                "age": request.age
-            ]
+            return request.asDictionary()  // 또는 Encodable extension 활용
         }
     }

DTO에 필드가 추가될 때 이 파일을 수정할 필요가 없어집니다.

🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/OnboardingAPI.swift` around
lines 48 - 56, The current bodyParameters implementation in OnboardingAPI
manually builds a dictionary for the .createProfile case; instead, return the
CreateProfileRequestDTO (which conforms to Encodable) so the NetworkService can
encode it automatically: update the bodyParameters accessor (or add a new
bodyEncodable property) to return the request DTO for .createProfile rather than
a [String:Any] literal, and ensure NetworkService sends Encodable bodies (or use
JSONEncoder in the endpoint implementation to produce Data from the DTO).
Reference: OnboardingAPI, bodyParameters, and CreateProfileRequestDTO.

Comment on lines 10 to 27
struct DefaultOnboardingRepository: OnboardingInterface {
private let networkService: NetworkService
private let userDefaultService: UserDefaultService

init(networkService: NetworkService, userDefaultService: UserDefaultService) {
self.networkService = networkService
self.userDefaultService = userDefaultService
}

func createProfile(name: String, age: Int) async throws -> Int {
let request = CreateProfileRequestDTO(name: name, age: age)
let response = try await networkService.request(
OnboardingAPI.createProfile(request: request),
decodingType: CreateProfileResponseDTO.self
)
_ = userDefaultService.save(response.id, key: .userID)
return response.id
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

저장소 구현이 잘 되어 있습니다.

클린 아키텍처 패턴에 따라 네트워크 요청과 로컬 저장을 적절히 분리했습니다.

다만, userDefaultService.save() 결과 처리를 고려해 주세요.

Line 25에서 저장 결과를 무시하고 있습니다. 저장 실패 시에도 성공으로 반환되어 데이터 불일치가 발생할 수 있습니다.

💡 저장 실패 처리 제안
-        _ = userDefaultService.save(response.id, key: .userID)
+        let saved = userDefaultService.save(response.id, key: .userID)
+        if !saved {
+            // 로깅 또는 에러 처리 고려
+        }
📝 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
struct DefaultOnboardingRepository: OnboardingInterface {
private let networkService: NetworkService
private let userDefaultService: UserDefaultService
init(networkService: NetworkService, userDefaultService: UserDefaultService) {
self.networkService = networkService
self.userDefaultService = userDefaultService
}
func createProfile(name: String, age: Int) async throws -> Int {
let request = CreateProfileRequestDTO(name: name, age: age)
let response = try await networkService.request(
OnboardingAPI.createProfile(request: request),
decodingType: CreateProfileResponseDTO.self
)
_ = userDefaultService.save(response.id, key: .userID)
return response.id
}
struct DefaultOnboardingRepository: OnboardingInterface {
private let networkService: NetworkService
private let userDefaultService: UserDefaultService
init(networkService: NetworkService, userDefaultService: UserDefaultService) {
self.networkService = networkService
self.userDefaultService = userDefaultService
}
func createProfile(name: String, age: Int) async throws -> Int {
let request = CreateProfileRequestDTO(name: name, age: age)
let response = try await networkService.request(
OnboardingAPI.createProfile(request: request),
decodingType: CreateProfileResponseDTO.self
)
let saved = userDefaultService.save(response.id, key: .userID)
if !saved {
// 로깅 또는 에러 처리 고려
}
return response.id
}
}
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/OnboardingRepository.swift` around
lines 10 - 27, DefaultOnboardingRepository.createProfile currently ignores the
result of userDefaultService.save, which can hide persistence failures; after
receiving response (CreateProfileResponseDTO) call
userDefaultService.save(response.id, key: .userID) and check its return/throw
result, and if it indicates failure throw or propagate a Storage/Repository
error from createProfile so callers don't assume success; update createProfile
to either convert a false/save-failure into a thrown error (or adjust signature
if save is async/throws) and include the unique symbols
DefaultOnboardingRepository.createProfile, CreateProfileResponseDTO, and
userDefaultService.save in your change.

Copy link
Contributor

@wotjs020708 wotjs020708 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 코드 참고 좀 하겠습니다!

Copy link
Contributor

@sum130 sum130 left a comment

Choose a reason for hiding this comment

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

구웃~ 어푸푸드립니다

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/SplashView.swift`:
- Around line 28-31: The delayed navigation in the SplashView's onAppear can be
scheduled as a cancellable DispatchWorkItem to prevent duplicate triggers; add a
`@State` optional DispatchWorkItem property (e.g., splashNavigationWorkItem) to
the SplashView, before creating a new DispatchWorkItem for calling
navigateBasedOnUserStatus() cancel any existing splashNavigationWorkItem, assign
the new work item and schedule it with DispatchQueue.main.asyncAfter, and ensure
you cancel the work item in onDisappear (or before scheduling) to avoid
duplicate/lingering navigation tasks.

Comment on lines 28 to 31
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
appCoordinator.navigationToOnboarding()
navigateBasedOnUserStatus()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

지연 네비게이션 중복 트리거 방지 필요.

onAppear가 여러 번 호출되면 3초 지연 작업이 중복 실행되어 네비게이션이 반복될 수 있습니다. 취소 가능한 작업으로 바꿔 주세요.

🛠️ 수정 제안 (취소 가능한 작업)
 struct SplashView: View {
     `@EnvironmentObject` private var appCoordinator: AppCoordinator
+    `@State` private var navigationWorkItem: DispatchWorkItem?
     
     private let userDefaultService: UserDefaultService? = DIContainer.shared.resolve(type: UserDefaultService.self)
 
     var body: some View {
         ZStack(alignment: .center) {
             LinearGradient(
@@
         .onAppear {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
-                navigateBasedOnUserStatus()
-            }
+            navigationWorkItem?.cancel()
+            let workItem = DispatchWorkItem { navigateBasedOnUserStatus() }
+            navigationWorkItem = workItem
+            DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: workItem)
         }
+        .onDisappear {
+            navigationWorkItem?.cancel()
+            navigationWorkItem = nil
+        }
         .ignoresSafeArea()
     }
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Onboarding/SplashView.swift`
around lines 28 - 31, The delayed navigation in the SplashView's onAppear can be
scheduled as a cancellable DispatchWorkItem to prevent duplicate triggers; add a
`@State` optional DispatchWorkItem property (e.g., splashNavigationWorkItem) to
the SplashView, before creating a new DispatchWorkItem for calling
navigateBasedOnUserStatus() cancel any existing splashNavigationWorkItem, assign
the new work item and schedule it with DispatchQueue.main.asyncAfter, and ensure
you cancel the work item in onDisappear (or before scheduling) to avoid
duplicate/lingering navigation tasks.

@soseoyo12 soseoyo12 merged commit 67c6dbf into develop Jan 20, 2026
1 check was pending
@soseoyo12 soseoyo12 deleted the feat/#90-informationViewAPI branch January 20, 2026 16:16
@coderabbitai coderabbitai bot mentioned this pull request Jan 20, 2026
Kimgyuilli pushed a commit that referenced this pull request Jan 21, 2026
y-eonee pushed a commit that referenced this pull request Jan 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature 새로운 기능 구현 및 API 연결 성용👶

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 인포메이션 뷰 네트워크 연결

4 participants