|
|
|
|
@ -17,6 +17,7 @@ enum Method: String { |
|
|
|
|
enum ServiceError: Error { |
|
|
|
|
case urlCreationError(url: String) |
|
|
|
|
case cantConvertToUUID(id: String) |
|
|
|
|
case missingUserName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class Services { |
|
|
|
|
@ -48,7 +49,13 @@ public class Services { |
|
|
|
|
|
|
|
|
|
// MARK: - Base |
|
|
|
|
|
|
|
|
|
fileprivate func runRequest<T : Decodable>(_ request: URLRequest, apiCallId: String? = nil) async throws -> T { |
|
|
|
|
fileprivate func _runRequest<T : Encodable, U : Decodable>(servicePath: String, method: Method, payload: T, apiCallId: String? = nil) async throws -> U { |
|
|
|
|
var request = try self._baseRequest(servicePath: servicePath, method: method) |
|
|
|
|
request.httpBody = try jsonEncoder.encode(payload) |
|
|
|
|
return try await _runRequest(request, apiCallId: apiCallId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _runRequest<T : Decodable>(_ request: URLRequest, apiCallId: String? = nil) async throws -> T { |
|
|
|
|
Logger.log("Run \(request.httpMethod ?? "") \(request.url?.absoluteString ?? "")") |
|
|
|
|
let task: (Data, URLResponse) = try await URLSession.shared.data(for: request) |
|
|
|
|
if let response = task.1 as? HTTPURLResponse { |
|
|
|
|
@ -83,9 +90,9 @@ public class Services { |
|
|
|
|
var request = URLRequest(url: url) |
|
|
|
|
request.httpMethod = method.rawValue |
|
|
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type") |
|
|
|
|
// if let token = try? self.keychainStore.getToken() { |
|
|
|
|
// request.addValue("Token \(token)", forHTTPHeaderField: "Authorization") |
|
|
|
|
// } |
|
|
|
|
if let token = try? self.keychainStore.getToken() { |
|
|
|
|
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return request |
|
|
|
|
} |
|
|
|
|
@ -94,12 +101,12 @@ public class Services { |
|
|
|
|
|
|
|
|
|
func get<T : Storable>() async throws -> [T] { |
|
|
|
|
let getRequest = try getRequest(servicePath: T.resourceName() + "/") |
|
|
|
|
return try await self.runRequest(getRequest) |
|
|
|
|
return try await self._runRequest(getRequest) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func runApiCall<T : Storable>(_ apiCall: ApiCall<T>) async throws -> T { |
|
|
|
|
let request = try self._request(from: apiCall) |
|
|
|
|
return try await self.runRequest(request, apiCallId: apiCall.id) |
|
|
|
|
return try await self._runRequest(request, apiCallId: apiCall.id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _request<T : Storable>(from apiCall: ApiCall<T>) throws -> URLRequest { |
|
|
|
|
@ -123,39 +130,69 @@ public class Services { |
|
|
|
|
|
|
|
|
|
// MARK: - Authentication |
|
|
|
|
|
|
|
|
|
public func createAccount(username: String, password: String, email: String) async throws { |
|
|
|
|
var postRequest = try self._baseRequest(servicePath: "users/", method: .post) |
|
|
|
|
let user = User(username: username, password: password, email: email) |
|
|
|
|
postRequest.httpBody = try jsonEncoder.encode(user) |
|
|
|
|
let _: User = try await self.runRequest(postRequest) |
|
|
|
|
let _ = try await requestToken(username: username, password: password) |
|
|
|
|
public func createAccount<U : UserPasswordBase, V : UserBase>(user: U) async throws -> V { |
|
|
|
|
|
|
|
|
|
let response: V = try await _runRequest(servicePath: "users/", method: .post, payload: user) |
|
|
|
|
|
|
|
|
|
// var postRequest = try self._baseRequest(servicePath: "users/", method: .post) |
|
|
|
|
// postRequest.httpBody = try jsonEncoder.encode(user) |
|
|
|
|
// let response: V = try await self.runRequest(postRequest) |
|
|
|
|
let _ = try await requestToken(username: user.username, password: user.password) |
|
|
|
|
Store.main.setUserName(user.username) |
|
|
|
|
return response |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func requestToken(username: String, password: String) async throws -> String { |
|
|
|
|
var postRequest = try self._baseRequest(servicePath: "plus/api-token-auth/", method: .post) |
|
|
|
|
let credentials = Credentials(username: username, password: password) |
|
|
|
|
postRequest.httpBody = try jsonEncoder.encode(credentials) |
|
|
|
|
let response: AuthResponse = try await self.runRequest(postRequest) |
|
|
|
|
let response: AuthResponse = try await self._runRequest(postRequest) |
|
|
|
|
self._storeToken(username: username, token: response.token) |
|
|
|
|
return response.token |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _storeToken(username: String, token: String) { |
|
|
|
|
do { |
|
|
|
|
try self.keychainStore.add(username: username, token: response.token) |
|
|
|
|
try self.keychainStore.add(username: username, token: token) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
return response.token |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func login(username: String, password: String) async throws -> User { |
|
|
|
|
public func login<U : UserBase>(username: String, password: String) async throws -> U { |
|
|
|
|
let token: String = try await requestToken(username: username, password: password) |
|
|
|
|
Logger.log("token = \(token)") |
|
|
|
|
var postRequest = try self._baseRequest(servicePath: "plus/user-by-token/", method: .post) |
|
|
|
|
postRequest.httpBody = try jsonEncoder.encode(Token(token: token)) |
|
|
|
|
let user: User = try await self.runRequest(postRequest) |
|
|
|
|
let user: U = try await self._runRequest(postRequest) |
|
|
|
|
Logger.log("user = \(user.username), id = \(user.id)") |
|
|
|
|
Store.main.setUserUUID(uuidString: user.id) |
|
|
|
|
return user |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func forgotPassword(user: User) async throws { |
|
|
|
|
public func changePassword(password1: String, password2: String) async throws { |
|
|
|
|
|
|
|
|
|
guard let username = Store.main.userName() else { |
|
|
|
|
throw ServiceError.missingUserName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct ChangePasswordParams: Codable { |
|
|
|
|
var new_password1: String |
|
|
|
|
var new_password2: String |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let params = ChangePasswordParams(new_password1: password1, new_password2: password2) |
|
|
|
|
let response: Token = try await self._runRequest( |
|
|
|
|
servicePath: "plus/change-password/", method: .post, payload: params) |
|
|
|
|
|
|
|
|
|
// var postRequest = try self._baseRequest(servicePath: "plus/change-password/", method: .post) |
|
|
|
|
// postRequest.httpBody = try jsonEncoder.encode(params) |
|
|
|
|
// let response: Token = try await self._runRequest(postRequest) |
|
|
|
|
|
|
|
|
|
self._storeToken(username: username, token: response.token) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func forgotPassword(user: UserBase) async throws { |
|
|
|
|
|
|
|
|
|
// var postRequest = try self._baseRequest(servicePath: "forgot-password/", method: .post) |
|
|
|
|
// postRequest.httpBody = try jsonEncoder.encode(credentials) |
|
|
|
|
@ -164,6 +201,10 @@ public class Services { |
|
|
|
|
// return response |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func disconnect() throws { |
|
|
|
|
try self.keychainStore.deleteToken() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct AuthResponse: Codable { |
|
|
|
|
@ -179,17 +220,15 @@ struct Token: Codable { |
|
|
|
|
var token: String |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public struct User: Codable { |
|
|
|
|
var id: String = Store.randomId() |
|
|
|
|
var username: String |
|
|
|
|
var password: String? |
|
|
|
|
var email: String? |
|
|
|
|
|
|
|
|
|
func uuid() throws -> UUID { |
|
|
|
|
if let uuid = UUID(uuidString: self.id) { |
|
|
|
|
return uuid |
|
|
|
|
} |
|
|
|
|
throw ServiceError.cantConvertToUUID(id: self.id) |
|
|
|
|
} |
|
|
|
|
public protocol UserBase : Codable { |
|
|
|
|
var id: String { get } |
|
|
|
|
var username: String { get } |
|
|
|
|
// var password: String? { get } |
|
|
|
|
var email: String? { get } |
|
|
|
|
|
|
|
|
|
func uuid() throws -> UUID |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public protocol UserPasswordBase: UserBase { |
|
|
|
|
var password: String { get } |
|
|
|
|
} |
|
|
|
|
|