diff --git a/AutoCat2.xcodeproj/project.pbxproj b/AutoCat2.xcodeproj/project.pbxproj index 8ae1f96..9458998 100644 --- a/AutoCat2.xcodeproj/project.pbxproj +++ b/AutoCat2.xcodeproj/project.pbxproj @@ -32,6 +32,10 @@ 7A40D5FF2693A91F009B0BC4 /* CocoaError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A40D5FD2693A91F009B0BC4 /* CocoaError.swift */; }; 7A40D6022694FF5D009B0BC4 /* Api.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A40D6012694FF5D009B0BC4 /* Api.swift */; }; 7A40D6032694FF5D009B0BC4 /* Api.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A40D6012694FF5D009B0BC4 /* Api.swift */; }; + 7A683999269612EA00B2188A /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A683998269612EA00B2188A /* Response.swift */; }; + 7A68399A269612EA00B2188A /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A683998269612EA00B2188A /* Response.swift */; }; + 7ACD05D72695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; }; + 7ACD05D82695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -101,6 +105,8 @@ 7A40D5F52693A63A009B0BC4 /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = ""; }; 7A40D5FD2693A91F009B0BC4 /* CocoaError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaError.swift; sourceTree = ""; }; 7A40D6012694FF5D009B0BC4 /* Api.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Api.swift; sourceTree = ""; }; + 7A683998269612EA00B2188A /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; + 7ACD05D62695C08A00557667 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -212,6 +218,7 @@ children = ( 7A40D5E026924AEC009B0BC4 /* User.swift */, 7A40D5E226924B09009B0BC4 /* Settings.swift */, + 7A683998269612EA00B2188A /* Response.swift */, ); path = Models; sourceTree = ""; @@ -259,6 +266,7 @@ isa = PBXGroup; children = ( 7A40D6012694FF5D009B0BC4 /* Api.swift */, + 7ACD05D62695C08A00557667 /* Constants.swift */, ); path = Utils; sourceTree = ""; @@ -454,8 +462,10 @@ buildActionMask = 2147483647; files = ( 7A40D5E926938BEC009B0BC4 /* AuthView.swift in Sources */, + 7ACD05D72695C08A00557667 /* Constants.swift in Sources */, 7A40D5E326924B09009B0BC4 /* Settings.swift in Sources */, 7A40D5A02691C6D8009B0BC4 /* AutoCat2App.swift in Sources */, + 7A683999269612EA00B2188A /* Response.swift in Sources */, 7A40D5A42691C6D8009B0BC4 /* Persistence.swift in Sources */, 7A40D5ED2693A1EA009B0BC4 /* AuthVM.swift in Sources */, 7A40D59E2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */, @@ -471,8 +481,10 @@ buildActionMask = 2147483647; files = ( 7A40D5EA26938BEC009B0BC4 /* AuthView.swift in Sources */, + 7ACD05D82695C08A00557667 /* Constants.swift in Sources */, 7A40D5A12691C6D8009B0BC4 /* AutoCat2App.swift in Sources */, 7A40D5A52691C6D8009B0BC4 /* Persistence.swift in Sources */, + 7A68399A269612EA00B2188A /* Response.swift in Sources */, 7A40D5E526924B0C009B0BC4 /* User.swift in Sources */, 7A40D5EE2693A1EA009B0BC4 /* AuthVM.swift in Sources */, 7A40D59F2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */, diff --git a/Shared/Models/Response.swift b/Shared/Models/Response.swift new file mode 100644 index 0000000..979efd6 --- /dev/null +++ b/Shared/Models/Response.swift @@ -0,0 +1,25 @@ +import Foundation + +class Response: Decodable where T: Decodable { + let success: Bool + let data: T? + let error: String? + + enum CodingKeys: String, CodingKey { + case success + case data + case error + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + success = try container.decode(Bool.self, forKey: .success) + if success { + data = try container.decode(T.self, forKey: .data) + error = nil + } else { + error = try container.decode(String.self, forKey: .error) + data = nil + } + } +} diff --git a/Shared/Utils/Api.swift b/Shared/Utils/Api.swift index d96a2e0..6661e6e 100644 --- a/Shared/Utils/Api.swift +++ b/Shared/Utils/Api.swift @@ -1,8 +1,105 @@ -// -// Api.swift -// AutoCat2 -// -// Created by Selim Mustafaev on 07.07.2021. -// - import Foundation + +public class Api { + + private var session: URLSession + + public static let shared = Api() + + public init(session: URLSession? = nil) { + 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 genError(_ msg: String, suggestion: String, code: Int = 0) -> Error { + return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion]) + } + + 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.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 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 self.genError("Error creating request", suggestion: "") + } + + let (data, response) = try await self.session.data(for: request) + + // 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 { + 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 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 + + public static func login(email: String, password: String) async throws -> User { + let body = [ + "email": email, + "password": password + ] + return try await self.shared.makeBodyRequest(api: "user/login", body: body) + } +} diff --git a/Shared/Utils/Constants.swift b/Shared/Utils/Constants.swift new file mode 100644 index 0000000..5588fa4 --- /dev/null +++ b/Shared/Utils/Constants.swift @@ -0,0 +1,13 @@ +import Foundation + +public struct Constants { + public static var baseUrl: String { + #if DEBUG + //return "http://127.0.0.1:3000/" + //return "http://192.168.1.67:3000/" + return "https://vps.aliencat.pro:8443/" + #else + return "https://vps.aliencat.pro:8443/" + #endif + } +}