diff --git a/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate b/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate index 973f12e..a4f7fe0 100644 Binary files a/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate and b/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AcaMate/1. View/11. Intro & Login/IntroView.swift b/AcaMate/1. View/11. Intro & Login/IntroView.swift index e776bc2..a2fed0d 100644 --- a/AcaMate/1. View/11. Intro & Login/IntroView.swift +++ b/AcaMate/1. View/11. Intro & Login/IntroView.swift @@ -99,10 +99,11 @@ struct IntroView: View { private func loadVersion() -> Future { return Future { promise in - APIManager.shared.loadAPIData(url: "\(API_URL)", - path: "/api/v1/in/app/version", - parameters: ["type":"I"], - decodingType: APIResponse.self) + let request = APIRequest(path: "/api/v1/in/app/version", + parameters: ["type":"I"], + decoding: APIResponse.self) + + APIManager.shared.loadAPIData(request) .sink { completion in switch completion { case .failure(let error): diff --git a/AcaMate/2. Model/API Request.swift b/AcaMate/2. Model/API Request.swift new file mode 100644 index 0000000..74f8415 --- /dev/null +++ b/AcaMate/2. Model/API Request.swift @@ -0,0 +1,29 @@ +// +// API Request.swift +// AcaMate +// +// Created by TAnine on 3/18/25. +// + +import Foundation +import Alamofire + +public struct APIRequest { + let url, path: String + let method: HTTPMethod + var parameters: [String: Any] + let headers: HTTPHeaders + let decoding: T.Type + + init(url: String = "\(API_URL)", path: String, + method: HTTPMethod = .get, headers: HTTPHeaders = [:], + parameters: [String : Any] = [:], decoding: T.Type) { + self.url = url + self.path = path + self.method = method + self.headers = headers + self.parameters = parameters + self.decoding = decoding + } +} + diff --git a/AcaMate/2. Model/API Response.swift b/AcaMate/2. Model/API Response.swift index 5a82663..70d64e4 100644 --- a/AcaMate/2. Model/API Response.swift +++ b/AcaMate/2. Model/API Response.swift @@ -6,13 +6,16 @@ // import Foundation +public protocol APIResponseProtocol: Decodable { + var status: Status { get } +} -class APIResponse: Codable { +class APIResponse: Codable, APIResponseProtocol { let status: Status let data: T? } -class Status: Codable { +public class Status: Codable { let code: APICode let message: String } @@ -58,6 +61,8 @@ enum APICode: Codable, RawRepresentable { } } + + // /api/v1/in/app/version ---------------- class VersionData: Codable { @@ -71,7 +76,15 @@ class Access: Codable { let access: String } - +// /api/v1/in/user ---------------- +class User: Codable { + let uid, name, type: String + let device_id, push_token: String? + let auto_login_yn: Bool + let login_date: String + let birth: String? + +} // /api/v1/in/user/login ---------------- diff --git a/AcaMate/3. ViewModel/LoginViewModel.swift b/AcaMate/3. ViewModel/LoginViewModel.swift index 9ace1dd..1c82f47 100644 --- a/AcaMate/3. ViewModel/LoginViewModel.swift +++ b/AcaMate/3. ViewModel/LoginViewModel.swift @@ -18,56 +18,42 @@ class LoginViewModel: ObservableObject { var bidArray: [String] = [] func USERPAITEST() { + @UserDefault(key: "token", defaultValue: "accToken") var accToken @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh - var acc = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJBTVRlc3RFbWFpbDIwMjUwMzE3IiwianRpIjoiYTQzN2Q4YjUtNzQyMi00NWVhLWFiNTktZWI2MTBkNDMwNWRlIiwiZXhwIjoxNzQyMjAwODQ3LCJpc3MiOiJBY2FNYXRlIiwiYXVkIjoiaHR0cHM6L2RldmFjYW1hdGUuaXBzdGVpbi5teWRzLm1lIn0.Y-xxdQWyu3BX_y5hlwWjAdxG5-QRZyHEgJ6T2pDkFRc" + let acc = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJBTVRlc3RFbWFpbDIwMjUwMzE4IiwianRpIjoiMjgwMjIwZDMtYzUwNS00YjFjLTgwMzgtZjBlNGJjYzM4MTE3IiwiZXhwIjoxNzQyMjYzMzgwLCJpc3MiOiJBY2FNYXRlIiwiYXVkIjoiaHR0cHM6L2RldmFjYW1hdGUuaXBzdGVpbi5teWRzLm1lIn0.f6kLKnsWhzlllSuYKxpFNuXuV4vOtJ2ox4IGSnxE67Y" - refresh = "tsLeVEtZTjy8iWDvT9vf7Kv7qQr3WgPjeNkFdMb9Rv4=" + refresh = "MRo+1HIvaPgECXrvwmGvtUpxSk7Pip7KtGSoWDqmjVA=" - APIManager.shared.loadAPIData(url: "\(API_URL)", - path: "/api/v1/in/user/cancel", - parameters: ["token": acc], - decodingType: APIResponse.self) - .flatMap { response -> AnyPublisher, Error> in - switch response.status.code { - case .inputErr(let code) where code == "101": - return APIManager.shared.reloadAccessToken() - .flatMap { newAcc -> AnyPublisher, Error> in - APIManager.shared.loadAPIData(url: "\(API_URL)", - path: "/api/v1/in/user/cancel", - parameters: ["token": (newAcc as! Access).access], - decodingType: APIResponse.self) - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - default: - return Just(response) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + let request = APIRequest(path: "/api/v1/in/user", + parameters: ["token": accToken], + decoding: APIResponse.self) + + APIManager.shared.loadUserAPIData(request: request) + .sink { completion in + switch completion { + case .failure(let error): + printLog("최종 에러: \(error)") + case .finished: + break + } + } receiveValue: { response in + guard let user = response.data as? User else { return } + printLog("최종 값 : \(user.name), \(user.birth)") } - } - .sink { completion in - switch completion { - case .failure(let error): - printLog("최종 \(error)") - case .finished: - break - } - } receiveValue: { response in - printLog("최종: \(response)") - } - .store(in: &cancellables) + .store(in: &cancellables) } + func loginAction(type: SNSLoginType) { + LoginController().login(type) .flatMap{ snsId in - APIManager.shared.loadAPIData(url: "\(API_URL)", - path: "/api/v1/in/user/login", - parameters: [ - "acctype": "\(type == .Apple ? "ST00": "ST01")", - "sns_id": "\(snsId.snsId)" - ], - decodingType: APIResponse.self) + APIManager.shared.loadAPIData(APIRequest(path: "/api/v1/in/user/login", + parameters: [ + "acctype": "\(type == .Apple ? "ST00": "ST01")", + "sns_id": "\(snsId.snsId)" + ], + decoding: APIResponse.self)) .map { response in return (snsId: snsId.snsId, response: response) } diff --git a/AcaMate/3. ViewModel/SelectAcademyViewModel.swift b/AcaMate/3. ViewModel/SelectAcademyViewModel.swift index ac13db9..4f4de90 100644 --- a/AcaMate/3. ViewModel/SelectAcademyViewModel.swift +++ b/AcaMate/3. ViewModel/SelectAcademyViewModel.swift @@ -18,11 +18,12 @@ class SelectAcademyViewModel: ObservableObject { func loadAcademy() { @UserDefault(key: "token", defaultValue: "accToken") var token @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh - APIManager.shared.loadAPIData(url: "\(API_URL)", - path: "/api/v1/in/user/academy", - method: .get, - parameters: ["token": token, "refresh": refresh], - decodingType: APIResponse<[AcademyName]>.self) + + let request = APIRequest(path: "/api/v1/in/user/academy", + parameters: ["token": token, "refresh": refresh], + decoding: APIResponse<[AcademyName]>.self) + + APIManager.shared.loadAPIData(request) .sink { completion in switch completion { case .failure(let error): diff --git a/AcaMate/5. Manager/APIManager.swift b/AcaMate/5. Manager/APIManager.swift index a69b594..ff16eb6 100644 --- a/AcaMate/5. Manager/APIManager.swift +++ b/AcaMate/5. Manager/APIManager.swift @@ -14,29 +14,26 @@ public class APIManager { private var cancellables = Set() public static let shared = APIManager() + @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh + @UserDefault(key: "token", defaultValue: "accToken") var accToken + private init(cancellables: Set = Set()) { self.cancellables = cancellables } - public func loadAPIData(url: String, path: String, - method: HTTPMethod = .get, - parameters: [String: Any], - headers: HTTPHeaders = [:],//["Accept": "application/json"], - decodingType: T.Type) -> Future { - let encoding: ParameterEncoding = (method == .get) ? URLEncoding.default : JSONEncoding.default + public func loadAPIData(_ request: APIRequest) -> Future { + let encoding: ParameterEncoding = (request.method == .get) ? URLEncoding.default : JSONEncoding.default + return Future { promise in - printLog(parameters) - AF.request("\(url)\(path)", - method: method, - parameters: parameters, + printLog(request.parameters) + AF.request("\(request.url)\(request.path)", + method: request.method, + parameters: request.parameters, encoding: encoding, - headers: headers + headers: request.headers ) .validate(statusCode: 200 ..< 300) - // .responseString { response in - // printLog(response) - // } - .responseDecodable(of: decodingType) { response in + .responseDecodable(of: request.decoding) { response in switch response.result { case .success(let value): @@ -53,48 +50,83 @@ public class APIManager { } - public func reloadAccessToken() -> Future { - - @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh return Future { [weak self] promise in guard let self = self else {return} - APIManager.shared.loadAPIData(url: "\(API_URL)", - path: "/api/v1/in/app/retryAccess", + + let request = APIRequest.init(path: "/api/v1/in/app/retryAccess", parameters: ["refresh": refresh], - decodingType: APIResponse.self) - .sink { completion in - switch completion { - case .failure(let error): - promise(.failure(error)) - printLog("\(error)") - case .finished: - printLog("엑세스 토큰 재발급 완료") - break - } - } receiveValue: { response in - guard let accToken = response as? APIResponse else { - promise(.failure(ACA_ERROR("Unknown ERROR"))) - return - } - - switch accToken.status.code { - case .success(let code): - if code == "000" { - if let tknData = accToken.data { promise(.success(tknData)) } + decoding: APIResponse.self) + + APIManager.shared.loadAPIData(request) + .sink { completion in + switch completion { + case .failure(let error): + promise(.failure(error)) + printLog("\(error)") + case .finished: + printLog("엑세스 토큰 재발급 완료") + break + } + } receiveValue: { response in + guard let accToken = response as? APIResponse else { + promise(.failure(ACA_ERROR("Unknown ERROR"))) + return + } + + switch accToken.status.code { + case .success(let code): + if code == "000" { + if let tknData = accToken.data { promise(.success(tknData)) } + } + case .inputErr(let code): + // 로그인 화면으로 전환하기 + promise(.failure(ACA_ERROR("Refresh token ERROR(\(code), \(accToken.status.message)"))) + default: + // 그외에 서버에서 처리를 하다가 문제가 생겨서 발생하는 에러는 여기로 보낼거임 + promise(.failure(ACA_ERROR("Unknown ERROR"))) } - case .inputErr(let code): - // 로그인 화면으로 전환하기 - promise(.failure(ACA_ERROR("Refresh token ERROR(\(code), \(accToken.status.message)"))) - default: - // 그외에 서버에서 처리를 하다가 문제가 생겨서 발생하는 에러는 여기로 보낼거임 - promise(.failure(ACA_ERROR("Unknown ERROR"))) } - } - .store(in: &self.cancellables) + .store(in: &self.cancellables) + } + } + + + public func loadUserAPIData(request: APIRequest) -> Future{ + return Future { [weak self] promise in + guard let self = self else {return} + printLog(request.parameters["token"]) + loadAPIData(request) + .flatMap { (response: T) -> AnyPublisher in + switch response.status.code { + case .inputErr(let code) where code == "101": + return self.reloadAccessToken() + .flatMap { response -> AnyPublisher in + self.accToken = (response as! Access).access + var updateRequest = request + updateRequest.parameters["token"] = self.accToken + return APIManager.shared.loadAPIData(updateRequest) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + default: + return Just(response) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + } + .sink { completion in + switch completion { + case .failure(let error): + promise(.failure(error)) + case .finished: + break + } + } receiveValue: { finalResponse in + promise(.success(finalResponse)) + } + .store(in: &self.cancellables) } - - } }