Skip to content

Commit e514cf5

Browse files
authored
Feat: 온보딩 서버 통신 구현 (#T3-102)
* Style: 통일성을 위한 폴더명 변경 * Refactor: BaseViewController 및 ViewModel에 public 접근제어자 삭제 * Refactor: NetworkService 리팩토링 - BaseResponseDTO DataSource에서 Network 모듈로 이관 - T 타입 반환에서 T? 타입 반환으로 수정 (EmptyResponse 대응) - Server Message 출력 * Feat: 온보딩 선택지 등록 및 추천 루틴 받기 구현 (#T3-102) - OnboardingEndpoint 정의 - OnboardingRepositoryProtocol · OnboardingRepository 구현 - OnboardingUseCaseProtocol · OnboardingUseCase 구현 * Feat: Onboarding 관련 의존성 조립 (#T3-102) - DataSource, Domain, Presentaion 레이어에서 OnboardingRepository, OnboardingUseCase, OnboardingViewModel 의존성 등록 * Feat: 로그인 결과에 따른 이용약관 vs 온보딩 화면 분기 처리 * Feat: CustomFontStyle 추가 - 디자인 시스템에 정의되어 있는 폰트 외에도 정의해서 사용할 수 있도록 custom 케이스 추가 * Feat: 온보딩 선택 결과 바탕으로 추천 받은 루틴 등록 구현 (#T3-102) * Refactor: AuthRepository do-catch 블록 수정 * Refactor: 의존성 없을 시 fatalError 발생하도록 수정 * Refactor: 온보딩 선택지 Dictonary 변경 로직 수정
1 parent ffb6323 commit e514cf5

60 files changed

Lines changed: 676 additions & 211 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ public struct DataSourceDependencyAssembler: DependencyAssemblerProtocol {
2525
let networkService = DIContainer.shared.resolve(type: NetworkServiceProtocol.self),
2626
let keychainStorage = DIContainer.shared.resolve(type: KeychainStorageProtocol.self),
2727
let userDefaultsStorage = DIContainer.shared.resolve(type: UserDefaultsStorageProtocol.self)
28-
else { return }
28+
else { fatalError("networkService, keychainStorage, userDefaultsStorage 의존성이 등록되지 않았습니다.") }
2929

3030
DIContainer.shared.register(type: AuthRepositoryProtocol.self) { _ in
3131
return AuthRepository(
3232
networkService: networkService,
3333
keychainStorage: keychainStorage,
3434
userDefaultsStorage: userDefaultsStorage)
3535
}
36+
37+
DIContainer.shared.register(type: OnboardingRepositoryProtocol.self) { _ in
38+
return OnboardingRepository(networkService: networkService, keychainStorage: keychainStorage)
39+
}
3640
}
3741
}

Projects/DataSource/Sources/Common/Error/AuthError.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ enum AuthError: Error, CustomStringConvertible {
1313
case nicknameSaveFailed
1414
case nicknameLoadFailed
1515
case nicknameRemoveFailed
16+
case invalidUserData
1617
case unknown(Error)
1718

1819
public var description: String {
@@ -31,6 +32,8 @@ enum AuthError: Error, CustomStringConvertible {
3132
return "닉네임 불러오기에 실패했습니다."
3233
case .nicknameRemoveFailed:
3334
return "닉네임 삭제에 실패했습니다."
35+
case .invalidUserData:
36+
return "서버 응답에 사용자 정보가 포함되어 있지 않습니다."
3437
case .unknown(let error):
3538
return "알 수 없는 에러가 발생했습니다. \(error.localizedDescription)"
3639
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//
2+
// EmptyResponseDTO.swift
3+
// DataSource
4+
//
5+
// Created by 최정인 on 7/15/25.
6+
//
7+
8+
struct EmptyResponseDTO: Decodable {}

Projects/DataSource/Sources/DTO/LoginResponseDTO.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77

88
import Domain
99

10-
typealias LoginResponseDTO = BaseResponseDTO<LoginResponse>
11-
12-
struct LoginResponse: Decodable {
10+
struct LoginResponseDTO: Decodable {
1311
let accessToken: String
1412
let refreshToken: String
1513
let userState: String
@@ -21,7 +19,7 @@ struct LoginResponse: Decodable {
2119
}
2220
}
2321

24-
extension LoginResponse {
22+
extension LoginResponseDTO {
2523
func toUserEntity() -> UserEntity {
2624
return UserEntity(
2725
accessToken: accessToken,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// RecommendedRoutineDTO.swift
3+
// DataSource
4+
//
5+
// Created by 최정인 on 7/15/25.
6+
//
7+
8+
import Domain
9+
10+
struct RecommendedRoutineListResponseDTO: Decodable {
11+
let recommendedRoutines: [RecommendedRoutineDTO]
12+
}
13+
14+
struct RecommendedRoutineDTO: Decodable {
15+
let id: Int
16+
let routineName: String
17+
let routineDescription: String
18+
let subRoutines: [SubRoutine]
19+
20+
enum CodingKeys: String, CodingKey {
21+
case id = "recommendedRoutineId"
22+
case routineName = "recommendedRoutineName"
23+
case routineDescription
24+
case subRoutines = "recommendedSubRoutines"
25+
}
26+
27+
func toRecommendedRoutineEntity() -> RecommendedRoutineEntity {
28+
return RecommendedRoutineEntity(
29+
id: id,
30+
title: routineName,
31+
description: routineDescription)
32+
}
33+
}
34+
35+
struct SubRoutine: Decodable {
36+
let id: Int
37+
let routineName: String
38+
39+
enum CodingKeys: String, CodingKey {
40+
case id = "recommendedSubRoutineId"
41+
case routineName = "recommendedSubRoutineName"
42+
}
43+
}

Projects/DataSource/Sources/Endpoints/AuthEndpoint.swift renamed to Projects/DataSource/Sources/Endpoint/AuthEndpoint.swift

File renamed without changes.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// OnboardingEndpoint.swift
3+
// DataSource
4+
//
5+
// Created by 최정인 on 7/15/25.
6+
//
7+
8+
import Foundation
9+
10+
enum OnboardingEndpoint {
11+
case registerOnboarding(accessToken: String, choices: [String: String])
12+
case registerRecommendedRoutine(accessToken: String, selectedRoutines: [Int])
13+
}
14+
15+
extension OnboardingEndpoint: Endpoint {
16+
var baseURL: String {
17+
return AppProperties.baseURL + "/api/v1/onboardings"
18+
}
19+
20+
var path: String {
21+
switch self {
22+
case .registerOnboarding: baseURL
23+
case .registerRecommendedRoutine: baseURL + "/routines"
24+
}
25+
}
26+
27+
var method: HTTPMethod {
28+
return .post
29+
}
30+
31+
var headers: [String : String] {
32+
var headers: [String: String] = [
33+
"Content-Type": "application/json",
34+
"accept": "*/*"
35+
]
36+
37+
switch self {
38+
case .registerOnboarding(let accessToken, _):
39+
headers["Authorization"] = "Bearer \(accessToken)"
40+
case .registerRecommendedRoutine(let accessToken, _):
41+
headers["Authorization"] = "Bearer \(accessToken)"
42+
}
43+
44+
return headers
45+
}
46+
47+
var queryParameters: [String : String] {
48+
return [:]
49+
}
50+
51+
var bodyParameters: [String : Any] {
52+
switch self {
53+
case .registerOnboarding(_, let choices):
54+
return choices
55+
case .registerRecommendedRoutine(_, let selectedRoutines):
56+
return ["recommendedRoutineIds": selectedRoutines]
57+
}
58+
}
59+
}

Projects/DataSource/Sources/Protocols/KeychainStorageProtocol.swift renamed to Projects/DataSource/Sources/Protocol/KeychainStorageProtocol.swift

File renamed without changes.

Projects/DataSource/Sources/Protocols/NetworkServiceProtocol.swift renamed to Projects/DataSource/Sources/Protocol/NetworkServiceProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ public protocol NetworkServiceProtocol {
1515
/// - endpoint: 요청을 보낼 API Endpoint 정보
1616
/// - type: 디코딩할 Response DTO 타입
1717
/// - Returns: 응답 데이터를 디코딩한 객체
18-
func request<T: Decodable>(endpoint: Endpoint, type: T.Type) async throws -> T
18+
func request<T: Decodable>(endpoint: Endpoint, type: T.Type) async throws -> T?
1919
}

Projects/DataSource/Sources/Protocols/UserDefaultsStorageProtocol.swift renamed to Projects/DataSource/Sources/Protocol/UserDefaultsStorageProtocol.swift

File renamed without changes.

0 commit comments

Comments
 (0)