304 lines
13 KiB
Swift
304 lines
13 KiB
Swift
import Foundation
|
|
import RxSwift
|
|
import RxCocoa
|
|
|
|
public class Api {
|
|
|
|
private static let session: URLSession = {
|
|
let sessionConfig = URLSessionConfiguration.default
|
|
sessionConfig.timeoutIntervalForRequest = 20.0
|
|
sessionConfig.timeoutIntervalForResource = 20.0
|
|
return URLSession(configuration: sessionConfig)
|
|
}()
|
|
|
|
// MARK: - Private wrappres
|
|
|
|
private static func genError(_ msg: String, suggestion: String, code: Int = 0) -> Error {
|
|
return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion])
|
|
}
|
|
|
|
private static func createRequest<B,P>(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.shared.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 static func makeRequest<T,B,P>(api: String, method: String = "GET", body: B?, params: [String:P]? = nil) -> Single<T> where T: Decodable, B: Encodable, P: LosslessStringConvertible {
|
|
guard let request = self.createRequest(api: api, method: method, body: body, params: params) else {
|
|
return Single.error(self.genError("Error creating request", suggestion: ""))
|
|
}
|
|
|
|
return self.session.rx.data(request: request).asSingle().map { data in
|
|
// 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 {
|
|
return resp.data!
|
|
} else {
|
|
throw self.genError(resp.error!, suggestion: "")
|
|
}
|
|
} catch let error as Swift.DecodingError {
|
|
throw CocoaError.error((error as CustomDebugStringConvertible).debugDescription)
|
|
} catch {
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func makeGetRequest<T,P>(api: String, params:[String: P]? = nil) -> Single<T> where T: Decodable, P: LosslessStringConvertible {
|
|
// Kind of hack to satisfy compiler
|
|
return self.makeRequest(api: api, method: "GET", body: nil as Int?, params: params)
|
|
}
|
|
|
|
private static func makeEmptyGetRequest<T>(api: String) -> Single<T> where T: Decodable {
|
|
// Same hack as before
|
|
return self.makeRequest(api: api, method: "GET", body: nil as Int?, params: nil as [String:Int]?)
|
|
}
|
|
|
|
private static func makeEmptyBodyRequest<T>(api: String, method: String = "POST") -> Single<T> where T: Decodable {
|
|
// Same hack as before
|
|
return self.makeRequest(api: api, method: method, body: nil as Int?, params: nil as [String:Int]?)
|
|
}
|
|
|
|
private static func makeBodyRequest<T,B>(api: String, body: B?, method: String = "POST") -> Single<T> where T: Decodable, B: Encodable {
|
|
// Same hack as before
|
|
return self.makeRequest(api: api, method: method, body: body, params: nil as [String:Int]?)
|
|
}
|
|
|
|
// MARK: - Firebase API
|
|
|
|
public static func refreshFbToken() -> Single<Void> {
|
|
guard let token = Settings.shared.user.firebaseIdToken, let refreshToken = Settings.shared.user.firebaseRefreshToken, let jwt = JWT<FirebasePayload>(string: token), jwt.expired else {
|
|
return .just(())
|
|
}
|
|
|
|
let refreshUrlString = Constants.fbRefreshToken + "?key=" + Constants.fbApiKey
|
|
|
|
let body = [
|
|
"grantType": "refresh_token",
|
|
"refreshToken": refreshToken
|
|
]
|
|
|
|
if let url = URL(string: refreshUrlString) {
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
|
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version")
|
|
request.addValue(Constants.secondProviderBundleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
|
|
request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
|
|
return self.session.rx.json(request: request).asSingle().map { resp in
|
|
guard let json = resp as? [String: Any] else { return }
|
|
if let newToken = json["id_token"] as? String {
|
|
Settings.shared.user.firebaseIdToken = newToken
|
|
//print("Token was successfully refresh to: \(newToken)")
|
|
}
|
|
if let newRefreshToken = json["refresh_token"] as? String {
|
|
Settings.shared.user.firebaseRefreshToken = newRefreshToken
|
|
}
|
|
}
|
|
} else {
|
|
return .just(())
|
|
}
|
|
}
|
|
|
|
public static func fbVerifyAssertion(provider: String, idToken: String, accessToken: String? = nil) -> Single<Void> {
|
|
let signupUrl = Constants.fbVerifyAssertion + "?key=" + (Constants.fbApiKey.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? Constants.fbApiKey)
|
|
if let url = URL(string: signupUrl) {
|
|
var innerBody = "providerId=" + provider
|
|
+ "&id_token=" + idToken
|
|
|
|
if let accessToken = accessToken {
|
|
innerBody += "&access_token=" + accessToken
|
|
}
|
|
|
|
let body: [String:Any] = [
|
|
"returnIdpCredential": true,
|
|
"returnSecureToken": true,
|
|
"autoCreate": true,
|
|
"requestUri": "http://localhost",
|
|
"postBody": innerBody
|
|
]
|
|
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
|
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version")
|
|
request.addValue(Constants.secondProviderBundleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
|
|
request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
|
|
|
|
return self.session.rx.json(request: request).asSingle().map { response in
|
|
guard let json = response as? [String: Any] else { return }
|
|
if let newToken = json["idToken"] as? String {
|
|
Settings.shared.user.firebaseIdToken = newToken
|
|
print("Token: \(newToken)")
|
|
}
|
|
if let newRefreshToken = json["refreshToken"] as? String {
|
|
Settings.shared.user.firebaseRefreshToken = newRefreshToken
|
|
print("Refresh token: \(newRefreshToken)")
|
|
}
|
|
}.catchError { err in
|
|
print(err)
|
|
return .just(())
|
|
}
|
|
} else {
|
|
return .just(())
|
|
}
|
|
}
|
|
|
|
// MARK: - AutoCat public API
|
|
|
|
public static func login(email: String, password: String) -> Single<User> {
|
|
let body = [
|
|
"email": email,
|
|
"password": password
|
|
]
|
|
return self.makeBodyRequest(api: "user/login", body: body)
|
|
}
|
|
|
|
public static func signIn(email: String, password: String) -> Single<User> {
|
|
let body = [
|
|
"email": email,
|
|
"password": password
|
|
]
|
|
return self.makeBodyRequest(api: "user/signIn", body: body)
|
|
}
|
|
|
|
public static func signUp(email: String, password: String) -> Single<User> {
|
|
let body = [
|
|
"email": email,
|
|
"password": password
|
|
]
|
|
return self.makeBodyRequest(api: "user/signup", body: body)
|
|
}
|
|
|
|
public static func getVehicles(with filter: Filter, pageToken: String? = nil) -> Single<PagedResponse<Vehicle>> {
|
|
var params = filter.queryDictionary()
|
|
if let token = pageToken {
|
|
params["pageToken"] = token;
|
|
}
|
|
return self.makeGetRequest(api: "vehicles", params: params)
|
|
}
|
|
|
|
public static func checkVehicle(by number: String, notes: [VehicleNote], events: [VehicleEvent], force: Bool = false) -> Single<Vehicle> {
|
|
return self.refreshFbToken().flatMap { () -> Single<Vehicle> in
|
|
var body = [
|
|
"number": AnyEncodable(number),
|
|
"forceUpdate": AnyEncodable(force)
|
|
]
|
|
|
|
if let token = Settings.shared.user.firebaseIdToken {
|
|
body["googleIdToken"] = AnyEncodable(token)
|
|
}
|
|
|
|
if !notes.isEmpty {
|
|
body["notes"] = AnyEncodable(notes)
|
|
}
|
|
|
|
if !events.isEmpty {
|
|
body["events"] = AnyEncodable(events)
|
|
}
|
|
|
|
return self.makeBodyRequest(api: "vehicles/check", body: body).map { (vehicle: Vehicle) -> Vehicle in
|
|
vehicle.addedDate = Date().timeIntervalSince1970
|
|
return vehicle
|
|
}
|
|
}
|
|
}
|
|
|
|
public static func getReport(for number: String) -> Single<Vehicle> {
|
|
return self.makeGetRequest(api: "vehicles/report", params: ["number": number])
|
|
}
|
|
|
|
public static func getBrands() -> Single<[String]> {
|
|
return self.makeEmptyGetRequest(api: "vehicles/brands")
|
|
}
|
|
|
|
public static func getModels(of brand: String) -> Single<[String]> {
|
|
return self.makeGetRequest(api: "vehicles/models", params: ["brand": brand])
|
|
}
|
|
|
|
public static func getColors() -> Single<[String]> {
|
|
return self.makeEmptyGetRequest(api: "vehicles/colors")
|
|
}
|
|
|
|
public static func getRegions() -> Single<[VehicleRegion]> {
|
|
return self.makeEmptyGetRequest(api: "vehicles/regions")
|
|
}
|
|
|
|
public static func getYears() -> Single<[Int]> {
|
|
return self.makeEmptyGetRequest(api: "vehicles/years")
|
|
}
|
|
|
|
public static func add(event: VehicleEvent, to number: String) -> Single<Vehicle> {
|
|
let body = ["number": AnyEncodable(number), "event": AnyEncodable(event)]
|
|
return self.makeBodyRequest(api: "events", body: body).map { (vehicle: Vehicle) -> Vehicle in
|
|
vehicle.addedDate = Date().timeIntervalSince1970
|
|
return vehicle
|
|
}
|
|
}
|
|
|
|
public static func remove(event id: String) -> Single<Vehicle> {
|
|
let body = ["eventId": id]
|
|
return self.makeBodyRequest(api: "events", body: body, method: "DELETE")
|
|
}
|
|
|
|
public static func edit(event: VehicleEvent) -> Single<Vehicle> {
|
|
let body = ["event": event]
|
|
return self.makeBodyRequest(api: "events", body: body, method: "PUT")
|
|
}
|
|
|
|
public static func events(with filter: Filter) -> Single<[VehicleEvent]> {
|
|
return self.makeGetRequest(api: "events", params: filter.queryDictionary())
|
|
}
|
|
|
|
public static func checkOsago(number: String?, vin: String?, date: Date, token: String) -> Single<Vehicle> {
|
|
let body = [
|
|
"date": AnyEncodable(date.timeIntervalSince1970),
|
|
"number": AnyEncodable(number),
|
|
"vin": AnyEncodable(vin),
|
|
"token": AnyEncodable(token)
|
|
]
|
|
return self.makeBodyRequest(api: "vehicles/checkOsago", body: body)
|
|
}
|
|
|
|
public static func add(notes: [VehicleNote], to number: String) -> Single<Vehicle> {
|
|
let body = ["number": AnyEncodable(number), "notes": AnyEncodable(notes)]
|
|
return self.makeBodyRequest(api: "notes", body: body)
|
|
}
|
|
|
|
public static func edit(note: VehicleNote) -> Single<Vehicle> {
|
|
return self.makeBodyRequest(api: "notes", body: ["note": note], method: "PUT")
|
|
}
|
|
|
|
public static func remove(note id: String) -> Single<Vehicle> {
|
|
return self.makeBodyRequest(api: "notes", body: ["noteId": id], method: "DELETE")
|
|
}
|
|
}
|