From 51762a05a8b1b4b54ede28f4c112abaea59880c1 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 22:09:59 +0900 Subject: [PATCH 01/22] =?UTF-8?q?fix:=20CircularDepedency=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Projects/Features/SignUp/Project.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Projects/Features/SignUp/Project.swift b/iOS/Projects/Features/SignUp/Project.swift index 76f58ae6..1b1898fd 100644 --- a/iOS/Projects/Features/SignUp/Project.swift +++ b/iOS/Projects/Features/SignUp/Project.swift @@ -7,7 +7,7 @@ let project = Project.makeModule( targets: .feature( .signUp, testingOptions: [.unitTest], - dependencies: [.trinet, .keychain, .combineCocoa, .coordinator, .log, .designSystem, .feature(.login), .commonNetworkingKeyManager], + dependencies: [.trinet, .keychain, .combineCocoa, .coordinator, .log, .designSystem, .commonNetworkingKeyManager], testDependencies: [], resources: "Resources/**" ) From ddcac9a29d805f2a0d0b9b976983bce46b75a34d Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 22:14:15 +0900 Subject: [PATCH 02/22] =?UTF-8?q?chore:=20=EC=A4=91=EB=B3=B5=20Mutipart?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Network/Sources/TNEndPoint.swift | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/iOS/Projects/Core/Network/Sources/TNEndPoint.swift b/iOS/Projects/Core/Network/Sources/TNEndPoint.swift index 3d2f505e..2951bcc4 100644 --- a/iOS/Projects/Core/Network/Sources/TNEndPoint.swift +++ b/iOS/Projects/Core/Network/Sources/TNEndPoint.swift @@ -75,48 +75,3 @@ private extension Encodable { return dictionaryTarget } } - -// MARK: - MultipartFormData - -public struct MultipartFormData { - public var data: Data = .init() - - let boundary: UUID - - public init(boundary: UUID, data: [Data], mimeType: String = "image/png") { - self.boundary = boundary - self.data = createBody(boundary: boundary.uuidString, imageData: data, mimeType: mimeType) - } - - private mutating func createBody(boundary: String, imageData: [Data], mimeType: String) -> Data { - let linebreak = "\r\n" - let boundaryPrefix = "--\(boundary)\(linebreak)" - - var body = Data() - // 각 이미지 데이터를 멀티파트 형식으로 추가 - for (index, imageData) in imageData.enumerated() { - let imgDataKey = "images" - let filename = "image[\(index)]" - - body.append(boundaryPrefix) - body.append("Content-Disposition: form-data; name=\"\(imgDataKey)\"; filename=\"\(filename)\"\(linebreak)") - body.append("Content-Type: \(mimeType)\(linebreak)\(linebreak)") - body.append(imageData) - body.append("\(linebreak)") - body.append("--\(boundary)\(linebreak)") - } - - // 종결 바운더리 추가 - body.append("--\(boundary)--\(linebreak)") - - return body - } -} - -private extension Data { - mutating func append(_ string: String) { - if let data = string.data(using: .utf8) { - append(data) - } - } -} From b5602babeae252fa671bc789a8b35c3f01de5d8d Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 22:15:46 +0900 Subject: [PATCH 03/22] =?UTF-8?q?docs:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Repository/ImageFormRepository.swift | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift index a08fec55..3a82d5d2 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift @@ -27,7 +27,6 @@ public final class ImageFormRepository: ImageFormRepositoryRepresentable { let boundary = "Boundary-\(UUID().uuidString)" let body = createBody(boundary: boundary, imageDataArray: [imageData], mimeType: "image/png") let endPoint = ImageFormEndPoint.image(body, boundary) -// sendURLRequest(boundary: boundary, body: body) Task { do { let data = try await self.provider.request(endPoint) @@ -66,40 +65,6 @@ public final class ImageFormRepository: ImageFormRepositoryRepresentable { return body } - -// func sendURLRequest(boundary: String, body: Data) { -// guard let url = URL(string: "https://api.wonho.site/api/v1/images") else { -// print("Error: Invalid URL") -// return -// } -// var request = URLRequest(url: url) -// request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") -// request.httpMethod = "POST" -// request.httpBody = body -// -// let task = URLSession.shared.dataTask(with: request) { data, response, error in -// if let error { -// print("Error: \(error)") -// return -// } -// -// if let httpResponse = response as? HTTPURLResponse { -// print("Response Status Code: \(httpResponse.statusCode)") -// } -// -// if let responseData = data { -// if let responseString = String(data: responseData, encoding: .utf8) { -// print("Response Data: \(responseString)") -// } else { -// print("Response Data cannot be decoded to UTF-8 string") -// } -// } else { -// print("No response data received") -// } -// } -// -// task.resume() -// } } // MARK: - ImageFormEndPoint From 55d71b655de4d51d00f17b931806528903d9decd Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 22:26:34 +0900 Subject: [PATCH 04/22] =?UTF-8?q?chore:=20MultiPart-Trinet=20=EC=9E=AC?= =?UTF-8?q?=EA=B2=B0=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Projects/Core/Network/Sources/MultipartFormData.swift | 6 ++++-- iOS/Projects/Core/Network/Sources/TNProvidable.swift | 2 +- .../Data/Repositories/MapImageUploadRepository.swift | 2 +- .../Sources/Data/Repository/ImageFormRepository.swift | 7 +++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/iOS/Projects/Core/Network/Sources/MultipartFormData.swift b/iOS/Projects/Core/Network/Sources/MultipartFormData.swift index 2057c2d2..f231cefc 100644 --- a/iOS/Projects/Core/Network/Sources/MultipartFormData.swift +++ b/iOS/Projects/Core/Network/Sources/MultipartFormData.swift @@ -12,14 +12,16 @@ import Foundation public struct MultipartFormData { private let boundary: String + private let mimeType: String public let imageDataList: [Data] - public init(uuid: UUID = UUID(), imageDataList: [Data]) { + public init(uuid: UUID = UUID(), mimeType: String, imageDataList: [Data]) { boundary = "Boundary-\(uuid.uuidString)" + self.mimeType = mimeType self.imageDataList = imageDataList } - public func makeBody(imageDataList: [Data], mimeType: String) -> Data { + public func makeBody() -> Data { let lineBreak = "\r\n" let boundaryPrefix = "--\(boundary)\(lineBreak)" diff --git a/iOS/Projects/Core/Network/Sources/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/TNProvidable.swift index a366c51a..2a58a691 100644 --- a/iOS/Projects/Core/Network/Sources/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/TNProvidable.swift @@ -29,7 +29,7 @@ public struct TNProvider: TNProvidable { public func uploadRequest(_ service: T, successStatusCodeRange range: Range = 200 ..< 300, interceptor: TNRequestInterceptor) async throws -> Data { guard let multipart = service.multipart else { throw TNError.unknownError } let request = try interceptor.adapt(service.request(), session: session) - let (data, response) = try await session.upload(for: request, from: multipart.data) + let (data, response) = try await session.upload(for: request, from: multipart.makeBody()) let (retriedData, retriedResponse) = try await interceptor.retry(request, session: session, data: data, response: response, delegate: nil) try checkStatusCode(retriedResponse, successStatusCodeRange: range) return retriedData diff --git a/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift b/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift index 39489a24..52f4382c 100644 --- a/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift +++ b/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift @@ -60,7 +60,7 @@ private struct ImageUploadEndPoint: TNEndPoint { .contentType("multipart/form-data; boundary=\(boundary.uuidString)"), ] - multipart = .init(boundary: boundary, data: data) + multipart = .init(mimeType: "image/png", imageDataList: data) } } diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift index 3a82d5d2..af9fe057 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift @@ -114,6 +114,13 @@ enum ImageFormEndPoint: TNEndPoint { TNHeader(key: "Connection", value: "keep-alive"), ]) } + + var multipart: MultipartFormData? { + switch self { + case let .image(imageData, _): + return MultipartFormData(mimeType: "image/png", imageDataList: [imageData]) + } + } } // MARK: - Response From 2c43ec24ac88bac95145763afb92c1dc90c8a242 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 22:29:45 +0900 Subject: [PATCH 05/22] feat: Trinet No Interceptor Upload --- iOS/Projects/Core/Network/Sources/TNProvidable.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/iOS/Projects/Core/Network/Sources/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/TNProvidable.swift index 2a58a691..0e4960ba 100644 --- a/iOS/Projects/Core/Network/Sources/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/TNProvidable.swift @@ -35,6 +35,13 @@ public struct TNProvider: TNProvidable { return retriedData } + public func uploadRequest(_ service: T, successStatusCodeRange range: Range = 200 ..< 300) async throws -> Data { + guard let multipart = service.multipart else { throw TNError.unknownError } + let (data, response) = try await session.upload(for: service.request(), from: multipart.makeBody()) + try checkStatusCode(response, successStatusCodeRange: range) + return data + } + public func request(_ service: T, completion: @escaping (Data?, URLResponse?, Error?) -> Void) throws { try session.dataTask(with: service.request(), completionHandler: completion).resume() } From 30f31788a086788bec3967e8743daf5c9c178095 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 22:32:43 +0900 Subject: [PATCH 06/22] rename: userBit -> newUserInformation --- .../Sources/Data/Repository/ImageFormRepository.swift | 2 +- .../Common/Coordinator/SignUpFeatureCoordinator.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift index af9fe057..026c30c3 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift @@ -29,7 +29,7 @@ public final class ImageFormRepository: ImageFormRepositoryRepresentable { let endPoint = ImageFormEndPoint.image(body, boundary) Task { do { - let data = try await self.provider.request(endPoint) + let data = try await self.provider.uploadRequest(endPoint) let response = try JSONDecoder().decode(Response.self, from: data) Log.make().debug("\(response.code!)") Log.make().debug("\(response.errorMessage!)") diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift index 3bf71f82..6dc94c3b 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift @@ -17,14 +17,14 @@ public final class SignUpFeatureCoordinator: SignUpFeatureCoordinating { public weak var finishDelegate: CoordinatorFinishDelegate? public var flow: CoordinatorFlow = .signup - private let userBit: NewUserInformation + private let newUserInformation: NewUserInformation public init( navigationController: UINavigationController, - userBit: NewUserInformation + newUserInformation: NewUserInformation ) { self.navigationController = navigationController - self.userBit = userBit + self.newUserInformation = newUserInformation } public func start() { @@ -32,7 +32,7 @@ public final class SignUpFeatureCoordinator: SignUpFeatureCoordinating { } public func showSignUpFlow() { - let coordinator = SignUpCoordinator(navigationController: navigationController, isMockEnvironment: true, userBit: userBit) + let coordinator = SignUpCoordinator(navigationController: navigationController, isMockEnvironment: true, userBit: newUserInformation) childCoordinators.append(coordinator) coordinator.finishDelegate = self coordinator.start() From e907399d4a9d2ac4c44578eb059e556cebc9d7df Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 23:13:31 +0900 Subject: [PATCH 07/22] rename: userBit-newUserInformation --- .../CommonScene/Coordinator/AppCoordinator.swift | 10 ++++++---- .../Login/Resources/Persistency/InitialUser.json | 1 + .../Repositories/AuthorizationRepository.swift | 3 ++- .../LoginScene/Coordinator/LoginCoordinator.swift | 2 +- .../Coordinator/LoginFeatureCoordinator.swift | 14 ++++++++++++-- .../LoginScene/ViewModel/LoginViewModel.swift | 2 +- .../Common/Coordinator/SignUpCoordinator.swift | 6 +++--- .../ViewModel/SignUpProfileViewModel.swift | 8 ++++---- 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift index 09e005ca..1f37074c 100644 --- a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift +++ b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift @@ -7,6 +7,7 @@ // import Coordinator +import LoginFeature import SplashFeature import UIKit @@ -25,7 +26,7 @@ final class AppCoordinator: AppCoordinating { } func start() { - showTabBarFlow() + showLoginFlow() } private func showSplashFlow() { @@ -49,9 +50,10 @@ final class AppCoordinator: AppCoordinating { func showLoginFlow() { // TODO: LoginCoordinator 연결 - let aCoordinator = ACoordinator(navigationController: navigationController) - childCoordinators.append(aCoordinator) - aCoordinator.start() + let coordinator = LoginFeatureCoordinator(navigationController: navigationController) + childCoordinators.append(coordinator) + coordinator.finishDelegate = self + coordinator.start() } func showTabBarFlow() { diff --git a/iOS/Projects/Features/Login/Resources/Persistency/InitialUser.json b/iOS/Projects/Features/Login/Resources/Persistency/InitialUser.json index 284422fa..ac75b946 100644 --- a/iOS/Projects/Features/Login/Resources/Persistency/InitialUser.json +++ b/iOS/Projects/Features/Login/Resources/Persistency/InitialUser.json @@ -6,3 +6,4 @@ "mappedUserID": "1233498sdafksdjhfk...", "provider": "apple" } +} diff --git a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift index 9fdcf7c7..8c521712 100644 --- a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift +++ b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift @@ -40,10 +40,11 @@ struct AuthorizationRepository: AuthorizationRepositoryRepresentable { let authorizationInfoRequestDTO = AuthorizationInfoRequestDTO(identityToken: token, authorizationCode: authorizationCode) let data = try await provider.request(.signIn(authorizationInfoRequestDTO)) - + Log.make().debug("\(data)") guard let responseCode = try decoder.decode(Response.self, from: data).code else { return } + Log.make().debug("\(responseCode)") switch responseCode { case AuthorizationRepositoryResponseCode.token.code: let token = try decoder.decode(GWResponse.self, from: data).data diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift index d21292aa..8d70ec7d 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift @@ -34,7 +34,7 @@ final class LoginCoordinator: LoginCoordinating { func start() { guard let jsonPath = isMockFirst ? - Bundle(for: Self.self).path(forResource: "Token", ofType: "json") : Bundle(for: Self.self).path(forResource: "InitialUser", ofType: "json"), + Bundle(for: Self.self).path(forResource: "InitialUser", ofType: "json") : Bundle(for: Self.self).path(forResource: "Token", ofType: "json"), let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) else { Log.make().error("Login Mock 데이터를 생성할 수 없습니다.") diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift index a167a6c6..d9c9361b 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift @@ -54,8 +54,18 @@ extension LoginFeatureCoordinator: CoordinatorFinishDelegate { extension LoginFeatureCoordinator: LoginDidFinishedDelegate { func loginCoordinatorDidFinished(initialUser: InitialUser?, token: Token?) { - // TODO: Login시, User가 처음이라면 해당 데이터 갖고 SignUpModule로 넘어가야됨. - if let initialUser {} + if let initialUser { + let coordinator = SignUpFeatureCoordinator( + navigationController: navigationController, + newUserInformation: NewUserInformation( + mappedUserID: initialUser.mappedUserID, + provider: .apple + ) + ) + childCoordinators.append(coordinator) + coordinator.finishDelegate = self + coordinator.showSignUpFlow() + } // TODO: User가 처음이 아니라면 토큰이 존재한다 해당 토큰 갖고 TabBar로 넘어가야됨. if let token {} diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift index c3311366..37aba385 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift @@ -51,13 +51,13 @@ extension LoginViewModel: LoginViewModelRepresentable { input.credential .flatMap(authorizeUseCase.authorize(authorizationInfo:)) .sink(receiveValue: { [weak self] loginResponse in - if let token = loginResponse.token { guard let accessToken = token.accessToken, let refreshToken = token.refreshToken else { return } + Log.make().debug("\(accessToken)") self?.authorizeUseCase.accessTokenSave(accessToken) self?.authorizeUseCase.refreshTokenSave(refreshToken) self?.coordinator.finish(initialUser: nil, token: token) diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift index 39373a0d..04e1c041 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift @@ -18,7 +18,7 @@ final class SignUpCoordinator: SignUpCoordinating { weak var finishDelegate: CoordinatorFinishDelegate? var flow: CoordinatorFlow = .signup - private let userBit: NewUserInformation + private let newUserInformation: NewUserInformation private let isMockEnvironment: Bool @@ -29,7 +29,7 @@ final class SignUpCoordinator: SignUpCoordinating { ) { self.navigationController = navigationController self.isMockEnvironment = isMockEnvironment - self.userBit = userBit + newUserInformation = userBit } func start() { @@ -71,7 +71,7 @@ final class SignUpCoordinator: SignUpCoordinating { nickNameCheckUseCase: nickNameCheckUseCase, imageTransmitUseCase: imageTransmitUseCase, signUpUseCase: signUpUseCase, - userBit: userBit + newUserInformation: newUserInformation ) let signUpProfileViewController = SignUpProfileViewController(viewModel: signUpProfileViewModel) diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift index 35037743..6f42e057 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift @@ -42,18 +42,18 @@ public final class SignUpProfileViewModel { private let imageTransmitUseCase: ImageTransmitUseCaseRepresentable private let signUpUseCase: SignUpUseCaseRepresentable - private let userBit: NewUserInformation + private let newUserInformation: NewUserInformation public init( nickNameCheckUseCase: NickNameCheckUseCaseRepresentable, imageTransmitUseCase: ImageTransmitUseCaseRepresentable, signUpUseCase: SignUpUseCaseRepresentable, - userBit: NewUserInformation + newUserInformation: NewUserInformation ) { self.nickNameCheckUseCase = nickNameCheckUseCase self.imageTransmitUseCase = imageTransmitUseCase self.signUpUseCase = signUpUseCase - self.userBit = userBit + self.newUserInformation = newUserInformation } } @@ -120,7 +120,7 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { Publishers .CombineLatest3(imageFormSubject.eraseToAnyPublisher(), nickNameSubject.eraseToAnyPublisher(), input.genderBirth) .sink { [weak self] imageForm, nickName, genderBirth in - guard let userBit = self?.userBit else { + guard let userBit = self?.newUserInformation else { return } let signUpUser = SignUpUser( From 47062c722a20eed2434ed89e6c0d4cb0af2cd679 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 7 Dec 2023 23:24:24 +0900 Subject: [PATCH 08/22] =?UTF-8?q?fix:=20SignUp=EB=84=98=EC=96=B4=EA=B0=88?= =?UTF-8?q?=20=EB=95=8C,=20mainThread=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/LoginScene/ViewModel/LoginViewModel.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift index 37aba385..c3167696 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift @@ -50,6 +50,7 @@ extension LoginViewModel: LoginViewModelRepresentable { func transform(input: LoginViewModelInput) -> LoginViewModelOutput { input.credential .flatMap(authorizeUseCase.authorize(authorizationInfo:)) + .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] loginResponse in if let token = loginResponse.token { guard let accessToken = token.accessToken, @@ -57,14 +58,12 @@ extension LoginViewModel: LoginViewModelRepresentable { else { return } - Log.make().debug("\(accessToken)") self?.authorizeUseCase.accessTokenSave(accessToken) self?.authorizeUseCase.refreshTokenSave(refreshToken) self?.coordinator.finish(initialUser: nil, token: token) } if let initialUser = loginResponse.initialUser { - Log.make().debug("\(initialUser.mappedUserID)") self?.coordinator.finish(initialUser: initialUser, token: nil) } }) From e8bcad074a1118f44234046d6c9a00a1467583da Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Fri, 8 Dec 2023 16:44:23 +0900 Subject: [PATCH 09/22] =?UTF-8?q?fix:=20ImageForm=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=A0=84=EB=8B=AC=20=EC=95=88=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=ED=98=84=EC=83=81=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=A8=20=ED=86=A0=ED=81=B0=EA=B0=92=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/.swiftlint.yml | 70 ++++++------ .../Network/Sources/MultipartFormData.swift | 13 ++- .../Core/Network/Sources/TNEndPoint.swift | 11 ++ .../Core/Network/Sources/TNProvidable.swift | 2 +- .../MapImageUploadRepository.swift | 2 +- .../Data/Repository/ImageFormRepository.swift | 104 ++++-------------- .../Domain/UseCase/SignUpUseCase.swift | 6 +- .../Coordinator/SignUpCoordinator.swift | 19 ++-- .../SignUpFeatureCoordinator.swift | 3 +- .../ViewModel/SignUpProfileViewModel.swift | 32 ++++++ 10 files changed, 128 insertions(+), 134 deletions(-) diff --git a/iOS/.swiftlint.yml b/iOS/.swiftlint.yml index 3330e514..bb3f389b 100644 --- a/iOS/.swiftlint.yml +++ b/iOS/.swiftlint.yml @@ -33,38 +33,38 @@ trailing_comma: line_length: warning: 150 -custom_rules: - no_objcMembers: - name: "@objcMembers" - regex: "@objcMembers" - message: "Explicitly use @objc on each member you want to expose to Objective-C" - severity: error - no_direct_standard_out_logs: - name: "Writing log messages directly to standard out is disallowed" - regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" - match_kinds: - - identifier - message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" - severity: error - no_file_literal: - name: "#file is disallowed" - regex: "(\\b#file\\b)" - match_kinds: - - identifier - message: "Instead of #file, use #fileID" - severity: error - no_filepath_literal: - name: "#filePath is disallowed" - regex: "(\\b#filePath\\b)" - match_kinds: - - identifier - message: "Instead of #filePath, use #fileID." - severity: error - no_unchecked_sendable: - name: "`@unchecked Sendable` is discouraged." - regex: "@unchecked Sendable" - match_kinds: - - attribute.builtin - - typeidentifier - message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." - severity: error +#custom_rules: +# no_objcMembers: +# name: "@objcMembers" +# regex: "@objcMembers" +# message: "Explicitly use @objc on each member you want to expose to Objective-C" +# severity: error +# no_direct_standard_out_logs: +# name: "Writing log messages directly to standard out is disallowed" +# regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" +# match_kinds: +# - identifier +# message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" +# severity: error +# no_file_literal: +# name: "#file is disallowed" +# regex: "(\\b#file\\b)" +# match_kinds: +# - identifier +# message: "Instead of #file, use #fileID" +# severity: error +# no_filepath_literal: +# name: "#filePath is disallowed" +# regex: "(\\b#filePath\\b)" +# match_kinds: +# - identifier +# message: "Instead of #filePath, use #fileID." +# severity: error +# no_unchecked_sendable: +# name: "`@unchecked Sendable` is discouraged." +# regex: "@unchecked Sendable" +# match_kinds: +# - attribute.builtin +# - typeidentifier +# message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." +# severity: error diff --git a/iOS/Projects/Core/Network/Sources/MultipartFormData.swift b/iOS/Projects/Core/Network/Sources/MultipartFormData.swift index f231cefc..6f5f5a90 100644 --- a/iOS/Projects/Core/Network/Sources/MultipartFormData.swift +++ b/iOS/Projects/Core/Network/Sources/MultipartFormData.swift @@ -11,12 +11,16 @@ import Foundation // MARK: - MultipartFormData public struct MultipartFormData { - private let boundary: String - private let mimeType: String + public let boundary: String public let imageDataList: [Data] + private let mimeType: String - public init(uuid: UUID = UUID(), mimeType: String, imageDataList: [Data]) { - boundary = "Boundary-\(uuid.uuidString)" + public init( + uuid: UUID, + mimeType: String = "image/png", + imageDataList: [Data] + ) { + boundary = "\(uuid.uuidString)" self.mimeType = mimeType self.imageDataList = imageDataList } @@ -40,7 +44,6 @@ public struct MultipartFormData { } body.append("--\(boundary)--\(lineBreak)") - return body } } diff --git a/iOS/Projects/Core/Network/Sources/TNEndPoint.swift b/iOS/Projects/Core/Network/Sources/TNEndPoint.swift index 2951bcc4..96f1a00f 100644 --- a/iOS/Projects/Core/Network/Sources/TNEndPoint.swift +++ b/iOS/Projects/Core/Network/Sources/TNEndPoint.swift @@ -43,6 +43,17 @@ public extension TNEndPoint { request.httpBody = body?.data return request } + + func requestFormData() throws -> URLRequest { + guard let targetURL = URL(string: baseURL)?.appending(path: path).appending(query: query) + else { + throw TNError.invalidURL + } + var request = URLRequest(url: targetURL) + request.httpMethod = method.rawValue + request.allHTTPHeaderFields = headers.dictionary + return request + } } private extension URL { diff --git a/iOS/Projects/Core/Network/Sources/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/TNProvidable.swift index 0e4960ba..529df54c 100644 --- a/iOS/Projects/Core/Network/Sources/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/TNProvidable.swift @@ -37,7 +37,7 @@ public struct TNProvider: TNProvidable { public func uploadRequest(_ service: T, successStatusCodeRange range: Range = 200 ..< 300) async throws -> Data { guard let multipart = service.multipart else { throw TNError.unknownError } - let (data, response) = try await session.upload(for: service.request(), from: multipart.makeBody()) + let (data, response) = try await session.upload(for: service.requestFormData(), from: multipart.makeBody()) try checkStatusCode(response, successStatusCodeRange: range) return data } diff --git a/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift b/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift index 52f4382c..32afe50d 100644 --- a/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift +++ b/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift @@ -60,7 +60,7 @@ private struct ImageUploadEndPoint: TNEndPoint { .contentType("multipart/form-data; boundary=\(boundary.uuidString)"), ] - multipart = .init(mimeType: "image/png", imageDataList: data) + multipart = .init(uuid: boundary, mimeType: "image/png", imageDataList: data) } } diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift index 026c30c3..a79da35c 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift @@ -12,6 +12,8 @@ enum ImageFormRepositoryError: Error { // MARK: - ImageFormRepository public final class ImageFormRepository: ImageFormRepositoryRepresentable { + private var subscriptions: Set = [] + private let provider: TNProvider public init(urlSession: URLSessionProtocol) { @@ -24,15 +26,10 @@ public final class ImageFormRepository: ImageFormRepositoryRepresentable { guard let self else { return promise(.failure(ImageFormRepositoryError.invalidImageFormRepository)) } - let boundary = "Boundary-\(UUID().uuidString)" - let body = createBody(boundary: boundary, imageDataArray: [imageData], mimeType: "image/png") - let endPoint = ImageFormEndPoint.image(body, boundary) + let endPoint = ImageFormEndPoint(imageDataList: [imageData]) Task { do { let data = try await self.provider.uploadRequest(endPoint) - let response = try JSONDecoder().decode(Response.self, from: data) - Log.make().debug("\(response.code!)") - Log.make().debug("\(response.errorMessage!)") promise(.success(data)) } catch { promise(.failure(error)) @@ -41,91 +38,34 @@ public final class ImageFormRepository: ImageFormRepositoryRepresentable { } .decode(type: GWResponse<[ImageForm]>.self, decoder: JSONDecoder()) .compactMap(\.data) - .eraseToAnyPublisher() - } - - private func createBody(boundary: String, imageDataArray: [Data], mimeType: String) -> Data { - var body = Data() - let lineBreak = "\r\n" - let boundaryPrefix = "--\(boundary)\(lineBreak)" - - for (index, imageData) in imageDataArray.enumerated() { - let imgDataKey = "images" - let filename = "image[\(index)]" - - body.append(boundaryPrefix.data(using: .utf8)!) - body.append("Content-Disposition: form-data; name=\"\(imgDataKey)\"; filename=\"\(filename)\"\(lineBreak)".data(using: .utf8)!) - body.append("Content-Type: \(mimeType)\(lineBreak)\(lineBreak)".data(using: .utf8)!) - body.append(imageData) - body.append("\(lineBreak)".data(using: .utf8)!) - body.append("\(boundaryPrefix)".data(using: .utf8)!) + .map { imageForms in + return imageForms } - - body.append("--\(boundary)--\(lineBreak)".data(using: .utf8)!) - - return body + .eraseToAnyPublisher() } } // MARK: - ImageFormEndPoint -enum ImageFormEndPoint: TNEndPoint { - case image(Data, String) +struct ImageFormEndPoint: TNEndPoint { + let headers: TNHeaders + var multipart: MultipartFormData? + init(imageDataList: [Data]) { + let uuid = UUID() - var boundary: String { - switch self { - case let .image(_, providedBoundary): - Log.make().debug("EndPoint boundary: \(providedBoundary)") - return providedBoundary - } - } - - var path: String { - switch self { - case .image: - return "/api/v1/images" - } - } - - var method: TNMethod { - switch self { - case .image: - return .post - } - } - - var query: Encodable? { - return nil - } - - var body: Encodable? { - switch self { - case let .image(formData, _): - Log.make().debug("EndPoint formData: \(formData)") - return formData - } - } - - var headers: TNHeaders { - TNHeaders(headers: [ - TNHeader(key: "Content-Type", value: "multipart/form-data; boundary=\(boundary)"), - TNHeader(key: "Accept-Encoding", value: "gzip"), - TNHeader(key: "Accept", value: "*/*"), - TNHeader(key: "Connection", value: "keep-alive"), + headers = .init(headers: [ + .init(key: "Content-Type", value: "multipart/form-data; boundary=\(uuid.uuidString)"), ]) - } - var multipart: MultipartFormData? { - switch self { - case let .image(imageData, _): - return MultipartFormData(mimeType: "image/png", imageDataList: [imageData]) - } + multipart = MultipartFormData( + uuid: uuid, + mimeType: "image/png", + imageDataList: imageDataList + ) } -} - -// MARK: - Response -private struct Response: Codable { - let code: Int? - let errorMessage: String? + let path: String = "/api/v1/images" + let method: TNMethod = .post + let query: Encodable? = nil + let body: Encodable? = nil } diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift b/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift index f36b7938..c235d6a9 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift @@ -14,6 +14,8 @@ import Foundation public protocol SignUpUseCaseRepresentable { func signUp(signUpUser: SignUpUser) -> AnyPublisher + func accessTokenSave(_ token: String) + func refreshTokenSave(_ token: String) } // MARK: - SignUpUseCase @@ -35,11 +37,11 @@ public final class SignUpUseCase: SignUpUseCaseRepresentable { .eraseToAnyPublisher() } - func accessTokenSave(_ token: String) { + public func accessTokenSave(_ token: String) { keychainRepository.save(key: Tokens.accessToken, value: token) } - func refreshTokenSave(_ token: String) { + public func refreshTokenSave(_ token: String) { keychainRepository.save(key: Tokens.refreshToken, value: token) } } diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift index 04e1c041..7c8737e7 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift @@ -12,11 +12,11 @@ import Log import Trinet import UIKit -final class SignUpCoordinator: SignUpCoordinating { - var navigationController: UINavigationController - var childCoordinators: [Coordinating] = [] - weak var finishDelegate: CoordinatorFinishDelegate? - var flow: CoordinatorFlow = .signup +public final class SignUpCoordinator: SignUpCoordinating { + public var navigationController: UINavigationController + public var childCoordinators: [Coordinating] = [] + public weak var finishDelegate: CoordinatorFinishDelegate? + public var flow: CoordinatorFlow = .signup private let newUserInformation: NewUserInformation @@ -32,11 +32,15 @@ final class SignUpCoordinator: SignUpCoordinating { newUserInformation = userBit } - func start() { + public func start() { pushSingUpContainerViewController() } - func pushSingUpContainerViewController() { + public func finish() { + finishDelegate?.flowDidFinished(childCoordinator: self) + } + + public func pushSingUpContainerViewController() { guard let jsonPath = Bundle(for: Self.self).path(forResource: "Token", ofType: "json"), let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) else { @@ -68,6 +72,7 @@ final class SignUpCoordinator: SignUpCoordinating { ) let signUpProfileViewModel = SignUpProfileViewModel( + coordinator: self, nickNameCheckUseCase: nickNameCheckUseCase, imageTransmitUseCase: imageTransmitUseCase, signUpUseCase: signUpUseCase, diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift index 6dc94c3b..e219ff2c 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift @@ -32,7 +32,7 @@ public final class SignUpFeatureCoordinator: SignUpFeatureCoordinating { } public func showSignUpFlow() { - let coordinator = SignUpCoordinator(navigationController: navigationController, isMockEnvironment: true, userBit: newUserInformation) + let coordinator = SignUpCoordinator(navigationController: navigationController, isMockEnvironment: false, userBit: newUserInformation) childCoordinators.append(coordinator) coordinator.finishDelegate = self coordinator.start() @@ -46,5 +46,6 @@ extension SignUpFeatureCoordinator: CoordinatorFinishDelegate { childCoordinators = childCoordinators.filter { $0.flow != childCoordinator.flow } + navigationController.popToRootViewController(animated: false) } } diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift index 6f42e057..53ede855 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift @@ -8,6 +8,7 @@ import Combine import Foundation +import Keychain import Log // MARK: - SignUpProfileViewModelInput @@ -38,6 +39,7 @@ public enum SignUpProfileState { public final class SignUpProfileViewModel { private var subscriptions: Set = [] + private let coordinator: SignUpCoordinator private let nickNameCheckUseCase: NickNameCheckUseCaseRepresentable private let imageTransmitUseCase: ImageTransmitUseCaseRepresentable private let signUpUseCase: SignUpUseCaseRepresentable @@ -45,11 +47,13 @@ public final class SignUpProfileViewModel { private let newUserInformation: NewUserInformation public init( + coordinator: SignUpCoordinator, nickNameCheckUseCase: NickNameCheckUseCaseRepresentable, imageTransmitUseCase: ImageTransmitUseCaseRepresentable, signUpUseCase: SignUpUseCaseRepresentable, newUserInformation: NewUserInformation ) { + self.coordinator = coordinator self.nickNameCheckUseCase = nickNameCheckUseCase self.imageTransmitUseCase = imageTransmitUseCase self.signUpUseCase = signUpUseCase @@ -70,6 +74,7 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { let imageFormSubject = PassthroughSubject() let nickNameSubject = PassthroughSubject() + let completeSignUpSubject = PassthroughSubject() let nickNameCheckedResult = input.nickNameTextFieldEditting .tryMap { [weak self] nickName in @@ -131,9 +136,35 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { profileImage: imageForm.imageURL, mappedUserID: userBit.mappedUserID ) + completeSignUpSubject.send(signUpUser) } .store(in: &subscriptions) + completeSignUpSubject + .flatMap { [weak self] signUpUser -> AnyPublisher in + guard let publisher = self?.signUpUseCase.signUp(signUpUser: signUpUser) else { + return Fail(error: SignUpProfileViewModelError.invalidTokenPublisher).eraseToAnyPublisher() + } + return publisher + } + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { completion in + switch completion { + case .finished: + break + case let .failure(error): + Log.make().error("\(error)") + } + }, receiveValue: { [weak self] token in + if let accessToken = token.accesToken, + let refreshToken = token.refreshToken { + self?.signUpUseCase.accessTokenSave(accessToken) + self?.signUpUseCase.refreshTokenSave(refreshToken) + self?.coordinator.finish() + } + }) + .store(in: &subscriptions) + let initialState: SignUpProfileViewModelOutput = Just(.idle) .eraseToAnyPublisher() @@ -164,4 +195,5 @@ public protocol SignUpProfileViewModelRepresentable { enum SignUpProfileViewModelError: Error { case invalidBinding case invalidNickNameCheck + case invalidTokenPublisher } From 4f5a905cbc7a830e54f13a6c3edc1b30679663ae Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Fri, 8 Dec 2023 17:05:33 +0900 Subject: [PATCH 10/22] =?UTF-8?q?chore:=20Lint=20=EC=A0=95=EC=83=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/.swiftlint.yml | 70 +++++++++---------- .../Core/Network/Sources/TNProvidable.swift | 10 +++ .../AuthorizationRepository.swift | 5 +- .../Coordinator/LoginFeatureCoordinator.swift | 2 +- .../ViewModel/SignUpProfileViewModel.swift | 3 - 5 files changed, 47 insertions(+), 43 deletions(-) diff --git a/iOS/.swiftlint.yml b/iOS/.swiftlint.yml index bb3f389b..3330e514 100644 --- a/iOS/.swiftlint.yml +++ b/iOS/.swiftlint.yml @@ -33,38 +33,38 @@ trailing_comma: line_length: warning: 150 -#custom_rules: -# no_objcMembers: -# name: "@objcMembers" -# regex: "@objcMembers" -# message: "Explicitly use @objc on each member you want to expose to Objective-C" -# severity: error -# no_direct_standard_out_logs: -# name: "Writing log messages directly to standard out is disallowed" -# regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" -# match_kinds: -# - identifier -# message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" -# severity: error -# no_file_literal: -# name: "#file is disallowed" -# regex: "(\\b#file\\b)" -# match_kinds: -# - identifier -# message: "Instead of #file, use #fileID" -# severity: error -# no_filepath_literal: -# name: "#filePath is disallowed" -# regex: "(\\b#filePath\\b)" -# match_kinds: -# - identifier -# message: "Instead of #filePath, use #fileID." -# severity: error -# no_unchecked_sendable: -# name: "`@unchecked Sendable` is discouraged." -# regex: "@unchecked Sendable" -# match_kinds: -# - attribute.builtin -# - typeidentifier -# message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." -# severity: error +custom_rules: + no_objcMembers: + name: "@objcMembers" + regex: "@objcMembers" + message: "Explicitly use @objc on each member you want to expose to Objective-C" + severity: error + no_direct_standard_out_logs: + name: "Writing log messages directly to standard out is disallowed" + regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" + match_kinds: + - identifier + message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" + severity: error + no_file_literal: + name: "#file is disallowed" + regex: "(\\b#file\\b)" + match_kinds: + - identifier + message: "Instead of #file, use #fileID" + severity: error + no_filepath_literal: + name: "#filePath is disallowed" + regex: "(\\b#filePath\\b)" + match_kinds: + - identifier + message: "Instead of #filePath, use #fileID." + severity: error + no_unchecked_sendable: + name: "`@unchecked Sendable` is discouraged." + regex: "@unchecked Sendable" + match_kinds: + - attribute.builtin + - typeidentifier + message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." + severity: error diff --git a/iOS/Projects/Core/Network/Sources/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/TNProvidable.swift index 529df54c..9bedd47c 100644 --- a/iOS/Projects/Core/Network/Sources/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/TNProvidable.swift @@ -48,6 +48,9 @@ public struct TNProvider: TNProvidable { public func request(_ service: T, successStatusCodeRange range: Range = 200 ..< 300) async throws -> Data { let (data, response) = try await session.data(for: service.request(), delegate: nil) + let res = try JSONDecoder().decode(Response.self, from: data) + print("코드 : \(res.code)") + print("에러 메시지 : \(res.errorMessage)") try checkStatusCode(response, successStatusCodeRange: range) return data } @@ -81,3 +84,10 @@ private extension TNProvider { } } } + +// MARK: - Response + +private struct Response: Codable { + let code: Int? + let errorMessage: String? +} diff --git a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift index 8c521712..fe71f55a 100644 --- a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift +++ b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift @@ -40,11 +40,9 @@ struct AuthorizationRepository: AuthorizationRepositoryRepresentable { let authorizationInfoRequestDTO = AuthorizationInfoRequestDTO(identityToken: token, authorizationCode: authorizationCode) let data = try await provider.request(.signIn(authorizationInfoRequestDTO)) - Log.make().debug("\(data)") guard let responseCode = try decoder.decode(Response.self, from: data).code else { return } - Log.make().debug("\(responseCode)") switch responseCode { case AuthorizationRepositoryResponseCode.token.code: let token = try decoder.decode(GWResponse.self, from: data).data @@ -72,7 +70,7 @@ enum AuthorizationRepositoryEndPoint: TNEndPoint { var path: String { switch self { case .signIn: - return "auth/apple/signin" + return "/api/v1/auth/apple/signin" } } @@ -96,7 +94,6 @@ enum AuthorizationRepositoryEndPoint: TNEndPoint { var headers: TNHeaders { return .init(headers: [ - // TODO: 헤더 설정 ]) } } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift index d9c9361b..80324d9c 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift @@ -30,7 +30,7 @@ public final class LoginFeatureCoordinator: LoginFeatureCoordinating { func showLoginFlow() { let coordinator = LoginCoordinator( navigationController: navigationController, - isMockEnvironment: true, + isMockEnvironment: false, isMockFirst: true ) childCoordinators.append(coordinator) diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift index 53ede855..788f1f42 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift @@ -119,9 +119,6 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { } .store(in: &subscriptions) - let genderBirth = input.genderBirth - - // Complete 버튼이 클릭되어야만 해당 코드가 동작된다. Publishers .CombineLatest3(imageFormSubject.eraseToAnyPublisher(), nickNameSubject.eraseToAnyPublisher(), input.genderBirth) .sink { [weak self] imageForm, nickName, genderBirth in From 58d639e9d22954c933f8a4895e7081d212d67629 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Fri, 8 Dec 2023 23:59:39 +0900 Subject: [PATCH 11/22] =?UTF-8?q?fix:=20SignUpGenderBirth=EC=97=90?= =?UTF-8?q?=EC=84=9C=20SignUpProfile=EA=B9=8C=EC=A7=80=20=EC=84=B1?= =?UTF-8?q?=EB=B3=84=EB=B2=8C=EC=8A=A4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=95=88=EB=90=98=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/.swiftlint.yml | 70 +++++++++---------- .../Core/Network/Sources/TNEndPoint.swift | 11 +++ .../Core/Network/Sources/TNProvidable.swift | 10 ++- .../AuthorizationRepository.swift | 22 +++--- .../LoginScene/ViewModel/LoginViewModel.swift | 1 + .../Data/Repository/SignUpRepository.swift | 2 +- .../DTO => Domain/Entities}/SignUpUser.swift | 2 +- .../Sources/Domain/Entities/Token.swift | 6 +- .../UseCase/DateFormmattingUseCase.swift | 9 --- .../SignUpProfileViewController.swift | 2 +- .../ViewModel/SignUpProfileViewModel.swift | 20 +++++- 11 files changed, 88 insertions(+), 67 deletions(-) rename iOS/Projects/Features/SignUp/Sources/{Data/DTO => Domain/Entities}/SignUpUser.swift (94%) delete mode 100644 iOS/Projects/Features/SignUp/Sources/Domain/UseCase/DateFormmattingUseCase.swift diff --git a/iOS/.swiftlint.yml b/iOS/.swiftlint.yml index 3330e514..bb3f389b 100644 --- a/iOS/.swiftlint.yml +++ b/iOS/.swiftlint.yml @@ -33,38 +33,38 @@ trailing_comma: line_length: warning: 150 -custom_rules: - no_objcMembers: - name: "@objcMembers" - regex: "@objcMembers" - message: "Explicitly use @objc on each member you want to expose to Objective-C" - severity: error - no_direct_standard_out_logs: - name: "Writing log messages directly to standard out is disallowed" - regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" - match_kinds: - - identifier - message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" - severity: error - no_file_literal: - name: "#file is disallowed" - regex: "(\\b#file\\b)" - match_kinds: - - identifier - message: "Instead of #file, use #fileID" - severity: error - no_filepath_literal: - name: "#filePath is disallowed" - regex: "(\\b#filePath\\b)" - match_kinds: - - identifier - message: "Instead of #filePath, use #fileID." - severity: error - no_unchecked_sendable: - name: "`@unchecked Sendable` is discouraged." - regex: "@unchecked Sendable" - match_kinds: - - attribute.builtin - - typeidentifier - message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." - severity: error +#custom_rules: +# no_objcMembers: +# name: "@objcMembers" +# regex: "@objcMembers" +# message: "Explicitly use @objc on each member you want to expose to Objective-C" +# severity: error +# no_direct_standard_out_logs: +# name: "Writing log messages directly to standard out is disallowed" +# regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" +# match_kinds: +# - identifier +# message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" +# severity: error +# no_file_literal: +# name: "#file is disallowed" +# regex: "(\\b#file\\b)" +# match_kinds: +# - identifier +# message: "Instead of #file, use #fileID" +# severity: error +# no_filepath_literal: +# name: "#filePath is disallowed" +# regex: "(\\b#filePath\\b)" +# match_kinds: +# - identifier +# message: "Instead of #filePath, use #fileID." +# severity: error +# no_unchecked_sendable: +# name: "`@unchecked Sendable` is discouraged." +# regex: "@unchecked Sendable" +# match_kinds: +# - attribute.builtin +# - typeidentifier +# message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." +# severity: error diff --git a/iOS/Projects/Core/Network/Sources/TNEndPoint.swift b/iOS/Projects/Core/Network/Sources/TNEndPoint.swift index 96f1a00f..4cec1014 100644 --- a/iOS/Projects/Core/Network/Sources/TNEndPoint.swift +++ b/iOS/Projects/Core/Network/Sources/TNEndPoint.swift @@ -86,3 +86,14 @@ private extension Encodable { return dictionaryTarget } } + +// MARK: - AuthorizationInfoRequestDTO + +/// 애플로그인을 통해 받아온 데이터 entity +struct AuthorizationInfoRequestDTO: Codable { + /// identityToken + let identityToken: String + + /// authorizationCode + let authorizationCode: String +} diff --git a/iOS/Projects/Core/Network/Sources/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/TNProvidable.swift index 9bedd47c..8a0b29c4 100644 --- a/iOS/Projects/Core/Network/Sources/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/TNProvidable.swift @@ -7,6 +7,7 @@ // import Foundation +import Log // MARK: - TNProvidable @@ -48,13 +49,16 @@ public struct TNProvider: TNProvidable { public func request(_ service: T, successStatusCodeRange range: Range = 200 ..< 300) async throws -> Data { let (data, response) = try await session.data(for: service.request(), delegate: nil) - let res = try JSONDecoder().decode(Response.self, from: data) - print("코드 : \(res.code)") - print("에러 메시지 : \(res.errorMessage)") try checkStatusCode(response, successStatusCodeRange: range) return data } + public func requestResponse(_ service: T, successStatusCodeRange range: Range = 200 ..< 300) async throws -> (Data, URLResponse) { + let (data, response) = try await session.data(for: service.request(), delegate: nil) + try checkStatusCode(response, successStatusCodeRange: range) + return (data, response) + } + public func request(_ service: T, successStatusCodeRange range: Range = 200 ..< 300, interceptor: TNRequestInterceptor) async throws -> Data { let request = try interceptor.adapt(service.request(), session: session) let (data, response) = try await session.data(for: request, delegate: nil) diff --git a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift index fe71f55a..c2cabfaf 100644 --- a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift +++ b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift @@ -16,6 +16,7 @@ import Trinet enum AuthorizationRepositoryError: Error { case invalidData case failureDecode + case resonseDecodingError } // MARK: - AuthorizationRepository @@ -32,18 +33,16 @@ struct AuthorizationRepository: AuthorizationRepositoryRepresentable { return Future { promise in Task { do { - guard let token = String(data: authorizationInfo.identityToken, encoding: .utf8), - let authorizationCode = String(data: authorizationInfo.authorizationCode, encoding: .utf8) - else { - return - } - let authorizationInfoRequestDTO = AuthorizationInfoRequestDTO(identityToken: token, authorizationCode: authorizationCode) + let token = String(decoding: authorizationInfo.identityToken, as: UTF8.self) + let authorizationCode = String(decoding: authorizationInfo.authorizationCode, as: UTF8.self) - let data = try await provider.request(.signIn(authorizationInfoRequestDTO)) - guard let responseCode = try decoder.decode(Response.self, from: data).code else { - return + let authorizationInfoRequestDTO = AuthorizationInfoRequestDTO(identityToken: token, authorizationCode: authorizationCode) + let (data, response) = try await provider.requestResponse(.signIn(authorizationInfoRequestDTO)) + guard let urlResponse = response as? HTTPURLResponse else { + throw AuthorizationRepositoryError.resonseDecodingError } - switch responseCode { + let statusCode = urlResponse.statusCode + switch statusCode { case AuthorizationRepositoryResponseCode.token.code: let token = try decoder.decode(GWResponse.self, from: data).data promise(.success((token, nil))) @@ -93,8 +92,7 @@ enum AuthorizationRepositoryEndPoint: TNEndPoint { } var headers: TNHeaders { - return .init(headers: [ - ]) + return .default } } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift index c3167696..be69eab2 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift @@ -52,6 +52,7 @@ extension LoginViewModel: LoginViewModelRepresentable { .flatMap(authorizeUseCase.authorize(authorizationInfo:)) .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] loginResponse in + if let token = loginResponse.token { guard let accessToken = token.accessToken, let refreshToken = token.refreshToken diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift index 009547dd..b522f921 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift @@ -74,6 +74,6 @@ private enum SignUpRepositoryEndPoint: TNEndPoint { } var headers: TNHeaders { - [] + return .default } } diff --git a/iOS/Projects/Features/SignUp/Sources/Data/DTO/SignUpUser.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/SignUpUser.swift similarity index 94% rename from iOS/Projects/Features/SignUp/Sources/Data/DTO/SignUpUser.swift rename to iOS/Projects/Features/SignUp/Sources/Domain/Entities/SignUpUser.swift index f93ecff6..1146e4ce 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/DTO/SignUpUser.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/SignUpUser.swift @@ -24,7 +24,7 @@ public struct SignUpUser { extension SignUpUser: Codable { enum CodingKeys: String, CodingKey { case provider - case nickName + case nickName = "nickname" case gender case birthDate = "birthdate" case profileImage diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/Token.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/Token.swift index b79eed3f..4d5cd4c9 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/Token.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/Token.swift @@ -13,13 +13,13 @@ import Foundation /// 백엔드로부터 받아온 JWT를 담을 데이터 entity public struct Token { /// accessToken - let accesToken: String? + let accessToken: String? /// refreshToken let refreshToken: String? - public init(accesToken: String? = nil, refreshToken: String? = nil) { - self.accesToken = accesToken + public init(accessToken: String? = nil, refreshToken: String? = nil) { + self.accessToken = accessToken self.refreshToken = refreshToken } } diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/DateFormmattingUseCase.swift b/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/DateFormmattingUseCase.swift deleted file mode 100644 index b0883513..00000000 --- a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/DateFormmattingUseCase.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// DateFormmattingUseCase.swift -// SignUpFeature -// -// Created by 안종표 on 12/5/23. -// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. -// - -import Foundation diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/SignUpProfileViewController.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/SignUpProfileViewController.swift index 33544b06..1039720d 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/SignUpProfileViewController.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/SignUpProfileViewController.swift @@ -29,6 +29,7 @@ public final class SignUpProfileViewController: UIViewController { public init(viewModel: SignUpProfileViewModelRepresentable) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) + bindViewModel() } @available(*, unavailable) @@ -93,7 +94,6 @@ public final class SignUpProfileViewController: UIViewController { super.viewDidLoad() configureUI() bindUI() - bindViewModel() } } diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift index 788f1f42..66c954b8 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift @@ -76,6 +76,18 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { let nickNameSubject = PassthroughSubject() let completeSignUpSubject = PassthroughSubject() + var imageFormPubliser: AnyPublisher { + return imageFormSubject.eraseToAnyPublisher() + } + + var nickNamePublisher: AnyPublisher { + return nickNameSubject.eraseToAnyPublisher() + } + + var genderBirthPublisher: AnyPublisher { + return input.genderBirth.eraseToAnyPublisher() + } + let nickNameCheckedResult = input.nickNameTextFieldEditting .tryMap { [weak self] nickName in guard let result = self?.nickNameCheckUseCase.check(nickName: nickName) else { @@ -120,7 +132,11 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { .store(in: &subscriptions) Publishers - .CombineLatest3(imageFormSubject.eraseToAnyPublisher(), nickNameSubject.eraseToAnyPublisher(), input.genderBirth) + .CombineLatest3( + imageFormPubliser, + nickNamePublisher, + genderBirthPublisher + ) .sink { [weak self] imageForm, nickName, genderBirth in guard let userBit = self?.newUserInformation else { return @@ -153,7 +169,7 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { Log.make().error("\(error)") } }, receiveValue: { [weak self] token in - if let accessToken = token.accesToken, + if let accessToken = token.accessToken, let refreshToken = token.refreshToken { self?.signUpUseCase.accessTokenSave(accessToken) self?.signUpUseCase.refreshTokenSave(refreshToken) From 929172f8a67567f80e002d080f01c5738a3c2e79 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 12:26:24 +0900 Subject: [PATCH 12/22] =?UTF-8?q?Login,=20SignUp,=20TabBar=20Coordinator?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinator/AppCoordinator.swift | 47 +++++++++- .../Sources/Domain/Entity/InitialUser.swift | 20 +++- .../Login/Sources/Domain/Entity/Token.swift | 4 +- .../Delegate/LoginDidFinishedDelegate.swift | 13 --- .../Coordinator/LoginCoordinator.swift | 60 ------------ .../Coordinator/LoginFeatureCoordinator.swift | 79 ++++++++-------- .../Protocol/LoginFeatureCoordinating.swift | 1 + .../LoginScene/ViewModel/LoginViewModel.swift | 4 +- .../Data/Repository/SignUpRepository.swift | 4 +- .../Entities/{Token.swift => NewToken.swift} | 8 +- .../SignUpRepositoryRepresentable.swift | 2 +- .../Domain/UseCase/SignUpUseCase.swift | 4 +- .../Protocol/SignUpFeatureCoordinating.swift | 2 +- .../Coordinator/SignUpCoordinator.swift | 91 ------------------- .../SignUpFeatureCoordinator.swift | 80 +++++++++++++--- .../ViewModel/SignUpProfileViewModel.swift | 6 +- 16 files changed, 182 insertions(+), 243 deletions(-) delete mode 100644 iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift delete mode 100644 iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift rename iOS/Projects/Features/SignUp/Sources/Domain/Entities/{Token.swift => NewToken.swift} (83%) delete mode 100644 iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift diff --git a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift index 1f37074c..cbd9d31e 100644 --- a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift +++ b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift @@ -8,6 +8,7 @@ import Coordinator import LoginFeature +import SignUpFeature import SplashFeature import UIKit @@ -49,8 +50,22 @@ final class AppCoordinator: AppCoordinating { } func showLoginFlow() { - // TODO: LoginCoordinator 연결 - let coordinator = LoginFeatureCoordinator(navigationController: navigationController) + let coordinator = LoginFeatureCoordinator( + navigationController: navigationController, + isMockEnvironment: false, + isMockFirst: true + ) + childCoordinators.append(coordinator) + coordinator.finishDelegate = self + coordinator.start() + } + + func showSignUpFlow(newUserInformation: NewUserInformation) { + let coordinator = SignUpFeatureCoordinator( + navigationController: navigationController, + newUserInformation: newUserInformation, + isMockEnvironment: false + ) childCoordinators.append(coordinator) coordinator.finishDelegate = self coordinator.start() @@ -71,6 +86,7 @@ extension AppCoordinator: CoordinatorFinishDelegate { childCoordinators = childCoordinators.filter { $0.flow != childCoordinator.flow } + navigationController.popToRootViewController(animated: false) } } @@ -85,3 +101,30 @@ extension AppCoordinator: SplashCoordinatorFinishDelegate { } } } + +// MARK: SignUpFeatureCoordinatorFinishDelegate + +extension AppCoordinator: SignUpFeatureCoordinatorFinishDelegate { + func signUpFeatureCooridnatorDidFinished() { + showTabBarFlow() + } +} + +// MARK: LoginFeatureFinishDelegate + +extension AppCoordinator: LoginFeatureFinishDelegate { + func loginFeatureCoordinatorDidFinished(initialUser: InitialUser?, token: Token?) { + if let initialUser { + // TODO: 처음접속하는 유저일 경우 signUp + showSignUpFlow(newUserInformation: NewUserInformation( + mappedUserID: initialUser.mappedUserID, + provider: initialUser.provider + ) + ) + } + + if let token { + // TODO: 기존에 접속해본적이 있어서 로그인하고 토큰을 받은 경우 + } + } +} diff --git a/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift b/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift index b392f606..f4bd6b86 100644 --- a/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift +++ b/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift @@ -11,15 +11,25 @@ import Foundation // MARK: - InitialUser /// 처음 로그인 하는 유저의 Response를 담을 Entity -struct InitialUser { +public struct InitialUser { /// 처음 로그인 하는지 아닌지 - let isFirstLogined: Bool + public let isFirstLogined: Bool /// - let mappedUserID: String + public let mappedUserID: String /// OAuth 로그인 종류 - let provider: AuthProvider + public let provider: AuthProvider + + public init( + isFirstLogined: Bool, + mappedUserID: String, + provider: AuthProvider + ) { + self.isFirstLogined = isFirstLogined + self.mappedUserID = mappedUserID + self.provider = provider + } } // MARK: Codable @@ -28,6 +38,6 @@ extension InitialUser: Codable {} // MARK: - AuthProvider -enum AuthProvider: String, Codable { +public enum AuthProvider: String, Codable { case apple } diff --git a/iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift b/iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift index 018a404f..474f3d96 100644 --- a/iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift +++ b/iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift @@ -11,14 +11,14 @@ import Foundation // MARK: - Token /// 백엔드로부터 받아온 JWT를 담을 데이터 entity -struct Token { +public struct Token { /// accessToken let accessToken: String? /// refreshToken let refreshToken: String? - init(accesToken: String? = nil, refreshToken: String? = nil) { + public init(accesToken: String? = nil, refreshToken: String? = nil) { accessToken = accesToken self.refreshToken = refreshToken } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift deleted file mode 100644 index aef631bf..00000000 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// LoginDidFinishedDelegate.swift -// LoginFeature -// -// Created by 안종표 on 12/7/23. -// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. -// - -import Foundation - -protocol LoginDidFinishedDelegate: AnyObject { - func loginCoordinatorDidFinished(initialUser: InitialUser?, token: Token?) -} diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift deleted file mode 100644 index 8d70ec7d..00000000 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginCoordinator.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// LoginCoordinator.swift -// LoginFeature -// -// Created by 안종표 on 12/7/23. -// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. -// - -import Coordinator -import Keychain -import Log -import Trinet -import UIKit - -final class LoginCoordinator: LoginCoordinating { - var navigationController: UINavigationController - var childCoordinators: [Coordinating] = [] - weak var finishDelegate: CoordinatorFinishDelegate? - weak var loginFinishDelegate: LoginDidFinishedDelegate? - var flow: CoordinatorFlow = .login - - private let isMockEnvironment: Bool - private let isMockFirst: Bool - - init( - navigationController: UINavigationController, - isMockEnvironment: Bool, - isMockFirst: Bool - ) { - self.navigationController = navigationController - self.isMockEnvironment = isMockEnvironment - self.isMockFirst = isMockFirst - } - - func start() { - guard let jsonPath = isMockFirst ? - Bundle(for: Self.self).path(forResource: "InitialUser", ofType: "json") : Bundle(for: Self.self).path(forResource: "Token", ofType: "json"), - let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) - else { - Log.make().error("Login Mock 데이터를 생성할 수 없습니다.") - return - } - - let urlSession: URLSessionProtocol = isMockEnvironment ? MockURLSession(mockData: jsonData) : URLSession.shared - - let authorizeUseCase = AuthorizeUseCase( - authorizationRepository: AuthorizationRepository(session: urlSession), - keychainRepository: KeychainRepository(keychain: Keychain.shared) - ) - let loginViewModel = LoginViewModel(coordinator: self, authorizeUseCase: authorizeUseCase) - let viewController = LoginViewController(viewModel: loginViewModel) - - navigationController.pushViewController(viewController, animated: false) - } - - func finish(initialUser: InitialUser? = nil, token: Token? = nil) { - loginFinishDelegate?.loginCoordinatorDidFinished(initialUser: initialUser, token: token) - finishDelegate?.flowDidFinished(childCoordinator: self) - } -} diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift index 80324d9c..474171fa 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift @@ -8,66 +8,65 @@ import Coordinator import Keychain -import SignUpFeature +import Log +import Trinet import UIKit +// MARK: - LoginFeatureFinishDelegate + +public protocol LoginFeatureFinishDelegate: AnyObject { + func loginFeatureCoordinatorDidFinished(initialUser: InitialUser?, token: Token?) +} + // MARK: - LoginFeatureCoordinator public final class LoginFeatureCoordinator: LoginFeatureCoordinating { public var navigationController: UINavigationController public var childCoordinators: [Coordinating] = [] public weak var finishDelegate: CoordinatorFinishDelegate? + public weak var loginFeatureFinishDelegate: LoginFeatureFinishDelegate? public var flow: CoordinatorFlow = .login - public init(navigationController: UINavigationController) { + private let isMockEnvironment: Bool + private let isMockFirst: Bool + + public init( + navigationController: UINavigationController, + isMockEnvironment: Bool, + isMockFirst: Bool + ) { self.navigationController = navigationController + self.isMockEnvironment = isMockEnvironment + self.isMockFirst = isMockFirst } public func start() { showLoginFlow() } - func showLoginFlow() { - let coordinator = LoginCoordinator( - navigationController: navigationController, - isMockEnvironment: false, - isMockFirst: true - ) - childCoordinators.append(coordinator) - coordinator.finishDelegate = self - coordinator.loginFinishDelegate = self - coordinator.start() - } -} - -// MARK: CoordinatorFinishDelegate - -extension LoginFeatureCoordinator: CoordinatorFinishDelegate { - public func flowDidFinished(childCoordinator: Coordinating) { - childCoordinators = childCoordinators.filter { - $0.flow != childCoordinator.flow + public func showLoginFlow() { + guard let jsonPath = isMockFirst ? + Bundle(for: Self.self).path(forResource: "InitialUser", ofType: "json") : Bundle(for: Self.self).path(forResource: "Token", ofType: "json"), + let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) + else { + Log.make().error("Login Mock 데이터를 생성할 수 없습니다.") + return } - } -} -// MARK: LoginDidFinishedDelegate + let urlSession: URLSessionProtocol = isMockEnvironment ? MockURLSession(mockData: jsonData) : URLSession.shared -extension LoginFeatureCoordinator: LoginDidFinishedDelegate { - func loginCoordinatorDidFinished(initialUser: InitialUser?, token: Token?) { - if let initialUser { - let coordinator = SignUpFeatureCoordinator( - navigationController: navigationController, - newUserInformation: NewUserInformation( - mappedUserID: initialUser.mappedUserID, - provider: .apple - ) - ) - childCoordinators.append(coordinator) - coordinator.finishDelegate = self - coordinator.showSignUpFlow() - } + let authorizeUseCase = AuthorizeUseCase( + authorizationRepository: AuthorizationRepository(session: urlSession), + keychainRepository: KeychainRepository(keychain: Keychain.shared) + ) + let loginViewModel = LoginViewModel(coordinator: self, authorizeUseCase: authorizeUseCase) + let viewController = LoginViewController(viewModel: loginViewModel) + + navigationController.pushViewController(viewController, animated: false) + } - // TODO: User가 처음이 아니라면 토큰이 존재한다 해당 토큰 갖고 TabBar로 넘어가야됨. - if let token {} + public func finish(initialUser: InitialUser? = nil, token: Token? = nil) { + loginFeatureFinishDelegate?.loginFeatureCoordinatorDidFinished(initialUser: initialUser, token: token) + finishDelegate?.flowDidFinished(childCoordinator: self) } } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift index 81bd2c9e..31cfd207 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift @@ -11,4 +11,5 @@ import Foundation protocol LoginFeatureCoordinating: Coordinating { func showLoginFlow() + func finish(initialUser: InitialUser?, token: Token?) } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift index be69eab2..ef8aded8 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift @@ -32,11 +32,11 @@ enum LoginState { final class LoginViewModel { private var subscriptions: Set = [] - private let coordinator: LoginCoordinating + private let coordinator: LoginFeatureCoordinating private let authorizeUseCase: AuthorizeUseCaseRepresentable init( - coordinator: LoginCoordinating, + coordinator: LoginFeatureCoordinating, authorizeUseCase: AuthorizeUseCaseRepresentable ) { self.coordinator = coordinator diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift index b522f921..8fce8132 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift @@ -26,7 +26,7 @@ public struct SignUpRepository: SignUpRepositoryRepresentable { provider = TNProvider(session: urlSession) } - public func signUp(signUpUser: SignUpUser) -> AnyPublisher { + public func signUp(signUpUser: SignUpUser) -> AnyPublisher { return Future { promise in Task { do { @@ -37,7 +37,7 @@ public struct SignUpRepository: SignUpRepositoryRepresentable { } } } - .decode(type: GWResponse.self, decoder: JSONDecoder()) + .decode(type: GWResponse.self, decoder: JSONDecoder()) .compactMap(\.data) .eraseToAnyPublisher() } diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/Token.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewToken.swift similarity index 83% rename from iOS/Projects/Features/SignUp/Sources/Domain/Entities/Token.swift rename to iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewToken.swift index 4d5cd4c9..330e15f4 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/Token.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewToken.swift @@ -1,5 +1,5 @@ // -// Token.swift +// NewToken.swift // SignUpFeature // // Created by 안종표 on 12/7/23. @@ -8,10 +8,10 @@ import Foundation -// MARK: - Token +// MARK: - NewToken /// 백엔드로부터 받아온 JWT를 담을 데이터 entity -public struct Token { +public struct NewToken { /// accessToken let accessToken: String? @@ -26,4 +26,4 @@ public struct Token { // MARK: Codable -extension Token: Codable {} +extension NewToken: Codable {} diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift index e02762e3..97531cf1 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift @@ -10,5 +10,5 @@ import Combine import Foundation public protocol SignUpRepositoryRepresentable { - func signUp(signUpUser: SignUpUser) -> AnyPublisher + func signUp(signUpUser: SignUpUser) -> AnyPublisher } diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift b/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift index c235d6a9..f845d61f 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift @@ -13,7 +13,7 @@ import Foundation // MARK: - SignUpUseCaseRepresentable public protocol SignUpUseCaseRepresentable { - func signUp(signUpUser: SignUpUser) -> AnyPublisher + func signUp(signUpUser: SignUpUser) -> AnyPublisher func accessTokenSave(_ token: String) func refreshTokenSave(_ token: String) } @@ -32,7 +32,7 @@ public final class SignUpUseCase: SignUpUseCaseRepresentable { self.keychainRepository = keychainRepository } - public func signUp(signUpUser: SignUpUser) -> AnyPublisher { + public func signUp(signUpUser: SignUpUser) -> AnyPublisher { return signUpRepository.signUp(signUpUser: signUpUser) .eraseToAnyPublisher() } diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/Protocol/SignUpFeatureCoordinating.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/Protocol/SignUpFeatureCoordinating.swift index d5b7b929..d6c48e6e 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/Protocol/SignUpFeatureCoordinating.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/Protocol/SignUpFeatureCoordinating.swift @@ -10,5 +10,5 @@ import Coordinator import Foundation public protocol SignUpFeatureCoordinating: Coordinating { - func showSignUpFlow() + func pushSingUpContainerViewController() } diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift deleted file mode 100644 index 7c8737e7..00000000 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpCoordinator.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// SignUpCoordinator.swift -// SignUpFeature -// -// Created by 안종표 on 12/7/23. -// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. -// - -import Coordinator -import Keychain -import Log -import Trinet -import UIKit - -public final class SignUpCoordinator: SignUpCoordinating { - public var navigationController: UINavigationController - public var childCoordinators: [Coordinating] = [] - public weak var finishDelegate: CoordinatorFinishDelegate? - public var flow: CoordinatorFlow = .signup - - private let newUserInformation: NewUserInformation - - private let isMockEnvironment: Bool - - init( - navigationController: UINavigationController, - isMockEnvironment: Bool, - userBit: NewUserInformation - ) { - self.navigationController = navigationController - self.isMockEnvironment = isMockEnvironment - newUserInformation = userBit - } - - public func start() { - pushSingUpContainerViewController() - } - - public func finish() { - finishDelegate?.flowDidFinished(childCoordinator: self) - } - - public func pushSingUpContainerViewController() { - guard let jsonPath = Bundle(for: Self.self).path(forResource: "Token", ofType: "json"), - let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) - else { - Log.make().error("Token 데이터를 생성할 수 없습니다.") - return - } - - let urlSession: URLSessionProtocol = isMockEnvironment ? MockURLSession(mockData: jsonData) : URLSession.shared - - let dateFormatUseCase = DateFormatUseCase() - - let signUpGenderBirthViewModel = SignUpGenderBirthViewModel(dateFormatUseCase: dateFormatUseCase) - - let signUpGenderBirthViewController = SignUpGenderBirthViewController(viewModel: signUpGenderBirthViewModel) - - let nickNameCheckUseCase = NickNameCheckUseCase() - - let imageFormRepository = ImageFormRepository(urlSession: urlSession) - - let imageTransmitUseCase = ImageTransmitUseCase(imageFormRepository: imageFormRepository) - - let signUpRepository = SignUpRepository(urlSession: urlSession) - - let keyChainRepository = KeychainRepository(keychain: Keychain.shared) - - let signUpUseCase = SignUpUseCase( - signUpRepository: signUpRepository, - keychainRepository: keyChainRepository - ) - - let signUpProfileViewModel = SignUpProfileViewModel( - coordinator: self, - nickNameCheckUseCase: nickNameCheckUseCase, - imageTransmitUseCase: imageTransmitUseCase, - signUpUseCase: signUpUseCase, - newUserInformation: newUserInformation - ) - - let signUpProfileViewController = SignUpProfileViewController(viewModel: signUpProfileViewModel) - - let signUpContainerViewController = SignUpContainerViewController( - signUpGenderBirthViewController: signUpGenderBirthViewController, - signUpProfileViewController: signUpProfileViewController - ) - - navigationController.pushViewController(signUpContainerViewController, animated: false) - } -} diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift index e219ff2c..e15a8ba7 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift @@ -7,45 +7,95 @@ // import Coordinator +import Keychain +import Log +import Trinet import UIKit +// MARK: - SignUpFeatureCoordinatorFinishDelegate + +public protocol SignUpFeatureCoordinatorFinishDelegate: AnyObject { + func signUpFeatureCooridnatorDidFinished() +} + // MARK: - SignUpFeatureCoordinator public final class SignUpFeatureCoordinator: SignUpFeatureCoordinating { public var navigationController: UINavigationController public var childCoordinators: [Coordinating] = [] public weak var finishDelegate: CoordinatorFinishDelegate? + public weak var signUpFeatureFinishDelegate: SignUpFeatureCoordinatorFinishDelegate? public var flow: CoordinatorFlow = .signup private let newUserInformation: NewUserInformation + private let isMockEnvironment: Bool + public init( navigationController: UINavigationController, - newUserInformation: NewUserInformation + newUserInformation: NewUserInformation, + isMockEnvironment: Bool ) { self.navigationController = navigationController self.newUserInformation = newUserInformation + self.isMockEnvironment = isMockEnvironment } public func start() { - showSignUpFlow() + pushSingUpContainerViewController() } - public func showSignUpFlow() { - let coordinator = SignUpCoordinator(navigationController: navigationController, isMockEnvironment: false, userBit: newUserInformation) - childCoordinators.append(coordinator) - coordinator.finishDelegate = self - coordinator.start() + public func finish() { + finishDelegate?.flowDidFinished(childCoordinator: self) + signUpFeatureFinishDelegate?.signUpFeatureCooridnatorDidFinished() } -} -// MARK: CoordinatorFinishDelegate - -extension SignUpFeatureCoordinator: CoordinatorFinishDelegate { - public func flowDidFinished(childCoordinator: Coordinating) { - childCoordinators = childCoordinators.filter { - $0.flow != childCoordinator.flow + public func pushSingUpContainerViewController() { + guard let jsonPath = Bundle(for: Self.self).path(forResource: "Token", ofType: "json"), + let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) + else { + Log.make().error("Token 데이터를 생성할 수 없습니다.") + return } - navigationController.popToRootViewController(animated: false) + + let urlSession: URLSessionProtocol = isMockEnvironment ? MockURLSession(mockData: jsonData) : URLSession.shared + + let dateFormatUseCase = DateFormatUseCase() + + let signUpGenderBirthViewModel = SignUpGenderBirthViewModel(dateFormatUseCase: dateFormatUseCase) + + let signUpGenderBirthViewController = SignUpGenderBirthViewController(viewModel: signUpGenderBirthViewModel) + + let nickNameCheckUseCase = NickNameCheckUseCase() + + let imageFormRepository = ImageFormRepository(urlSession: urlSession) + + let imageTransmitUseCase = ImageTransmitUseCase(imageFormRepository: imageFormRepository) + + let signUpRepository = SignUpRepository(urlSession: urlSession) + + let keyChainRepository = KeychainRepository(keychain: Keychain.shared) + + let signUpUseCase = SignUpUseCase( + signUpRepository: signUpRepository, + keychainRepository: keyChainRepository + ) + + let signUpProfileViewModel = SignUpProfileViewModel( + coordinator: self, + nickNameCheckUseCase: nickNameCheckUseCase, + imageTransmitUseCase: imageTransmitUseCase, + signUpUseCase: signUpUseCase, + newUserInformation: newUserInformation + ) + + let signUpProfileViewController = SignUpProfileViewController(viewModel: signUpProfileViewModel) + + let signUpContainerViewController = SignUpContainerViewController( + signUpGenderBirthViewController: signUpGenderBirthViewController, + signUpProfileViewController: signUpProfileViewController + ) + + navigationController.pushViewController(signUpContainerViewController, animated: false) } } diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift index 66c954b8..776096e7 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift @@ -39,7 +39,7 @@ public enum SignUpProfileState { public final class SignUpProfileViewModel { private var subscriptions: Set = [] - private let coordinator: SignUpCoordinator + private let coordinator: SignUpFeatureCoordinator private let nickNameCheckUseCase: NickNameCheckUseCaseRepresentable private let imageTransmitUseCase: ImageTransmitUseCaseRepresentable private let signUpUseCase: SignUpUseCaseRepresentable @@ -47,7 +47,7 @@ public final class SignUpProfileViewModel { private let newUserInformation: NewUserInformation public init( - coordinator: SignUpCoordinator, + coordinator: SignUpFeatureCoordinator, nickNameCheckUseCase: NickNameCheckUseCaseRepresentable, imageTransmitUseCase: ImageTransmitUseCaseRepresentable, signUpUseCase: SignUpUseCaseRepresentable, @@ -154,7 +154,7 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { .store(in: &subscriptions) completeSignUpSubject - .flatMap { [weak self] signUpUser -> AnyPublisher in + .flatMap { [weak self] signUpUser -> AnyPublisher in guard let publisher = self?.signUpUseCase.signUp(signUpUser: signUpUser) else { return Fail(error: SignUpProfileViewModelError.invalidTokenPublisher).eraseToAnyPublisher() } From 5b8e43ccd9a9181bd0e4beb6fe034d5275a0c131 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 12:30:11 +0900 Subject: [PATCH 13/22] =?UTF-8?q?add:=20Auth=EB=AA=A8=EB=93=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dependency+Target.swift | 1 + iOS/Projects/Features/Login/Project.swift | 2 +- iOS/Projects/Features/SignUp/Project.swift | 2 +- iOS/Projects/Shared/Auth/Project.swift | 19 +++++++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 iOS/Projects/Shared/Auth/Project.swift diff --git a/iOS/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift b/iOS/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift index 029a9be4..c64b4024 100644 --- a/iOS/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift +++ b/iOS/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift @@ -34,6 +34,7 @@ public extension TargetDependency { static let keychain: TargetDependency = .project(target: "Keychain", path: .relativeToCore("Keychain")) static let cacher: TargetDependency = .project(target: "Cacher", path: .relativeToCore("Cacher")) static let userInformationManager: TargetDependency = .project(target: "UserInformationManager", path: .relativeToShared("UserInformationManager")) + static let auth: TargetDependency = .project(target: "Auth", path: .relativeToShared("Auth")) static func feature(_ feature: Feature) -> TargetDependency { return .project( diff --git a/iOS/Projects/Features/Login/Project.swift b/iOS/Projects/Features/Login/Project.swift index cc6bda7b..fb980410 100644 --- a/iOS/Projects/Features/Login/Project.swift +++ b/iOS/Projects/Features/Login/Project.swift @@ -7,7 +7,7 @@ let project = Project.makeModule( targets: .feature( .login, testingOptions: [.unitTest], - dependencies: [.trinet, .keychain, .combineCocoa, .log, TargetDependency.feature(.signUp)], + dependencies: [.trinet, .keychain, .combineCocoa, .log, .auth], testDependencies: [], resources: "Resources/**" ) diff --git a/iOS/Projects/Features/SignUp/Project.swift b/iOS/Projects/Features/SignUp/Project.swift index 1b1898fd..76e9470f 100644 --- a/iOS/Projects/Features/SignUp/Project.swift +++ b/iOS/Projects/Features/SignUp/Project.swift @@ -7,7 +7,7 @@ let project = Project.makeModule( targets: .feature( .signUp, testingOptions: [.unitTest], - dependencies: [.trinet, .keychain, .combineCocoa, .coordinator, .log, .designSystem, .commonNetworkingKeyManager], + dependencies: [.trinet, .keychain, .combineCocoa, .coordinator, .log, .designSystem, .commonNetworkingKeyManager, .auth], testDependencies: [], resources: "Resources/**" ) diff --git a/iOS/Projects/Shared/Auth/Project.swift b/iOS/Projects/Shared/Auth/Project.swift new file mode 100644 index 00000000..e835373a --- /dev/null +++ b/iOS/Projects/Shared/Auth/Project.swift @@ -0,0 +1,19 @@ +// +// Project.swift +// ProjectDescriptionHelpers +// +// Created by 안종표 on 12/9/23. +// + +import ProjectDescription +import ProjectDescriptionHelpers + +let project = Project.makeModule( + name: "Auth", + targets: .custom( + name: "Auth", + product: .framework, + testingOptions: [] + ) +) + From a1375b0a84bd33429304602b91b603a91da4fc22 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 12:31:02 +0900 Subject: [PATCH 14/22] =?UTF-8?q?add:=20Auth=EB=AA=A8=EB=93=88=EC=97=90=20?= =?UTF-8?q?Token=20Entity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Projects/Shared/Auth/Sources/Token.swift | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 iOS/Projects/Shared/Auth/Sources/Token.swift diff --git a/iOS/Projects/Shared/Auth/Sources/Token.swift b/iOS/Projects/Shared/Auth/Sources/Token.swift new file mode 100644 index 00000000..474f3d96 --- /dev/null +++ b/iOS/Projects/Shared/Auth/Sources/Token.swift @@ -0,0 +1,29 @@ +// +// Token.swift +// LoginFeature +// +// Created by 안종표 on 11/29/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import Foundation + +// MARK: - Token + +/// 백엔드로부터 받아온 JWT를 담을 데이터 entity +public struct Token { + /// accessToken + let accessToken: String? + + /// refreshToken + let refreshToken: String? + + public init(accesToken: String? = nil, refreshToken: String? = nil) { + accessToken = accesToken + self.refreshToken = refreshToken + } +} + +// MARK: Codable + +extension Token: Codable {} From a55e413f6b109f0315fa3f494cbf84a06e242f9c Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 12:34:23 +0900 Subject: [PATCH 15/22] =?UTF-8?q?add:=20Auth=EB=AA=A8=EB=93=88=EC=97=90=20?= =?UTF-8?q?AuthProvider=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinator/AppCoordinator.swift | 1 + .../AuthorizationRepository.swift | 1 + .../Sources/Domain/Entity/InitialUser.swift | 7 +---- .../Login/Sources/Domain/Entity/Token.swift | 29 ------------------- ...AuthorizationRepositoryRepresentable.swift | 1 + .../AuthorizeUseCaseRepresentable.swift | 1 + .../Coordinator/LoginFeatureCoordinator.swift | 1 + .../Protocol/LoginCoordinating.swift | 1 + .../Protocol/LoginFeatureCoordinating.swift | 1 + .../Sources/Domain/Entities/NewToken.swift | 29 ------------------- .../Domain/Entities/NewUserInformation.swift | 7 +---- iOS/Projects/Shared/Auth/Project.swift | 1 - .../Shared/Auth/Sources/AuthProvider.swift | 15 ++++++++++ 13 files changed, 24 insertions(+), 71 deletions(-) delete mode 100644 iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift delete mode 100644 iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewToken.swift create mode 100644 iOS/Projects/Shared/Auth/Sources/AuthProvider.swift diff --git a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift index cbd9d31e..67c33a34 100644 --- a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift +++ b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Coordinator import LoginFeature import SignUpFeature diff --git a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift index c2cabfaf..e5964336 100644 --- a/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift +++ b/iOS/Projects/Features/Login/Sources/Data/Repositories/AuthorizationRepository.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Combine import Foundation import Log diff --git a/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift b/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift index f4bd6b86..0118025b 100644 --- a/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift +++ b/iOS/Projects/Features/Login/Sources/Domain/Entity/InitialUser.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Foundation // MARK: - InitialUser @@ -35,9 +36,3 @@ public struct InitialUser { // MARK: Codable extension InitialUser: Codable {} - -// MARK: - AuthProvider - -public enum AuthProvider: String, Codable { - case apple -} diff --git a/iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift b/iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift deleted file mode 100644 index 474f3d96..00000000 --- a/iOS/Projects/Features/Login/Sources/Domain/Entity/Token.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Token.swift -// LoginFeature -// -// Created by 안종표 on 11/29/23. -// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. -// - -import Foundation - -// MARK: - Token - -/// 백엔드로부터 받아온 JWT를 담을 데이터 entity -public struct Token { - /// accessToken - let accessToken: String? - - /// refreshToken - let refreshToken: String? - - public init(accesToken: String? = nil, refreshToken: String? = nil) { - accessToken = accesToken - self.refreshToken = refreshToken - } -} - -// MARK: Codable - -extension Token: Codable {} diff --git a/iOS/Projects/Features/Login/Sources/Domain/Interfaces/Repositories/AuthorizationRepositoryRepresentable.swift b/iOS/Projects/Features/Login/Sources/Domain/Interfaces/Repositories/AuthorizationRepositoryRepresentable.swift index 29bf182a..213c4ddb 100644 --- a/iOS/Projects/Features/Login/Sources/Domain/Interfaces/Repositories/AuthorizationRepositoryRepresentable.swift +++ b/iOS/Projects/Features/Login/Sources/Domain/Interfaces/Repositories/AuthorizationRepositoryRepresentable.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Combine import Foundation diff --git a/iOS/Projects/Features/Login/Sources/Domain/UseCases/Protocol/AuthorizeUseCaseRepresentable.swift b/iOS/Projects/Features/Login/Sources/Domain/UseCases/Protocol/AuthorizeUseCaseRepresentable.swift index 9a486bd8..c97e669a 100644 --- a/iOS/Projects/Features/Login/Sources/Domain/UseCases/Protocol/AuthorizeUseCaseRepresentable.swift +++ b/iOS/Projects/Features/Login/Sources/Domain/UseCases/Protocol/AuthorizeUseCaseRepresentable.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Combine import Foundation diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift index 474171fa..070d595a 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Coordinator import Keychain import Log diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginCoordinating.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginCoordinating.swift index 9b6a3434..2030ceae 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginCoordinating.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginCoordinating.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Coordinator import Foundation diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift index 31cfd207..e9a32479 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Coordinator import Foundation diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewToken.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewToken.swift deleted file mode 100644 index 330e15f4..00000000 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewToken.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// NewToken.swift -// SignUpFeature -// -// Created by 안종표 on 12/7/23. -// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. -// - -import Foundation - -// MARK: - NewToken - -/// 백엔드로부터 받아온 JWT를 담을 데이터 entity -public struct NewToken { - /// accessToken - let accessToken: String? - - /// refreshToken - let refreshToken: String? - - public init(accessToken: String? = nil, refreshToken: String? = nil) { - self.accessToken = accessToken - self.refreshToken = refreshToken - } -} - -// MARK: Codable - -extension NewToken: Codable {} diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewUserInformation.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewUserInformation.swift index 4ba73c83..d93ee88a 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewUserInformation.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/NewUserInformation.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Foundation // MARK: - NewUserInformation @@ -27,9 +28,3 @@ public struct NewUserInformation { // MARK: Codable extension NewUserInformation: Codable {} - -// MARK: - AuthProvider - -public enum AuthProvider: String, Codable { - case apple -} diff --git a/iOS/Projects/Shared/Auth/Project.swift b/iOS/Projects/Shared/Auth/Project.swift index e835373a..7d5a05bb 100644 --- a/iOS/Projects/Shared/Auth/Project.swift +++ b/iOS/Projects/Shared/Auth/Project.swift @@ -16,4 +16,3 @@ let project = Project.makeModule( testingOptions: [] ) ) - diff --git a/iOS/Projects/Shared/Auth/Sources/AuthProvider.swift b/iOS/Projects/Shared/Auth/Sources/AuthProvider.swift new file mode 100644 index 00000000..039d8bf1 --- /dev/null +++ b/iOS/Projects/Shared/Auth/Sources/AuthProvider.swift @@ -0,0 +1,15 @@ +// +// AuthProvider.swift +// Auth +// +// Created by 안종표 on 12/9/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import Foundation + +// MARK: - AuthProvider + +public enum AuthProvider: String, Codable { + case apple +} From 8695a5a1c9c8849569dbe043f4a837f9dbaa79e7 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 12:35:45 +0900 Subject: [PATCH 16/22] =?UTF-8?q?chore:=20Token=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SignUp/Sources/Data/Repository/SignUpRepository.swift | 5 +++-- .../Domain/Interfaces/SignUpRepositoryRepresentable.swift | 3 ++- .../SignUp/Sources/Domain/UseCase/SignUpUseCase.swift | 5 +++-- .../ViewModel/SignUpProfileViewModel.swift | 3 ++- iOS/Projects/Shared/Auth/Sources/Token.swift | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift index 8fce8132..7d307c7d 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/SignUpRepository.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Combine import Foundation import Log @@ -26,7 +27,7 @@ public struct SignUpRepository: SignUpRepositoryRepresentable { provider = TNProvider(session: urlSession) } - public func signUp(signUpUser: SignUpUser) -> AnyPublisher { + public func signUp(signUpUser: SignUpUser) -> AnyPublisher { return Future { promise in Task { do { @@ -37,7 +38,7 @@ public struct SignUpRepository: SignUpRepositoryRepresentable { } } } - .decode(type: GWResponse.self, decoder: JSONDecoder()) + .decode(type: GWResponse.self, decoder: JSONDecoder()) .compactMap(\.data) .eraseToAnyPublisher() } diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift index 97531cf1..676af3dc 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/Interfaces/SignUpRepositoryRepresentable.swift @@ -6,9 +6,10 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Combine import Foundation public protocol SignUpRepositoryRepresentable { - func signUp(signUpUser: SignUpUser) -> AnyPublisher + func signUp(signUpUser: SignUpUser) -> AnyPublisher } diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift b/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift index f845d61f..dd79b3b0 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/UseCase/SignUpUseCase.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Combine import CommonNetworkingKeyManager import Foundation @@ -13,7 +14,7 @@ import Foundation // MARK: - SignUpUseCaseRepresentable public protocol SignUpUseCaseRepresentable { - func signUp(signUpUser: SignUpUser) -> AnyPublisher + func signUp(signUpUser: SignUpUser) -> AnyPublisher func accessTokenSave(_ token: String) func refreshTokenSave(_ token: String) } @@ -32,7 +33,7 @@ public final class SignUpUseCase: SignUpUseCaseRepresentable { self.keychainRepository = keychainRepository } - public func signUp(signUpUser: SignUpUser) -> AnyPublisher { + public func signUp(signUpUser: SignUpUser) -> AnyPublisher { return signUpRepository.signUp(signUpUser: signUpUser) .eraseToAnyPublisher() } diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift index 776096e7..bc1798c7 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Combine import Foundation import Keychain @@ -154,7 +155,7 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { .store(in: &subscriptions) completeSignUpSubject - .flatMap { [weak self] signUpUser -> AnyPublisher in + .flatMap { [weak self] signUpUser -> AnyPublisher in guard let publisher = self?.signUpUseCase.signUp(signUpUser: signUpUser) else { return Fail(error: SignUpProfileViewModelError.invalidTokenPublisher).eraseToAnyPublisher() } diff --git a/iOS/Projects/Shared/Auth/Sources/Token.swift b/iOS/Projects/Shared/Auth/Sources/Token.swift index 474f3d96..fe0c08f2 100644 --- a/iOS/Projects/Shared/Auth/Sources/Token.swift +++ b/iOS/Projects/Shared/Auth/Sources/Token.swift @@ -13,10 +13,10 @@ import Foundation /// 백엔드로부터 받아온 JWT를 담을 데이터 entity public struct Token { /// accessToken - let accessToken: String? + public let accessToken: String? /// refreshToken - let refreshToken: String? + public let refreshToken: String? public init(accesToken: String? = nil, refreshToken: String? = nil) { accessToken = accesToken From 6dd8a37f53fbf605c249d8183d1578d082945225 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 12:40:10 +0900 Subject: [PATCH 17/22] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=EC=8B=9C,=20=EB=B6=84=EA=B8=B0=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinator/AppCoordinator.swift | 17 +++++++++-------- .../Coordinator/LoginFeatureCoordinator.swift | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift index 67c33a34..523492fe 100644 --- a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift +++ b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift @@ -58,6 +58,7 @@ final class AppCoordinator: AppCoordinating { ) childCoordinators.append(coordinator) coordinator.finishDelegate = self + coordinator.loginFeatureFinishDelegate = self coordinator.start() } @@ -69,6 +70,7 @@ final class AppCoordinator: AppCoordinating { ) childCoordinators.append(coordinator) coordinator.finishDelegate = self + coordinator.signUpFeatureFinishDelegate = self coordinator.start() } @@ -87,7 +89,6 @@ extension AppCoordinator: CoordinatorFinishDelegate { childCoordinators = childCoordinators.filter { $0.flow != childCoordinator.flow } - navigationController.popToRootViewController(animated: false) } } @@ -116,16 +117,16 @@ extension AppCoordinator: SignUpFeatureCoordinatorFinishDelegate { extension AppCoordinator: LoginFeatureFinishDelegate { func loginFeatureCoordinatorDidFinished(initialUser: InitialUser?, token: Token?) { if let initialUser { - // TODO: 처음접속하는 유저일 경우 signUp - showSignUpFlow(newUserInformation: NewUserInformation( - mappedUserID: initialUser.mappedUserID, - provider: initialUser.provider - ) + showSignUpFlow( + newUserInformation: NewUserInformation( + mappedUserID: initialUser.mappedUserID, + provider: initialUser.provider + ) ) } - if let token { - // TODO: 기존에 접속해본적이 있어서 로그인하고 토큰을 받은 경우 + if token != nil { + showTabBarFlow() } } } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift index 070d595a..7da3ceeb 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift @@ -67,7 +67,7 @@ public final class LoginFeatureCoordinator: LoginFeatureCoordinating { } public func finish(initialUser: InitialUser? = nil, token: Token? = nil) { - loginFeatureFinishDelegate?.loginFeatureCoordinatorDidFinished(initialUser: initialUser, token: token) finishDelegate?.flowDidFinished(childCoordinator: self) + loginFeatureFinishDelegate?.loginFeatureCoordinatorDidFinished(initialUser: initialUser, token: token) } } From d647ec17e0151765d836843a6dea6209da95ce1f Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 13:47:38 +0900 Subject: [PATCH 18/22] =?UTF-8?q?fix:=20Encoder=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=B4=20=EC=95=A1=EC=84=B8=EC=8A=A4=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=9D=B4=20=ED=82=A4=EC=B2=B4=EC=9D=B8=EC=97=90=20=EC=8C=8D?= =?UTF-8?q?=EB=94=B0=EC=98=B4=ED=91=9C=EA=B0=80=20=EB=B6=99=EC=96=B4?= =?UTF-8?q?=EC=84=9C=20=EC=A0=80=EC=9E=A5=EB=90=98=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/CommonScene/Coordinator/AppCoordinator.swift | 2 +- iOS/Projects/Core/Network/Sources/TNProvidable.swift | 7 ------- .../Sources/Data/Repository/KeyChainRepository.swift | 6 ++++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift index 523492fe..e27e9289 100644 --- a/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift +++ b/iOS/Projects/App/WeTri/Sources/CommonScene/Coordinator/AppCoordinator.swift @@ -28,7 +28,7 @@ final class AppCoordinator: AppCoordinating { } func start() { - showLoginFlow() + showSplashFlow() } private func showSplashFlow() { diff --git a/iOS/Projects/Core/Network/Sources/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/TNProvidable.swift index 8a0b29c4..2bb61990 100644 --- a/iOS/Projects/Core/Network/Sources/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/TNProvidable.swift @@ -88,10 +88,3 @@ private extension TNProvider { } } } - -// MARK: - Response - -private struct Response: Codable { - let code: Int? - let errorMessage: String? -} diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/KeyChainRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/KeyChainRepository.swift index 215f5762..59cbf011 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/KeyChainRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/KeyChainRepository.swift @@ -15,6 +15,7 @@ import Log enum KeychainRepositoryError: Error { case invalidKey + case invalidValue } // MARK: - KeychainRepository @@ -28,8 +29,9 @@ final class KeychainRepository: KeychainRepositoryRepresentable { func save(key: String, value: String) { do { - let encoder = JSONEncoder() - let data = try encoder.encode(value) + guard let data = value.data(using: .utf8) else { + throw KeychainRepositoryError.invalidValue + } keychain.save(key: key, data: data) } catch { Log.make().error("\(error)") From e9b9603df9c216cec4af581d2a6314572ee0bf61 Mon Sep 17 00:00:00 2001 From: SeungHyun Hong Date: Sat, 9 Dec 2023 14:06:13 +0900 Subject: [PATCH 19/22] =?UTF-8?q?chore:=20lint=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/.swiftlint.yml | 74 +++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/iOS/.swiftlint.yml b/iOS/.swiftlint.yml index f7492e54..2ef6038a 100644 --- a/iOS/.swiftlint.yml +++ b/iOS/.swiftlint.yml @@ -15,7 +15,7 @@ only_rules: - unowned_variable_capture - custom_rules - trailing_comma -# - line_length + - line_length excluded: - Carthage @@ -29,42 +29,42 @@ indentation: 2 trailing_comma: mandatory_comma: true - + line_length: warning: 150 -#custom_rules: -# no_objcMembers: -# name: "@objcMembers" -# regex: "@objcMembers" -# message: "Explicitly use @objc on each member you want to expose to Objective-C" -# severity: error -# no_direct_standard_out_logs: -# name: "Writing log messages directly to standard out is disallowed" -# regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" -# match_kinds: -# - identifier -# message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" -# severity: error -# no_file_literal: -# name: "#file is disallowed" -# regex: "(\\b#file\\b)" -# match_kinds: -# - identifier -# message: "Instead of #file, use #fileID" -# severity: error -# no_filepath_literal: -# name: "#filePath is disallowed" -# regex: "(\\b#filePath\\b)" -# match_kinds: -# - identifier -# message: "Instead of #filePath, use #fileID." -# severity: error -# no_unchecked_sendable: -# name: "`@unchecked Sendable` is discouraged." -# regex: "@unchecked Sendable" -# match_kinds: -# - attribute.builtin -# - typeidentifier -# message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." -# severity: error +custom_rules: + no_objcMembers: + name: "@objcMembers" + regex: "@objcMembers" + message: "Explicitly use @objc on each member you want to expose to Objective-C" + severity: error + no_direct_standard_out_logs: + name: "Writing log messages directly to standard out is disallowed" + regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" + match_kinds: + - identifier + message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" + severity: error + no_file_literal: + name: "#file is disallowed" + regex: "(\\b#file\\b)" + match_kinds: + - identifier + message: "Instead of #file, use #fileID" + severity: error + no_filepath_literal: + name: "#filePath is disallowed" + regex: "(\\b#filePath\\b)" + match_kinds: + - identifier + message: "Instead of #filePath, use #fileID." + severity: error + no_unchecked_sendable: + name: "`@unchecked Sendable` is discouraged." + regex: "@unchecked Sendable" + match_kinds: + - attribute.builtin + - typeidentifier + message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." + severity: error From 6d010f5cb540f6cfb880a3998401d81e8c8f2f49 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 14:29:43 +0900 Subject: [PATCH 20/22] fix: merge conflict --- iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift | 5 ----- .../Core/Network/Sources/Multipart/MultipartFormData.swift | 5 +++-- .../Core/Network/Sources/Provider/TNProvidable.swift | 1 - .../Coordinator/Delegate/LoginDidFinishedDelegate.swift | 1 + .../Sources/Data/Repositories/MapImageUploadRepository.swift | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift b/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift index 7662dd95..8375d8cb 100644 --- a/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift +++ b/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift @@ -9,11 +9,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { - let accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkNWNkN2I2Ni03ZWU2LTQ0NTMtYTczZS0wMjYxMjY4NjFlOTYiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzAxOTYyNzM5LCJleHAiOjE3MDIwNDkxMzl9.Wu-xloayJ2T_sWaL6FCeml7j6UBQZlA7A0vUms3aK9Q".data(using: .utf8)! - Keychain.shared.save(key: Tokens.accessToken, data: accessToken) - - let refreshToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkNWNkN2I2Ni03ZWU2LTQ0NTMtYTczZS0wMjYxMjY4NjFlOTYiLCJ0eXBlIjoicmVmcmVzaCIsImlhdCI6MTcwMTk2MjczOSwiZXhwIjoxNzAyMDQ5MTM5fQ.8_R9fb67KO7z5Yu3AGPvD1DIdySDur285JU8C7UEjpg".data(using: .utf8)! - Keychain.shared.save(key: Tokens.refreshToken, data: refreshToken) return true } diff --git a/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift b/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift index f9e8a961..5196450a 100644 --- a/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift +++ b/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift @@ -18,17 +18,18 @@ public struct MultipartFormData { public init( uuid: UUID, - mimeType: String = "image/png", + mimeType _: String = "image/png", imageDataList: [Data] ) { boundary = "\(uuid.uuidString)" - self.multipartItems = imageDataList.map { MultipartItem(data: $0, mimeType: .imagePNG) } + multipartItems = imageDataList.map { MultipartItem(data: $0, mimeType: .imagePNG) } self.imageDataList = imageDataList } public init(uuid: UUID, multipartItems: [MultipartItem]) { boundary = uuid.uuidString self.multipartItems = multipartItems + imageDataList = [] } public func makeBody() -> Data { diff --git a/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift index e9287d52..971455dd 100644 --- a/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift @@ -7,7 +7,6 @@ // import Foundation -import Log // MARK: - TNProvidable diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift index 2b9784dc..79d3752b 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Delegate/LoginDidFinishedDelegate.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import Auth import Foundation public protocol LoginDidFinishedDelegate: AnyObject { diff --git a/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift b/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift index 0a1eeaec..6ffdbb0d 100644 --- a/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift +++ b/iOS/Projects/Features/Record/Sources/Data/Repositories/MapImageUploadRepository.swift @@ -73,4 +73,4 @@ struct ImageModel: Codable { case imageName case imageURL = "imageUrl" } -} \ No newline at end of file +} From 233260635786511482e02ba748abb68e702d79ac Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 16:26:43 +0900 Subject: [PATCH 21/22] =?UTF-8?q?fix:=20form-Data=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B7=B8=EB=A6=B0=EC=95=84=EC=9D=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=8A=95=EA=B8=B0=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20=EC=9D=B4=EC=9C=A0=20:=20fileName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/.swiftlint.yml | 70 +++++++++---------- .../Sources/Multipart/MultipartFormData.swift | 2 +- .../Sources/Multipart/MultipartItem.swift | 14 +++- .../Sources/Provider/TNProvidable.swift | 10 +++ 4 files changed, 59 insertions(+), 37 deletions(-) diff --git a/iOS/.swiftlint.yml b/iOS/.swiftlint.yml index 2ef6038a..2d9eb06c 100644 --- a/iOS/.swiftlint.yml +++ b/iOS/.swiftlint.yml @@ -33,38 +33,38 @@ trailing_comma: line_length: warning: 150 -custom_rules: - no_objcMembers: - name: "@objcMembers" - regex: "@objcMembers" - message: "Explicitly use @objc on each member you want to expose to Objective-C" - severity: error - no_direct_standard_out_logs: - name: "Writing log messages directly to standard out is disallowed" - regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" - match_kinds: - - identifier - message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" - severity: error - no_file_literal: - name: "#file is disallowed" - regex: "(\\b#file\\b)" - match_kinds: - - identifier - message: "Instead of #file, use #fileID" - severity: error - no_filepath_literal: - name: "#filePath is disallowed" - regex: "(\\b#filePath\\b)" - match_kinds: - - identifier - message: "Instead of #filePath, use #fileID." - severity: error - no_unchecked_sendable: - name: "`@unchecked Sendable` is discouraged." - regex: "@unchecked Sendable" - match_kinds: - - attribute.builtin - - typeidentifier - message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." - severity: error +#custom_rules: +# no_objcMembers: +# name: "@objcMembers" +# regex: "@objcMembers" +# message: "Explicitly use @objc on each member you want to expose to Objective-C" +# severity: error +# no_direct_standard_out_logs: +# name: "Writing log messages directly to standard out is disallowed" +# regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" +# match_kinds: +# - identifier +# message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" +# severity: error +# no_file_literal: +# name: "#file is disallowed" +# regex: "(\\b#file\\b)" +# match_kinds: +# - identifier +# message: "Instead of #file, use #fileID" +# severity: error +# no_filepath_literal: +# name: "#filePath is disallowed" +# regex: "(\\b#filePath\\b)" +# match_kinds: +# - identifier +# message: "Instead of #filePath, use #fileID." +# severity: error +# no_unchecked_sendable: +# name: "`@unchecked Sendable` is discouraged." +# regex: "@unchecked Sendable" +# match_kinds: +# - attribute.builtin +# - typeidentifier +# message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." +# severity: error diff --git a/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift b/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift index 5196450a..e8f5b31b 100644 --- a/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift +++ b/iOS/Projects/Core/Network/Sources/Multipart/MultipartFormData.swift @@ -40,7 +40,7 @@ public struct MultipartFormData { for item in multipartItems { let imageFieldName = "images" - let filename = "image\(UUID().uuidString)" + let filename = "image\(UUID().uuidString)\(item.fileExtension.rawValue)" body.append(boundaryPrefix) body.append(#"Content-Disposition: form-data; name="\#(imageFieldName)"; filename="\#(filename)"\#(lineBreak)"#) diff --git a/iOS/Projects/Core/Network/Sources/Multipart/MultipartItem.swift b/iOS/Projects/Core/Network/Sources/Multipart/MultipartItem.swift index 4e1540f0..bf45728a 100644 --- a/iOS/Projects/Core/Network/Sources/Multipart/MultipartItem.swift +++ b/iOS/Projects/Core/Network/Sources/Multipart/MultipartItem.swift @@ -8,16 +8,28 @@ import Foundation +// MARK: - MultipartItem + public struct MultipartItem { let data: Data let mimeType: MimeType + let fileExtension: FileExtension - public init(data: Data, mimeType: MimeType) { + public init(data: Data, mimeType: MimeType, fileExtension: FileExtension = .png) { self.data = data self.mimeType = mimeType + self.fileExtension = fileExtension } public enum MimeType: String { case imagePNG = "image/png" } } + +// MARK: - FileExtension + +public enum FileExtension: String { + case png = ".png" + case jpg = ".jpg" + case jpeg = ".jpeg" +} diff --git a/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift index 971455dd..1d366c1e 100644 --- a/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift @@ -38,6 +38,9 @@ public struct TNProvider: TNProvidable { public func uploadRequest(_ service: T, successStatusCodeRange range: Range = 200 ..< 300) async throws -> Data { guard let multipart = service.multipart else { throw TNError.unknownError } let (data, response) = try await session.upload(for: service.requestFormData(), from: multipart.makeBody()) + let decoded = try! JSONDecoder().decode(Response.self, from: data) + print(decoded.code) + print(decoded.errorMessage) try checkStatusCode(response, successStatusCodeRange: range) return data } @@ -89,3 +92,10 @@ private extension TNProvider { } } } + +// MARK: - Response + +struct Response: Codable { + let code: Int? + let errorMessage: String? +} From 9ff660a3b83dd471a671828046fe61caaf394f20 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 9 Dec 2023 17:33:46 +0900 Subject: [PATCH 22/22] =?UTF-8?q?chore:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/.swiftlint.yml | 70 +++++++++---------- .../Sources/Provider/TNProvidable.swift | 10 --- .../Coordinator/LoginFeatureCoordinator.swift | 4 +- .../Protocol/LoginFeatureCoordinating.swift | 2 +- .../LoginScene/ViewModel/LoginViewModel.swift | 4 +- .../Data/Repository/ImageFormRepository.swift | 6 +- .../Sources/Domain/Entities/SignUpUser.swift | 4 +- .../SignUp/Sources/Domain/Entities/User.swift | 16 ----- .../ViewModel/SignUpProfileViewModel.swift | 19 ++--- 9 files changed, 52 insertions(+), 83 deletions(-) delete mode 100644 iOS/Projects/Features/SignUp/Sources/Domain/Entities/User.swift diff --git a/iOS/.swiftlint.yml b/iOS/.swiftlint.yml index 2d9eb06c..2ef6038a 100644 --- a/iOS/.swiftlint.yml +++ b/iOS/.swiftlint.yml @@ -33,38 +33,38 @@ trailing_comma: line_length: warning: 150 -#custom_rules: -# no_objcMembers: -# name: "@objcMembers" -# regex: "@objcMembers" -# message: "Explicitly use @objc on each member you want to expose to Objective-C" -# severity: error -# no_direct_standard_out_logs: -# name: "Writing log messages directly to standard out is disallowed" -# regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" -# match_kinds: -# - identifier -# message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" -# severity: error -# no_file_literal: -# name: "#file is disallowed" -# regex: "(\\b#file\\b)" -# match_kinds: -# - identifier -# message: "Instead of #file, use #fileID" -# severity: error -# no_filepath_literal: -# name: "#filePath is disallowed" -# regex: "(\\b#filePath\\b)" -# match_kinds: -# - identifier -# message: "Instead of #filePath, use #fileID." -# severity: error -# no_unchecked_sendable: -# name: "`@unchecked Sendable` is discouraged." -# regex: "@unchecked Sendable" -# match_kinds: -# - attribute.builtin -# - typeidentifier -# message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." -# severity: error +custom_rules: + no_objcMembers: + name: "@objcMembers" + regex: "@objcMembers" + message: "Explicitly use @objc on each member you want to expose to Objective-C" + severity: error + no_direct_standard_out_logs: + name: "Writing log messages directly to standard out is disallowed" + regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" + match_kinds: + - identifier + message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" + severity: error + no_file_literal: + name: "#file is disallowed" + regex: "(\\b#file\\b)" + match_kinds: + - identifier + message: "Instead of #file, use #fileID" + severity: error + no_filepath_literal: + name: "#filePath is disallowed" + regex: "(\\b#filePath\\b)" + match_kinds: + - identifier + message: "Instead of #filePath, use #fileID." + severity: error + no_unchecked_sendable: + name: "`@unchecked Sendable` is discouraged." + regex: "@unchecked Sendable" + match_kinds: + - attribute.builtin + - typeidentifier + message: "Instead of using `@unchecked Sendable`, consider a safe alternative like a standard `Sendable` conformance or using `@preconcurrency import`. If you really must use `@unchecked Sendable`, you can add a `// swiftlint:disable:next no_unchecked_sendable` annotation with an explanation for how we know the type is thread-safe, and why we have to use @unchecked Sendable instead of Sendable. More explanation and suggested safe alternatives are available at https://github.com/airbnb/swift#unchecked-sendable." + severity: error diff --git a/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift b/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift index 1d366c1e..971455dd 100644 --- a/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift +++ b/iOS/Projects/Core/Network/Sources/Provider/TNProvidable.swift @@ -38,9 +38,6 @@ public struct TNProvider: TNProvidable { public func uploadRequest(_ service: T, successStatusCodeRange range: Range = 200 ..< 300) async throws -> Data { guard let multipart = service.multipart else { throw TNError.unknownError } let (data, response) = try await session.upload(for: service.requestFormData(), from: multipart.makeBody()) - let decoded = try! JSONDecoder().decode(Response.self, from: data) - print(decoded.code) - print(decoded.errorMessage) try checkStatusCode(response, successStatusCodeRange: range) return data } @@ -92,10 +89,3 @@ private extension TNProvider { } } } - -// MARK: - Response - -struct Response: Codable { - let code: Int? - let errorMessage: String? -} diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift index 7da3ceeb..c31fc2be 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/LoginFeatureCoordinator.swift @@ -66,8 +66,8 @@ public final class LoginFeatureCoordinator: LoginFeatureCoordinating { navigationController.pushViewController(viewController, animated: false) } - public func finish(initialUser: InitialUser? = nil, token: Token? = nil) { - finishDelegate?.flowDidFinished(childCoordinator: self) + public func finishLogin(initialUser: InitialUser? = nil, token: Token? = nil) { + finish() loginFeatureFinishDelegate?.loginFeatureCoordinatorDidFinished(initialUser: initialUser, token: token) } } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift index e9a32479..0def9346 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/Coordinator/Protocol/LoginFeatureCoordinating.swift @@ -12,5 +12,5 @@ import Foundation protocol LoginFeatureCoordinating: Coordinating { func showLoginFlow() - func finish(initialUser: InitialUser?, token: Token?) + func finishLogin(initialUser: InitialUser?, token: Token?) } diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift index ef8aded8..72d4c43d 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/ViewModel/LoginViewModel.swift @@ -61,11 +61,11 @@ extension LoginViewModel: LoginViewModelRepresentable { } self?.authorizeUseCase.accessTokenSave(accessToken) self?.authorizeUseCase.refreshTokenSave(refreshToken) - self?.coordinator.finish(initialUser: nil, token: token) + self?.coordinator.finishLogin(initialUser: nil, token: token) } if let initialUser = loginResponse.initialUser { - self?.coordinator.finish(initialUser: initialUser, token: nil) + self?.coordinator.finishLogin(initialUser: initialUser, token: nil) } }) .store(in: &subscriptions) diff --git a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift index a79da35c..c4e4417e 100644 --- a/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift +++ b/iOS/Projects/Features/SignUp/Sources/Data/Repository/ImageFormRepository.swift @@ -53,9 +53,9 @@ struct ImageFormEndPoint: TNEndPoint { init(imageDataList: [Data]) { let uuid = UUID() - headers = .init(headers: [ - .init(key: "Content-Type", value: "multipart/form-data; boundary=\(uuid.uuidString)"), - ]) + headers = [ + .contentType("multipart/form-data; boundary=\(uuid.uuidString)"), + ] multipart = MultipartFormData( uuid: uuid, diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/SignUpUser.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/SignUpUser.swift index 1146e4ce..6dda6806 100644 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/SignUpUser.swift +++ b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/SignUpUser.swift @@ -12,7 +12,7 @@ import Foundation public struct SignUpUser { let provider: String - let nickName: String + let nickname: String let gender: String let birthDate: String let profileImage: URL? @@ -24,7 +24,7 @@ public struct SignUpUser { extension SignUpUser: Codable { enum CodingKeys: String, CodingKey { case provider - case nickName = "nickname" + case nickname case gender case birthDate = "birthdate" case profileImage diff --git a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/User.swift b/iOS/Projects/Features/SignUp/Sources/Domain/Entities/User.swift deleted file mode 100644 index 9d928b25..00000000 --- a/iOS/Projects/Features/SignUp/Sources/Domain/Entities/User.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// User.swift -// SignUpFeature -// -// Created by 안종표 on 12/5/23. -// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. -// - -import Foundation - -struct User { - let nickName: String - let gender: Gender - let birthDate: String - let prifileImage: String -} diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift index bc1798c7..87487a6e 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/SignUpProfileScene/ViewModel/SignUpProfileViewModel.swift @@ -40,7 +40,7 @@ public enum SignUpProfileState { public final class SignUpProfileViewModel { private var subscriptions: Set = [] - private let coordinator: SignUpFeatureCoordinator + private let coordinator: SignUpFeatureCoordinating private let nickNameCheckUseCase: NickNameCheckUseCaseRepresentable private let imageTransmitUseCase: ImageTransmitUseCaseRepresentable private let signUpUseCase: SignUpUseCaseRepresentable @@ -48,7 +48,7 @@ public final class SignUpProfileViewModel { private let newUserInformation: NewUserInformation public init( - coordinator: SignUpFeatureCoordinator, + coordinator: SignUpFeatureCoordinating, nickNameCheckUseCase: NickNameCheckUseCaseRepresentable, imageTransmitUseCase: ImageTransmitUseCaseRepresentable, signUpUseCase: SignUpUseCaseRepresentable, @@ -139,28 +139,23 @@ extension SignUpProfileViewModel: SignUpProfileViewModelRepresentable { genderBirthPublisher ) .sink { [weak self] imageForm, nickName, genderBirth in - guard let userBit = self?.newUserInformation else { + guard let newUserInformation = self?.newUserInformation else { return } let signUpUser = SignUpUser( - provider: userBit.provider.rawValue, - nickName: nickName, + provider: newUserInformation.provider.rawValue, + nickname: nickName, gender: genderBirth.gender.rawValue, birthDate: genderBirth.birth, profileImage: imageForm.imageURL, - mappedUserID: userBit.mappedUserID + mappedUserID: newUserInformation.mappedUserID ) completeSignUpSubject.send(signUpUser) } .store(in: &subscriptions) completeSignUpSubject - .flatMap { [weak self] signUpUser -> AnyPublisher in - guard let publisher = self?.signUpUseCase.signUp(signUpUser: signUpUser) else { - return Fail(error: SignUpProfileViewModelError.invalidTokenPublisher).eraseToAnyPublisher() - } - return publisher - } + .flatMap(signUpUseCase.signUp(signUpUser:)) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { completion in switch completion {