From d13819448956f40cc10bc35c2ac21c0028af8593 Mon Sep 17 00:00:00 2001 From: hooni Date: Tue, 16 Jul 2024 23:05:00 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix/#187=20=ED=82=A4=EC=B2=B4=EC=9D=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=9D=B4=EC=8A=88=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 40 ++-- .../xcshareddata/swiftpm/Package.resolved | 5 +- KkuMulKum/KkuMulKumDebug.entitlements | 14 ++ .../DTO/Model/Auth/NicknameModel.swift | 12 + .../DTO/TargetType/NicknameTargetType.swift | 54 +++++ KkuMulKum/Resource/Info.plist | 102 +++++---- .../KeyChain/KeychainAccessible.swift | 97 ++++---- .../Resource/KeyChain/KeychainService.swift | 213 +++++++++--------- .../Service/AuthService.swift | 20 +- .../Source/Onboarding/Login/Keychain.swift | 79 +++++++ .../Login/VIewModel/LoginViewModel.swift | 66 ++---- .../NicknameViewController.swift | 39 +++- .../ViewModel/NicknameViewModel.swift | 70 ++++++ 13 files changed, 522 insertions(+), 289 deletions(-) create mode 100644 KkuMulKum/KkuMulKumDebug.entitlements create mode 100644 KkuMulKum/Network/DTO/Model/Auth/NicknameModel.swift create mode 100644 KkuMulKum/Network/DTO/TargetType/NicknameTargetType.swift rename KkuMulKum/{Source/Onboarding/Login => Resource}/Service/AuthService.swift (57%) create mode 100644 KkuMulKum/Source/Onboarding/Login/Keychain.swift diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 7731b54a..a39abf29 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 789873342C3D1A7B00435E96 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789873312C3D1A7B00435E96 /* LoginView.swift */; }; 789AD4B32C3C0093002E2688 /* SocialLoginResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */; }; 789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */; }; + 789D73A72C46AF4900C7077D /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A62C46AF4900C7077D /* Keychain.swift */; }; 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; }; @@ -67,6 +68,8 @@ 78BD612F2C4561B9005752FD /* KeychainAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */; }; 78BD61312C456799005752FD /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61302C456799005752FD /* KeychainService.swift */; }; 78BD61342C45B4A7005752FD /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61332C45B4A7005752FD /* AuthService.swift */; }; + 78BD61362C463B82005752FD /* NicknameTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61352C463B82005752FD /* NicknameTargetType.swift */; }; + 78BD61382C463C8C005752FD /* NicknameModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61372C463C8C005752FD /* NicknameModel.swift */; }; A3DD9C3D2C41BAD000E58A13 /* MeetingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C322C41BAD000E58A13 /* MeetingTableViewCell.swift */; }; A3DD9C3F2C41BAD000E58A13 /* MeetingListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C362C41BAD000E58A13 /* MeetingListView.swift */; }; A3DD9C402C41BAD000E58A13 /* MeetingListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C382C41BAD000E58A13 /* MeetingListViewController.swift */; }; @@ -202,6 +205,7 @@ 789873312C3D1A7B00435E96 /* LoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginResponseModel.swift; sourceTree = ""; }; 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResissueResponseModel.swift; sourceTree = ""; }; + 789D73A62C46AF4900C7077D /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 78AED1312C3D94F2000AD80A /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; @@ -216,6 +220,8 @@ 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessible.swift; sourceTree = ""; }; 78BD61302C456799005752FD /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; 78BD61332C45B4A7005752FD /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + 78BD61352C463B82005752FD /* NicknameTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameTargetType.swift; sourceTree = ""; }; + 78BD61372C463C8C005752FD /* NicknameModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameModel.swift; sourceTree = ""; }; A3DD9C322C41BAD000E58A13 /* MeetingTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingTableViewCell.swift; sourceTree = ""; }; A3DD9C362C41BAD000E58A13 /* MeetingListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingListView.swift; sourceTree = ""; }; A3DD9C382C41BAD000E58A13 /* MeetingListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingListViewController.swift; sourceTree = ""; }; @@ -542,6 +548,7 @@ isa = PBXGroup; children = ( 78BD61262C446A97005752FD /* LoginTargetType.swift */, + 78BD61352C463B82005752FD /* NicknameTargetType.swift */, ); path = TargetType; sourceTree = ""; @@ -563,30 +570,6 @@ path = KeyChain; sourceTree = ""; }; - 78BD61322C45B4A7005752FD /* Service */ = { - isa = PBXGroup; - children = ( - 78BD61332C45B4A7005752FD /* AuthService.swift */, - ); - path = Service; - sourceTree = ""; - }; - A3DD9C332C41BAD000E58A13 /* Cell */ = { - isa = PBXGroup; - children = ( - A3DD9C322C41BAD000E58A13 /* MeetingTableViewCell.swift */, - ); - path = Cell; - sourceTree = ""; - }; - A3DD9C352C41BAD000E58A13 /* Model */ = { - isa = PBXGroup; - children = ( - A3DD9C342C41BAD000E58A13 /* MeetingDummyModel.swift */, - ); - path = Model; - sourceTree = ""; - }; A3DD9C372C41BAD000E58A13 /* View */ = { isa = PBXGroup; children = ( @@ -633,6 +616,7 @@ A3DD9C642C45B30600E58A13 /* Service */ = { isa = PBXGroup; children = ( + 78BD61332C45B4A7005752FD /* AuthService.swift */, A3DD9C612C455E3300E58A13 /* MeetingListService.swift */, ); path = Service; @@ -978,7 +962,7 @@ DD865B662C39210E00C351A2 /* Login */ = { isa = PBXGroup; children = ( - 78BD61322C45B4A7005752FD /* Service */, + 789D73A62C46AF4900C7077D /* Keychain.swift */, 789873372C3D1B4800435E96 /* ViewController */, 789873362C3D1B3900435E96 /* VIewModel */, 789873352C3D1B3000435E96 /* View */, @@ -1271,6 +1255,7 @@ DE9E18912C3BCC9D00DB76B4 /* SocialLoginRequestModel.swift */, 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */, 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */, + 78BD61372C463C8C005752FD /* NicknameModel.swift */, ); path = Auth; sourceTree = ""; @@ -1480,6 +1465,7 @@ DED5DBEC2C345210006ECE7E /* BaseViewController.swift in Sources */, DE6D4D142C3F14D80005584B /* MeetingPromiseCell.swift in Sources */, DD3976892C41C2AD00E2A4C4 /* TodayEmptyView.swift in Sources */, + 789D73A72C46AF4900C7077D /* Keychain.swift in Sources */, DD43937C2C412F4500EC1799 /* InviteCodeViewController.swift in Sources */, DD4393782C412F4500EC1799 /* CheckInviteCodeView.swift in Sources */, DE6D4D102C3F14D80005584B /* InvitationCodePopUpView.swift in Sources */, @@ -1501,6 +1487,7 @@ DD43937D2C412F4500EC1799 /* CheckInviteCodeViewController.swift in Sources */, DD931B742C3DAB9A00526452 /* ReadyPlanInfoView.swift in Sources */, DD4393772C412F4500EC1799 /* CreateMeetingView.swift in Sources */, + 78BD61362C463B82005752FD /* NicknameTargetType.swift in Sources */, 789873342C3D1A7B00435E96 /* LoginView.swift in Sources */, 782B40822C3E4925008B0CA7 /* NicknameViewModel.swift in Sources */, 782B406F2C3DBF93008B0CA7 /* ProfileSetupViewController.swift in Sources */, @@ -1520,6 +1507,7 @@ DDAF1C922C3D6E3D008A37D3 /* PromiseInfoViewController.swift in Sources */, 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */, DE254AB42C31199B00A4015E /* UITextField+.swift in Sources */, + 78BD61382C463C8C005752FD /* NicknameModel.swift in Sources */, DDAF1C912C3D6E3D008A37D3 /* TardyViewController.swift in Sources */, 782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */, DE8248002C36E857000601BC /* ObservablePattern.swift in Sources */, @@ -1747,7 +1735,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = KkuMulKum/KkuMulKum.entitlements; + CODE_SIGN_ENTITLEMENTS = KkuMulKum/KkuMulKumDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; diff --git a/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bdd12205..69ed5788 100644 --- a/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "6f65ada45f770fae6123df6430e807e91049442b2f17b5aa0f91df3077eedc5a", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -175,7 +174,7 @@ { "identity" : "rxswift", "kind" : "remoteSourceControl", - "location" : "https://github.com/ReactiveX/RxSwift.git", + "location" : "https://github.com/ReactiveX/RxSwift", "state" : { "revision" : "b06a8c8596e4c3e8e7788e08e720e3248563ce6a", "version" : "6.7.1" @@ -218,5 +217,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/KkuMulKum/KkuMulKumDebug.entitlements b/KkuMulKum/KkuMulKumDebug.entitlements new file mode 100644 index 00000000..0ccc911f --- /dev/null +++ b/KkuMulKum/KkuMulKumDebug.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.applesignin + + Default + + keychain-access-groups + + $(AppIdentifierPrefix)KkuMulKum.yizihn + + + diff --git a/KkuMulKum/Network/DTO/Model/Auth/NicknameModel.swift b/KkuMulKum/Network/DTO/Model/Auth/NicknameModel.swift new file mode 100644 index 00000000..bf1c1788 --- /dev/null +++ b/KkuMulKum/Network/DTO/Model/Auth/NicknameModel.swift @@ -0,0 +1,12 @@ +// +// NicknameModel.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation + +struct NameResponseModel: ResponseModelType { + let name: String +} diff --git a/KkuMulKum/Network/DTO/TargetType/NicknameTargetType.swift b/KkuMulKum/Network/DTO/TargetType/NicknameTargetType.swift new file mode 100644 index 00000000..f00f1453 --- /dev/null +++ b/KkuMulKum/Network/DTO/TargetType/NicknameTargetType.swift @@ -0,0 +1,54 @@ +// +// LoginTatgetType.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation + +import Moya + +enum NicknameTargetType { + case updateName(name: String) +} + +extension NicknameTargetType: TargetType { + + var baseURL: URL { + guard let privacyInfo = Bundle.main.privacyInfo, + let urlString = privacyInfo["BASE_URL"] as? String, + let url = URL(string: urlString) else { + fatalError("Invalid BASE_URL in PrivacyInfo.plist") + } + return url + } + + var path: String { + switch self { + case .updateName: + return "/api/v1/users/me/name" + } + } + + var method: Moya.Method { + switch self { + case .updateName: + return .patch + } + } + + var task: Task { + switch self { + case .updateName(let name): + return .requestParameters(parameters: ["name": name], encoding: JSONEncoding.default) + } + } + + var headers: [String : String]? { + guard let token = DefaultKeychainService.shared.accessToken else { + fatalError("No access token available") + } + return ["Content-Type": "application/json", "Authorization": "Bearer \(token)"] + } +} diff --git a/KkuMulKum/Resource/Info.plist b/KkuMulKum/Resource/Info.plist index b4103525..2ed41a5a 100644 --- a/KkuMulKum/Resource/Info.plist +++ b/KkuMulKum/Resource/Info.plist @@ -2,50 +2,62 @@ - NSPhotoLibraryAddUsageDescription - 정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진! - NSCameraUsageDescription - 정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진! - UIUserInterfaceStyle - Light - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - - - - UIAppFonts - - Pretendard-Black.otf - Pretendard-Bold.otf - Pretendard-ExtraBold.otf - Pretendard-ExtraLight.otf - Pretendard-Light.otf - Pretendard-Medium.otf - Pretendard-Regular.otf - Pretendard-SemiBold.otf - Pretendard-Thin.otf - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - - - - + LSApplicationQueriesSchemes + + kakaotalk + kakaoplus + kakaolink + kakaokompassauth + + NSPhotoLibraryAddUsageDescription + 사진 라이브러리 접근 권한이 필요합니다. + NSCameraUsageDescription + 카메라 사용 권한이 필요합니다. + UIUserInterfaceStyle + Light + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + kakao69aeef4a49d5b6772d62efdf1686994c + + + + KeychainAccessGroups + + $(AppIdentifierPrefix)KkuMulKum.yizihn + + UIAppFonts + + Pretendard-Black.otf + Pretendard-Bold.otf + Pretendard-ExtraBold.otf + Pretendard-ExtraLight.otf + Pretendard-Light.otf + Pretendard-Medium.otf + Pretendard-Regular.otf + Pretendard-SemiBold.otf + Pretendard-Thin.otf + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + diff --git a/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift b/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift index 52c8ab3c..29730560 100644 --- a/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift +++ b/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift @@ -1,46 +1,57 @@ +//// +//// KeychainWrapper.swift +//// KkuMulKum +//// +//// Created by 이지훈 on 7/15/24. // -// KeychainWrapper.swift -// KkuMulKum +//import Foundation +//import SwiftKeychainWrapper // -// Created by 이지훈 on 7/15/24. +//protocol KeychainAccessible { +// func saveToken(_ key: String, _ value: String) -> Bool +// func saveExpiresIn(_ key: String, _ value: Int) -> Bool +// func getToken(_ key: String) -> String? +// func getExpiresIn(_ Key: String) -> Int? +// func remove(_ key: String) -> Bool +// func removeAll() -> Bool +//} // - -import Foundation -import SwiftKeychainWrapper - -protocol KeychainAccessible { - func saveToken(_ key: String, _ value: String) - func saveExpiresIn(_ key: String, _ value: Int) - func getToken(_ key: String) -> String? - func getExpiresIn(_ Key: String) -> Int? - func remove(_ key: String) - func removeAll() -} - -class DefaultKeychainAccessible: KeychainAccessible { - private let keychain = KeychainWrapper.standard - - func saveToken(_ key: String, _ value: String) { - keychain.set(value, forKey: key) - } - - func saveExpiresIn(_ key: String, _ value: Int) { - keychain.set(value, forKey: key) - } - - func getToken(_ key: String) -> String? { - keychain.string(forKey: key) - } - - func getExpiresIn(_ Key: String) -> Int? { - keychain.integer(forKey: Key) - } - - func remove(_ key: String) { - keychain.removeObject(forKey: key) - } - - func removeAll() { - keychain.removeAllKeys() - } -} +//class DefaultKeychainAccessible: KeychainAccessible { +// private var keychain = KeychainWrapper.standard +// +// init() { +// let bundleId = Bundle.main.bundleIdentifier ?? "KkuMulKum.yizihn" +// let accessGroup = "D2DRA3F792.KkuMulKum.yizihn" // 엔타이틀먼트 파일의 접근 그룹과 일치해야 함 +// self.keychain = KeychainWrapper(serviceName: bundleId, accessGroup: accessGroup) +// print("Keychain initialized with bundleId: \(bundleId), accessGroup: \(accessGroup)") +// } +// +// func saveToken(_ key: String, _ value: String) -> Bool { +// // withAccessibility 옵션을 .afterFirstUnlock으로 설정 +// let result = keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlock) +// return result +// } +// +// func getToken(_ key: String) -> String? { +// let token = keychain.string(forKey: key) +// return token +// } +// +// func saveExpiresIn(_ key: String, _ value: Int) -> Bool { +// return keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlockThisDeviceOnly) +// } +// +// +// +// func getExpiresIn(_ Key: String) -> Int? { +// return keychain.integer(forKey: Key) +// } +// +// func remove(_ key: String) -> Bool { +// return keychain.removeObject(forKey: key) +// } +// +// func removeAll() -> Bool { +// return keychain.removeAllKeys() +// } +//} diff --git a/KkuMulKum/Resource/KeyChain/KeychainService.swift b/KkuMulKum/Resource/KeyChain/KeychainService.swift index ddbf57c1..5b358493 100644 --- a/KkuMulKum/Resource/KeyChain/KeychainService.swift +++ b/KkuMulKum/Resource/KeyChain/KeychainService.swift @@ -1,110 +1,109 @@ +//// +//// KeychainService.swift +//// KkuMulKum +//// +//// Created by 이지훈 on 7/15/24. +//// // -// KeychainService.swift -// KkuMulKum +//import Foundation // -// Created by 이지훈 on 7/15/24. +//protocol KeychainService { +// var accessToken: String? { get set } +// var refreshToken: String? { get set } +// var accessTokenExpiresIn: Int? { get set } +//} // - -import Foundation - -protocol KeychainService { - var accessToken: String? { get set } - var refreshToken: String? { get set } - var accessTokenExpiresIn: Int? { get set } -} - -class DefaultKeychainService: KeychainService { - static let shared = DefaultKeychainService() - private init() {} - - private let keychainAccess = DefaultKeychainAccessible() - - struct Key { - static let tempAccessToken = "tempAccessToken" - static let tempRefreshToken = "tempRefreshToken" - static let tempAccessTokenExpiresIn = "tempAccessTokenExpiresIn" - - static let accessToken = "accessToken" - static let refreshToken = "refreshToken" - static let accessTokenExpiresIn = "accessTokenExpiresIn" - } - - var tempAccessToken: String? { - get { - keychainAccess.getToken(Key.tempAccessToken) - } - set { - if newValue != nil { - keychainAccess.saveToken(Key.tempAccessToken, newValue ?? "") - } else { - keychainAccess.remove(Key.tempAccessToken) - } - } - } - - var tempRefreshToken: String? { - get { - keychainAccess.getToken(Key.tempRefreshToken) - } - set { - if newValue != nil { - keychainAccess.saveToken(Key.tempRefreshToken, newValue ?? "") - } else { - keychainAccess.remove(Key.tempRefreshToken) - } - } - } - - var tempAccessTokenExpiresIn: Int? { - get { - keychainAccess.getExpiresIn(Key.tempAccessTokenExpiresIn) - } - set { - if newValue != nil { - keychainAccess.saveExpiresIn(Key.tempAccessTokenExpiresIn, newValue ?? 0) - } else { - keychainAccess.remove(Key.tempAccessTokenExpiresIn) - } - } - } - - - var accessToken: String? { - get { - keychainAccess.getToken(Key.accessToken) - } - set { - if newValue != nil { - keychainAccess.saveToken(Key.accessToken, newValue ?? "") - } else { - keychainAccess.remove(Key.accessToken) - } - } - } - - var refreshToken: String? { - get { - keychainAccess.getToken(Key.refreshToken) - } - set { - if newValue != nil { - keychainAccess.saveToken(Key.refreshToken, newValue ?? "") - } else { - keychainAccess.remove(Key.refreshToken) - } - } - } - - var accessTokenExpiresIn: Int? { - get { - keychainAccess.getExpiresIn(Key.accessTokenExpiresIn) - } - set { - if newValue != nil { - keychainAccess.saveExpiresIn(Key.accessTokenExpiresIn, newValue ?? 0) - } else { - keychainAccess.remove(Key.accessTokenExpiresIn) - } - } - } -} +//class DefaultKeychainService: KeychainService { +// static let shared = DefaultKeychainService() +// private init() {} +// +// private let keychainAccess = DefaultKeychainAccessible() +// +// struct Key { +// static let tempAccessToken = "tempAccessToken" +// static let tempRefreshToken = "tempRefreshToken" +// static let tempAccessTokenExpiresIn = "tempAccessTokenExpiresIn" +// +// static let accessToken = "accessToken" +// static let refreshToken = "refreshToken" +// static let accessTokenExpiresIn = "accessTokenExpiresIn" +// } +// +// var tempAccessToken: String? { +// get { +// keychainAccess.getToken(Key.tempAccessToken) +// } +// set { +// if newValue != nil { +// keychainAccess.saveToken(Key.tempAccessToken, newValue ?? "") +// } else { +// keychainAccess.remove(Key.tempAccessToken) +// } +// } +// } +// +// var tempRefreshToken: String? { +// get { +// keychainAccess.getToken(Key.tempRefreshToken) +// } +// set { +// if newValue != nil { +// keychainAccess.saveToken(Key.tempRefreshToken, newValue ?? "") +// } else { +// keychainAccess.remove(Key.tempRefreshToken) +// } +// } +// } +// +// var tempAccessTokenExpiresIn: Int? { +// get { +// keychainAccess.getExpiresIn(Key.tempAccessTokenExpiresIn) +// } +// set { +// if newValue != nil { +// keychainAccess.saveExpiresIn(Key.tempAccessTokenExpiresIn, newValue ?? 0) +// } else { +// keychainAccess.remove(Key.tempAccessTokenExpiresIn) +// } +// } +// } +// +// var accessToken: String? { +// get { +// keychainAccess.getToken(Key.accessToken) +// } +// set { +// if newValue != nil { +// keychainAccess.saveToken(Key.accessToken, newValue ?? "") +// } else { +// keychainAccess.remove(Key.accessToken) +// } +// } +// } +// +// var refreshToken: String? { +// get { +// keychainAccess.getToken(Key.refreshToken) +// } +// set { +// if newValue != nil { +// keychainAccess.saveToken(Key.refreshToken, newValue ?? "") +// } else { +// keychainAccess.remove(Key.refreshToken) +// } +// } +// } +// +// var accessTokenExpiresIn: Int? { +// get { +// keychainAccess.getExpiresIn(Key.accessTokenExpiresIn) +// } +// set { +// if newValue != nil { +// keychainAccess.saveExpiresIn(Key.accessTokenExpiresIn, newValue ?? 0) +// } else { +// keychainAccess.remove(Key.accessTokenExpiresIn) +// } +// } +// } +//} diff --git a/KkuMulKum/Source/Onboarding/Login/Service/AuthService.swift b/KkuMulKum/Resource/Service/AuthService.swift similarity index 57% rename from KkuMulKum/Source/Onboarding/Login/Service/AuthService.swift rename to KkuMulKum/Resource/Service/AuthService.swift index faea5b93..273c6a55 100644 --- a/KkuMulKum/Source/Onboarding/Login/Service/AuthService.swift +++ b/KkuMulKum/Resource/Service/AuthService.swift @@ -8,11 +8,11 @@ import Foundation protocol AuthServiceType { - func saveAccessToken(_ token: String) - func saveRefreshToken(_ token: String) + func saveAccessToken(_ token: String) -> Bool + func saveRefreshToken(_ token: String) -> Bool func getAccessToken() -> String? func getRefreshToken() -> String? - func clearTokens() + func clearTokens() -> Bool } class AuthService: AuthServiceType { @@ -22,12 +22,14 @@ class AuthService: AuthServiceType { self.keychainService = keychainService } - func saveAccessToken(_ token: String) { + func saveAccessToken(_ token: String) -> Bool { keychainService.accessToken = token + return keychainService.accessToken == token } - func saveRefreshToken(_ token: String) { + func saveRefreshToken(_ token: String) -> Bool { keychainService.refreshToken = token + return keychainService.refreshToken == token } func getAccessToken() -> String? { @@ -38,9 +40,9 @@ class AuthService: AuthServiceType { return keychainService.refreshToken } - ///앱잼내 구현 X - func clearTokens() { -// keychainService.accessToken = nil -// keychainService.refreshToken = nil + func clearTokens() -> Bool { + keychainService.accessToken = nil + keychainService.refreshToken = nil + return keychainService.accessToken == nil && keychainService.refreshToken == nil } } diff --git a/KkuMulKum/Source/Onboarding/Login/Keychain.swift b/KkuMulKum/Source/Onboarding/Login/Keychain.swift new file mode 100644 index 00000000..7b7576de --- /dev/null +++ b/KkuMulKum/Source/Onboarding/Login/Keychain.swift @@ -0,0 +1,79 @@ +// +// Keychain.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation +import Security + +protocol KeychainService { + var accessToken: String? { get set } + var refreshToken: String? { get set } +} + +class DefaultKeychainService: KeychainService { + static let shared = DefaultKeychainService() + + private init() {} + + private func save(_ data: Data, forKey key: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecValueData as String: data + ] + + SecItemDelete(query as CFDictionary) + SecItemAdd(query as CFDictionary, nil) + } + + private func load(forKey key: String) -> Data? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: true + ] + + var result: AnyObject? + SecItemCopyMatching(query as CFDictionary, &result) + return result as? Data + } + + var accessToken: String? { + get { + guard let data = load(forKey: "accessToken") else { return nil } + return String(data: data, encoding: .utf8) + } + set { + guard let newValue = newValue else { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: "accessToken" + ] + SecItemDelete(query as CFDictionary) + return + } + save(Data(newValue.utf8), forKey: "accessToken") + } + } + + var refreshToken: String? { + get { + guard let data = load(forKey: "refreshToken") else { return nil } + return String(data: data, encoding: .utf8) + } + set { + guard let newValue = newValue else { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: "refreshToken" + ] + SecItemDelete(query as CFDictionary) + return + } + save(Data(newValue.utf8), forKey: "refreshToken") + } + } +} diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 658639ef..96eaaf3e 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -23,16 +23,16 @@ class LoginViewModel: NSObject { var error: ObservablePattern = ObservablePattern("") private let provider: MoyaProvider - private var keychainService: KeychainService + private var authService: AuthServiceType init( provider: MoyaProvider = MoyaProvider( plugins: [NetworkLoggerPlugin(configuration: .init(logOptions: .verbose))] ), - keychainService: KeychainService = DefaultKeychainService.shared + authService: AuthServiceType = AuthService() ) { self.provider = provider - self.keychainService = keychainService + self.authService = authService super.init() } @@ -125,47 +125,24 @@ class LoginViewModel: NSObject { } } } + + private func saveTokens(accessToken: String, refreshToken: String) { + print("Attempting to save tokens") + let accessTokenSaved = authService.saveAccessToken(accessToken) + let refreshTokenSaved = authService.saveRefreshToken(refreshToken) - private func saveTokens(accessToken: String, refreshToken: String) { - keychainService.accessToken = accessToken - keychainService.refreshToken = refreshToken - print("Tokens saved to keychain") - } + print("Access token saved: \(accessTokenSaved), Refresh token saved: \(refreshTokenSaved)") - func refreshToken() { - guard let refreshToken = keychainService.refreshToken else { - error.value = "No refresh token available" - return - } - - provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in - switch result { - case .success(let response): - do { - let refreshResponse = try response.map(ResponseBodyDTO.self) - if refreshResponse.success, let data = refreshResponse.data { - self?.saveTokens(accessToken: data.accessToken, refreshToken: data.refreshToken) - self?.loginState.value = .login - } else if let error = refreshResponse.error { - self?.error.value = error.message - } - } catch { - self?.error.value = "Failed to decode refresh token response" - } - case .failure(let error): - self?.error.value = "Token refresh failed: \(error.localizedDescription)" - } - } + if accessTokenSaved && refreshTokenSaved { + print("Tokens successfully saved") + } else { + print("Failed to save tokens") } } +} - -extension LoginViewModel: ASAuthorizationControllerDelegate, - ASAuthorizationControllerPresentationContextProviding { - func authorizationController( - controller: ASAuthorizationController, - didCompleteWithAuthorization authorization: ASAuthorization - ) { +extension LoginViewModel: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { print("Apple authorization completed") guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, let identityToken = appleIDCredential.identityToken, @@ -173,19 +150,16 @@ extension LoginViewModel: ASAuthorizationControllerDelegate, print("Failed to get Apple ID Credential or identity token") return } - + print("Apple Login Successful, identity token: \(tokenString)") loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: "dummy_fcm_token")) } - - func authorizationController( - controller: ASAuthorizationController, - didCompleteWithError error: Error - ) { + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { print("Apple authorization error: \(error.localizedDescription)") self.error.value = error.localizedDescription } - + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { print("Providing presentation anchor for Apple Login") let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene diff --git a/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift b/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift index 5f9be66d..349ddd4f 100644 --- a/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift +++ b/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift @@ -4,6 +4,7 @@ // // Created by 이지훈 on 7/10/24. // +// NicknameViewController.swift import UIKit @@ -22,6 +23,15 @@ class NicknameViewController: BaseViewController { setupTextField() setupTapGesture() setupNavigationBarTitle(with: "닉네임 설정") + + print("---") + let keychainService = DefaultKeychainService.shared + + if let accessToken = keychainService.accessToken { + print("Access token is available in NicknameViewController: \(accessToken)") + } else { + print("No access token available in NicknameViewController. User may need to log in.") + } } override func setupAction() { @@ -64,6 +74,12 @@ class NicknameViewController: BaseViewController { viewModel.characterCount.bind { [weak self] count in self?.nicknameView.characterCountLabel.text = count } + + viewModel.serverResponse.bind { response in + if let response = response { + print("서버 응답: \(response)") + } + } } private func setupTextField() { @@ -81,16 +97,19 @@ class NicknameViewController: BaseViewController { } @objc private func nextButtonTapped() { - let profileSetupVC = ProfileSetupViewController( - viewModel: ProfileSetupViewModel( - nickname: viewModel.nickname.value - ) - ) -// profileSetupVC.modalPresentationStyle = .fullScreen -// present(profileSetupVC, animated: true, completion: nil) - // TODO: 온보딩 플로우 네비게이션으로 실행 - navigationController?.pushViewController(profileSetupVC, animated: true) - + viewModel.updateNickname { [weak self] success in + if success { + print("닉네임이 성공적으로 서버에 등록되었습니다.") + let profileSetupVC = ProfileSetupViewController( + viewModel: ProfileSetupViewModel( + nickname: self?.viewModel.nickname.value ?? "" + ) + ) + self?.navigationController?.pushViewController(profileSetupVC, animated: true) + } else { + print("닉네임 등록에 실패했습니다.") + } + } } @objc private func dismissKeyboard() { diff --git a/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift index ef6d3b2d..d1b90e05 100644 --- a/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift @@ -7,6 +7,8 @@ import Foundation +import Moya + enum NicknameState { case empty case valid @@ -21,6 +23,14 @@ class NicknameViewModel { let characterCount = ObservablePattern("0/5") private let nicknameRegex = "^[가-힣a-zA-Z0-9]{1,5}$" + private let provider = MoyaProvider() + let serverResponse = ObservablePattern(nil) + + private let authService: AuthServiceType + + init(authService: AuthServiceType = AuthService()) { + self.authService = authService + } func validateNickname(_ name: String) { nickname.value = name @@ -40,4 +50,64 @@ class NicknameViewModel { isNextButtonEnabled.value = false } } + + func updateNickname(completion: @escaping (Bool) -> Void) { + guard nicknameState.value == .valid else { + completion(false) + return + } + + guard let accessToken = authService.getAccessToken() else { + print("No access token available") + completion(false) + return + } + + print("닉네임 업데이트 요청 시작: \(nickname.value)") + + let headers = [ + "Content-Type": "application/json", + "Authorization": "Bearer \(accessToken)" + ] + + print("요청 헤더: \(headers)") + print("요청 바디: \(["name": nickname.value])") + + provider.request(.updateName(name: nickname.value)) { [weak self] result in + switch result { + case .success(let response): + print("서버 응답: \(String(data: response.data, encoding: .utf8) ?? "Unable to decode response")") + do { + let decodedResponse = try JSONDecoder().decode(ResponseBodyDTO.self, from: response.data) + if decodedResponse.success { + self?.serverResponse.value = "닉네임이 성공적으로 업데이트되었습니다." + print("닉네임 업데이트 성공: \(self?.nickname.value ?? "")") + completion(true) + } else { + if let errorCode = decodedResponse.error?.code { + switch errorCode { + case 40420: + self?.serverResponse.value = "사용자를 찾을 수 없습니다." + default: + self?.serverResponse.value = decodedResponse.error?.message ?? "알 수 없는 오류가 발생했습니다." + } + } else { + self?.serverResponse.value = decodedResponse.error?.message ?? "알 수 없는 오류가 발생했습니다." + } + print("닉네임 업데이트 실패: \(self?.serverResponse.value ?? "알 수 없는 오류")") + completion(false) + } + } catch { + self?.serverResponse.value = "데이터 디코딩 중 오류가 발생했습니다." + print("닉네임 업데이트 실패: 데이터 디코딩 오류 - \(error)") + completion(false) + } + case .failure(let error): + self?.serverResponse.value = "네트워크 오류: \(error.localizedDescription)" + print("닉네임 업데이트 실패: 네트워크 오류 - \(error.localizedDescription)") + completion(false) + } + } + } + } From 74381088ea252def94845ea591fdfd86a25473e5 Mon Sep 17 00:00:00 2001 From: hooni Date: Tue, 16 Jul 2024 23:24:08 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor/#187=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 12 +- .../KeyChain/KeychainAccessible.swift | 102 +++++----- .../Resource/KeyChain/KeychainService.swift | 182 ++++++++---------- .../Source/Onboarding/Login/Keychain.swift | 79 -------- .../Login/VIewModel/LoginViewModel.swift | 47 +++-- 5 files changed, 163 insertions(+), 259 deletions(-) delete mode 100644 KkuMulKum/Source/Onboarding/Login/Keychain.swift diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index a39abf29..d5eb7592 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -55,7 +55,7 @@ 789873342C3D1A7B00435E96 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789873312C3D1A7B00435E96 /* LoginView.swift */; }; 789AD4B32C3C0093002E2688 /* SocialLoginResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */; }; 789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */; }; - 789D73A72C46AF4900C7077D /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A62C46AF4900C7077D /* Keychain.swift */; }; + 789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A62C46AF4900C7077D /* KeychainService.swift */; }; 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; }; @@ -66,7 +66,6 @@ 78BD61272C446A97005752FD /* LoginTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61262C446A97005752FD /* LoginTargetType.swift */; }; 78BD612B2C4550A6005752FD /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD612A2C4550A6005752FD /* Bundle.swift */; }; 78BD612F2C4561B9005752FD /* KeychainAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */; }; - 78BD61312C456799005752FD /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61302C456799005752FD /* KeychainService.swift */; }; 78BD61342C45B4A7005752FD /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61332C45B4A7005752FD /* AuthService.swift */; }; 78BD61362C463B82005752FD /* NicknameTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61352C463B82005752FD /* NicknameTargetType.swift */; }; 78BD61382C463C8C005752FD /* NicknameModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61372C463C8C005752FD /* NicknameModel.swift */; }; @@ -205,7 +204,7 @@ 789873312C3D1A7B00435E96 /* LoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginResponseModel.swift; sourceTree = ""; }; 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResissueResponseModel.swift; sourceTree = ""; }; - 789D73A62C46AF4900C7077D /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; + 789D73A62C46AF4900C7077D /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; 78AED1312C3D94F2000AD80A /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; @@ -218,7 +217,6 @@ 78BD61262C446A97005752FD /* LoginTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginTargetType.swift; sourceTree = ""; }; 78BD612A2C4550A6005752FD /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessible.swift; sourceTree = ""; }; - 78BD61302C456799005752FD /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; 78BD61332C45B4A7005752FD /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; 78BD61352C463B82005752FD /* NicknameTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameTargetType.swift; sourceTree = ""; }; 78BD61372C463C8C005752FD /* NicknameModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameModel.swift; sourceTree = ""; }; @@ -565,7 +563,7 @@ isa = PBXGroup; children = ( 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */, - 78BD61302C456799005752FD /* KeychainService.swift */, + 789D73A62C46AF4900C7077D /* KeychainService.swift */, ); path = KeyChain; sourceTree = ""; @@ -962,7 +960,6 @@ DD865B662C39210E00C351A2 /* Login */ = { isa = PBXGroup; children = ( - 789D73A62C46AF4900C7077D /* Keychain.swift */, 789873372C3D1B4800435E96 /* ViewController */, 789873362C3D1B3900435E96 /* VIewModel */, 789873352C3D1B3000435E96 /* View */, @@ -1465,7 +1462,7 @@ DED5DBEC2C345210006ECE7E /* BaseViewController.swift in Sources */, DE6D4D142C3F14D80005584B /* MeetingPromiseCell.swift in Sources */, DD3976892C41C2AD00E2A4C4 /* TodayEmptyView.swift in Sources */, - 789D73A72C46AF4900C7077D /* Keychain.swift in Sources */, + 789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */, DD43937C2C412F4500EC1799 /* InviteCodeViewController.swift in Sources */, DD4393782C412F4500EC1799 /* CheckInviteCodeView.swift in Sources */, DE6D4D102C3F14D80005584B /* InvitationCodePopUpView.swift in Sources */, @@ -1573,7 +1570,6 @@ DD931B6E2C3DA27F00526452 /* ParticipantCollectionViewCell.swift in Sources */, DD3976882C41C2AD00E2A4C4 /* UpcomingEmptyView.swift in Sources */, DD931B762C3DC16100526452 /* PromiseInfoView.swift in Sources */, - 78BD61312C456799005752FD /* KeychainService.swift in Sources */, DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */, DD3976872C41C2AD00E2A4C4 /* TodayPromiseView.swift in Sources */, A3DD9C5A2C43F99800E58A13 /* SetReadyInfoView.swift in Sources */, diff --git a/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift b/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift index 29730560..16320e87 100644 --- a/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift +++ b/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift @@ -1,57 +1,49 @@ -//// -//// KeychainWrapper.swift -//// KkuMulKum -//// -//// Created by 이지훈 on 7/15/24. // -//import Foundation -//import SwiftKeychainWrapper +// KeychainWrapper.swift +// KkuMulKum // -//protocol KeychainAccessible { -// func saveToken(_ key: String, _ value: String) -> Bool -// func saveExpiresIn(_ key: String, _ value: Int) -> Bool -// func getToken(_ key: String) -> String? -// func getExpiresIn(_ Key: String) -> Int? -// func remove(_ key: String) -> Bool -// func removeAll() -> Bool -//} -// -//class DefaultKeychainAccessible: KeychainAccessible { -// private var keychain = KeychainWrapper.standard -// -// init() { -// let bundleId = Bundle.main.bundleIdentifier ?? "KkuMulKum.yizihn" -// let accessGroup = "D2DRA3F792.KkuMulKum.yizihn" // 엔타이틀먼트 파일의 접근 그룹과 일치해야 함 -// self.keychain = KeychainWrapper(serviceName: bundleId, accessGroup: accessGroup) -// print("Keychain initialized with bundleId: \(bundleId), accessGroup: \(accessGroup)") -// } -// -// func saveToken(_ key: String, _ value: String) -> Bool { -// // withAccessibility 옵션을 .afterFirstUnlock으로 설정 -// let result = keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlock) -// return result -// } -// -// func getToken(_ key: String) -> String? { -// let token = keychain.string(forKey: key) -// return token -// } -// -// func saveExpiresIn(_ key: String, _ value: Int) -> Bool { -// return keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlockThisDeviceOnly) -// } -// -// -// -// func getExpiresIn(_ Key: String) -> Int? { -// return keychain.integer(forKey: Key) -// } -// -// func remove(_ key: String) -> Bool { -// return keychain.removeObject(forKey: key) -// } -// -// func removeAll() -> Bool { -// return keychain.removeAllKeys() -// } -//} +// Created by 이지훈 on 7/15/24. + +import Foundation +import SwiftKeychainWrapper + +protocol KeychainAccessible { + func saveToken(_ key: String, _ value: String) -> Bool + func saveExpiresIn(_ key: String, _ value: Int) -> Bool + func getToken(_ key: String) -> String? + func getExpiresIn(_ Key: String) -> Int? + func remove(_ key: String) -> Bool + func removeAll() -> Bool +} + +class DefaultKeychainAccessible: KeychainAccessible { + private var keychain = KeychainWrapper.standard + + func saveToken(_ key: String, _ value: String) -> Bool { + let result = keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlock) + return result + } + + func getToken(_ key: String) -> String? { + let token = keychain.string(forKey: key) + return token + } + + func saveExpiresIn(_ key: String, _ value: Int) -> Bool { + return keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlockThisDeviceOnly) + } + + + + func getExpiresIn(_ Key: String) -> Int? { + return keychain.integer(forKey: Key) + } + + func remove(_ key: String) -> Bool { + return keychain.removeObject(forKey: key) + } + + func removeAll() -> Bool { + return keychain.removeAllKeys() + } +} diff --git a/KkuMulKum/Resource/KeyChain/KeychainService.swift b/KkuMulKum/Resource/KeyChain/KeychainService.swift index 5b358493..7b7576de 100644 --- a/KkuMulKum/Resource/KeyChain/KeychainService.swift +++ b/KkuMulKum/Resource/KeyChain/KeychainService.swift @@ -1,109 +1,79 @@ -//// -//// KeychainService.swift -//// KkuMulKum -//// -//// Created by 이지훈 on 7/15/24. -//// // -//import Foundation +// Keychain.swift +// KkuMulKum // -//protocol KeychainService { -// var accessToken: String? { get set } -// var refreshToken: String? { get set } -// var accessTokenExpiresIn: Int? { get set } -//} +// Created by 이지훈 on 7/16/24. // -//class DefaultKeychainService: KeychainService { -// static let shared = DefaultKeychainService() -// private init() {} -// -// private let keychainAccess = DefaultKeychainAccessible() -// -// struct Key { -// static let tempAccessToken = "tempAccessToken" -// static let tempRefreshToken = "tempRefreshToken" -// static let tempAccessTokenExpiresIn = "tempAccessTokenExpiresIn" -// -// static let accessToken = "accessToken" -// static let refreshToken = "refreshToken" -// static let accessTokenExpiresIn = "accessTokenExpiresIn" -// } -// -// var tempAccessToken: String? { -// get { -// keychainAccess.getToken(Key.tempAccessToken) -// } -// set { -// if newValue != nil { -// keychainAccess.saveToken(Key.tempAccessToken, newValue ?? "") -// } else { -// keychainAccess.remove(Key.tempAccessToken) -// } -// } -// } -// -// var tempRefreshToken: String? { -// get { -// keychainAccess.getToken(Key.tempRefreshToken) -// } -// set { -// if newValue != nil { -// keychainAccess.saveToken(Key.tempRefreshToken, newValue ?? "") -// } else { -// keychainAccess.remove(Key.tempRefreshToken) -// } -// } -// } -// -// var tempAccessTokenExpiresIn: Int? { -// get { -// keychainAccess.getExpiresIn(Key.tempAccessTokenExpiresIn) -// } -// set { -// if newValue != nil { -// keychainAccess.saveExpiresIn(Key.tempAccessTokenExpiresIn, newValue ?? 0) -// } else { -// keychainAccess.remove(Key.tempAccessTokenExpiresIn) -// } -// } -// } -// -// var accessToken: String? { -// get { -// keychainAccess.getToken(Key.accessToken) -// } -// set { -// if newValue != nil { -// keychainAccess.saveToken(Key.accessToken, newValue ?? "") -// } else { -// keychainAccess.remove(Key.accessToken) -// } -// } -// } -// -// var refreshToken: String? { -// get { -// keychainAccess.getToken(Key.refreshToken) -// } -// set { -// if newValue != nil { -// keychainAccess.saveToken(Key.refreshToken, newValue ?? "") -// } else { -// keychainAccess.remove(Key.refreshToken) -// } -// } -// } -// -// var accessTokenExpiresIn: Int? { -// get { -// keychainAccess.getExpiresIn(Key.accessTokenExpiresIn) -// } -// set { -// if newValue != nil { -// keychainAccess.saveExpiresIn(Key.accessTokenExpiresIn, newValue ?? 0) -// } else { -// keychainAccess.remove(Key.accessTokenExpiresIn) -// } -// } -// } -//} + +import Foundation +import Security + +protocol KeychainService { + var accessToken: String? { get set } + var refreshToken: String? { get set } +} + +class DefaultKeychainService: KeychainService { + static let shared = DefaultKeychainService() + + private init() {} + + private func save(_ data: Data, forKey key: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecValueData as String: data + ] + + SecItemDelete(query as CFDictionary) + SecItemAdd(query as CFDictionary, nil) + } + + private func load(forKey key: String) -> Data? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: true + ] + + var result: AnyObject? + SecItemCopyMatching(query as CFDictionary, &result) + return result as? Data + } + + var accessToken: String? { + get { + guard let data = load(forKey: "accessToken") else { return nil } + return String(data: data, encoding: .utf8) + } + set { + guard let newValue = newValue else { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: "accessToken" + ] + SecItemDelete(query as CFDictionary) + return + } + save(Data(newValue.utf8), forKey: "accessToken") + } + } + + var refreshToken: String? { + get { + guard let data = load(forKey: "refreshToken") else { return nil } + return String(data: data, encoding: .utf8) + } + set { + guard let newValue = newValue else { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: "refreshToken" + ] + SecItemDelete(query as CFDictionary) + return + } + save(Data(newValue.utf8), forKey: "refreshToken") + } + } +} diff --git a/KkuMulKum/Source/Onboarding/Login/Keychain.swift b/KkuMulKum/Source/Onboarding/Login/Keychain.swift deleted file mode 100644 index 7b7576de..00000000 --- a/KkuMulKum/Source/Onboarding/Login/Keychain.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// Keychain.swift -// KkuMulKum -// -// Created by 이지훈 on 7/16/24. -// - -import Foundation -import Security - -protocol KeychainService { - var accessToken: String? { get set } - var refreshToken: String? { get set } -} - -class DefaultKeychainService: KeychainService { - static let shared = DefaultKeychainService() - - private init() {} - - private func save(_ data: Data, forKey key: String) { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecValueData as String: data - ] - - SecItemDelete(query as CFDictionary) - SecItemAdd(query as CFDictionary, nil) - } - - private func load(forKey key: String) -> Data? { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecReturnData as String: true - ] - - var result: AnyObject? - SecItemCopyMatching(query as CFDictionary, &result) - return result as? Data - } - - var accessToken: String? { - get { - guard let data = load(forKey: "accessToken") else { return nil } - return String(data: data, encoding: .utf8) - } - set { - guard let newValue = newValue else { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: "accessToken" - ] - SecItemDelete(query as CFDictionary) - return - } - save(Data(newValue.utf8), forKey: "accessToken") - } - } - - var refreshToken: String? { - get { - guard let data = load(forKey: "refreshToken") else { return nil } - return String(data: data, encoding: .utf8) - } - set { - guard let newValue = newValue else { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: "refreshToken" - ] - SecItemDelete(query as CFDictionary) - return - } - save(Data(newValue.utf8), forKey: "refreshToken") - } - } -} diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 96eaaf3e..d64a9068 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -83,9 +83,15 @@ class LoginViewModel: NSObject { case .success(let response): print("Received response from server: \(response)") do { - let loginResponse = try response.map(ResponseBodyDTO.self) - print("Successfully mapped response: \(loginResponse)") - self?.handleLoginResponse(loginResponse) + let loginResponse = try response.map( + ResponseBodyDTO.self + ) + print( + "Successfully mapped response: \(loginResponse)" + ) + self?.handleLoginResponse( + loginResponse + ) } catch { print("Failed to decode response: \(error)") self?.error.value = "Failed to decode response: \(error.localizedDescription)" @@ -110,7 +116,10 @@ class LoginViewModel: NSObject { loginState.value = .needOnboarding } - saveTokens(accessToken: data.jwtTokenDTO.accessToken, refreshToken: data.jwtTokenDTO.refreshToken) + saveTokens( + accessToken: data.jwtTokenDTO.accessToken, + refreshToken: data.jwtTokenDTO.refreshToken + ) } else { print("Warning: No data received in response") error.value = "No data received" @@ -141,13 +150,24 @@ class LoginViewModel: NSObject { } } -extension LoginViewModel: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { - func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { - print("Apple authorization completed") +extension LoginViewModel: ASAuthorizationControllerDelegate, + ASAuthorizationControllerPresentationContextProviding { + func authorizationController( + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + print( + "Apple authorization completed" + ) guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, let identityToken = appleIDCredential.identityToken, - let tokenString = String(data: identityToken, encoding: .utf8) else { - print("Failed to get Apple ID Credential or identity token") + let tokenString = String( + data: identityToken, + encoding: .utf8 + ) else { + print( + "Failed to get Apple ID Credential or identity token" + ) return } @@ -155,8 +175,13 @@ extension LoginViewModel: ASAuthorizationControllerDelegate, ASAuthorizationCont loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: "dummy_fcm_token")) } - func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { - print("Apple authorization error: \(error.localizedDescription)") + func authorizationController( + controller: ASAuthorizationController, + didCompleteWithError error: Error + ) { + print( + "Apple authorization error: \(error.localizedDescription)" + ) self.error.value = error.localizedDescription } From 542f36f144586b56d386ad97cdbaeda011f9b837 Mon Sep 17 00:00:00 2001 From: hooni Date: Tue, 16 Jul 2024 23:32:42 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix/#187=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 14 ++++---------- .../ViewModel/PagePromiseViewModel.swift | 2 +- .../ViewController/ReadyStatusViewController.swift | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 653abe8f..23975481 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 789AD4B32C3C0093002E2688 /* SocialLoginResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */; }; 789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */; }; 789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A62C46AF4900C7077D /* KeychainService.swift */; }; + 789D73A92C46BBB000C7077D /* PagePromiseSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */; }; 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; }; @@ -205,6 +206,7 @@ 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginResponseModel.swift; sourceTree = ""; }; 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResissueResponseModel.swift; sourceTree = ""; }; 789D73A62C46AF4900C7077D /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; + 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseSegmentedControl.swift; sourceTree = ""; }; 78AED1312C3D94F2000AD80A /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; @@ -284,7 +286,6 @@ DD931B752C3DC16100526452 /* PromiseInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseInfoView.swift; sourceTree = ""; }; DDA2EE722C385EB9007C6059 /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; DDAF1C832C3D5D19008A37D3 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; - DDAF1C892C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseSegmentedControl.swift; sourceTree = ""; }; DDAF1C8A2C3D6E3D008A37D3 /* PagePromiseViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseViewModel.swift; sourceTree = ""; }; DDAF1C8B2C3D6E3D008A37D3 /* TardyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TardyViewController.swift; sourceTree = ""; }; DDAF1C8C2C3D6E3D008A37D3 /* PromiseInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseInfoViewController.swift; sourceTree = ""; }; @@ -541,14 +542,6 @@ path = KeyChain; sourceTree = ""; }; - 78BD61322C45B4A7005752FD /* Service */ = { - isa = PBXGroup; - children = ( - 78BD61332C45B4A7005752FD /* AuthService.swift */, - ); - path = Service; - sourceTree = ""; - }; A3DD9C372C41BAD000E58A13 /* View */ = { isa = PBXGroup; children = ( @@ -709,7 +702,7 @@ DD3976902C41CACF00E2A4C4 /* View */ = { isa = PBXGroup; children = ( - DDAF1C892C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift */, + 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */, ); path = View; sourceTree = ""; @@ -1535,6 +1528,7 @@ DDAF1C912C3D6E3D008A37D3 /* TardyViewController.swift in Sources */, 782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */, DE8248002C36E857000601BC /* ObservablePattern.swift in Sources */, + 789D73A92C46BBB000C7077D /* PagePromiseSegmentedControl.swift in Sources */, DDAF1C902C3D6E3D008A37D3 /* PagePromiseViewModel.swift in Sources */, DE254AAA2C31190E00A4015E /* UIStackView+.swift in Sources */, DE159D362C406E1600425101 /* MyPageViewController.swift in Sources */, diff --git a/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift b/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift index 3be9e9b9..f7d90a04 100644 --- a/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift +++ b/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift @@ -19,7 +19,7 @@ class PagePromiseViewModel { // MARK: - Extension -private extension PagePromiseViewModel { +extension PagePromiseViewModel { func didSegmentIndexChanged(index: Int) { currentPage.value = index } diff --git a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift index 633f9a63..91548f91 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift @@ -115,7 +115,7 @@ extension ReadyStatusViewController: UICollectionViewDataSource { color: .gray8 ) - if let imageURL = readyStatusViewModel.participantInfos.value[indexPath.row].profileImg { + if let imageURL = readyStatusViewModel.participantInfos.value[indexPath.row].profileImageURL { cell.profileImageView.kf.setImage(with: URL(string: imageURL)) } else { cell.profileImageView.image = .imgProfile From e20c7c65183e272b5563e3b139c2b17d02231c28 Mon Sep 17 00:00:00 2001 From: hooni Date: Wed, 17 Jul 2024 00:29:20 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat/#187=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=84=9C=EB=B2=84=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 12 +++- .../Network/DTO/Model/Auth/ProfileModel.swift | 12 ++++ .../DTO/TargetType/ProfileTargetType.swift | 59 ++++++++++++++++ .../ViewController/LoginViewController.swift | 2 +- .../ProfileSetupViewController.swift | 18 +++-- .../ViewModel/ProfileSetupViewModel.swift | 69 +++++++++++++++++++ 6 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift create mode 100644 KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 23975481..d0c34198 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -54,6 +54,8 @@ 789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */; }; 789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A62C46AF4900C7077D /* KeychainService.swift */; }; 789D73A92C46BBB000C7077D /* PagePromiseSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */; }; + 789D73AB2C46BEFF00C7077D /* ProfileTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */; }; + 789D73AD2C46C19500C7077D /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73AC2C46C19500C7077D /* ProfileModel.swift */; }; 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; }; @@ -207,6 +209,8 @@ 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResissueResponseModel.swift; sourceTree = ""; }; 789D73A62C46AF4900C7077D /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseSegmentedControl.swift; sourceTree = ""; }; + 789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTargetType.swift; sourceTree = ""; }; + 789D73AC2C46C19500C7077D /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = ""; }; 78AED1312C3D94F2000AD80A /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; @@ -521,6 +525,7 @@ children = ( 78BD61262C446A97005752FD /* LoginTargetType.swift */, 78BD61352C463B82005752FD /* NicknameTargetType.swift */, + 789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */, ); path = TargetType; sourceTree = ""; @@ -946,8 +951,8 @@ DD865B652C3920F600C351A2 /* Onboarding */ = { isa = PBXGroup; children = ( - DDFA50782C4693BD000A62E2 /* Profile */, 782B40762C3E389F008B0CA7 /* Welcome */, + DDFA50782C4693BD000A62E2 /* Profile */, 78AED1322C3D9514000AD80A /* Nickname */, DD865B662C39210E00C351A2 /* Login */, ); @@ -1006,8 +1011,8 @@ DDFA50782C4693BD000A62E2 /* Profile */ = { isa = PBXGroup; children = ( - DDFA50792C4693BD000A62E2 /* ViewModel */, DDFA507B2C4693BD000A62E2 /* View */, + DDFA50792C4693BD000A62E2 /* ViewModel */, DDFA507D2C4693BD000A62E2 /* VIewController */, ); path = Profile; @@ -1274,6 +1279,7 @@ 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */, 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */, 78BD61372C463C8C005752FD /* NicknameModel.swift */, + 789D73AC2C46C19500C7077D /* ProfileModel.swift */, ); path = Auth; sourceTree = ""; @@ -1567,6 +1573,7 @@ 78B9286E2C29402C006D9942 /* SceneDelegate.swift in Sources */, DD39766F2C41B54400E2A4C4 /* InviteCodeService.swift in Sources */, DE6D4D172C3F14D80005584B /* MeetingInfoViewModel.swift in Sources */, + 789D73AD2C46C19500C7077D /* ProfileModel.swift in Sources */, 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */, A3FB185B2C3BF7DF001483E5 /* MeetingMembersResponseModel.swift in Sources */, DD3072222C3C0DA300416D9F /* PromiseParticipantListResponseModel.swift in Sources */, @@ -1587,6 +1594,7 @@ DD39766B2C41995A00E2A4C4 /* FinishCreateNavigationView.swift in Sources */, DD3072202C3C0D4500416D9F /* MyReadyStatusResponseModel.swift in Sources */, DD931B6E2C3DA27F00526452 /* ParticipantCollectionViewCell.swift in Sources */, + 789D73AB2C46BEFF00C7077D /* ProfileTargetType.swift in Sources */, DD3976882C41C2AD00E2A4C4 /* UpcomingEmptyView.swift in Sources */, DD931B762C3DC16100526452 /* PromiseInfoView.swift in Sources */, DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */, diff --git a/KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift b/KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift new file mode 100644 index 00000000..ea9e7ab1 --- /dev/null +++ b/KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift @@ -0,0 +1,12 @@ +// +// ProfileModel.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation + +struct ProfileImageResponseModel: ResponseModelType { + // 성공 시 data가 null +} diff --git a/KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift b/KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift new file mode 100644 index 00000000..19f1609b --- /dev/null +++ b/KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift @@ -0,0 +1,59 @@ +// +// ProfileTargetType.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation + +import Moya + +enum ProfileTargetType { + ///jpg 고정이 아닌 jpg, png, webp 같이 받기위함 + case updateProfileImage(image: Data, fileName: String, mimeType: String) +} + +extension ProfileTargetType: TargetType { + var baseURL: URL { + guard let privacyInfo = Bundle.main.privacyInfo, + let urlString = privacyInfo["BASE_URL"] as? String, + let url = URL(string: urlString) else { + fatalError("Invalid BASE_URL in PrivacyInfo.plist") + } + return url + } + + var path: String { + return "/api/v1/users/me/image" + } + + var method: Moya.Method { + return .patch + } + + var task: Task { + switch self { + case .updateProfileImage(let imageData, let fileName, let mimeType): + let formData: [MultipartFormData] = [ + MultipartFormData( + provider: .data(imageData), + name: "image", + fileName: fileName, + mimeType: mimeType + ) + ] + return .uploadMultipart(formData) + } + } + + var headers: [String : String]? { + guard let token = DefaultKeychainService.shared.accessToken else { + return ["Content-Type": "multipart/form-data"] + } + return [ + "Authorization": "Bearer \(token)", + "Content-Type": "multipart/form-data" + ] + } +} diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index f445f116..b1cfd275 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -65,7 +65,7 @@ class LoginViewController: BaseViewController { print("Login State: Not logged in") case .login: print("Login State: Logged in with user info: ") - owner.navigateToMainScreen() + owner.navigateToOnboardingScreen() case .needOnboarding: print("Login State: Need onboarding") owner.navigateToOnboardingScreen() diff --git a/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift index 2a0b3ded..d4debc42 100644 --- a/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift +++ b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift @@ -50,12 +50,18 @@ class ProfileSetupViewController: BaseViewController { } @objc private func confirmButtonTapped() { - let welcomeVC = WelcomeViewController( - viewModel: WelcomeViewModel(nickname: viewModel.nickname) - ) - welcomeVC.modalPresentationStyle = .fullScreen - present(welcomeVC, animated: true, completion: nil) - } + viewModel.uploadProfileImage { [weak self] success in + if success { + DispatchQueue.main.async { + let welcomeVC = WelcomeViewController( + viewModel: WelcomeViewModel(nickname: self?.viewModel.nickname ?? "") + ) + welcomeVC.modalPresentationStyle = .fullScreen + self?.present(welcomeVC, animated: true, completion: nil) + } + } + } + } @objc private func skipButtonTapped() { let welcomeVC = WelcomeViewController( diff --git a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift index 4a2f9cc2..fda10f79 100644 --- a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift @@ -7,10 +7,16 @@ import UIKit +import Moya +import MobileCoreServices + class ProfileSetupViewModel { let profileImage = ObservablePattern(UIImage.imgProfile) let isConfirmButtonEnabled = ObservablePattern(false) let nickname: String + let serverResponse = ObservablePattern(nil) + + private let provider = MoyaProvider() init(nickname: String) { self.nickname = nickname @@ -20,4 +26,67 @@ class ProfileSetupViewModel { profileImage.value = image isConfirmButtonEnabled.value = image != nil } + + func uploadProfileImage(completion: @escaping (Bool) -> Void) { + print("uploadProfileImage 함수 호출됨") + guard let image = profileImage.value, + let imageData = image.jpegData(compressionQuality: 0.8) else { + print("이미지 변환 실패") + serverResponse.value = "이미지 변환 중 오류가 발생했습니다." + completion(false) + return + } + + print("이미지 데이터 크기: \(imageData.count) bytes") + + let fileName = "profile_image.jpg" + let mimeType = "image/jpeg" + + provider.request(.updateProfileImage(image: imageData, fileName: fileName, mimeType: mimeType)) { [weak self] result in + print("네트워크 요청 완료") + switch result { + case .success(let response): + print("서버 응답 상태 코드: \(response.statusCode)") + print("서버 응답 데이터: \(String(data: response.data, encoding: .utf8) ?? "디코딩 불가")") + do { + let decodedResponse = try JSONDecoder().decode( + ResponseBodyDTO.self, + from: response.data + ) + if decodedResponse.success { + self?.serverResponse.value = "프로필 이미지가 성공적으로 업로드되었습니다." + print("프로필 이미지 업로드 성공") + completion(true) + } else { + if let errorCode = decodedResponse.error?.code { + switch errorCode { + case 40080: + self?.serverResponse.value = "이미지 확장자는 jpg, png, webp만 가능합니다." + case 40081: + self?.serverResponse.value = "이미지 사이즈는 5MB를 넘을 수 없습니다." + case 40420: + self?.serverResponse.value = "유저를 찾을 수 없습니다." + default: + self?.serverResponse.value = decodedResponse.error?.message ?? + "알 수 없는 오류가 발생했습니다." + } + } else { + self?.serverResponse.value = decodedResponse.error?.message ?? + "알 수 없는 오류가 발생했습니다." + } + print("프로필 이미지 업로드 실패: \(self?.serverResponse.value ?? "")") + completion(false) + } + } catch { + self?.serverResponse.value = "데이터 디코딩 중 오류가 발생했습니다." + print("데이터 디코딩 오류: \(error)") + completion(false) + } + case .failure(let error): + self?.serverResponse.value = "네트워크 오류: \(error.localizedDescription)" + print("네트워크 오류: \(error.localizedDescription)") + completion(false) + } + } + } }