AutoCat2/AutoCatCore/Utils/Api.swift

174 lines
6.7 KiB
Swift

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: any SettingsProtocol
public static let shared = Api()
public init(session: URLSession? = nil, settings: any 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: (some Encodable)? = nil, params: [String: some LosslessStringConvertible]? = nil) -> URLRequest? {
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<T>(api: String, method: String = "GET", body: (some Encodable)?, params: [String: some LosslessStringConvertible]? = nil) async throws -> T where T: Decodable {
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<T, Error>) 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<T>.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<T>(api: String, params:[String: some LosslessStringConvertible]? = nil) async throws -> T where T: Decodable {
return try await self.makeRequest(api: api, method: "GET", body: nil as Int?, params: params)
}
private func makeEmptyGetRequest<T>(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<T>(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<T>(api: String, body: (some Encodable)?, method: String = "POST") async throws -> T where T: Decodable {
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)
}
}