Bug fixes
This commit is contained in:
@@ -16,7 +16,6 @@ struct ApiRequest {
|
||||
let body: Data?
|
||||
|
||||
/// The path to the Cookbook application on the nextcloud server.
|
||||
let cookbookPath = "/index.php/apps/cookbook"
|
||||
|
||||
init(
|
||||
path: String,
|
||||
@@ -32,11 +31,11 @@ struct ApiRequest {
|
||||
self.body = body
|
||||
}
|
||||
|
||||
func send() async -> (Data?, NetworkError?) {
|
||||
func send(pathCompletion: Bool = true) async -> (Data?, NetworkError?) {
|
||||
Logger.network.debug("\(method.rawValue) \(path) sending ...")
|
||||
|
||||
// Prepare URL
|
||||
let urlString = UserSettings.shared.serverProtocol + UserSettings.shared.serverAddress + cookbookPath + path
|
||||
let urlString = pathCompletion ? UserSettings.shared.serverProtocol + UserSettings.shared.serverAddress + path : path
|
||||
print("Full path: \(urlString)")
|
||||
//Logger.network.debug("Full path: \(urlString)")
|
||||
guard let urlStringSanitized = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return (nil, .unknownError) }
|
||||
@@ -78,9 +77,10 @@ struct ApiRequest {
|
||||
return (nil, error)
|
||||
}
|
||||
if let data = data {
|
||||
print(data, String(data: data, encoding: .utf8))
|
||||
print(data, String(data: data, encoding: .utf8) as Any)
|
||||
return (data, nil)
|
||||
}
|
||||
return (data!, nil)
|
||||
return (nil, .unknownError)
|
||||
} catch {
|
||||
let error = decodeURLResponse(response: response as? HTTPURLResponse)
|
||||
Logger.network.debug("\(method.rawValue) \(path) FAILURE: \(error.debugDescription)")
|
||||
@@ -10,7 +10,26 @@ import OSLog
|
||||
import UIKit
|
||||
|
||||
|
||||
/// The Cookbook API class used for requests to the Nextcloud Cookbook service.
|
||||
let cookbookApi: CookbookApi.Type = getApi()
|
||||
|
||||
func getApi() -> CookbookApi.Type {
|
||||
switch UserSettings.shared.cookbookApiVersion {
|
||||
case .v1:
|
||||
return CookbookApiV1.self
|
||||
}
|
||||
}
|
||||
|
||||
/// The Cookbook API version.
|
||||
enum CookbookApiVersion: String {
|
||||
case v1 = "v1"
|
||||
}
|
||||
|
||||
|
||||
/// A protocol defining common API endpoints that are likely to remain the same over future Cookbook API versions.
|
||||
protocol CookbookApi {
|
||||
static var basePath: String { get }
|
||||
|
||||
/// Not implemented yet.
|
||||
static func importRecipe(
|
||||
auth: String,
|
||||
|
||||
@@ -10,9 +10,11 @@ import UIKit
|
||||
|
||||
|
||||
class CookbookApiV1: CookbookApi {
|
||||
static let basePath: String = "/index.php/apps/cookbook/api/v1"
|
||||
|
||||
static func importRecipe(auth: String, data: Data) async -> (RecipeDetail?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/import",
|
||||
path: basePath + "/import",
|
||||
method: .POST,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON), HeaderField.contentType(value: .JSON)]
|
||||
@@ -26,7 +28,7 @@ class CookbookApiV1: CookbookApi {
|
||||
static func getImage(auth: String, id: Int, size: RecipeImage.RecipeImageSize) async -> (UIImage?, NetworkError?) {
|
||||
let imageSize = (size == .FULL ? "full" : "thumb")
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/recipes/\(id)/image?size=\(imageSize)",
|
||||
path: basePath + "/recipes/\(id)/image?size=\(imageSize)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .IMAGE)]
|
||||
@@ -39,7 +41,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func getRecipes(auth: String) async -> ([Recipe]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/recipes",
|
||||
path: basePath + "/recipes",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
@@ -56,7 +58,7 @@ class CookbookApiV1: CookbookApi {
|
||||
}
|
||||
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/recipes",
|
||||
path: basePath + "/recipes",
|
||||
method: .POST,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON), HeaderField.contentType(value: .JSON)],
|
||||
@@ -80,7 +82,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func getRecipe(auth: String, id: Int) async -> (RecipeDetail?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/recipes/\(id)",
|
||||
path: basePath + "/recipes/\(id)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
@@ -96,7 +98,7 @@ class CookbookApiV1: CookbookApi {
|
||||
return .dataError
|
||||
}
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/recipes/\(recipe.id)",
|
||||
path: basePath + "/recipes/\(recipe.id)",
|
||||
method: .PUT,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON), HeaderField.contentType(value: .JSON)],
|
||||
@@ -120,7 +122,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func deleteRecipe(auth: String, id: Int) async -> (NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/recipes/\(id)",
|
||||
path: basePath + "/recipes/\(id)",
|
||||
method: .DELETE,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
@@ -133,7 +135,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func getCategories(auth: String) async -> ([Category]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/categories",
|
||||
path: basePath + "/categories",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
@@ -146,7 +148,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func getCategory(auth: String, named categoryName: String) async -> ([Recipe]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/category/\(categoryName)",
|
||||
path: basePath + "/category/\(categoryName)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
@@ -159,7 +161,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func renameCategory(auth: String, named categoryName: String, newName: String) async -> (NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/category/\(categoryName)",
|
||||
path: basePath + "/category/\(categoryName)",
|
||||
method: .PUT,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
@@ -172,7 +174,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func getTags(auth: String) async -> ([RecipeKeyword]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/keywords",
|
||||
path: basePath + "/keywords",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
@@ -185,7 +187,7 @@ class CookbookApiV1: CookbookApi {
|
||||
|
||||
static func getRecipesTagged(auth: String, keyword: String) async -> ([Recipe]?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: "/api/v1/tags/\(keyword)",
|
||||
path: basePath + "/tags/\(keyword)",
|
||||
method: .GET,
|
||||
authString: auth,
|
||||
headerFields: [HeaderField.ocsRequest(value: true), HeaderField.accept(value: .JSON)]
|
||||
|
||||
@@ -7,6 +7,72 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The `NextcloudApi` class provides functionalities to interact with the Nextcloud API, particularly for user authentication.
|
||||
class NextcloudApi {
|
||||
|
||||
/// Initiates the login process with Nextcloud using the Login Flow v2.
|
||||
///
|
||||
/// This static function sends a POST request to the Nextcloud server to obtain a `LoginV2Request` object.
|
||||
/// The object contains necessary details for the second step of the authentication process.
|
||||
///
|
||||
/// - Returns: A tuple containing an optional `LoginV2Request` and an optional `NetworkError`.
|
||||
/// - `LoginV2Request?`: An object containing the necessary information for the second step of the login process, if successful.
|
||||
/// - `NetworkError?`: An error encountered during the network request, if any.
|
||||
|
||||
static func loginV2Request() async -> (LoginV2Request?, NetworkError?) {
|
||||
let path = UserSettings.shared.serverProtocol + UserSettings.shared.serverAddress
|
||||
let request = ApiRequest(
|
||||
path: path + "/index.php/login/v2",
|
||||
method: .POST
|
||||
)
|
||||
|
||||
let (data, error) = await request.send(pathCompletion: false)
|
||||
|
||||
if let error = error {
|
||||
return (nil, error)
|
||||
}
|
||||
guard let data = data else {
|
||||
return (nil, NetworkError.dataError)
|
||||
}
|
||||
guard let loginRequest: LoginV2Request = JSONDecoder.safeDecode(data) else {
|
||||
return (nil, NetworkError.decodingFailed)
|
||||
}
|
||||
return (loginRequest, nil)
|
||||
}
|
||||
|
||||
/// Completes the user authentication process with Nextcloud using the Login Flow v2.
|
||||
///
|
||||
/// This static function sends a POST request to the Nextcloud server with the login token obtained from `loginV2Request`.
|
||||
/// On successful validation of the token, it returns a `LoginV2Response` object, completing the user login.
|
||||
///
|
||||
/// - Parameter req: A `LoginV2Request` object containing the token and endpoint information for the authentication request.
|
||||
///
|
||||
/// - Returns: A tuple containing an optional `LoginV2Response` and an optional `NetworkError`.
|
||||
/// - `LoginV2Response?`: An object representing the response of the login process, if successful.
|
||||
/// - `NetworkError?`: An error encountered during the network request, if any.
|
||||
|
||||
static func loginV2Response(req: LoginV2Request) async -> (LoginV2Response?, NetworkError?) {
|
||||
let request = ApiRequest(
|
||||
path: req.poll.endpoint,
|
||||
method: .POST,
|
||||
headerFields: [
|
||||
HeaderField.ocsRequest(value: true),
|
||||
HeaderField.accept(value: .JSON),
|
||||
HeaderField.contentType(value: .FORM)
|
||||
],
|
||||
body: "token=\(req.poll.token)".data(using: .utf8)
|
||||
)
|
||||
let (data, error) = await request.send(pathCompletion: false)
|
||||
|
||||
if let error = error {
|
||||
return (nil, error)
|
||||
}
|
||||
guard let data = data else {
|
||||
return (nil, NetworkError.dataError)
|
||||
}
|
||||
guard let loginResponse: LoginV2Response = JSONDecoder.safeDecode(data) else {
|
||||
return (nil, NetworkError.decodingFailed)
|
||||
}
|
||||
return (loginResponse, nil)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user