diff --git a/Package.swift b/Package.swift index c7ba17d..3e916e1 100644 --- a/Package.swift +++ b/Package.swift @@ -22,8 +22,8 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.17.0"), - .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.19.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.58.0"), ], targets: [ .target( diff --git a/README.md b/README.md index 2b7455c..c8aca6c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Swift Package Manager](https://img.shields.io/github/v/release/appwrite/sdk-for-apple.svg?color=green&style=flat-square) ![License](https://img.shields.io/github/license/appwrite/sdk-for-apple.svg?style=flat-square) -![Version](https://img.shields.io/badge/api%20version-1.5.7-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-1.6.0-blue.svg?style=flat-square) [![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator) [![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord) @@ -31,7 +31,7 @@ Add the package to your `Package.swift` dependencies: ```swift dependencies: [ - .package(url: "git@github.com:appwrite/sdk-for-apple.git", from: "6.0.0"), + .package(url: "git@github.com:appwrite/sdk-for-apple.git", from: "7.0.0-rc.1"), ], ``` diff --git a/Sources/Appwrite/Client.swift b/Sources/Appwrite/Client.swift index 6a72a6d..c8ccbd5 100644 --- a/Sources/Appwrite/Client.swift +++ b/Sources/Appwrite/Client.swift @@ -23,8 +23,8 @@ open class Client { "x-sdk-name": "Apple", "x-sdk-platform": "client", "x-sdk-language": "apple", - "x-sdk-version": "6.0.0", - "x-appwrite-response-format": "1.5.0" + "x-sdk-version": "7.0.0-rc.1", + "x-appwrite-response-format": "1.6.0" ] internal var config: [String: String] = [:] diff --git a/Sources/Appwrite/OAuth/WebAuthComponent.swift b/Sources/Appwrite/OAuth/WebAuthComponent.swift index 9cb5da4..e8cdbd3 100644 --- a/Sources/Appwrite/OAuth/WebAuthComponent.swift +++ b/Sources/Appwrite/OAuth/WebAuthComponent.swift @@ -53,17 +53,24 @@ public class WebAuthComponent { /// - url: The URL containing the cookie /// public static func handleIncomingCookie(from url: URL) { - let components = URLComponents(string: url.absoluteString)! - let cookieParts = [String: String](uniqueKeysWithValues: components.queryItems!.map { - ($0.name, $0.value!) + guard let components = URLComponents(string: url.absoluteString), + let queryItems = components.queryItems else { + return + } + + let cookieParts = [String: String](uniqueKeysWithValues: queryItems.compactMap { item in + item.value.map { (item.name, $0) } }) - var domain = cookieParts["domain"]! + guard var domain = cookieParts["domain"], + let key = cookieParts["key"], + let secret = cookieParts["secret"] else { + return + } + domain.remove(at: domain.startIndex) - let key: String = cookieParts["key"]! - let secret: String = cookieParts["secret"]! let path: String? = cookieParts["path"] let expires: String? = cookieParts["expires"] let maxAge: String? = cookieParts["maxAge"] @@ -92,10 +99,7 @@ public class WebAuthComponent { cookie += "; secure" } - let existing = UserDefaults.standard.stringArray(forKey: domain) - let new = [cookie] - - UserDefaults.standard.set(new, forKey: domain) + UserDefaults.standard.set([cookie], forKey: domain) WebAuthComponent.onCallback( scheme: components.scheme!, diff --git a/Sources/Appwrite/Services/Account.swift b/Sources/Appwrite/Services/Account.swift index a11688e..87e1b98 100644 --- a/Sources/Appwrite/Services/Account.swift +++ b/Sources/Appwrite/Services/Account.swift @@ -402,7 +402,7 @@ open class Account: Service { } /// - /// Add Authenticator + /// Create Authenticator /// /// Add an authenticator app to be used as an MFA factor. Verify the /// authenticator using the [verify @@ -508,20 +508,16 @@ open class Account: Service { /// Delete an authenticator for a user by ID. /// /// @param AppwriteEnums.AuthenticatorType type - /// @param String otp /// @throws Exception /// @return array /// open func deleteMfaAuthenticator( - type: AppwriteEnums.AuthenticatorType, - otp: String + type: AppwriteEnums.AuthenticatorType ) async throws -> Any { let apiPath: String = "/account/mfa/authenticators/{type}" .replacingOccurrences(of: "{type}", with: type.rawValue) - let apiParams: [String: Any?] = [ - "otp": otp - ] + let apiParams: [String: Any] = [:] let apiHeaders: [String: String] = [ "content-type": "application/json" @@ -535,7 +531,7 @@ open class Account: Service { } /// - /// Create 2FA Challenge + /// Create MFA Challenge /// /// Begin the process of MFA verification after sign-in. Finish the flow with /// [updateMfaChallenge](/docs/references/cloud/client-web/account#updateMfaChallenge) @@ -2037,7 +2033,7 @@ open class Account: Service { } /// - /// Create phone verification (confirmation) + /// Update phone verification (confirmation) /// /// Use this endpoint to complete the user phone verification process. Use the /// **userId** and **secret** that were sent to your user's phone number to diff --git a/Sources/Appwrite/Services/Avatars.swift b/Sources/Appwrite/Services/Avatars.swift index ffd0768..2a1d2fa 100644 --- a/Sources/Appwrite/Services/Avatars.swift +++ b/Sources/Appwrite/Services/Avatars.swift @@ -101,6 +101,7 @@ open class Avatars: Service { /// Use this endpoint to fetch the favorite icon (AKA favicon) of any remote /// website URL. /// + /// This endpoint does not follow HTTP redirects. /// /// @param String url /// @throws Exception @@ -180,6 +181,7 @@ open class Avatars: Service { /// image at source quality. If dimensions are not specified, the default size /// of image returned is 400x400px. /// + /// This endpoint does not follow HTTP redirects. /// /// @param String url /// @param Int width diff --git a/Sources/Appwrite/Services/Functions.swift b/Sources/Appwrite/Services/Functions.swift index ea1b37d..6979028 100644 --- a/Sources/Appwrite/Services/Functions.swift +++ b/Sources/Appwrite/Services/Functions.swift @@ -8,6 +8,116 @@ import AppwriteModels /// The Functions Service allows you view, create and manage your Cloud Functions. open class Functions: Service { + /// + /// List function templates + /// + /// List available function templates. You can use template details in + /// [createFunction](/docs/references/cloud/server-nodejs/functions#create) + /// method. + /// + /// @param [String] runtimes + /// @param [String] useCases + /// @param Int limit + /// @param Int offset + /// @throws Exception + /// @return array + /// + open func listTemplates( + runtimes: [String]? = nil, + useCases: [String]? = nil, + limit: Int? = nil, + offset: Int? = nil + ) async throws -> AppwriteModels.TemplateFunctionList { + let apiPath: String = "/functions/templates" + + let apiParams: [String: Any?] = [ + "runtimes": runtimes, + "useCases": useCases, + "limit": limit, + "offset": offset + ] + + let apiHeaders: [String: String] = [ + "content-type": "application/json" + ] + + let converter: (Any) -> AppwriteModels.TemplateFunctionList = { response in + return AppwriteModels.TemplateFunctionList.from(map: response as! [String: Any]) + } + + return try await client.call( + method: "GET", + path: apiPath, + headers: apiHeaders, + params: apiParams, + converter: converter + ) + } + + /// + /// Get function template + /// + /// Get a function template using ID. You can use template details in + /// [createFunction](/docs/references/cloud/server-nodejs/functions#create) + /// method. + /// + /// @param String templateId + /// @throws Exception + /// @return array + /// + open func getTemplate( + templateId: String + ) async throws -> AppwriteModels.TemplateFunction { + let apiPath: String = "/functions/templates/{templateId}" + .replacingOccurrences(of: "{templateId}", with: templateId) + + let apiParams: [String: Any] = [:] + + let apiHeaders: [String: String] = [ + "content-type": "application/json" + ] + + let converter: (Any) -> AppwriteModels.TemplateFunction = { response in + return AppwriteModels.TemplateFunction.from(map: response as! [String: Any]) + } + + return try await client.call( + method: "GET", + path: apiPath, + headers: apiHeaders, + params: apiParams, + converter: converter + ) + } + + /// + /// Download deployment + /// + /// Get a Deployment's contents by its unique ID. This endpoint supports range + /// requests for partial or streaming file download. + /// + /// @param String functionId + /// @param String deploymentId + /// @throws Exception + /// @return array + /// + open func getDeploymentDownload( + functionId: String, + deploymentId: String + ) async throws -> ByteBuffer { + let apiPath: String = "/functions/{functionId}/deployments/{deploymentId}/download" + .replacingOccurrences(of: "{functionId}", with: functionId) + .replacingOccurrences(of: "{deploymentId}", with: deploymentId) + + let apiParams: [String: Any] = [:] + + return try await client.call( + method: "GET", + path: apiPath, + params: apiParams + ) + } + /// /// List executions /// @@ -75,12 +185,13 @@ open class Functions: Service { path: String? = nil, method: AppwriteEnums.ExecutionMethod? = nil, headers: Any? = nil, - scheduledAt: String? = nil + scheduledAt: String? = nil, + onProgress: ((UploadProgress) -> Void)? = nil ) async throws -> AppwriteModels.Execution { let apiPath: String = "/functions/{functionId}/executions" .replacingOccurrences(of: "{functionId}", with: functionId) - let apiParams: [String: Any?] = [ + var apiParams: [String: Any?] = [ "body": body, "async": async, "path": path, @@ -89,20 +200,23 @@ open class Functions: Service { "scheduledAt": scheduledAt ] - let apiHeaders: [String: String] = [ - "content-type": "application/json" + var apiHeaders: [String: String] = [ + "content-type": "multipart/form-data" ] let converter: (Any) -> AppwriteModels.Execution = { response in return AppwriteModels.Execution.from(map: response as! [String: Any]) } - return try await client.call( - method: "POST", + let idParamName: String? = nil + return try await client.chunkedUpload( path: apiPath, - headers: apiHeaders, - params: apiParams, - converter: converter + headers: &apiHeaders, + params: &apiParams, + paramName: paramName, + idParamName: idParamName, + converter: converter, + onProgress: onProgress ) } diff --git a/Sources/AppwriteModels/Execution.swift b/Sources/AppwriteModels/Execution.swift index 2cb6d9a..add85f6 100644 --- a/Sources/AppwriteModels/Execution.swift +++ b/Sources/AppwriteModels/Execution.swift @@ -52,6 +52,9 @@ public class Execution { /// Function execution duration in seconds. public let duration: Double + /// The scheduled time for execution. If left empty, execution will be queued immediately. + public let scheduledAt: String?? + init( id: String, @@ -69,7 +72,8 @@ public class Execution { responseHeaders: [Headers], logs: String, errors: String, - duration: Double + duration: Double, + scheduledAt: String?? ) { self.id = id self.createdAt = createdAt @@ -87,6 +91,7 @@ public class Execution { self.logs = logs self.errors = errors self.duration = duration + self.scheduledAt = scheduledAt } public func toMap() -> [String: Any] { @@ -106,7 +111,8 @@ public class Execution { "responseHeaders": responseHeaders.map { $0.toMap() } as Any, "logs": logs as Any, "errors": errors as Any, - "duration": duration as Any + "duration": duration as Any, + "scheduledAt": scheduledAt as Any ] } @@ -127,7 +133,8 @@ public class Execution { responseHeaders: (map["responseHeaders"] as! [[String: Any]]).map { Headers.from(map: $0) }, logs: map["logs"] as! String, errors: map["errors"] as! String, - duration: map["duration"] as! Double + duration: map["duration"] as! Double, + scheduledAt: map["scheduledAt"] as? String? ) } } diff --git a/Sources/AppwriteModels/TemplateFunction.swift b/Sources/AppwriteModels/TemplateFunction.swift new file mode 100644 index 0000000..e174a05 --- /dev/null +++ b/Sources/AppwriteModels/TemplateFunction.swift @@ -0,0 +1,140 @@ +import Foundation +import JSONCodable + +/// Template Function +public class TemplateFunction { + + /// Function Template Icon. + public let icon: String + + /// Function Template ID. + public let id: String + + /// Function Template Name. + public let name: String + + /// Function Template Tagline. + public let tagline: String + + /// Execution permissions. + public let permissions: [Any] + + /// Function trigger events. + public let events: [Any] + + /// Function execution schedult in CRON format. + public let cron: String + + /// Function execution timeout in seconds. + public let timeout: Int + + /// Function use cases. + public let useCases: [Any] + + /// List of runtimes that can be used with this template. + public let runtimes: [TemplateRuntime] + + /// Function Template Instructions. + public let instructions: String + + /// VCS (Version Control System) Provider. + public let vcsProvider: String + + /// VCS (Version Control System) Repository ID + public let providerRepositoryId: String + + /// VCS (Version Control System) Owner. + public let providerOwner: String + + /// VCS (Version Control System) branch version (tag). + public let providerVersion: String + + /// Function variables. + public let variables: [TemplateVariable] + + /// Function scopes. + public let scopes: [Any] + + + init( + icon: String, + id: String, + name: String, + tagline: String, + permissions: [Any], + events: [Any], + cron: String, + timeout: Int, + useCases: [Any], + runtimes: [TemplateRuntime], + instructions: String, + vcsProvider: String, + providerRepositoryId: String, + providerOwner: String, + providerVersion: String, + variables: [TemplateVariable], + scopes: [Any] + ) { + self.icon = icon + self.id = id + self.name = name + self.tagline = tagline + self.permissions = permissions + self.events = events + self.cron = cron + self.timeout = timeout + self.useCases = useCases + self.runtimes = runtimes + self.instructions = instructions + self.vcsProvider = vcsProvider + self.providerRepositoryId = providerRepositoryId + self.providerOwner = providerOwner + self.providerVersion = providerVersion + self.variables = variables + self.scopes = scopes + } + + public func toMap() -> [String: Any] { + return [ + "icon": icon as Any, + "id": id as Any, + "name": name as Any, + "tagline": tagline as Any, + "permissions": permissions as Any, + "events": events as Any, + "cron": cron as Any, + "timeout": timeout as Any, + "useCases": useCases as Any, + "runtimes": runtimes.map { $0.toMap() } as Any, + "instructions": instructions as Any, + "vcsProvider": vcsProvider as Any, + "providerRepositoryId": providerRepositoryId as Any, + "providerOwner": providerOwner as Any, + "providerVersion": providerVersion as Any, + "variables": variables.map { $0.toMap() } as Any, + "scopes": scopes as Any + ] + } + + public static func from(map: [String: Any] ) -> TemplateFunction { + return TemplateFunction( + icon: map["icon"] as! String, + id: map["id"] as! String, + name: map["name"] as! String, + tagline: map["tagline"] as! String, + permissions: map["permissions"] as! [Any], + events: map["events"] as! [Any], + cron: map["cron"] as! String, + timeout: map["timeout"] as! Int, + useCases: map["useCases"] as! [Any], + runtimes: (map["runtimes"] as! [[String: Any]]).map { TemplateRuntime.from(map: $0) }, + instructions: map["instructions"] as! String, + vcsProvider: map["vcsProvider"] as! String, + providerRepositoryId: map["providerRepositoryId"] as! String, + providerOwner: map["providerOwner"] as! String, + providerVersion: map["providerVersion"] as! String, + variables: (map["variables"] as! [[String: Any]]).map { TemplateVariable.from(map: $0) }, + scopes: map["scopes"] as! [Any] + ) + } +} diff --git a/Sources/AppwriteModels/TemplateFunctionList.swift b/Sources/AppwriteModels/TemplateFunctionList.swift new file mode 100644 index 0000000..2fe319b --- /dev/null +++ b/Sources/AppwriteModels/TemplateFunctionList.swift @@ -0,0 +1,35 @@ +import Foundation +import JSONCodable + +/// Function Templates List +public class TemplateFunctionList { + + /// Total number of templates documents that matched your query. + public let total: Int + + /// List of templates. + public let templates: [TemplateFunction] + + + init( + total: Int, + templates: [TemplateFunction] + ) { + self.total = total + self.templates = templates + } + + public func toMap() -> [String: Any] { + return [ + "total": total as Any, + "templates": templates.map { $0.toMap() } as Any + ] + } + + public static func from(map: [String: Any] ) -> TemplateFunctionList { + return TemplateFunctionList( + total: map["total"] as! Int, + templates: (map["templates"] as! [[String: Any]]).map { TemplateFunction.from(map: $0) } + ) + } +} diff --git a/Sources/AppwriteModels/TemplateRuntime.swift b/Sources/AppwriteModels/TemplateRuntime.swift new file mode 100644 index 0000000..637c020 --- /dev/null +++ b/Sources/AppwriteModels/TemplateRuntime.swift @@ -0,0 +1,49 @@ +import Foundation +import JSONCodable + +/// Template Runtime +public class TemplateRuntime { + + /// Runtime Name. + public let name: String + + /// The build command used to build the deployment. + public let commands: String + + /// The entrypoint file used to execute the deployment. + public let entrypoint: String + + /// Path to function in VCS (Version Control System) repository + public let providerRootDirectory: String + + + init( + name: String, + commands: String, + entrypoint: String, + providerRootDirectory: String + ) { + self.name = name + self.commands = commands + self.entrypoint = entrypoint + self.providerRootDirectory = providerRootDirectory + } + + public func toMap() -> [String: Any] { + return [ + "name": name as Any, + "commands": commands as Any, + "entrypoint": entrypoint as Any, + "providerRootDirectory": providerRootDirectory as Any + ] + } + + public static func from(map: [String: Any] ) -> TemplateRuntime { + return TemplateRuntime( + name: map["name"] as! String, + commands: map["commands"] as! String, + entrypoint: map["entrypoint"] as! String, + providerRootDirectory: map["providerRootDirectory"] as! String + ) + } +} diff --git a/Sources/AppwriteModels/TemplateVariable.swift b/Sources/AppwriteModels/TemplateVariable.swift new file mode 100644 index 0000000..71c0a57 --- /dev/null +++ b/Sources/AppwriteModels/TemplateVariable.swift @@ -0,0 +1,63 @@ +import Foundation +import JSONCodable + +/// Template Variable +public class TemplateVariable { + + /// Variable Name. + public let name: String + + /// Variable Description. + public let description: String + + /// Variable Value. + public let value: String + + /// Variable Placeholder. + public let placeholder: String + + /// Is the variable required? + public let `required`: Bool + + /// Variable Type. + public let type: String + + + init( + name: String, + description: String, + value: String, + placeholder: String, + `required`: Bool, + type: String + ) { + self.name = name + self.description = description + self.value = value + self.placeholder = placeholder + self.`required` = `required` + self.type = type + } + + public func toMap() -> [String: Any] { + return [ + "name": name as Any, + "description": description as Any, + "value": value as Any, + "placeholder": placeholder as Any, + "`required`": `required` as Any, + "type": type as Any + ] + } + + public static func from(map: [String: Any] ) -> TemplateVariable { + return TemplateVariable( + name: map["name"] as! String, + description: map["description"] as! String, + value: map["value"] as! String, + placeholder: map["placeholder"] as! String, + `required`: map["required"] as! Bool, + type: map["type"] as! String + ) + } +} diff --git a/docs/examples/account/delete-mfa-authenticator.md b/docs/examples/account/delete-mfa-authenticator.md index 00190a4..805c673 100644 --- a/docs/examples/account/delete-mfa-authenticator.md +++ b/docs/examples/account/delete-mfa-authenticator.md @@ -8,7 +8,6 @@ let client = Client() let account = Account(client) let result = try await account.deleteMfaAuthenticator( - type: .totp, - otp: "" + type: .totp ) diff --git a/docs/examples/functions/get-deployment-download.md b/docs/examples/functions/get-deployment-download.md new file mode 100644 index 0000000..910c363 --- /dev/null +++ b/docs/examples/functions/get-deployment-download.md @@ -0,0 +1,13 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + +let functions = Functions(client) + +let bytes = try await functions.getDeploymentDownload( + functionId: "", + deploymentId: "" +) + diff --git a/docs/examples/functions/get-template.md b/docs/examples/functions/get-template.md new file mode 100644 index 0000000..e4fa185 --- /dev/null +++ b/docs/examples/functions/get-template.md @@ -0,0 +1,12 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + +let functions = Functions(client) + +let templateFunction = try await functions.getTemplate( + templateId: "" +) + diff --git a/docs/examples/functions/list-templates.md b/docs/examples/functions/list-templates.md new file mode 100644 index 0000000..d93beef --- /dev/null +++ b/docs/examples/functions/list-templates.md @@ -0,0 +1,15 @@ +import Appwrite + +let client = Client() + .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + +let functions = Functions(client) + +let templateFunctionList = try await functions.listTemplates( + runtimes: [], // optional + useCases: [], // optional + limit: 1, // optional + offset: 0 // optional +) +