|
|
|
|
@ -78,20 +78,20 @@ public class Services { |
|
|
|
|
/// The base API URL to send requests |
|
|
|
|
fileprivate(set) var baseURL: String |
|
|
|
|
|
|
|
|
|
fileprivate var jsonEncoder: JSONEncoder = { |
|
|
|
|
let encoder = JSONEncoder() |
|
|
|
|
encoder.keyEncodingStrategy = .convertToSnakeCase |
|
|
|
|
encoder.outputFormatting = .prettyPrinted |
|
|
|
|
encoder.dateEncodingStrategy = .iso8601 |
|
|
|
|
return encoder |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
fileprivate var jsonDecoder: JSONDecoder = { |
|
|
|
|
let decoder = JSONDecoder() |
|
|
|
|
decoder.keyDecodingStrategy = .convertFromSnakeCase |
|
|
|
|
decoder.dateDecodingStrategy = .iso8601 |
|
|
|
|
return decoder |
|
|
|
|
}() |
|
|
|
|
// fileprivate var jsonEncoder: JSONEncoder = { |
|
|
|
|
// let encoder = JSONEncoder() |
|
|
|
|
// encoder.keyEncodingStrategy = .convertToSnakeCase |
|
|
|
|
// encoder.outputFormatting = .prettyPrinted |
|
|
|
|
// encoder.dateEncodingStrategy = .iso8601 |
|
|
|
|
// return encoder |
|
|
|
|
// }() |
|
|
|
|
// |
|
|
|
|
// fileprivate var jsonDecoder: JSONDecoder = { |
|
|
|
|
// let decoder = JSONDecoder() |
|
|
|
|
// decoder.keyDecodingStrategy = .convertFromSnakeCase |
|
|
|
|
// decoder.dateDecodingStrategy = .iso8601 |
|
|
|
|
// return decoder |
|
|
|
|
// }() |
|
|
|
|
|
|
|
|
|
// MARK: - Base |
|
|
|
|
|
|
|
|
|
@ -102,7 +102,7 @@ public class Services { |
|
|
|
|
/// - apiCallId: an optional id referencing an ApiCall |
|
|
|
|
fileprivate func _runRequest<T: Encodable, U: Decodable>(serviceCall: ServiceCall, payload: T) async throws -> U { |
|
|
|
|
var request = try self._baseRequest(call: serviceCall) |
|
|
|
|
request.httpBody = try jsonEncoder.encode(payload) |
|
|
|
|
request.httpBody = try JSON.encoder.encode(payload) |
|
|
|
|
return try await _runRequest(request) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -143,9 +143,9 @@ public class Services { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !(V.self is Empty?.Type) && !(V.self is Empty.Type) { |
|
|
|
|
return try jsonDecoder.decode(V.self, from: task.0) |
|
|
|
|
return try JSON.decoder.decode(V.self, from: task.0) |
|
|
|
|
} else { |
|
|
|
|
return try jsonDecoder.decode(V.self, from: "{}".data(using: .utf8)!) |
|
|
|
|
return try JSON.decoder.decode(V.self, from: "{}".data(using: .utf8)!) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -179,7 +179,7 @@ public class Services { |
|
|
|
|
StoreCenter.main.log(message: message) |
|
|
|
|
Logger.w(message) |
|
|
|
|
} |
|
|
|
|
return try jsonDecoder.decode(V.self, from: task.0) |
|
|
|
|
return try JSON.decoder.decode(V.self, from: task.0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns if the token is required for a request |
|
|
|
|
@ -202,38 +202,38 @@ public class Services { |
|
|
|
|
let requiresToken = self._isTokenRequired(type: T.self, method: .get) |
|
|
|
|
return try self._baseRequest(servicePath: T.path(), method: .get, requiresToken: requiresToken, identifier: identifier) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a POST request for the resource |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - type: the type of the request resource |
|
|
|
|
fileprivate func _postRequest<T: Storable>(type: T.Type) throws -> URLRequest { |
|
|
|
|
let requiresToken = self._isTokenRequired(type: T.self, method: .post) |
|
|
|
|
return try self._baseRequest(servicePath: T.path(), method: .post, requiresToken: requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a PUT request for the resource |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - type: the type of the request resource |
|
|
|
|
fileprivate func _putRequest<T: Storable>(type: T.Type, id: String) throws -> URLRequest { |
|
|
|
|
let requiresToken = self._isTokenRequired(type: T.self, method: .put) |
|
|
|
|
return try self._baseRequest(servicePath: T.path(id: id), method: .put, requiresToken: requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a DELETE request for the resource |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - type: the type of the request resource |
|
|
|
|
fileprivate func _deleteRequest<T: Storable>(type: T.Type, id: String) throws -> URLRequest { |
|
|
|
|
let requiresToken = self._isTokenRequired(type: T.self, method: .delete) |
|
|
|
|
return try self._baseRequest(servicePath: T.path(id: id), method: .delete, requiresToken: requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the base URLRequest for a ServiceConf instance |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - conf: a ServiceConf instance |
|
|
|
|
fileprivate func _baseRequest(call: ServiceCall) throws -> URLRequest { |
|
|
|
|
return try self._baseRequest(servicePath: call.path, method: call.method, requiresToken: call.requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns a POST request for the resource |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - type: the type of the request resource |
|
|
|
|
fileprivate func _postRequest<T: Storable>(type: T.Type) throws -> URLRequest { |
|
|
|
|
let requiresToken = self._isTokenRequired(type: T.self, method: .post) |
|
|
|
|
return try self._baseRequest(servicePath: T.path(), method: .post, requiresToken: requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a PUT request for the resource |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - type: the type of the request resource |
|
|
|
|
fileprivate func _putRequest<T: Storable>(type: T.Type, id: String) throws -> URLRequest { |
|
|
|
|
let requiresToken = self._isTokenRequired(type: T.self, method: .put) |
|
|
|
|
return try self._baseRequest(servicePath: T.path(id: id), method: .put, requiresToken: requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a DELETE request for the resource |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - type: the type of the request resource |
|
|
|
|
fileprivate func _deleteRequest<T: Storable>(type: T.Type, id: String) throws -> URLRequest { |
|
|
|
|
let requiresToken = self._isTokenRequired(type: T.self, method: .delete) |
|
|
|
|
return try self._baseRequest(servicePath: T.path(id: id), method: .delete, requiresToken: requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the base URLRequest for a ServiceConf instance |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - conf: a ServiceConf instance |
|
|
|
|
fileprivate func _baseRequest(call: ServiceCall) throws -> URLRequest { |
|
|
|
|
return try self._baseRequest(servicePath: call.path, method: call.method, requiresToken: call.requiresToken) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a base request for a path and method |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - servicePath: the path to add to the API base URL |
|
|
|
|
@ -251,6 +251,7 @@ public class Services { |
|
|
|
|
var request = URLRequest(url: url) |
|
|
|
|
request.httpMethod = method.rawValue |
|
|
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type") |
|
|
|
|
request.addAppVersion() |
|
|
|
|
if !(requiresToken == false) { |
|
|
|
|
let token = try self.keychainStore.getValue() |
|
|
|
|
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization") |
|
|
|
|
@ -269,14 +270,14 @@ public class Services { |
|
|
|
|
/// Executes a POST request |
|
|
|
|
public func post<T: Storable>(_ instance: T) async throws -> T { |
|
|
|
|
var postRequest = try self._postRequest(type: T.self) |
|
|
|
|
postRequest.httpBody = try jsonEncoder.encode(instance) |
|
|
|
|
postRequest.httpBody = try JSON.encoder.encode(instance) |
|
|
|
|
return try await self._runRequest(postRequest) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Executes a PUT request |
|
|
|
|
public func put<T: Storable>(_ instance: T) async throws -> T { |
|
|
|
|
var postRequest = try self._putRequest(type: T.self, id: instance.stringId) |
|
|
|
|
postRequest.httpBody = try jsonEncoder.encode(instance) |
|
|
|
|
postRequest.httpBody = try JSON.encoder.encode(instance) |
|
|
|
|
return try await self._runRequest(postRequest) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -301,6 +302,7 @@ public class Services { |
|
|
|
|
request.httpMethod = apiCall.method.rawValue |
|
|
|
|
request.httpBody = apiCall.body.data(using: .utf8) |
|
|
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type") |
|
|
|
|
request.addAppVersion() |
|
|
|
|
|
|
|
|
|
if self._isTokenRequired(type: T.self, method: apiCall.method) { |
|
|
|
|
do { |
|
|
|
|
@ -349,7 +351,7 @@ public class Services { |
|
|
|
|
var postRequest = try self._baseRequest(call: requestTokenCall) |
|
|
|
|
let deviceId = StoreCenter.main.deviceId() |
|
|
|
|
let credentials = Credentials(username: username, password: password, deviceId: deviceId) |
|
|
|
|
postRequest.httpBody = try jsonEncoder.encode(credentials) |
|
|
|
|
postRequest.httpBody = try JSON.encoder.encode(credentials) |
|
|
|
|
let response: AuthResponse = try await self._runRequest(postRequest) |
|
|
|
|
self._storeToken(username: username, token: response.token) |
|
|
|
|
return response.token |
|
|
|
|
@ -430,7 +432,7 @@ public class Services { |
|
|
|
|
/// - email: the email of the user |
|
|
|
|
public func forgotPassword(email: String) async throws { |
|
|
|
|
var postRequest = try self._baseRequest(servicePath: "dj-rest-auth/password/reset/", method: .post, requiresToken: false) |
|
|
|
|
postRequest.httpBody = try jsonEncoder.encode(Email(email: email)) |
|
|
|
|
postRequest.httpBody = try JSON.encoder.encode(Email(email: email)) |
|
|
|
|
let response: Email = try await self._runRequest(postRequest) |
|
|
|
|
Logger.log("response = \(response)") |
|
|
|
|
} |
|
|
|
|
@ -537,3 +539,14 @@ public protocol UserBase: Codable { |
|
|
|
|
public protocol UserPasswordBase: UserBase { |
|
|
|
|
var password: String { get } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate extension URLRequest { |
|
|
|
|
|
|
|
|
|
mutating func addAppVersion() { |
|
|
|
|
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" |
|
|
|
|
let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" |
|
|
|
|
let appVersion = "\(version) (\(build))" |
|
|
|
|
self.setValue(appVersion, forHTTPHeaderField: "App-Version") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|