import Foundation public protocol ApiProtocol { func login(email: String, password: String) async throws -> User func check(plateNumber: String, force: Bool) async throws -> Vehicle } public class Api: ApiProtocol { private let session: URLSession private let settings: SettingsProtocol public static let shared = Api() public init(session: URLSession? = nil, settings: SettingsProtocol = Settings.shared) { self.settings = settings if Testing.isUITesting { if let testSession = Testing.testUrlSession { self.session = testSession } else { fatalError("Error creating test session") } return } if let session = session { self.session = session } else { let sessionConfig = URLSessionConfiguration.default sessionConfig.timeoutIntervalForRequest = 60.0 sessionConfig.timeoutIntervalForResource = 60.0 self.session = URLSession(configuration: sessionConfig) } } // MARK: - Private wrappres private func createRequest(api: String, method: String, body: B? = nil, params: [String:P]? = nil) -> URLRequest? where B: Encodable, P: LosslessStringConvertible { guard var urlComponents = URLComponents(string: Constants.baseUrl + api) else { return nil } if let params = params, method.uppercased() == "GET" { urlComponents.queryItems = params.map { URLQueryItem(name: $0, value: String($1)) } } var request = URLRequest(url: urlComponents.url!) request.httpMethod = method request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("Bearer " + settings.user.token, forHTTPHeaderField: "Authorization") if let body = body, method.uppercased() != "GET" { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted if let data = try? encoder.encode(body) { request.httpBody = data } } return request } private func makeRequest(api: String, method: String = "GET", body: B?, params: [String:P]? = nil) async throws -> T where T: Decodable, B: Encodable, P: LosslessStringConvertible { guard let request = self.createRequest(api: api, method: method, body: body, params: params) else { throw ApiError.message("Error creating request") } return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in let task = self.session.dataTask(with: request) { data, response, error in if let error = error { continuation.resume(throwing: error) return } guard let data = data, data.count > 0 else { continuation.resume(throwing: ApiError.message("No data in response")) return } // let str = String(data: data, encoding: .utf8) // print("================================") // if let string = str?.replacingOccurrences(of: "\\\"", with: "\"") // .replacingOccurrences(of: "\\'", with: "'") // .replacingOccurrences(of: "\\n", with: "") { // print(string) // } // print("================================") do { let resp = try JSONDecoder().decode(Response.self, from: data) if resp.success { continuation.resume(returning: resp.data!) } else { if let code = resp.errorCode { continuation.resume(throwing: ApiError(code: code)) } else if let errorMessage = resp.error { continuation.resume(throwing: ApiError.message(errorMessage)) } else { continuation.resume(throwing: ApiError.generic) } } } catch let error as Swift.DecodingError { continuation.resume(throwing: ApiError.message((error as CustomDebugStringConvertible).debugDescription)) } catch { continuation.resume(throwing: error) } } task.resume() } } private func makeGetRequest(api: String, params:[String: P]? = nil) async throws -> T where T: Decodable, P: LosslessStringConvertible { return try await self.makeRequest(api: api, method: "GET", body: nil as Int?, params: params) } private func makeEmptyGetRequest(api: String) async throws -> T where T: Decodable { return try await self.makeRequest(api: api, method: "GET", body: nil as Int?, params: nil as [String:Int]?) } private func makeEmptyBodyRequest(api: String, method: String = "POST") async throws -> T where T: Decodable { return try await self.makeRequest(api: api, method: method, body: nil as Int?, params: nil as [String:Int]?) } private func makeBodyRequest(api: String, body: B?, method: String = "POST") async throws -> T where T: Decodable, B: Encodable { return try await self.makeRequest(api: api, method: method, body: body, params: nil as [String:Int]?) } // MARK: - AutoCat public API @MainActor public func login(email: String, password: String) async throws -> User { let body = [ "email": email, "password": password ] return try await self.makeBodyRequest(api: "user/login", body: body) } @MainActor public func signup(email: String, password: String) async throws -> User { let body = [ "email": email, "password": password ] return try await self.makeBodyRequest(api: "user/signup", body: body) } @MainActor public func check(plateNumber: String, force: Bool = false) async throws -> Vehicle { var body = [ "number": AnyEncodable(plateNumber), "forceUpdate": AnyEncodable(force) ] if let token = settings.user.firebaseIdToken { body["googleIdToken"] = AnyEncodable(token) } return try await self.makeBodyRequest(api: "vehicles/check", body: body) } }